Commit 66145cdf authored by Jose Ivan Vargas's avatar Jose Ivan Vargas

Merge branch 'revert-1f1ce4a6' into 'master'

Revert "Merge branch 'fe-fix-incorrect-mitt-usage' into 'master'"

See merge request gitlab-org/gitlab!35074
parents 120d4147 aa538823
...@@ -89,10 +89,10 @@ export default { ...@@ -89,10 +89,10 @@ export default {
eventHub.$emit('clearDetailIssue', isMultiSelect); eventHub.$emit('clearDetailIssue', isMultiSelect);
if (isMultiSelect) { if (isMultiSelect) {
eventHub.$emit('newDetailIssue', [this.issue, isMultiSelect]); eventHub.$emit('newDetailIssue', this.issue, isMultiSelect);
} }
} else { } else {
eventHub.$emit('newDetailIssue', [this.issue, isMultiSelect]); eventHub.$emit('newDetailIssue', this.issue, isMultiSelect);
boardsStore.setListDetail(this.list); boardsStore.setListDetail(this.list);
} }
} }
......
...@@ -202,7 +202,7 @@ export default () => { ...@@ -202,7 +202,7 @@ export default () => {
updateTokens() { updateTokens() {
this.filterManager.updateTokens(); this.filterManager.updateTokens();
}, },
updateDetailIssue([newIssue, multiSelect = false]) { updateDetailIssue(newIssue, multiSelect = false) {
const { sidebarInfoEndpoint } = newIssue; const { sidebarInfoEndpoint } = newIssue;
if (sidebarInfoEndpoint && newIssue.subscribed === undefined) { if (sidebarInfoEndpoint && newIssue.subscribed === undefined) {
newIssue.setFetchingState('subscriptions', true); newIssue.setFetchingState('subscriptions', true);
......
...@@ -30,12 +30,9 @@ export default { ...@@ -30,12 +30,9 @@ export default {
doAction() { doAction() {
this.isLoading = true; this.isLoading = true;
eventHub.$emit(`${this.type}.key`, [ eventHub.$emit(`${this.type}.key`, this.deployKey, () => {
this.deployKey, this.isLoading = false;
() => { });
this.isLoading = false;
},
]);
}, },
}, },
}; };
......
...@@ -90,13 +90,13 @@ export default { ...@@ -90,13 +90,13 @@ export default {
return new Flash(s__('DeployKeys|Error getting deploy keys')); return new Flash(s__('DeployKeys|Error getting deploy keys'));
}); });
}, },
enableKey([deployKey]) { enableKey(deployKey) {
this.service this.service
.enableKey(deployKey.id) .enableKey(deployKey.id)
.then(this.fetchKeys) .then(this.fetchKeys)
.catch(() => new Flash(s__('DeployKeys|Error enabling deploy key'))); .catch(() => new Flash(s__('DeployKeys|Error enabling deploy key')));
}, },
disableKey([deployKey, callback]) { disableKey(deployKey, callback) {
if ( if (
// eslint-disable-next-line no-alert // eslint-disable-next-line no-alert
window.confirm(s__('DeployKeys|You are going to remove this deploy key. Are you sure?')) window.confirm(s__('DeployKeys|You are going to remove this deploy key. Are you sure?'))
......
...@@ -123,7 +123,7 @@ export default { ...@@ -123,7 +123,7 @@ export default {
this.updateGroups(res, Boolean(filterGroupsBy)); this.updateGroups(res, Boolean(filterGroupsBy));
}); });
}, },
fetchPage([page, filterGroupsBy, sortBy, archived]) { fetchPage(page, filterGroupsBy, sortBy, archived) {
this.isLoading = true; this.isLoading = true;
return this.fetchGroups({ return this.fetchGroups({
...@@ -169,7 +169,7 @@ export default { ...@@ -169,7 +169,7 @@ export default {
parentGroup.isOpen = false; parentGroup.isOpen = false;
} }
}, },
showLeaveGroupModal([group, parentGroup]) { showLeaveGroupModal(group, parentGroup) {
const { fullName } = group; const { fullName } = group;
this.targetGroup = group; this.targetGroup = group;
this.targetParentGroup = parentGroup; this.targetParentGroup = parentGroup;
......
...@@ -35,12 +35,7 @@ export default { ...@@ -35,12 +35,7 @@ export default {
const filterGroupsParam = getParameterByName('filter'); const filterGroupsParam = getParameterByName('filter');
const sortParam = getParameterByName('sort'); const sortParam = getParameterByName('sort');
const archivedParam = getParameterByName('archived'); const archivedParam = getParameterByName('archived');
eventHub.$emit(`${this.action}fetchPage`, [ eventHub.$emit(`${this.action}fetchPage`, page, filterGroupsParam, sortParam, archivedParam);
page,
filterGroupsParam,
sortParam,
archivedParam,
]);
}, },
}, },
}; };
......
...@@ -37,7 +37,7 @@ export default { ...@@ -37,7 +37,7 @@ export default {
}, },
methods: { methods: {
onLeaveGroup() { onLeaveGroup() {
eventHub.$emit(`${this.action}showLeaveGroupModal`, [this.group, this.parentGroup]); eventHub.$emit(`${this.action}showLeaveGroupModal`, this.group, this.parentGroup);
}, },
}, },
}; };
......
import mitt from 'mitt'; import Vue from 'vue';
/**
* Return a Vue like event hub
*
* - $on
* - $off
* - $once
* - $emit
*
* Please note, this was once implemented with `mitt`, but since then has been reverted
* because of some API issues. https://gitlab.com/gitlab-org/gitlab/-/merge_requests/35074
*
* We'd like to shy away from using a full fledged Vue instance from this in the future.
*/
export default () => { export default () => {
const emitter = mitt(); return new Vue();
const originalEmit = emitter.emit;
emitter.once = (event, handler) => {
const wrappedHandler = evt => {
handler(evt);
emitter.off(event, wrappedHandler);
};
emitter.on(event, wrappedHandler);
};
emitter.emit = (event, args = []) => {
originalEmit(event, args);
};
emitter.$on = emitter.on;
emitter.$once = emitter.once;
emitter.$off = emitter.off;
emitter.$emit = emitter.emit;
return emitter;
}; };
import createEventHub from '~/helpers/event_hub_factory'; import Vue from 'vue';
const eventHub = createEventHub(); const eventHub = new Vue();
// TODO: remove eventHub hack after code splitting refactor // TODO: remove eventHub hack after code splitting refactor
window.emitSidebarEvent = (...args) => eventHub.$emit(...args); window.emitSidebarEvent = (...args) => eventHub.$emit(...args);
......
/* eslint-disable class-methods-use-this, no-param-reassign */ /* eslint-disable class-methods-use-this, no-param-reassign */
/* /*
no-param-reassign is disabled because one method of BoardsStoreEE no-param-reassign is disabled because one method of BoardsStoreEE
modify the passed parameter in conformity with non-ee BoardsStore. modify the passed parameter in conformity with non-ee BoardsStore.
*/ */
...@@ -190,7 +190,7 @@ class BoardsStoreEE { ...@@ -190,7 +190,7 @@ class BoardsStoreEE {
issue.epic = newEpic; issue.epic = newEpic;
} }
updateWeight([newWeight, id]) { updateWeight(newWeight, id) {
const { issue } = this.store.detail; const { issue } = this.store.detail;
if (issue.id === id && issue.sidebarInfoEndpoint) { if (issue.id === id && issue.sidebarInfoEndpoint) {
issue.setLoadingState('weight', true); issue.setLoadingState('weight', true);
......
...@@ -27,7 +27,7 @@ export default { ...@@ -27,7 +27,7 @@ export default {
}, },
methods: { methods: {
onUpdateWeight([newWeight]) { onUpdateWeight(newWeight) {
this.mediator.updateWeight(newWeight).catch(() => { this.mediator.updateWeight(newWeight).catch(() => {
Flash(__('Error occurred while updating the issue weight')); Flash(__('Error occurred while updating the issue weight'));
}); });
......
...@@ -140,14 +140,14 @@ export default { ...@@ -140,14 +140,14 @@ export default {
$(this.$el).trigger('hidden.gl.dropdown'); $(this.$el).trigger('hidden.gl.dropdown');
if (isNewValue) { if (isNewValue) {
eventHub.$emit('updateWeight', [value, this.id]); eventHub.$emit('updateWeight', value, this.id);
} }
this.showEditField(false); this.showEditField(false);
} }
}, },
removeWeight() { removeWeight() {
eventHub.$emit('updateWeight', ['', this.id]); eventHub.$emit('updateWeight', '', this.id);
}, },
}, },
}; };
......
...@@ -125,7 +125,7 @@ describe('Weight', () => { ...@@ -125,7 +125,7 @@ describe('Weight', () => {
vm.$refs.editableField.dispatchEvent(event); vm.$refs.editableField.dispatchEvent(event);
expect(vm.hasValidInput).toBe(true); expect(vm.hasValidInput).toBe(true);
expect(eventHub.$emit).toHaveBeenCalledWith('updateWeight', [expectedWeightValue, ID]); expect(eventHub.$emit).toHaveBeenCalledWith('updateWeight', expectedWeightValue, ID);
}); });
}); });
...@@ -143,7 +143,7 @@ describe('Weight', () => { ...@@ -143,7 +143,7 @@ describe('Weight', () => {
return vm.$nextTick(() => { return vm.$nextTick(() => {
expect(vm.hasValidInput).toBe(true); expect(vm.hasValidInput).toBe(true);
expect(eventHub.$emit).toHaveBeenCalledWith('updateWeight', ['', ID]); expect(eventHub.$emit).toHaveBeenCalledWith('updateWeight', '', ID);
}); });
}); });
......
...@@ -196,7 +196,7 @@ describe('Board card', () => { ...@@ -196,7 +196,7 @@ describe('Board card', () => {
wrapper.trigger('mousedown'); wrapper.trigger('mousedown');
wrapper.trigger('mouseup'); wrapper.trigger('mouseup');
expect(eventHub.$emit).toHaveBeenCalledWith('newDetailIssue', [wrapper.vm.issue, undefined]); expect(eventHub.$emit).toHaveBeenCalledWith('newDetailIssue', wrapper.vm.issue, undefined);
expect(boardsStore.detail.list).toEqual(wrapper.vm.list); expect(boardsStore.detail.list).toEqual(wrapper.vm.list);
}); });
......
...@@ -32,7 +32,7 @@ describe('Deploy keys action btn', () => { ...@@ -32,7 +32,7 @@ describe('Deploy keys action btn', () => {
wrapper.trigger('click'); wrapper.trigger('click');
return wrapper.vm.$nextTick().then(() => { return wrapper.vm.$nextTick().then(() => {
expect(eventHub.$emit).toHaveBeenCalledWith('enable.key', [deployKey, expect.any(Function)]); expect(eventHub.$emit).toHaveBeenCalledWith('enable.key', deployKey, expect.anything());
}); });
}); });
......
...@@ -88,7 +88,7 @@ describe('Deploy keys app component', () => { ...@@ -88,7 +88,7 @@ describe('Deploy keys app component', () => {
jest.spyOn(wrapper.vm.service, 'getKeys').mockImplementation(() => {}); jest.spyOn(wrapper.vm.service, 'getKeys').mockImplementation(() => {});
jest.spyOn(wrapper.vm.service, 'enableKey').mockImplementation(() => Promise.resolve()); jest.spyOn(wrapper.vm.service, 'enableKey').mockImplementation(() => Promise.resolve());
eventHub.$emit('enable.key', [key]); eventHub.$emit('enable.key', key);
return wrapper.vm.$nextTick(); return wrapper.vm.$nextTick();
}) })
...@@ -106,7 +106,7 @@ describe('Deploy keys app component', () => { ...@@ -106,7 +106,7 @@ describe('Deploy keys app component', () => {
jest.spyOn(wrapper.vm.service, 'getKeys').mockImplementation(() => {}); jest.spyOn(wrapper.vm.service, 'getKeys').mockImplementation(() => {});
jest.spyOn(wrapper.vm.service, 'disableKey').mockImplementation(() => Promise.resolve()); jest.spyOn(wrapper.vm.service, 'disableKey').mockImplementation(() => Promise.resolve());
eventHub.$emit('disable.key', [key]); eventHub.$emit('disable.key', key);
return wrapper.vm.$nextTick(); return wrapper.vm.$nextTick();
}) })
...@@ -124,7 +124,7 @@ describe('Deploy keys app component', () => { ...@@ -124,7 +124,7 @@ describe('Deploy keys app component', () => {
jest.spyOn(wrapper.vm.service, 'getKeys').mockImplementation(() => {}); jest.spyOn(wrapper.vm.service, 'getKeys').mockImplementation(() => {});
jest.spyOn(wrapper.vm.service, 'disableKey').mockImplementation(() => Promise.resolve()); jest.spyOn(wrapper.vm.service, 'disableKey').mockImplementation(() => Promise.resolve());
eventHub.$emit('remove.key', [key]); eventHub.$emit('remove.key', key);
return wrapper.vm.$nextTick(); return wrapper.vm.$nextTick();
}) })
......
...@@ -184,7 +184,7 @@ describe('AppComponent', () => { ...@@ -184,7 +184,7 @@ describe('AppComponent', () => {
jest.spyOn(window.history, 'replaceState').mockImplementation(() => {}); jest.spyOn(window.history, 'replaceState').mockImplementation(() => {});
jest.spyOn($, 'scrollTo').mockImplementation(() => {}); jest.spyOn($, 'scrollTo').mockImplementation(() => {});
const fetchPagePromise = vm.fetchPage([2, null, null, true]); const fetchPagePromise = vm.fetchPage(2, null, null, true);
expect(vm.isLoading).toBe(true); expect(vm.isLoading).toBe(true);
expect(vm.fetchGroups).toHaveBeenCalledWith({ expect(vm.fetchGroups).toHaveBeenCalledWith({
...@@ -275,7 +275,7 @@ describe('AppComponent', () => { ...@@ -275,7 +275,7 @@ describe('AppComponent', () => {
expect(vm.targetGroup).toBe(null); expect(vm.targetGroup).toBe(null);
expect(vm.targetParentGroup).toBe(null); expect(vm.targetParentGroup).toBe(null);
vm.showLeaveGroupModal([group, mockParentGroupItem]); vm.showLeaveGroupModal(group, mockParentGroupItem);
expect(vm.targetGroup).not.toBe(null); expect(vm.targetGroup).not.toBe(null);
expect(vm.targetParentGroup).not.toBe(null); expect(vm.targetParentGroup).not.toBe(null);
...@@ -286,7 +286,7 @@ describe('AppComponent', () => { ...@@ -286,7 +286,7 @@ describe('AppComponent', () => {
expect(vm.showModal).toBe(false); expect(vm.showModal).toBe(false);
expect(vm.groupLeaveConfirmationMessage).toBe(''); expect(vm.groupLeaveConfirmationMessage).toBe('');
vm.showLeaveGroupModal([group, mockParentGroupItem]); vm.showLeaveGroupModal(group, mockParentGroupItem);
expect(vm.showModal).toBe(true); expect(vm.showModal).toBe(true);
expect(vm.groupLeaveConfirmationMessage).toBe( expect(vm.groupLeaveConfirmationMessage).toBe(
...@@ -298,7 +298,7 @@ describe('AppComponent', () => { ...@@ -298,7 +298,7 @@ describe('AppComponent', () => {
describe('hideLeaveGroupModal', () => { describe('hideLeaveGroupModal', () => {
it('hides modal confirmation which is shown before leaving the group', () => { it('hides modal confirmation which is shown before leaving the group', () => {
const group = { ...mockParentGroupItem }; const group = { ...mockParentGroupItem };
vm.showLeaveGroupModal([group, mockParentGroupItem]); vm.showLeaveGroupModal(group, mockParentGroupItem);
expect(vm.showModal).toBe(true); expect(vm.showModal).toBe(true);
vm.hideLeaveGroupModal(); vm.hideLeaveGroupModal();
......
...@@ -41,12 +41,13 @@ describe('GroupsComponent', () => { ...@@ -41,12 +41,13 @@ describe('GroupsComponent', () => {
vm.change(2); vm.change(2);
expect(eventHub.$emit).toHaveBeenCalledWith('fetchPage', [ expect(eventHub.$emit).toHaveBeenCalledWith(
'fetchPage',
2, 2,
expect.any(Object), expect.any(Object),
expect.any(Object), expect.any(Object),
expect.any(Object), expect.any(Object),
]); );
}); });
}); });
}); });
......
...@@ -31,10 +31,11 @@ describe('ItemActionsComponent', () => { ...@@ -31,10 +31,11 @@ describe('ItemActionsComponent', () => {
jest.spyOn(eventHub, '$emit').mockImplementation(() => {}); jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
vm.onLeaveGroup(); vm.onLeaveGroup();
expect(eventHub.$emit).toHaveBeenCalledWith('showLeaveGroupModal', [ expect(eventHub.$emit).toHaveBeenCalledWith(
'showLeaveGroupModal',
vm.group, vm.group,
vm.parentGroup, vm.parentGroup,
]); );
}); });
}); });
}); });
......
import createEventHub from '~/helpers/event_hub_factory'; import createEventHub from '~/helpers/event_hub_factory';
const TEST_EVENT = 'foobar';
describe('event bus factory', () => { describe('event bus factory', () => {
let eventBus; let eventBus;
let handler;
beforeEach(() => { beforeEach(() => {
eventBus = createEventHub(); eventBus = createEventHub();
handler = jest.fn();
}); });
afterEach(() => { afterEach(() => {
eventBus = null; eventBus = null;
}); });
describe('underlying module', () => { describe('instance', () => {
let mitt; it.each`
method
${'$on'}
${'$once'}
${'$off'}
${'$emit'}
`('has $method method', ({ method }) => {
expect(eventBus[method]).toEqual(expect.any(Function));
});
});
describe('$on', () => {
beforeEach(() => { beforeEach(() => {
jest.resetModules(); eventBus.$on(TEST_EVENT, handler);
jest.mock('mitt'); });
// eslint-disable-next-line global-require it('calls handler when event is emitted', () => {
mitt = require('mitt'); eventBus.$emit(TEST_EVENT);
mitt.mockReturnValue(() => ({})); expect(handler).toHaveBeenCalledWith();
});
const createEventHubActual = jest.requireActual('~/helpers/event_hub_factory').default; it('calls handler with multiple args', () => {
eventBus = createEventHubActual(); eventBus.$emit(TEST_EVENT, 'arg1', 'arg2', 'arg3');
expect(handler).toHaveBeenCalledWith('arg1', 'arg2', 'arg3');
}); });
it('creates an emitter', () => { it('calls handler multiple times', () => {
expect(mitt).toHaveBeenCalled(); eventBus.$emit(TEST_EVENT, 'arg1', 'arg2', 'arg3');
eventBus.$emit(TEST_EVENT, 'arg1', 'arg2', 'arg3');
expect(handler).toHaveBeenCalledTimes(2);
}); });
});
describe('instance', () => { it('does not call handler after $off with handler', () => {
it.each` eventBus.$off(TEST_EVENT, handler);
method
${'on'} eventBus.$emit(TEST_EVENT);
${'once'}
${'off'} expect(handler).not.toHaveBeenCalled();
${'emit'}
`('binds $$method to $method ', ({ method }) => {
expect(typeof eventBus[method]).toBe('function');
expect(eventBus[method]).toBe(eventBus[`$${method}`]);
}); });
});
describe('once', () => { it('does not call handler after $off', () => {
const event = 'foobar'; eventBus.$off(TEST_EVENT);
let handler;
beforeEach(() => { eventBus.$emit(TEST_EVENT);
jest.spyOn(eventBus, 'on');
jest.spyOn(eventBus, 'off'); expect(handler).not.toHaveBeenCalled();
handler = jest.fn();
eventBus.once(event, handler);
}); });
});
it('calls on internally', () => { describe('$once', () => {
expect(eventBus.on).toHaveBeenCalled(); beforeEach(() => {
eventBus.$once(TEST_EVENT, handler);
}); });
it('calls handler when event is emitted', () => { it('calls handler when event is emitted', () => {
eventBus.emit(event); eventBus.$emit(TEST_EVENT);
expect(handler).toHaveBeenCalled(); expect(handler).toHaveBeenCalled();
}); });
it('calls off when event is emitted', () => {
eventBus.emit(event);
expect(eventBus.off).toHaveBeenCalled();
});
it('calls the handler only once when event is emitted multiple times', () => { it('calls the handler only once when event is emitted multiple times', () => {
eventBus.emit(event); eventBus.$emit(TEST_EVENT);
eventBus.emit(event); eventBus.$emit(TEST_EVENT);
expect(handler).toHaveBeenCalledTimes(1); expect(handler).toHaveBeenCalledTimes(1);
}); });
...@@ -80,14 +87,18 @@ describe('event bus factory', () => { ...@@ -80,14 +87,18 @@ describe('event bus factory', () => {
handler = jest.fn().mockImplementation(() => { handler = jest.fn().mockImplementation(() => {
throw new Error(); throw new Error();
}); });
eventBus.once(event, handler); eventBus.$once(TEST_EVENT, handler);
}); });
it('calls off when event is emitted', () => { it('calls off when event is emitted', () => {
expect(() => { expect(() => {
eventBus.emit(event); eventBus.$emit(TEST_EVENT);
}).toThrow(); }).toThrow();
expect(eventBus.off).toHaveBeenCalled(); expect(() => {
eventBus.$emit(TEST_EVENT);
}).not.toThrow();
expect(handler).toHaveBeenCalledTimes(1);
}); });
}); });
}); });
......
...@@ -8232,11 +8232,6 @@ mississippi@^3.0.0: ...@@ -8232,11 +8232,6 @@ mississippi@^3.0.0:
stream-each "^1.1.0" stream-each "^1.1.0"
through2 "^2.0.0" through2 "^2.0.0"
mitt@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/mitt/-/mitt-1.2.0.tgz#cb24e6569c806e31bd4e3995787fe38a04fdf90d"
integrity sha512-r6lj77KlwqLhIUku9UWYes7KJtsczvolZkzp8hbaDPPaE24OmWl5s539Mytlj22siEQKosZ26qCBgda2PKwoJw==
mixin-deep@^1.2.0: mixin-deep@^1.2.0:
version "1.3.2" version "1.3.2"
resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566"
...@@ -9763,16 +9758,11 @@ rc@^1.2.8, rc@~1.2.7: ...@@ -9763,16 +9758,11 @@ rc@^1.2.8, rc@~1.2.7:
minimist "^1.2.0" minimist "^1.2.0"
strip-json-comments "~2.0.1" strip-json-comments "~2.0.1"
react-is@^16.12.0: react-is@^16.12.0, react-is@^16.8.4:
version "16.13.1" version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
react-is@^16.8.4:
version "16.8.6"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16"
integrity sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==
read-pkg-up@^1.0.1: read-pkg-up@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02"
......
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