Commit 04f6f0eb authored by Heinrich Lee Yu's avatar Heinrich Lee Yu

Merge branch 'psi-iteration-cad-form' into 'master'

Allow creating and editing iterations within a cadence

See merge request gitlab-org/gitlab!63601
parents 45e92f65 147d2f6d
......@@ -123,6 +123,14 @@ export default {
},
};
},
newIteration() {
return {
name: 'newIteration',
params: {
cadenceId: getIdFromGraphQLId(this.cadenceId),
},
};
},
},
methods: {
fetchMore() {
......@@ -204,6 +212,10 @@ export default {
text-sr-only
no-caret
>
<gl-dropdown-item v-if="!durationInWeeks" :to="newIteration">
{{ s__('Iterations|Add iteration') }}
</gl-dropdown-item>
<gl-dropdown-item :to="editCadence">
{{ s__('Iterations|Edit cadence') }}
</gl-dropdown-item>
......
<script>
import { GlButton, GlForm, GlFormInput } from '@gitlab/ui';
import { GlAlert, GlButton, GlForm, GlFormInput } from '@gitlab/ui';
import initDatePicker from '~/behaviors/date_picker';
import createFlash from '~/flash';
import { visitUrl } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
import { convertToGraphQLId, getIdFromGraphQLId } from '~/graphql_shared/utils';
import { __, s__ } from '~/locale';
import MarkdownField from '~/vue_shared/components/markdown/field.vue';
import createIteration from '../queries/create_iteration.mutation.graphql';
import readIteration from '../queries/iteration.query.graphql';
import createIteration from '../queries/iteration_create.mutation.graphql';
import updateIteration from '../queries/update_iteration.mutation.graphql';
export default {
cadencesList: {
name: 'index',
},
components: {
GlAlert,
GlButton,
GlForm,
GlFormInput,
MarkdownField,
},
props: {
groupPath: {
type: String,
required: true,
},
previewMarkdownPath: {
type: String,
required: false,
default: '',
},
iterationsListPath: {
type: String,
required: false,
default: '',
},
isEditing: {
type: Boolean,
required: false,
default: false,
},
iteration: {
type: Object,
required: false,
default: () => ({}),
apollo: {
group: {
query: readIteration,
skip() {
return !this.iterationId;
},
/* eslint-disable @gitlab/require-i18n-strings */
variables() {
return {
fullPath: this.fullPath,
id: convertToGraphQLId('Iteration', this.iterationId),
isGroup: true,
};
},
/* eslint-enable @gitlab/require-i18n-strings */
result({ data }) {
const iteration = data.group.iterations?.nodes[0];
if (!iteration) {
this.error = s__('Iterations|Unable to find iteration.');
return;
}
this.title = iteration.title;
this.description = iteration.description;
this.startDate = iteration.startDate;
this.dueDate = iteration.dueDate;
},
error(err) {
this.error = err.message;
},
},
},
inject: ['fullPath', 'previewMarkdownPath'],
data() {
return {
iterations: [],
loading: false,
title: this.iteration.title,
description: this.iteration.description,
startDate: this.iteration.startDate,
dueDate: this.iteration.dueDate,
error: '',
group: { iteration: {} },
title: '',
description: '',
startDate: '',
dueDate: '',
};
},
computed: {
cadenceId() {
return this.$router.currentRoute.params.cadenceId;
},
iterationId() {
return this.$router.currentRoute.params.iterationId;
},
isEditing() {
return Boolean(this.iterationId);
},
variables() {
return {
input: {
groupPath: this.groupPath,
title: this.title,
description: this.description,
startDate: this.startDate,
dueDate: this.dueDate,
},
groupPath: this.fullPath,
title: this.title,
description: this.description,
startDate: this.startDate,
dueDate: this.dueDate,
};
},
},
......@@ -73,21 +94,18 @@ export default {
this.loading = true;
return this.isEditing ? this.updateIteration() : this.createIteration();
},
cancel() {
if (this.iterationsListPath) {
visitUrl(this.iterationsListPath);
} else {
this.$emit('cancel');
}
},
createIteration() {
return this.$apollo
.mutate({
mutation: createIteration,
variables: this.variables,
variables: {
...this.variables,
iterationsCadenceId: convertToGraphQLId('Iterations::Cadence', this.cadenceId),
},
})
.then(({ data }) => {
const { errors, iteration } = data.createIteration;
const { iteration, errors } = data.iterationCreate;
if (errors.length > 0) {
this.loading = false;
createFlash({
......@@ -96,7 +114,13 @@ export default {
return;
}
visitUrl(iteration.webUrl);
this.$router.push({
name: 'iteration',
params: {
cadenceId: this.cadenceId,
iterationId: getIdFromGraphQLId(iteration.id),
},
});
})
.catch(() => {
this.loading = false;
......@@ -111,8 +135,8 @@ export default {
mutation: updateIteration,
variables: {
input: {
...this.variables.input,
id: this.iteration.id,
...this.variables,
id: this.iterationId,
},
},
})
......@@ -125,7 +149,13 @@ export default {
return;
}
this.$emit('updated');
this.$router.push({
name: 'iteration',
params: {
cadenceId: this.cadenceId,
iterationId: this.iterationId,
},
});
})
.catch(() => {
createFlash({
......@@ -154,6 +184,10 @@ export default {
</h3>
</div>
<hr class="gl-mt-0" />
<gl-alert v-if="error" class="gl-mb-5" variant="danger" @dismiss="error = ''">{{
error
}}</gl-alert>
<gl-form class="row common-note-form">
<div class="col-md-6">
<div class="form-group row">
......@@ -248,13 +282,13 @@ export default {
<gl-button
:loading="loading"
data-testid="save-iteration"
variant="success"
variant="confirm"
data-qa-selector="save_iteration_button"
@click="save"
>
{{ isEditing ? __('Update iteration') : __('Create iteration') }}
</gl-button>
<gl-button class="ml-auto" data-testid="cancel-iteration" @click="cancel">
<gl-button class="gl-ml-3" data-testid="cancel-iteration" :to="$options.cadencesList">
{{ __('Cancel') }}
</gl-button>
</div>
......
......@@ -107,13 +107,22 @@ export function initCadenceApp({ namespaceType }) {
cadencesListPath,
canCreateCadence,
canEditCadence,
canCreateIteration,
canEditIteration,
hasScopedLabelsFeature,
labelsFetchPath,
previewMarkdownPath,
noIssuesSvgPath,
} = el.dataset;
const router = createRouter({ base: cadencesListPath });
const router = createRouter({
base: cadencesListPath,
permissions: {
canCreateCadence: parseBoolean(canCreateCadence),
canEditCadence: parseBoolean(canEditCadence),
canCreateIteration: parseBoolean(canCreateIteration),
canEditIteration: parseBoolean(canEditIteration),
},
});
return new Vue({
el,
......@@ -126,6 +135,7 @@ export function initCadenceApp({ namespaceType }) {
canCreateCadence: parseBoolean(canCreateCadence),
canEditCadence: parseBoolean(canEditCadence),
namespaceType,
canCreateIteration: parseBoolean(canCreateIteration),
canEditIteration: parseBoolean(canEditIteration),
hasScopedLabelsFeature: parseBoolean(hasScopedLabelsFeature),
labelsFetchPath,
......
mutation createIteration($input: iterationCreateInput!) {
iterationCreate(input: $input) {
iteration {
title
description
descriptionHtml
startDate
dueDate
}
errors
}
}
......@@ -2,34 +2,63 @@ import Vue from 'vue';
import VueRouter from 'vue-router';
import IterationCadenceForm from './components/iteration_cadence_form.vue';
import IterationCadenceList from './components/iteration_cadences_list.vue';
import IterationForm from './components/iteration_form.vue';
import IterationReport from './components/iteration_report.vue';
Vue.use(VueRouter);
const routes = [
{
name: 'new',
path: '/new',
component: IterationCadenceForm,
},
{
name: 'edit',
path: '/:cadenceId/edit',
component: IterationCadenceForm,
},
{
name: 'index',
path: '/',
component: IterationCadenceList,
},
{
name: 'iteration',
path: '/:cadenceId/iterations/:iterationId',
component: IterationReport,
},
];
function checkPermission(permission) {
return (to, from, next) => {
if (permission) {
next();
} else {
next({ path: '/' });
}
};
}
export default function createRouter({ base, permissions = {} }) {
const routes = [
{
name: 'index',
path: '/',
component: IterationCadenceList,
},
{
name: 'new',
path: '/new',
component: IterationCadenceForm,
beforeEnter: checkPermission(permissions.canCreateCadence),
},
{
name: 'edit',
path: '/:cadenceId/edit',
component: IterationCadenceForm,
beforeEnter: checkPermission(permissions.canEditCadence),
},
{
name: 'newIteration',
path: '/:cadenceId/iterations/new',
component: IterationForm,
beforeEnter: checkPermission(permissions.canCreateIteration),
},
{
name: 'iteration',
path: '/:cadenceId/iterations/:iterationId',
component: IterationReport,
},
{
name: 'editIteration',
path: '/:cadenceId/iterations/:iterationId/edit',
component: IterationForm,
beforeEnter: checkPermission(permissions.canEditIteration),
},
{
path: '*',
redirect: '/',
},
];
export default function createRouter({ base }) {
const router = new VueRouter({
base,
mode: 'history',
......
......@@ -3,15 +3,9 @@
class Groups::IterationCadencesController < Groups::ApplicationController
before_action :check_cadences_available!
before_action :authorize_show_cadence!, only: [:index]
before_action :authorize_admin_cadence!, only: [:edit]
before_action :authorize_create_cadence!, only: [:new]
feature_category :issue_tracking
def new; end
def edit; end
def index; end
private
......@@ -20,14 +14,6 @@ class Groups::IterationCadencesController < Groups::ApplicationController
render_404 unless group.iteration_cadences_feature_flag_enabled?
end
def authorize_admin_cadence!
render_404 unless can?(current_user, :admin_iteration_cadence, group)
end
def authorize_create_cadence!
render_404 unless can?(current_user, :create_iteration_cadence, group)
end
def authorize_show_cadence!
render_404 unless can?(current_user, :read_iteration_cadence, group)
end
......
.js-iteration-cadence-app{ data: { group_full_path: @group.full_path,
cadences_list_path: group_iteration_cadences_path(@group),
can_create_cadence: can?(current_user, :create_iteration_cadence, @group).to_s,
can_edit_cadence: can?(current_user, :admin_iteration_cadence, @group).to_s,
can_edit_iteration: can?(current_user, :admin_iteration, @group).to_s,
has_scoped_labels_feature: @group.licensed_feature_available?(:scoped_labels).to_s,
labels_fetch_path: group_labels_path(@group, format: :json, include_ancestor_groups: true),
preview_markdown_path: preview_markdown_path(@group),
no_issues_svg_path: image_path('illustrations/issues.svg') } }
- add_to_breadcrumbs _('Iteration cadences'), group_iteration_cadences_path(@group)
- breadcrumb_title params[:id]
- page_title _('Edit iteration cadence')
= render 'js_app'
- page_title _('Iteration cadences')
= render 'js_app'
.js-iteration-cadence-app{ data: { group_full_path: @group.full_path,
cadences_list_path: group_iteration_cadences_path(@group),
can_create_cadence: can?(current_user, :create_iteration_cadence, @group).to_s,
can_edit_cadence: can?(current_user, :admin_iteration_cadence, @group).to_s,
can_create_iteration: can?(current_user, :create_iteration, @group).to_s,
can_edit_iteration: can?(current_user, :admin_iteration, @group).to_s,
has_scoped_labels_feature: @group.licensed_feature_available?(:scoped_labels).to_s,
labels_fetch_path: group_labels_path(@group, format: :json, include_ancestor_groups: true),
preview_markdown_path: preview_markdown_path(@group),
no_issues_svg_path: image_path('illustrations/issues.svg') } }
- add_to_breadcrumbs _('Iteration cadences'), group_iteration_cadences_path(@group)
- breadcrumb_title _('New')
- page_title _('New iteration cadence')
= render 'js_app'
......@@ -120,7 +120,9 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
resources :iterations, only: [:index, :new, :edit, :show], constraints: { id: /\d+/ }
resources :iteration_cadences, only: [:index, :new, :edit], path: 'cadences', constraints: { id: /\d+/ }
resources :iteration_cadences, path: 'cadences(/*vueroute)', action: :index do
resources :iterations, only: [:index, :new, :edit, :show], constraints: { id: /\d+/ }, controller: :iteration_cadences, action: :index
end
resources :issues, only: [] do
collection do
......
......@@ -15,28 +15,13 @@ RSpec.describe Groups::IterationCadencesController do
sign_in(user)
end
describe 'new' do
subject { get :new, params: { group_id: group } }
describe 'index' do
subject { get :index, params: { group_id: group } }
where(:feature_flag_available, :role, :status) do
false | :developer | :not_found
true | :none | :not_found
true | :guest | :not_found
true | :developer | :success
end
with_them do
it_behaves_like 'returning response status', params[:status]
end
end
describe 'edit' do
subject { get :edit, params: { group_id: group, id: cadence } }
where(:feature_flag_available, :role, :status) do
false | :developer | :not_found
true | :none | :not_found
true | :guest | :not_found
true | :guest | :success
true | :developer | :success
end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'User edits iteration cadence', :js do
let_it_be(:group) { create(:group) }
let_it_be(:user) { create(:group_member, :maintainer, user: create(:user), group: group ).user }
let_it_be(:guest_user) { create(:group_member, :guest, user: create(:user), group: group ).user }
let_it_be(:cadence) { create(:iterations_cadence, group: group, description: 'an example iteration cadence', duration_in_weeks: 3, iterations_in_advance: 2) }
dropdown_selector = '[data-testid="actions-dropdown"]'
context 'with license' do
before do
stub_licensed_features(iterations: true)
end
context 'as authorized user' do
before do
sign_in(user)
end
it 'prefills fields and allows updating values' do
visit edit_group_iteration_cadence_path(cadence.group, id: cadence.id)
wait_for_requests
aggregate_failures do
expect(title_input.value).to eq(cadence.title)
expect(description_input.value).to eq(cadence.description)
expect(start_date_input.value).to have_content(cadence.start_date)
end
updated_title = 'Updated cadence title'
fill_in('Title', with: updated_title)
click_button('Save cadence')
expect(page).to have_content(updated_title)
end
end
context 'as guest user' do
before do
sign_in(guest_user)
end
it 'does not show edit dropdown' do
visit group_iteration_cadences_path(cadence.group)
expect(page).to have_content(cadence.title)
expect(page).not_to have_selector(dropdown_selector)
end
it 'redirects to list page when loading edit cadence page' do
visit edit_group_iteration_cadence_path(cadence.group, id: cadence.id)
# vue-router has trailing slash but _path helper doesn't
expect(page).to have_current_path("#{group_iteration_cadences_path(cadence.group)}/")
end
it 'redirects to list page when loading new cadence page' do
visit new_group_iteration_cadence_path(cadence.group)
# vue-router has trailing slash but _path helper doesn't
expect(page).to have_current_path("#{group_iteration_cadences_path(cadence.group)}/")
end
end
def title_input
page.find('#cadence-title')
end
def description_input
page.find('#cadence-description')
end
def start_date_input
page.find('#cadence-start-date')
end
end
end
......@@ -2,16 +2,19 @@
require 'spec_helper'
RSpec.describe 'User views iteration' do
RSpec.describe 'User edits iteration' do
let_it_be(:now) { Time.now }
let_it_be(:group) { create(:group) }
let_it_be(:user) { create(:group_member, :maintainer, user: create(:user), group: group ).user }
let_it_be(:guest_user) { create(:group_member, :guest, user: create(:user), group: group ).user }
let_it_be(:iteration) { create(:iteration, :skip_future_date_validation, group: group, title: 'Correct Iteration', description: 'Iteration description', start_date: now - 1.day, due_date: now) }
let_it_be(:cadence) { create(:iterations_cadence, group: group) }
let_it_be(:iteration) { create(:iteration, :skip_future_date_validation, group: group, title: 'Correct Iteration', description: 'Iteration description', start_date: now - 1.day, due_date: now, iterations_cadence: cadence) }
dropdown_selector = '[data-testid="actions-dropdown"]'
context 'with license' do
using RSpec::Parameterized::TableSyntax
before do
stub_licensed_features(iterations: true)
end
......@@ -21,55 +24,64 @@ RSpec.describe 'User views iteration' do
sign_in(user)
end
context 'load edit page directly', :js do
before do
visit edit_group_iteration_path(group, iteration.id)
end
where(using_cadences: [true, false])
it 'prefills fields and allows updating all values' do
aggregate_failures do
expect(title_input.value).to eq(iteration.title)
expect(description_input.value).to eq(iteration.description)
expect(start_date_input.value).to have_content(iteration.start_date)
expect(due_date_input.value).to have_content(iteration.due_date)
end
with_them do
let(:iteration_page) { using_cadences ? group_iteration_cadence_iteration_path(group, iteration_cadence_id: cadence.id, id: iteration.id) : group_iteration_path(iteration.group, iteration.id) }
let(:edit_iteration_page) { using_cadences ? edit_group_iteration_cadence_iteration_path(group, iteration_cadence_id: cadence.id, id: iteration.id) : edit_group_iteration_path(iteration.group, iteration.id) }
updated_title = 'Updated iteration title'
updated_desc = 'Updated iteration desc'
updated_start_date = now + 4.days
updated_due_date = now + 5.days
fill_in('Title', with: updated_title)
fill_in('Description', with: updated_desc)
fill_in('Start date', with: updated_start_date.strftime('%Y-%m-%d'))
fill_in('Due date', with: updated_due_date.strftime('%Y-%m-%d'))
click_button('Update iteration')
aggregate_failures do
expect(page).to have_content(updated_title)
expect(page).to have_content(updated_desc)
expect(page).to have_content(updated_start_date.strftime('%b %-d, %Y'))
expect(page).to have_content(updated_due_date.strftime('%b %-d, %Y'))
expect(page).to have_current_path(group_iteration_path(group, iteration.id))
context 'load edit page directly', :js do
before do
visit edit_iteration_page
wait_for_requests
end
end
end
context 'load edit page from report', :js do
before do
visit group_iteration_path(iteration.group, iteration.id)
it 'prefills fields and allows updating all values' do
aggregate_failures do
expect(title_input.value).to eq(iteration.title)
expect(description_input.value).to eq(iteration.description)
expect(start_date_input.value).to have_content(iteration.start_date)
expect(due_date_input.value).to have_content(iteration.due_date)
end
updated_title = 'Updated iteration title'
updated_desc = 'Updated iteration desc'
updated_start_date = now + 4.days
updated_due_date = now + 5.days
fill_in('Title', with: updated_title)
fill_in('Description', with: updated_desc)
fill_in('Start date', with: updated_start_date.strftime('%Y-%m-%d'))
fill_in('Due date', with: updated_due_date.strftime('%Y-%m-%d'))
click_button('Update iteration')
aggregate_failures do
expect(page).to have_content(updated_title)
expect(page).to have_content(updated_desc)
expect(page).to have_content(updated_start_date.strftime('%b %-d, %Y'))
expect(page).to have_content(updated_due_date.strftime('%b %-d, %Y'))
expect(page).to have_current_path(iteration_page)
end
end
end
it 'prefills fields and updates URL' do
find(dropdown_selector).click
click_button('Edit iteration')
context 'load edit page from report', :js do
before do
visit iteration_page
end
aggregate_failures do
expect(title_input.value).to eq(iteration.title)
expect(description_input.value).to eq(iteration.description)
expect(start_date_input.value).to have_content(iteration.start_date)
expect(due_date_input.value).to have_content(iteration.due_date)
expect(page).to have_current_path(edit_group_iteration_path(iteration.group, iteration.id))
it 'prefills fields and updates URL' do
find(dropdown_selector).click
click_link_or_button('Edit iteration')
aggregate_failures do
expect(title_input.value).to eq(iteration.title)
expect(description_input.value).to eq(iteration.description)
expect(start_date_input.value).to have_content(iteration.start_date)
expect(due_date_input.value).to have_content(iteration.due_date)
expect(page).to have_current_path(edit_iteration_page)
end
end
end
end
......@@ -80,17 +92,35 @@ RSpec.describe 'User views iteration' do
sign_in(guest_user)
end
it 'does not show edit dropdown', :js do
visit group_iteration_path(iteration.group, iteration.id)
context 'with cadences', :js do
it 'does not show edit dropdown' do
visit group_iteration_cadence_iteration_path(iteration.group, iteration_cadence_id: cadence.id, id: iteration.id)
expect(page).to have_content(iteration.title)
expect(page).not_to have_selector(dropdown_selector)
expect(page).to have_content(iteration.title)
expect(page).not_to have_selector(dropdown_selector)
end
it 'redirects to cadence list page when loading edit page directly' do
visit edit_group_iteration_cadence_iteration_path(iteration.group, iteration_cadence_id: cadence.id, id: iteration.id)
expect(page).to have_content(cadence.title)
expect(page).to have_current_path("#{group_iteration_cadences_path(group)}/")
end
end
it '404s when loading edit page directly' do
visit edit_group_iteration_path(iteration.group, iteration.id)
context 'without cadences' do
it 'does not show edit dropdown', :js do
visit group_iteration_path(iteration.group, iteration.id)
expect(page).to have_gitlab_http_status(:not_found)
expect(page).to have_content(iteration.title)
expect(page).not_to have_selector(dropdown_selector)
end
it '404s when loading edit page directly' do
visit edit_group_iteration_path(iteration.group, iteration.id)
expect(page).to have_gitlab_http_status(:not_found)
end
end
end
......
import { mount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import IterationForm from 'ee/iterations/components/iteration_form.vue';
import readIteration from 'ee/iterations/queries/iteration.query.graphql';
import createIteration from 'ee/iterations/queries/iteration_create.mutation.graphql';
import updateIteration from 'ee/iterations/queries/update_iteration.mutation.graphql';
import createRouter from 'ee/iterations/router';
import createMockApollo from 'helpers/mock_apollo_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { convertToGraphQLId } from '~/graphql_shared/utils';
const baseUrl = '/cadences/';
function createMockApolloProvider(requestHandlers) {
Vue.use(VueApollo);
return createMockApollo(requestHandlers);
}
describe('Iteration Form', () => {
let wrapper;
let router;
const groupPath = 'gitlab-org';
const iterationId = 72;
const cadenceId = 2;
const iteration = {
id: `gid://gitlab/Iteration/${iterationId}`,
iid: 70,
title: 'An iteration',
state: 'opened',
webPath: '/test',
description: 'The words',
descriptionHtml: '<p>The words</p>',
startDate: '2020-06-28',
dueDate: '2020-07-05',
};
const readMutationSuccess = {
data: { group: { iterations: { nodes: [iteration] }, errors: [] } },
};
const createMutationSuccess = { data: { iterationCreate: { iteration, errors: [] } } };
const createMutationFailure = {
data: { iterationCreate: { iteration, errors: ['alas, your data is unchanged'] } },
};
const updateMutationSuccess = { data: { updateIteration: { iteration, errors: [] } } };
function createComponent({
mutationQuery = createIteration,
mutationResult = createMutationSuccess,
query = readIteration,
result = readMutationSuccess,
resolverMock = jest.fn().mockResolvedValue(mutationResult),
} = {}) {
const apolloProvider = createMockApolloProvider([
[query, jest.fn().mockResolvedValue(result)],
[mutationQuery, resolverMock],
]);
wrapper = extendedWrapper(
mount(IterationForm, {
provide: {
fullPath: groupPath,
previewMarkdownPath: '',
},
apolloProvider,
router,
}),
);
}
beforeEach(() => {
router = createRouter({
base: baseUrl,
permissions: { canCreateIteration: true, canEditIteration: true },
});
});
afterEach(() => {
wrapper.destroy();
});
const findPageTitle = () => wrapper.find({ ref: 'pageTitle' });
const findTitle = () => wrapper.find('#iteration-title');
const findDescription = () => wrapper.find('#iteration-description');
const findStartDate = () => wrapper.find('#iteration-start-date');
const findDueDate = () => wrapper.find('#iteration-due-date');
const findSaveButton = () => wrapper.findByTestId('save-iteration');
const findCancelButton = () => wrapper.findByTestId('cancel-iteration');
const clickSave = () => findSaveButton().trigger('click');
describe('New iteration', () => {
const resolverMock = jest.fn().mockResolvedValue(createMutationSuccess);
beforeEach(() => {
router.replace({ name: 'newIteration', params: { cadenceId, iterationId: undefined } });
createComponent({ resolverMock });
});
afterEach(() => {
router.replace({ name: 'index' });
});
it('cancel button links to list page', () => {
expect(findCancelButton().attributes('href')).toBe(baseUrl);
});
describe('save', () => {
it('triggers mutation with form data', async () => {
const title = 'Iteration 5';
const description = 'The fifth iteration';
const startDate = '2020-05-05';
const dueDate = '2020-05-25';
findTitle().vm.$emit('input', title);
findDescription().setValue(description);
findStartDate().vm.$emit('input', startDate);
findDueDate().vm.$emit('input', dueDate);
await clickSave();
expect(resolverMock).toHaveBeenCalledWith({
groupPath,
title,
description,
iterationsCadenceId: convertToGraphQLId('Iterations::Cadence', cadenceId),
startDate,
dueDate,
});
});
it('redirects to Iteration page on success', async () => {
createComponent();
await clickSave();
expect(findSaveButton().props('loading')).toBe(true);
await waitForPromises();
expect(router.currentRoute.name).toBe('iteration');
expect(router.currentRoute.params).toEqual({
cadenceId,
iterationId,
});
});
it('loading=false on error', () => {
createComponent({ mutationResult: createMutationFailure });
clickSave();
return waitForPromises().then(() => {
expect(findSaveButton().props('loading')).toBe(false);
});
});
});
});
describe('Edit iteration', () => {
beforeEach(() => {
router.replace({ name: 'editIteration', params: { cadenceId, iterationId } });
});
afterEach(() => {
router.replace({ name: 'index' });
});
it('shows update text title', () => {
createComponent();
expect(findPageTitle().text()).toBe('Edit iteration');
});
it('prefills form fields', async () => {
createComponent();
await waitForPromises();
expect(findTitle().element.value).toBe(iteration.title);
expect(findDescription().element.value).toBe(iteration.description);
expect(findStartDate().element.value).toBe(iteration.startDate);
expect(findDueDate().element.value).toBe(iteration.dueDate);
});
it('shows update text on submit button', () => {
createComponent();
expect(findSaveButton().text()).toBe('Update iteration');
});
it('triggers mutation with form data', () => {
const resolverMock = jest.fn().mockResolvedValue(updateMutationSuccess);
createComponent({ mutationQuery: updateIteration, resolverMock });
const title = 'Updated title';
const description = 'Updated description';
const startDate = '2020-05-06';
const dueDate = '2020-05-26';
findTitle().vm.$emit('input', title);
findDescription().setValue(description);
findStartDate().vm.$emit('input', startDate);
findDueDate().vm.$emit('input', dueDate);
clickSave();
expect(resolverMock).toHaveBeenCalledWith({
input: {
groupPath,
id: iterationId,
title,
description,
startDate,
dueDate,
},
});
});
it('calls update mutation', async () => {
const resolverMock = jest.fn().mockResolvedValue(updateMutationSuccess);
createComponent({
mutationQuery: updateIteration,
resolverMock,
});
clickSave();
await nextTick();
expect(findSaveButton().props('loading')).toBe(true);
expect(resolverMock).toHaveBeenCalledWith({
input: {
groupPath,
id: iterationId,
startDate: '',
dueDate: '',
title: '',
description: '',
},
});
});
});
});
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'cadences routing' do
let_it_be(:group_path) { 'group.abc123' }
let_it_be(:group) { create(:group, path: group_path) }
let(:cadence) do
create(:iterations_cadence, group: group, title: 'test cadence')
end
it "routes to show cadences list" do
expect(get("/groups/#{group_path}/-/cadences")).to route_to('groups/iteration_cadences#index', group_id: group_path)
end
it "routes to new cadence" do
expect(get("/groups/#{group_path}/-/cadences/new")).to route_to('groups/iteration_cadences#index', vueroute: "new", group_id: group_path)
end
it "routes to edit cadence" do
expect(get("/groups/#{group_path}/-/cadences/1/edit")).to route_to('groups/iteration_cadences#index', group_id: group_path, vueroute: "1/edit")
end
it "routes to list iterations within cadence" do
expect(get("/groups/#{group_path}/-/cadences/1/iterations")).to route_to('groups/iteration_cadences#index', group_id: group_path, iteration_cadence_id: "1")
end
it "routes to show iteration within cadence" do
expect(get("/groups/#{group_path}/-/cadences/1/iterations/2")).to route_to('groups/iteration_cadences#index', group_id: group_path, iteration_cadence_id: "1", id: "2")
end
it "routes to edit iteration within cadence" do
expect(get("/groups/#{group_path}/-/cadences/1/iterations/2/edit")).to route_to('groups/iteration_cadences#index', group_id: group_path, iteration_cadence_id: "1", id: "2")
end
it "routes to new iteration within cadence" do
expect(get("/groups/#{group_path}/-/cadences/1/iterations/new")).to route_to('groups/iteration_cadences#index', group_id: group_path, iteration_cadence_id: "1")
end
end
......@@ -11716,9 +11716,6 @@ msgstr ""
msgid "Edit iteration"
msgstr ""
msgid "Edit iteration cadence"
msgstr ""
msgid "Edit public deploy key"
msgstr ""
......@@ -18205,6 +18202,9 @@ msgstr ""
msgid "Iterations"
msgstr ""
msgid "Iterations|Add iteration"
msgstr ""
msgid "Iterations|Automated scheduling"
msgstr ""
......@@ -18286,6 +18286,9 @@ msgstr ""
msgid "Iterations|Title"
msgstr ""
msgid "Iterations|Unable to find iteration."
msgstr ""
msgid "Iteration|Dates cannot overlap with other existing Iterations within this group"
msgstr ""
......@@ -21796,9 +21799,6 @@ msgstr ""
msgid "New iteration"
msgstr ""
msgid "New iteration cadence"
msgstr ""
msgid "New iteration created"
msgstr ""
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment