Commit 4b9a82a6 authored by Martin Wortschack's avatar Martin Wortschack

Merge branch 'env-items-to-jest' into 'master'

Migrate two environment tests from karma to jest

See merge request gitlab-org/gitlab!21326
parents e6f407d5 babb5e6e
import Vue from 'vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import environmentTableComp from '~/environments/components/environments_table.vue';
import { mount } from '@vue/test-utils';
import EnvironmentTable from '~/environments/components/environments_table.vue';
import eventHub from '~/environments/event_hub';
import { deployBoardMockData } from './mock_data';
import deployBoardMockData from './mock_data';
describe('Environment table', () => {
let Component;
let vm;
beforeEach(() => {
Component = Vue.extend(environmentTableComp);
});
let wrapper;
const factory = (options = {}) => {
// This destroys any wrappers created before a nested call to factory reassigns it
if (wrapper && wrapper.destroy) {
wrapper.destroy();
}
wrapper = mount(EnvironmentTable, {
...options,
});
};
afterEach(() => {
vm.$destroy();
wrapper.destroy();
});
it('Should render a table', () => {
......@@ -25,17 +29,19 @@ describe('Environment table', () => {
environment_path: 'url',
};
vm = mountComponent(Component, {
environments: [mockItem],
canReadEnvironment: true,
canaryDeploymentFeatureId: 'canary_deployment',
showCanaryDeploymentCallout: true,
userCalloutsPath: '/callouts',
lockPromotionSvgPath: '/assets/illustrations/lock-promotion.svg',
helpCanaryDeploymentsPath: 'help/canary-deployments',
factory({
propsData: {
environments: [mockItem],
canReadEnvironment: true,
canaryDeploymentFeatureId: 'canary_deployment',
showCanaryDeploymentCallout: true,
userCalloutsPath: '/callouts',
lockPromotionSvgPath: '/assets/illustrations/lock-promotion.svg',
helpCanaryDeploymentsPath: 'help/canary-deployments',
},
});
expect(vm.$el.getAttribute('class')).toContain('ci-table');
expect(wrapper.classes()).toContain('ci-table');
});
it('should render deploy board container when data is provided', () => {
......@@ -51,19 +57,21 @@ describe('Environment table', () => {
isEmptyDeployBoard: false,
};
vm = mountComponent(Component, {
environments: [mockItem],
canCreateDeployment: false,
canReadEnvironment: true,
canaryDeploymentFeatureId: 'canary_deployment',
showCanaryDeploymentCallout: true,
userCalloutsPath: '/callouts',
lockPromotionSvgPath: '/assets/illustrations/lock-promotion.svg',
helpCanaryDeploymentsPath: 'help/canary-deployments',
factory({
propsData: {
environments: [mockItem],
canCreateDeployment: false,
canReadEnvironment: true,
canaryDeploymentFeatureId: 'canary_deployment',
showCanaryDeploymentCallout: true,
userCalloutsPath: '/callouts',
lockPromotionSvgPath: '/assets/illustrations/lock-promotion.svg',
helpCanaryDeploymentsPath: 'help/canary-deployments',
},
});
expect(vm.$el.querySelector('.js-deploy-board-row')).toBeDefined();
expect(vm.$el.querySelector('.deploy-board-icon')).not.toBeNull();
expect(wrapper.find('.js-deploy-board-row')).toBeDefined();
expect(wrapper.find('.deploy-board-icon')).not.toBeNull();
});
it('should toggle deploy board visibility when arrow is clicked', done => {
......@@ -88,17 +96,19 @@ describe('Environment table', () => {
done();
});
vm = mountComponent(Component, {
environments: [mockItem],
canReadEnvironment: true,
canaryDeploymentFeatureId: 'canary_deployment',
showCanaryDeploymentCallout: true,
userCalloutsPath: '/callouts',
lockPromotionSvgPath: '/assets/illustrations/lock-promotion.svg',
helpCanaryDeploymentsPath: 'help/canary-deployments',
factory({
propsData: {
environments: [mockItem],
canReadEnvironment: true,
canaryDeploymentFeatureId: 'canary_deployment',
showCanaryDeploymentCallout: true,
userCalloutsPath: '/callouts',
lockPromotionSvgPath: '/assets/illustrations/lock-promotion.svg',
helpCanaryDeploymentsPath: 'help/canary-deployments',
},
});
vm.$el.querySelector('.deploy-board-icon').click();
wrapper.find('.deploy-board-icon').trigger('click');
});
it('should render canary callout', () => {
......@@ -111,17 +121,19 @@ describe('Environment table', () => {
showCanaryCallout: true,
};
vm = mountComponent(Component, {
environments: [mockItem],
canCreateDeployment: false,
canReadEnvironment: true,
canaryDeploymentFeatureId: 'canary_deployment',
showCanaryDeploymentCallout: true,
userCalloutsPath: '/callouts',
lockPromotionSvgPath: '/assets/illustrations/lock-promotion.svg',
helpCanaryDeploymentsPath: 'help/canary-deployments',
factory({
propsData: {
environments: [mockItem],
canCreateDeployment: false,
canReadEnvironment: true,
canaryDeploymentFeatureId: 'canary_deployment',
showCanaryDeploymentCallout: true,
userCalloutsPath: '/callouts',
lockPromotionSvgPath: '/assets/illustrations/lock-promotion.svg',
helpCanaryDeploymentsPath: 'help/canary-deployments',
},
});
expect(vm.$el.querySelector('.canary-deployment-callout')).not.toBeNull();
expect(wrapper.find('.canary-deployment-callout')).not.toBeNull();
});
});
const deployBoardMockData = {
instances: [
{ status: 'finished', tooltip: 'tanuki-2334 Finished', pod_name: 'production-tanuki-1' },
{ status: 'finished', tooltip: 'tanuki-2335 Finished', pod_name: 'production-tanuki-1' },
{ status: 'finished', tooltip: 'tanuki-2336 Finished', pod_name: 'production-tanuki-1' },
{ status: 'finished', tooltip: 'tanuki-2337 Finished', pod_name: 'production-tanuki-1' },
{ status: 'finished', tooltip: 'tanuki-2338 Finished', pod_name: 'production-tanuki-1' },
{ status: 'finished', tooltip: 'tanuki-2339 Finished', pod_name: 'production-tanuki-1' },
{ status: 'finished', tooltip: 'tanuki-2340 Finished', pod_name: 'production-tanuki-1' },
{ status: 'finished', tooltip: 'tanuki-2334 Finished', pod_name: 'production-tanuki-1' },
{ status: 'finished', tooltip: 'tanuki-2335 Finished', pod_name: 'production-tanuki-1' },
{ status: 'finished', tooltip: 'tanuki-2336 Finished', pod_name: 'production-tanuki-1' },
{ status: 'finished', tooltip: 'tanuki-2337 Finished', pod_name: 'production-tanuki-1' },
{ status: 'finished', tooltip: 'tanuki-2338 Finished', pod_name: 'production-tanuki-1' },
{ status: 'finished', tooltip: 'tanuki-2339 Finished', pod_name: 'production-tanuki-1' },
{ status: 'finished', tooltip: 'tanuki-2340 Finished', pod_name: 'production-tanuki-1' },
{ status: 'deploying', tooltip: 'tanuki-2341 Deploying', pod_name: 'production-tanuki-1' },
{ status: 'deploying', tooltip: 'tanuki-2342 Deploying', pod_name: 'production-tanuki-1' },
{ status: 'deploying', tooltip: 'tanuki-2343 Deploying', pod_name: 'production-tanuki-1' },
{ status: 'failed', tooltip: 'tanuki-2344 Failed', pod_name: 'production-tanuki-1' },
{ status: 'ready', tooltip: 'tanuki-2345 Ready', pod_name: 'production-tanuki-1' },
{ status: 'ready', tooltip: 'tanuki-2346 Ready', pod_name: 'production-tanuki-1' },
{ status: 'preparing', tooltip: 'tanuki-2348 Preparing', pod_name: 'production-tanuki-1' },
{ status: 'preparing', tooltip: 'tanuki-2349 Preparing', pod_name: 'production-tanuki-1' },
{ status: 'preparing', tooltip: 'tanuki-2350 Preparing', pod_name: 'production-tanuki-1' },
{ status: 'preparing', tooltip: 'tanuki-2353 Preparing', pod_name: 'production-tanuki-1' },
{ status: 'waiting', tooltip: 'tanuki-2354 Waiting', pod_name: 'production-tanuki-1' },
{ status: 'waiting', tooltip: 'tanuki-2355 Waiting', pod_name: 'production-tanuki-1' },
{ status: 'waiting', tooltip: 'tanuki-2356 Waiting', pod_name: 'production-tanuki-1' },
],
abort_url: 'url',
rollback_url: 'url',
completion: 100,
status: 'found',
};
export default deployBoardMockData;
import { mount } from '@vue/test-utils';
import { format } from 'timeago.js';
import EnvironmentItem from '~/environments/components/environment_item.vue';
import { environment, folder, tableData } from './mock_data';
describe('Environment item', () => {
let wrapper;
const factory = (options = {}) => {
// This destroys any wrappers created before a nested call to factory reassigns it
if (wrapper && wrapper.destroy) {
wrapper.destroy();
}
wrapper = mount(EnvironmentItem, {
...options,
});
};
beforeEach(() => {
factory({
propsData: {
model: environment,
canReadEnvironment: true,
tableData,
},
});
});
afterEach(() => {
wrapper.destroy();
});
describe('when item is not folder', () => {
it('should render environment name', () => {
expect(wrapper.find('.environment-name').text()).toContain(environment.name);
});
describe('With deployment', () => {
it('should render deployment internal id', () => {
expect(wrapper.find('.deployment-column span').text()).toContain(
environment.last_deployment.iid,
);
expect(wrapper.find('.deployment-column span').text()).toContain('#');
});
it('should render last deployment date', () => {
const formatedDate = format(environment.last_deployment.deployed_at);
expect(wrapper.find('.environment-created-date-timeago').text()).toContain(formatedDate);
});
describe('With user information', () => {
it('should render user avatar with link to profile', () => {
expect(wrapper.find('.js-deploy-user-container').attributes('href')).toEqual(
environment.last_deployment.user.web_url,
);
});
});
describe('With build url', () => {
it('should link to build url provided', () => {
expect(wrapper.find('.build-link').attributes('href')).toEqual(
environment.last_deployment.deployable.build_path,
);
});
it('should render deployable name and id', () => {
expect(wrapper.find('.build-link').attributes('href')).toEqual(
environment.last_deployment.deployable.build_path,
);
});
});
describe('With commit information', () => {
it('should render commit component', () => {
expect(wrapper.find('.js-commit-component')).toBeDefined();
});
});
});
describe('With manual actions', () => {
it('should render actions component', () => {
expect(wrapper.find('.js-manual-actions-container')).toBeDefined();
});
});
describe('With external URL', () => {
it('should render external url component', () => {
expect(wrapper.find('.js-external-url-container')).toBeDefined();
});
});
describe('With stop action', () => {
it('should render stop action component', () => {
expect(wrapper.find('.js-stop-component-container')).toBeDefined();
});
});
describe('With retry action', () => {
it('should render rollback component', () => {
expect(wrapper.find('.js-rollback-component-container')).toBeDefined();
});
});
});
describe('When item is folder', () => {
beforeEach(() => {
factory({
propsData: {
model: folder,
canReadEnvironment: true,
tableData,
},
});
});
afterEach(() => {
wrapper.destroy();
});
it('should render folder icon and name', () => {
expect(wrapper.find('.folder-name').text()).toContain(folder.name);
expect(wrapper.find('.folder-icon')).toBeDefined();
});
it('should render the number of children in a badge', () => {
expect(wrapper.find('.folder-name .badge').text()).toContain(folder.size);
});
});
});
import Vue from 'vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import environmentTableComp from '~/environments/components/environments_table.vue';
import { mount } from '@vue/test-utils';
import EnvironmentTable from '~/environments/components/environments_table.vue';
import { folder } from './mock_data';
const eeOnlyProps = {
canaryDeploymentFeatureId: 'canary_deployment',
showCanaryDeploymentCallout: true,
userCalloutsPath: '/callouts',
lockPromotionSvgPath: '/assets/illustrations/lock-promotion.svg',
helpCanaryDeploymentsPath: 'help/canary-deployments',
};
describe('Environment table', () => {
let Component;
let vm;
let wrapper;
const eeOnlyProps = {
canaryDeploymentFeatureId: 'canary_deployment',
showCanaryDeploymentCallout: true,
userCalloutsPath: '/callouts',
lockPromotionSvgPath: '/assets/illustrations/lock-promotion.svg',
helpCanaryDeploymentsPath: 'help/canary-deployments',
const factory = (options = {}) => {
// This destroys any wrappers created before a nested call to factory reassigns it
if (wrapper && wrapper.destroy) {
wrapper.destroy();
}
wrapper = mount(EnvironmentTable, {
...options,
});
};
beforeEach(() => {
Component = Vue.extend(environmentTableComp);
factory({
propsData: {
environments: [folder],
canReadEnvironment: true,
...eeOnlyProps,
},
});
});
afterEach(() => {
vm.$destroy();
wrapper.destroy();
});
it('Should render a table', () => {
const mockItem = {
name: 'review',
size: 3,
isFolder: true,
latest: {
environment_path: 'url',
},
};
vm = mountComponent(Component, {
environments: [mockItem],
canReadEnvironment: true,
...eeOnlyProps,
});
expect(vm.$el.getAttribute('class')).toContain('ci-table');
expect(wrapper.classes()).toContain('ci-table');
});
describe('sortEnvironments', () => {
......@@ -73,15 +73,17 @@ describe('Environment table', () => {
},
];
vm = mountComponent(Component, {
environments: mockItems,
canReadEnvironment: true,
...eeOnlyProps,
factory({
propsData: {
environments: mockItems,
canReadEnvironment: true,
...eeOnlyProps,
},
});
const [old, newer, older, noDeploy] = mockItems;
expect(vm.sortEnvironments(mockItems)).toEqual([newer, old, older, noDeploy]);
expect(wrapper.vm.sortEnvironments(mockItems)).toEqual([newer, old, older, noDeploy]);
});
it('should push environments with no deployments to the bottom', () => {
......@@ -137,15 +139,17 @@ describe('Environment table', () => {
},
];
vm = mountComponent(Component, {
environments: mockItems,
canReadEnvironment: true,
...eeOnlyProps,
factory({
propsData: {
environments: mockItems,
canReadEnvironment: true,
...eeOnlyProps,
},
});
const [prod, review, staging] = mockItems;
expect(vm.sortEnvironments(mockItems)).toEqual([review, staging, prod]);
expect(wrapper.vm.sortEnvironments(mockItems)).toEqual([review, staging, prod]);
});
it('should sort environments by folder first', () => {
......@@ -174,15 +178,17 @@ describe('Environment table', () => {
},
];
vm = mountComponent(Component, {
environments: mockItems,
canReadEnvironment: true,
...eeOnlyProps,
factory({
propsData: {
environments: mockItems,
canReadEnvironment: true,
...eeOnlyProps,
},
});
const [old, newer, older] = mockItems;
expect(vm.sortEnvironments(mockItems)).toEqual([older, newer, old]);
expect(wrapper.vm.sortEnvironments(mockItems)).toEqual([older, newer, old]);
});
it('should break ties by name', () => {
......@@ -201,15 +207,17 @@ describe('Environment table', () => {
},
];
vm = mountComponent(Component, {
environments: mockItems,
canReadEnvironment: true,
...eeOnlyProps,
factory({
propsData: {
environments: mockItems,
canReadEnvironment: true,
...eeOnlyProps,
},
});
const [old, newer, older] = mockItems;
expect(vm.sortEnvironments(mockItems)).toEqual([older, newer, old]);
expect(wrapper.vm.sortEnvironments(mockItems)).toEqual([older, newer, old]);
});
});
......@@ -250,19 +258,21 @@ describe('Environment table', () => {
const [production, review, staging] = mockItems;
const [addcibuildstatus, master] = mockItems[1].children;
vm = mountComponent(Component, {
environments: mockItems,
canReadEnvironment: true,
...eeOnlyProps,
factory({
propsData: {
environments: mockItems,
canReadEnvironment: true,
...eeOnlyProps,
},
});
expect(vm.sortedEnvironments.map(env => env.name)).toEqual([
expect(wrapper.vm.sortedEnvironments.map(env => env.name)).toEqual([
review.name,
staging.name,
production.name,
]);
expect(vm.sortedEnvironments[0].children).toEqual([master, addcibuildstatus]);
expect(wrapper.vm.sortedEnvironments[0].children).toEqual([master, addcibuildstatus]);
});
});
});
const environment = {
name: 'production',
size: 1,
state: 'stopped',
external_url: 'http://external.com',
environment_type: null,
last_deployment: {
id: 66,
iid: 6,
sha: '500aabcb17c97bdcf2d0c410b70cb8556f0362dd',
ref: {
name: 'master',
ref_url: 'root/ci-folders/tree/master',
},
tag: true,
'last?': true,
user: {
name: 'Administrator',
username: 'root',
id: 1,
state: 'active',
avatar_url:
'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
web_url: 'http://localhost:3000/root',
},
commit: {
id: '500aabcb17c97bdcf2d0c410b70cb8556f0362dd',
short_id: '500aabcb',
title: 'Update .gitlab-ci.yml',
author_name: 'Administrator',
author_email: 'admin@example.com',
created_at: '2016-11-07T18:28:13.000+00:00',
message: 'Update .gitlab-ci.yml',
author: {
name: 'Administrator',
username: 'root',
id: 1,
state: 'active',
avatar_url:
'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
web_url: 'http://localhost:3000/root',
},
commit_path: '/root/ci-folders/tree/500aabcb17c97bdcf2d0c410b70cb8556f0362dd',
},
deployable: {
id: 1279,
name: 'deploy',
build_path: '/root/ci-folders/builds/1279',
retry_path: '/root/ci-folders/builds/1279/retry',
created_at: '2016-11-29T18:11:58.430Z',
updated_at: '2016-11-29T18:11:58.430Z',
},
manual_actions: [
{
name: 'action',
play_path: '/play',
},
],
deployed_at: '2016-11-29T18:11:58.430Z',
},
has_stop_action: true,
environment_path: 'root/ci-folders/environments/31',
log_path: 'root/ci-folders/environments/31/logs',
created_at: '2016-11-07T11:11:16.525Z',
updated_at: '2016-11-10T15:55:58.778Z',
};
const folder = {
name: 'review',
folderName: 'review',
size: 3,
isFolder: true,
environment_path: 'url',
log_path: 'url',
latest: {
environment_path: 'url',
},
};
const tableData = {
name: {
title: 'Environment',
spacing: 'section-15',
},
deploy: {
title: 'Deployment',
spacing: 'section-10',
},
build: {
title: 'Job',
spacing: 'section-15',
},
commit: {
title: 'Commit',
spacing: 'section-20',
},
date: {
title: 'Updated',
spacing: 'section-10',
},
actions: {
spacing: 'section-25',
},
};
export { environment, folder, tableData };
import { format } from 'timeago.js';
import Vue from 'vue';
import environmentItemComp from '~/environments/components/environment_item.vue';
const tableData = {
name: {
title: 'Environment',
spacing: 'section-15',
},
deploy: {
title: 'Deployment',
spacing: 'section-10',
},
build: {
title: 'Job',
spacing: 'section-15',
},
commit: {
title: 'Commit',
spacing: 'section-20',
},
date: {
title: 'Updated',
spacing: 'section-10',
},
actions: {
spacing: 'section-25',
},
};
describe('Environment item', () => {
let EnvironmentItem;
beforeEach(() => {
EnvironmentItem = Vue.extend(environmentItemComp);
});
describe('When item is folder', () => {
let mockItem;
let component;
beforeEach(() => {
mockItem = {
name: 'review',
folderName: 'review',
size: 3,
isFolder: true,
environment_path: 'url',
log_path: 'url',
};
component = new EnvironmentItem({
propsData: {
model: mockItem,
canReadEnvironment: true,
tableData,
},
}).$mount();
});
it('should render folder icon and name', () => {
expect(component.$el.querySelector('.folder-name').textContent).toContain(mockItem.name);
expect(component.$el.querySelector('.folder-icon')).toBeDefined();
});
it('should render the number of children in a badge', () => {
expect(component.$el.querySelector('.folder-name .badge').textContent).toContain(
mockItem.size,
);
});
});
describe('when item is not folder', () => {
let environment;
let component;
beforeEach(() => {
environment = {
name: 'production',
size: 1,
state: 'stopped',
external_url: 'http://external.com',
environment_type: null,
last_deployment: {
id: 66,
iid: 6,
sha: '500aabcb17c97bdcf2d0c410b70cb8556f0362dd',
ref: {
name: 'master',
ref_url: 'root/ci-folders/tree/master',
},
tag: true,
'last?': true,
user: {
name: 'Administrator',
username: 'root',
id: 1,
state: 'active',
avatar_url:
'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
web_url: 'http://localhost:3000/root',
},
commit: {
id: '500aabcb17c97bdcf2d0c410b70cb8556f0362dd',
short_id: '500aabcb',
title: 'Update .gitlab-ci.yml',
author_name: 'Administrator',
author_email: 'admin@example.com',
created_at: '2016-11-07T18:28:13.000+00:00',
message: 'Update .gitlab-ci.yml',
author: {
name: 'Administrator',
username: 'root',
id: 1,
state: 'active',
avatar_url:
'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
web_url: 'http://localhost:3000/root',
},
commit_path: '/root/ci-folders/tree/500aabcb17c97bdcf2d0c410b70cb8556f0362dd',
},
deployable: {
id: 1279,
name: 'deploy',
build_path: '/root/ci-folders/builds/1279',
retry_path: '/root/ci-folders/builds/1279/retry',
created_at: '2016-11-29T18:11:58.430Z',
updated_at: '2016-11-29T18:11:58.430Z',
},
manual_actions: [
{
name: 'action',
play_path: '/play',
},
],
deployed_at: '2016-11-29T18:11:58.430Z',
},
has_stop_action: true,
environment_path: 'root/ci-folders/environments/31',
log_path: 'root/ci-folders/environments/31/logs',
created_at: '2016-11-07T11:11:16.525Z',
updated_at: '2016-11-10T15:55:58.778Z',
};
component = new EnvironmentItem({
propsData: {
model: environment,
canReadEnvironment: true,
tableData,
},
}).$mount();
});
it('should render environment name', () => {
expect(component.$el.querySelector('.environment-name').textContent).toContain(
environment.name,
);
});
describe('With deployment', () => {
it('should render deployment internal id', () => {
expect(component.$el.querySelector('.deployment-column span').textContent).toContain(
environment.last_deployment.iid,
);
expect(component.$el.querySelector('.deployment-column span').textContent).toContain('#');
});
it('should render last deployment date', () => {
const formatedDate = format(environment.last_deployment.deployed_at);
expect(
component.$el.querySelector('.environment-created-date-timeago').textContent,
).toContain(formatedDate);
});
describe('With user information', () => {
it('should render user avatar with link to profile', () => {
expect(
component.$el.querySelector('.js-deploy-user-container').getAttribute('href'),
).toEqual(environment.last_deployment.user.web_url);
});
});
describe('With build url', () => {
it('should link to build url provided', () => {
expect(component.$el.querySelector('.build-link').getAttribute('href')).toEqual(
environment.last_deployment.deployable.build_path,
);
});
it('should render deployable name and id', () => {
expect(component.$el.querySelector('.build-link').getAttribute('href')).toEqual(
environment.last_deployment.deployable.build_path,
);
});
});
describe('With commit information', () => {
it('should render commit component', () => {
expect(component.$el.querySelector('.js-commit-component')).toBeDefined();
});
});
});
describe('With manual actions', () => {
it('should render actions component', () => {
expect(component.$el.querySelector('.js-manual-actions-container')).toBeDefined();
});
});
describe('With external URL', () => {
it('should render external url component', () => {
expect(component.$el.querySelector('.js-external-url-container')).toBeDefined();
});
});
describe('With stop action', () => {
it('should render stop action component', () => {
expect(component.$el.querySelector('.js-stop-component-container')).toBeDefined();
});
});
describe('With retry action', () => {
it('should render rollback component', () => {
expect(component.$el.querySelector('.js-rollback-component-container')).toBeDefined();
});
});
});
});
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