refactor(issue): Refactored issue tests from Karma to Jest
re #58827 Added changelog Added sync false, and removed unneeded destroyers
This commit is contained in:
parent
66ff5f3dc6
commit
396dbbd415
|
@ -17,15 +17,13 @@ export default {
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
milestoneDue: this.milestone.due_date ? parsePikadayDate(this.milestone.due_date) : null,
|
|
||||||
milestoneStart: this.milestone.start_date
|
|
||||||
? parsePikadayDate(this.milestone.start_date)
|
|
||||||
: null,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
computed: {
|
||||||
|
milestoneDue() {
|
||||||
|
return this.milestone.due_date ? parsePikadayDate(this.milestone.due_date) : null;
|
||||||
|
},
|
||||||
|
milestoneStart() {
|
||||||
|
return this.milestone.start_date ? parsePikadayDate(this.milestone.start_date) : null;
|
||||||
|
},
|
||||||
isMilestoneStarted() {
|
isMilestoneStarted() {
|
||||||
if (!this.milestoneStart) {
|
if (!this.milestoneStart) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
|
import '~/commons/bootstrap';
|
||||||
import { GlTooltipDirective } from '@gitlab/ui';
|
import { GlTooltipDirective } from '@gitlab/ui';
|
||||||
import { sprintf } from '~/locale';
|
import { sprintf } from '~/locale';
|
||||||
import IssueMilestone from '../../components/issue/issue_milestone.vue';
|
import IssueMilestone from '../../components/issue/issue_milestone.vue';
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: 'refactor(issue): Refactored issue tests from Karma to Jest'
|
||||||
|
merge_request: 27673
|
||||||
|
author: Martin Hobert
|
||||||
|
type: other
|
|
@ -2,8 +2,8 @@ import Vue from 'vue';
|
||||||
|
|
||||||
import IssueAssignees from '~/vue_shared/components/issue/issue_assignees.vue';
|
import IssueAssignees from '~/vue_shared/components/issue/issue_assignees.vue';
|
||||||
|
|
||||||
import mountComponent from 'spec/helpers/vue_mount_component_helper';
|
import mountComponent from 'helpers/vue_mount_component_helper';
|
||||||
import { mockAssigneesList } from 'spec/boards/mock_data';
|
import { mockAssigneesList } from '../../../../javascripts/boards/mock_data';
|
||||||
|
|
||||||
const createComponent = (assignees = mockAssigneesList, cssClass = '') => {
|
const createComponent = (assignees = mockAssigneesList, cssClass = '') => {
|
||||||
const Component = Vue.extend(IssueAssignees);
|
const Component = Vue.extend(IssueAssignees);
|
|
@ -0,0 +1,172 @@
|
||||||
|
import Vue from 'vue';
|
||||||
|
import { mount } from '@vue/test-utils';
|
||||||
|
|
||||||
|
import IssueMilestone from '~/vue_shared/components/issue/issue_milestone.vue';
|
||||||
|
|
||||||
|
import { mockMilestone } from '../../../../javascripts/boards/mock_data';
|
||||||
|
|
||||||
|
const createComponent = (milestone = mockMilestone) => {
|
||||||
|
const Component = Vue.extend(IssueMilestone);
|
||||||
|
|
||||||
|
return mount(Component, {
|
||||||
|
propsData: {
|
||||||
|
milestone,
|
||||||
|
},
|
||||||
|
sync: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('IssueMilestoneComponent', () => {
|
||||||
|
let wrapper;
|
||||||
|
let vm;
|
||||||
|
|
||||||
|
beforeEach(done => {
|
||||||
|
wrapper = createComponent();
|
||||||
|
|
||||||
|
({ vm } = wrapper);
|
||||||
|
|
||||||
|
Vue.nextTick(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
wrapper.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('computed', () => {
|
||||||
|
describe('isMilestoneStarted', () => {
|
||||||
|
it('should return `false` when milestoneStart prop is not defined', () => {
|
||||||
|
wrapper.setProps({
|
||||||
|
milestone: Object.assign({}, mockMilestone, {
|
||||||
|
start_date: '',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(wrapper.vm.isMilestoneStarted).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return `true` when milestone start date is past current date', () => {
|
||||||
|
wrapper.setProps({
|
||||||
|
milestone: Object.assign({}, mockMilestone, {
|
||||||
|
start_date: '1990-07-22',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(wrapper.vm.isMilestoneStarted).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isMilestonePastDue', () => {
|
||||||
|
it('should return `false` when milestoneDue prop is not defined', () => {
|
||||||
|
wrapper.setProps({
|
||||||
|
milestone: Object.assign({}, mockMilestone, {
|
||||||
|
due_date: '',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(wrapper.vm.isMilestonePastDue).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return `true` when milestone due is past current date', () => {
|
||||||
|
wrapper.setProps({
|
||||||
|
milestone: Object.assign({}, mockMilestone, {
|
||||||
|
due_date: '1990-07-22',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(wrapper.vm.isMilestonePastDue).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('milestoneDatesAbsolute', () => {
|
||||||
|
it('returns string containing absolute milestone due date', () => {
|
||||||
|
expect(vm.milestoneDatesAbsolute).toBe('(December 31, 2019)');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns string containing absolute milestone start date when due date is not present', () => {
|
||||||
|
wrapper.setProps({
|
||||||
|
milestone: Object.assign({}, mockMilestone, {
|
||||||
|
due_date: '',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(wrapper.vm.milestoneDatesAbsolute).toBe('(January 1, 2018)');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns empty string when both milestone start and due dates are not present', () => {
|
||||||
|
wrapper.setProps({
|
||||||
|
milestone: Object.assign({}, mockMilestone, {
|
||||||
|
start_date: '',
|
||||||
|
due_date: '',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(wrapper.vm.milestoneDatesAbsolute).toBe('');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('milestoneDatesHuman', () => {
|
||||||
|
it('returns string containing milestone due date when date is yet to be due', () => {
|
||||||
|
wrapper.setProps({
|
||||||
|
milestone: Object.assign({}, mockMilestone, {
|
||||||
|
due_date: `${new Date().getFullYear() + 10}-01-01`,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(wrapper.vm.milestoneDatesHuman).toContain('years remaining');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns string containing milestone start date when date has already started and due date is not present', () => {
|
||||||
|
wrapper.setProps({
|
||||||
|
milestone: Object.assign({}, mockMilestone, {
|
||||||
|
start_date: '1990-07-22',
|
||||||
|
due_date: '',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(wrapper.vm.milestoneDatesHuman).toContain('Started');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns string containing milestone start date when date is yet to start and due date is not present', () => {
|
||||||
|
wrapper.setProps({
|
||||||
|
milestone: Object.assign({}, mockMilestone, {
|
||||||
|
start_date: `${new Date().getFullYear() + 10}-01-01`,
|
||||||
|
due_date: '',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(wrapper.vm.milestoneDatesHuman).toContain('Starts');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns empty string when milestone start and due dates are not present', () => {
|
||||||
|
wrapper.setProps({
|
||||||
|
milestone: Object.assign({}, mockMilestone, {
|
||||||
|
start_date: '',
|
||||||
|
due_date: '',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(wrapper.vm.milestoneDatesHuman).toBe('');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('template', () => {
|
||||||
|
it('renders component root element with class `issue-milestone-details`', () => {
|
||||||
|
expect(vm.$el.classList.contains('issue-milestone-details')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders milestone icon', () => {
|
||||||
|
expect(vm.$el.querySelector('svg use').getAttribute('xlink:href')).toContain('clock');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders milestone title', () => {
|
||||||
|
expect(vm.$el.querySelector('.milestone-title').innerText.trim()).toBe(mockMilestone.title);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders milestone tooltip', () => {
|
||||||
|
expect(vm.$el.querySelector('.js-item-milestone').innerText.trim()).toContain(
|
||||||
|
mockMilestone.title,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,6 +1,6 @@
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import issueWarning from '~/vue_shared/components/issue/issue_warning.vue';
|
import issueWarning from '~/vue_shared/components/issue/issue_warning.vue';
|
||||||
import mountComponent from 'spec/helpers/vue_mount_component_helper';
|
import mountComponent from 'helpers/vue_mount_component_helper';
|
||||||
|
|
||||||
const IssueWarning = Vue.extend(issueWarning);
|
const IssueWarning = Vue.extend(issueWarning);
|
||||||
|
|
||||||
|
@ -19,7 +19,9 @@ describe('Issue Warning Component', () => {
|
||||||
isLocked: true,
|
isLocked: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(vm.$el.querySelector('.icon use').href.baseVal).toMatch(/lock$/);
|
expect(
|
||||||
|
vm.$el.querySelector('.icon use').getAttributeNS('http://www.w3.org/1999/xlink', 'href'),
|
||||||
|
).toMatch(/lock$/);
|
||||||
expect(formatWarning(vm.$el.querySelector('span').textContent)).toEqual(
|
expect(formatWarning(vm.$el.querySelector('span').textContent)).toEqual(
|
||||||
'This issue is locked. Only project members can comment.',
|
'This issue is locked. Only project members can comment.',
|
||||||
);
|
);
|
||||||
|
@ -32,7 +34,9 @@ describe('Issue Warning Component', () => {
|
||||||
isConfidential: true,
|
isConfidential: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(vm.$el.querySelector('.icon use').href.baseVal).toMatch(/eye-slash$/);
|
expect(
|
||||||
|
vm.$el.querySelector('.icon use').getAttributeNS('http://www.w3.org/1999/xlink', 'href'),
|
||||||
|
).toMatch(/eye-slash$/);
|
||||||
expect(formatWarning(vm.$el.querySelector('span').textContent)).toEqual(
|
expect(formatWarning(vm.$el.querySelector('span').textContent)).toEqual(
|
||||||
'This is a confidential issue. Your comment will not be visible to the public.',
|
'This is a confidential issue. Your comment will not be visible to the public.',
|
||||||
);
|
);
|
|
@ -1,7 +1,11 @@
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
import { formatDate } from '~/lib/utils/datetime_utility';
|
||||||
import { mount, createLocalVue } from '@vue/test-utils';
|
import { mount, createLocalVue } from '@vue/test-utils';
|
||||||
import RelatedIssuableItem from '~/vue_shared/components/issue/related_issuable_item.vue';
|
import RelatedIssuableItem from '~/vue_shared/components/issue/related_issuable_item.vue';
|
||||||
import { defaultMilestone, defaultAssignees } from './related_issuable_mock_data';
|
import {
|
||||||
|
defaultAssignees,
|
||||||
|
defaultMilestone,
|
||||||
|
} from '../../../../javascripts/vue_shared/components/issue/related_issuable_mock_data';
|
||||||
|
|
||||||
describe('RelatedIssuableItem', () => {
|
describe('RelatedIssuableItem', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
|
@ -85,11 +89,11 @@ describe('RelatedIssuableItem', () => {
|
||||||
|
|
||||||
it('renders state title', () => {
|
it('renders state title', () => {
|
||||||
const stateTitle = tokenState.attributes('data-original-title');
|
const stateTitle = tokenState.attributes('data-original-title');
|
||||||
|
const formatedCreateDate = formatDate(props.createdAt);
|
||||||
|
|
||||||
expect(stateTitle).toContain('<span class="bold">Opened</span>');
|
expect(stateTitle).toContain('<span class="bold">Opened</span>');
|
||||||
expect(stateTitle).toContain(
|
|
||||||
'<span class="text-tertiary">Dec 1, 2018 12:00am GMT+0000</span>',
|
expect(stateTitle).toContain(`<span class="text-tertiary">${formatedCreateDate}</span>`);
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders aria label', () => {
|
it('renders aria label', () => {
|
|
@ -1,234 +0,0 @@
|
||||||
import Vue from 'vue';
|
|
||||||
|
|
||||||
import IssueMilestone from '~/vue_shared/components/issue/issue_milestone.vue';
|
|
||||||
|
|
||||||
import mountComponent from 'spec/helpers/vue_mount_component_helper';
|
|
||||||
import { mockMilestone } from 'spec/boards/mock_data';
|
|
||||||
|
|
||||||
const createComponent = (milestone = mockMilestone) => {
|
|
||||||
const Component = Vue.extend(IssueMilestone);
|
|
||||||
|
|
||||||
return mountComponent(Component, {
|
|
||||||
milestone,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('IssueMilestoneComponent', () => {
|
|
||||||
let vm;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
vm = createComponent();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
vm.$destroy();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('computed', () => {
|
|
||||||
describe('isMilestoneStarted', () => {
|
|
||||||
it('should return `false` when milestoneStart prop is not defined', done => {
|
|
||||||
const vmStartUndefined = createComponent(
|
|
||||||
Object.assign({}, mockMilestone, {
|
|
||||||
start_date: '',
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
Vue.nextTick()
|
|
||||||
.then(() => {
|
|
||||||
expect(vmStartUndefined.isMilestoneStarted).toBe(false);
|
|
||||||
})
|
|
||||||
.then(done)
|
|
||||||
.catch(done.fail);
|
|
||||||
|
|
||||||
vmStartUndefined.$destroy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return `true` when milestone start date is past current date', done => {
|
|
||||||
const vmStarted = createComponent(
|
|
||||||
Object.assign({}, mockMilestone, {
|
|
||||||
start_date: '1990-07-22',
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
Vue.nextTick()
|
|
||||||
.then(() => {
|
|
||||||
expect(vmStarted.isMilestoneStarted).toBe(true);
|
|
||||||
})
|
|
||||||
.then(done)
|
|
||||||
.catch(done.fail);
|
|
||||||
|
|
||||||
vmStarted.$destroy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('isMilestonePastDue', () => {
|
|
||||||
it('should return `false` when milestoneDue prop is not defined', done => {
|
|
||||||
const vmDueUndefined = createComponent(
|
|
||||||
Object.assign({}, mockMilestone, {
|
|
||||||
due_date: '',
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
Vue.nextTick()
|
|
||||||
.then(() => {
|
|
||||||
expect(vmDueUndefined.isMilestonePastDue).toBe(false);
|
|
||||||
})
|
|
||||||
.then(done)
|
|
||||||
.catch(done.fail);
|
|
||||||
|
|
||||||
vmDueUndefined.$destroy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return `true` when milestone due is past current date', done => {
|
|
||||||
const vmPastDue = createComponent(
|
|
||||||
Object.assign({}, mockMilestone, {
|
|
||||||
due_date: '1990-07-22',
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
Vue.nextTick()
|
|
||||||
.then(() => {
|
|
||||||
expect(vmPastDue.isMilestonePastDue).toBe(true);
|
|
||||||
})
|
|
||||||
.then(done)
|
|
||||||
.catch(done.fail);
|
|
||||||
|
|
||||||
vmPastDue.$destroy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('milestoneDatesAbsolute', () => {
|
|
||||||
it('returns string containing absolute milestone due date', () => {
|
|
||||||
expect(vm.milestoneDatesAbsolute).toBe('(December 31, 2019)');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns string containing absolute milestone start date when due date is not present', done => {
|
|
||||||
const vmDueUndefined = createComponent(
|
|
||||||
Object.assign({}, mockMilestone, {
|
|
||||||
due_date: '',
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
Vue.nextTick()
|
|
||||||
.then(() => {
|
|
||||||
expect(vmDueUndefined.milestoneDatesAbsolute).toBe('(January 1, 2018)');
|
|
||||||
})
|
|
||||||
.then(done)
|
|
||||||
.catch(done.fail);
|
|
||||||
|
|
||||||
vmDueUndefined.$destroy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns empty string when both milestone start and due dates are not present', done => {
|
|
||||||
const vmDatesUndefined = createComponent(
|
|
||||||
Object.assign({}, mockMilestone, {
|
|
||||||
start_date: '',
|
|
||||||
due_date: '',
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
Vue.nextTick()
|
|
||||||
.then(() => {
|
|
||||||
expect(vmDatesUndefined.milestoneDatesAbsolute).toBe('');
|
|
||||||
})
|
|
||||||
.then(done)
|
|
||||||
.catch(done.fail);
|
|
||||||
|
|
||||||
vmDatesUndefined.$destroy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('milestoneDatesHuman', () => {
|
|
||||||
it('returns string containing milestone due date when date is yet to be due', done => {
|
|
||||||
const vmFuture = createComponent(
|
|
||||||
Object.assign({}, mockMilestone, {
|
|
||||||
due_date: `${new Date().getFullYear() + 10}-01-01`,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
Vue.nextTick()
|
|
||||||
.then(() => {
|
|
||||||
expect(vmFuture.milestoneDatesHuman).toContain('years remaining');
|
|
||||||
})
|
|
||||||
.then(done)
|
|
||||||
.catch(done.fail);
|
|
||||||
|
|
||||||
vmFuture.$destroy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns string containing milestone start date when date has already started and due date is not present', done => {
|
|
||||||
const vmStarted = createComponent(
|
|
||||||
Object.assign({}, mockMilestone, {
|
|
||||||
start_date: '1990-07-22',
|
|
||||||
due_date: '',
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
Vue.nextTick()
|
|
||||||
.then(() => {
|
|
||||||
expect(vmStarted.milestoneDatesHuman).toContain('Started');
|
|
||||||
})
|
|
||||||
.then(done)
|
|
||||||
.catch(done.fail);
|
|
||||||
|
|
||||||
vmStarted.$destroy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns string containing milestone start date when date is yet to start and due date is not present', done => {
|
|
||||||
const vmStarts = createComponent(
|
|
||||||
Object.assign({}, mockMilestone, {
|
|
||||||
start_date: `${new Date().getFullYear() + 10}-01-01`,
|
|
||||||
due_date: '',
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
Vue.nextTick()
|
|
||||||
.then(() => {
|
|
||||||
expect(vmStarts.milestoneDatesHuman).toContain('Starts');
|
|
||||||
})
|
|
||||||
.then(done)
|
|
||||||
.catch(done.fail);
|
|
||||||
|
|
||||||
vmStarts.$destroy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns empty string when milestone start and due dates are not present', done => {
|
|
||||||
const vmDatesUndefined = createComponent(
|
|
||||||
Object.assign({}, mockMilestone, {
|
|
||||||
start_date: '',
|
|
||||||
due_date: '',
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
Vue.nextTick()
|
|
||||||
.then(() => {
|
|
||||||
expect(vmDatesUndefined.milestoneDatesHuman).toBe('');
|
|
||||||
})
|
|
||||||
.then(done)
|
|
||||||
.catch(done.fail);
|
|
||||||
|
|
||||||
vmDatesUndefined.$destroy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('template', () => {
|
|
||||||
it('renders component root element with class `issue-milestone-details`', () => {
|
|
||||||
expect(vm.$el.classList.contains('issue-milestone-details')).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders milestone icon', () => {
|
|
||||||
expect(vm.$el.querySelector('svg use').getAttribute('xlink:href')).toContain('clock');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders milestone title', () => {
|
|
||||||
expect(vm.$el.querySelector('.milestone-title').innerText.trim()).toBe(mockMilestone.title);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders milestone tooltip', () => {
|
|
||||||
expect(vm.$el.querySelector('.js-item-milestone').innerText.trim()).toContain(
|
|
||||||
mockMilestone.title,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
Loading…
Reference in New Issue