From 4f1cf166c87bbdf808caacf97a20cbbcb3923ed9 Mon Sep 17 00:00:00 2001 From: Julia Miocene Date: Tue, 14 Jan 2025 16:25:22 +0100 Subject: [PATCH 1/3] Add a multi-step form template --- .../multi_step_form_template.stories.js | 52 ++++++++++ .../components/multi_step_form_template.vue | 65 +++++++++++++ locale/gitlab.pot | 6 ++ .../multi_step_form_template_spec.js | 94 +++++++++++++++++++ 4 files changed, 217 insertions(+) create mode 100644 app/assets/javascripts/vue_shared/components/multi_step_form_template.stories.js create mode 100644 app/assets/javascripts/vue_shared/components/multi_step_form_template.vue create mode 100644 spec/frontend/vue_shared/components/multi_step_form_template_spec.js diff --git a/app/assets/javascripts/vue_shared/components/multi_step_form_template.stories.js b/app/assets/javascripts/vue_shared/components/multi_step_form_template.stories.js new file mode 100644 index 00000000000000..d147d36d9e10de --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/multi_step_form_template.stories.js @@ -0,0 +1,52 @@ +import { GlButton } from '@gitlab/ui'; +import MultiStepFormTemplate from './multi_step_form_template.vue'; + +export default { + component: MultiStepFormTemplate, + title: 'vue_shared/multi_step_form_template', + argTypes: { + title: { + control: 'text', + }, + thisStep: { + control: 'number', + }, + stepsNumber: { + control: 'number', + }, + }, +}; + +const Template = (args, { argTypes }) => ({ + components: { MultiStepFormTemplate, GlButton }, + props: Object.keys(argTypes), + template: ` + + + + + `, +}); + +export const Default = Template.bind({}); +Default.args = { + title: 'Create new project', + thisStep: 1, + stepsNumber: 2, +}; diff --git a/app/assets/javascripts/vue_shared/components/multi_step_form_template.vue b/app/assets/javascripts/vue_shared/components/multi_step_form_template.vue new file mode 100644 index 00000000000000..0eb525d99f0986 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/multi_step_form_template.vue @@ -0,0 +1,65 @@ + + + + diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 3f4f6eda46ff1a..93ff2445f3c941 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -55234,6 +55234,12 @@ msgstr "" msgid "Step %{step}" msgstr "" +msgid "Step %{thisStep}" +msgstr "" + +msgid "Step %{thisStep} of %{stepsNumber}" +msgstr "" + msgid "Step 1." msgstr "" diff --git a/spec/frontend/vue_shared/components/multi_step_form_template_spec.js b/spec/frontend/vue_shared/components/multi_step_form_template_spec.js new file mode 100644 index 00000000000000..20c840e25b0f21 --- /dev/null +++ b/spec/frontend/vue_shared/components/multi_step_form_template_spec.js @@ -0,0 +1,94 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlSprintf } from '@gitlab/ui'; +import MultiStepFormTemplate from '~/vue_shared/components/multi_step_form_template.vue'; + +describe('MultiStepFormTemplate', () => { + let wrapper; + const defaultProps = { + title: 'Form title', + thisStep: 1, + }; + + const createComponent = (props = {}, slots) => { + wrapper = shallowMount(MultiStepFormTemplate, { + propsData: { + ...defaultProps, + ...props, + }, + slots, + }); + }; + + const findTitle = () => wrapper.find('.multi-step-form-title'); + const findContent = () => wrapper.find('.multi-step-form-content'); + const findSprintf = () => wrapper.findComponent(GlSprintf); + const findActions = () => wrapper.find('.multi-step-form-action'); + const findFooter = () => wrapper.find('.multi-step-form-footer'); + + it('renders title', () => { + createComponent(); + + expect(findTitle().text()).toEqual('Form title'); + }); + + describe('step display', () => { + it('displays step X of N when stepsNumber is provided', () => { + createComponent({ stepsNumber: 2 }); + + expect(findSprintf().attributes('message')).toBe('Step %{thisStep} of %{stepsNumber}'); + }); + + it('displays only step X when stepsNumber is not provided', () => { + createComponent(); + + expect(findSprintf().attributes('message')).toBe('Step %{thisStep}'); + }); + }); + + describe('slots', () => { + it('renders form slot content', () => { + createComponent({}, { form: '
Form Content
' }); + + expect(findContent().exists()).toBe(true); + expect(findContent().find('.test-form').exists()).toBe(true); + }); + + it('renders action buttons when back and next slots are provided', () => { + createComponent( + {}, + { + back: '', + next: '', + }, + ); + + expect(findActions().exists()).toBe(true); + expect(findActions().find('button.back').exists()).toBe(true); + expect(findActions().find('button.next').exists()).toBe(true); + }); + + it('does not render action section when no back or next slots are provided', () => { + createComponent(); + + expect(findActions().exists()).toBe(false); + }); + + it('renders footer slot content when provided', () => { + createComponent( + {}, + { + footer: '', + }, + ); + + expect(findFooter().exists()).toBe(true); + expect(findFooter().find('.test-footer').exists()).toBe(true); + }); + + it('does not render footer section when no footer slot is provided', () => { + createComponent(); + + expect(findFooter().exists()).toBe(false); + }); + }); +}); -- GitLab From da02759bc602407155d8018852a8804441a7a948 Mon Sep 17 00:00:00 2001 From: Julia Miocene Date: Thu, 16 Jan 2025 14:31:07 +0100 Subject: [PATCH 2/3] Apply suggestions --- .../multi_step_form_template.stories.js | 13 +++++-- .../components/multi_step_form_template.vue | 37 ++++++++++++------- locale/gitlab.pot | 8 ++-- .../multi_step_form_template_spec.js | 37 ++++++++----------- 4 files changed, 52 insertions(+), 43 deletions(-) diff --git a/app/assets/javascripts/vue_shared/components/multi_step_form_template.stories.js b/app/assets/javascripts/vue_shared/components/multi_step_form_template.stories.js index d147d36d9e10de..864850cefcdc36 100644 --- a/app/assets/javascripts/vue_shared/components/multi_step_form_template.stories.js +++ b/app/assets/javascripts/vue_shared/components/multi_step_form_template.stories.js @@ -8,10 +8,10 @@ export default { title: { control: 'text', }, - thisStep: { + currentStep: { control: 'number', }, - stepsNumber: { + stepsTotal: { control: 'number', }, }, @@ -47,6 +47,11 @@ const Template = (args, { argTypes }) => ({ export const Default = Template.bind({}); Default.args = { title: 'Create new project', - thisStep: 1, - stepsNumber: 2, + currentStep: 1, + stepsTotal: 2, +}; +export const NumberOfStepsIsNotDefined = Template.bind({}); +NumberOfStepsIsNotDefined.args = { + title: 'Create new project', + currentStep: 1, }; diff --git a/app/assets/javascripts/vue_shared/components/multi_step_form_template.vue b/app/assets/javascripts/vue_shared/components/multi_step_form_template.vue index 0eb525d99f0986..930b70d5809e1d 100644 --- a/app/assets/javascripts/vue_shared/components/multi_step_form_template.vue +++ b/app/assets/javascripts/vue_shared/components/multi_step_form_template.vue @@ -3,8 +3,8 @@ import { GlSprintf } from '@gitlab/ui'; import { __ } from '~/locale'; export const i18n = { - stepXOfNSteps: __('Step %{thisStep} of %{stepsNumber}'), - stepX: __('Step %{thisStep}'), + stepXOfNSteps: __('Step %{currentStep} of %{stepsTotal}'), + stepX: __('Step %{currentStep}'), }; export default { @@ -18,11 +18,11 @@ export default { type: String, required: true, }, - thisStep: { + currentStep: { type: Number, required: true, }, - stepsNumber: { + stepsTotal: { type: Number, required: false, default: null, @@ -31,28 +31,37 @@ export default { };