Commit 81062a7a authored by Denys Mishunov's avatar Denys Mishunov

Merge branch '341121-scoped-label-fi' into 'master'

Fix sidebar labels scoped label selection bug

See merge request gitlab-org/gitlab!72693
parents dfa36ea9 e9e8f919
...@@ -688,17 +688,20 @@ export const searchBy = (query = '', searchSpace = {}) => { ...@@ -688,17 +688,20 @@ export const searchBy = (query = '', searchSpace = {}) => {
*/ */
export const isScopedLabel = ({ title = '' } = {}) => title.includes(SCOPED_LABEL_DELIMITER); export const isScopedLabel = ({ title = '' } = {}) => title.includes(SCOPED_LABEL_DELIMITER);
const scopedLabelRegex = new RegExp(`(.*)${SCOPED_LABEL_DELIMITER}.*`);
/** /**
* Returns the base value of the scoped label * Returns the key of a scoped label.
* * For example:
* Expected Label to be an Object with `title` as a key: * - returns `scoped` if the label is `scoped::value`.
* { title: 'LabelTitle', ...otherProperties }; * - returns `scoped::label` if the label is `scoped::label::value`.
* *
* @param {Object} label * @param {Object} label object containing `title` property
* @returns String * @returns String scoped label key, or full label if it is not a scoped label
*/ */
export const scopedLabelKey = ({ title = '' }) => export const scopedLabelKey = ({ title = '' }) => {
isScopedLabel({ title }) && title.split(SCOPED_LABEL_DELIMITER)[0]; return title.replace(scopedLabelRegex, '$1');
};
// Methods to set and get Cookie // Methods to set and get Cookie
export const setCookie = (name, value) => Cookies.set(name, value, { expires: 365 }); export const setCookie = (name, value) => Cookies.set(name, value, { expires: 365 });
......
import { isScopedLabel, scopedLabelKey } from '~/lib/utils/common_utils'; import { isScopedLabel, scopedLabelKey } from '~/lib/utils/common_utils';
import { SCOPED_LABEL_DELIMITER } from '~/vue_shared/components/sidebar/labels_select_widget/constants';
import { DropdownVariant } from '../constants'; import { DropdownVariant } from '../constants';
import * as types from './mutation_types'; import * as types from './mutation_types';
...@@ -67,9 +66,11 @@ export default { ...@@ -67,9 +66,11 @@ export default {
} }
if (isScopedLabel(candidateLabel)) { if (isScopedLabel(candidateLabel)) {
const scopedKeyWithDelimiter = `${scopedLabelKey(candidateLabel)}${SCOPED_LABEL_DELIMITER}`;
const currentActiveScopedLabel = state.labels.find( const currentActiveScopedLabel = state.labels.find(
({ title }) => title.startsWith(scopedKeyWithDelimiter) && title !== candidateLabel.title, ({ set, title }) =>
set &&
title !== candidateLabel.title &&
scopedLabelKey({ title }) === scopedLabelKey(candidateLabel),
); );
if (currentActiveScopedLabel) { if (currentActiveScopedLabel) {
......
...@@ -1008,6 +1008,21 @@ describe('common_utils', () => { ...@@ -1008,6 +1008,21 @@ describe('common_utils', () => {
}); });
}); });
describe('scopedLabelKey', () => {
it.each`
label | expectedLabelKey
${undefined} | ${''}
${''} | ${''}
${'title'} | ${'title'}
${'scoped::value'} | ${'scoped'}
${'scoped::label::value'} | ${'scoped::label'}
${'scoped::label-some::value'} | ${'scoped::label-some'}
${'scoped::label::some::value'} | ${'scoped::label::some'}
`('returns "$expectedLabelKey" when label is "$label"', ({ label, expectedLabelKey }) => {
expect(commonUtils.scopedLabelKey({ title: label })).toBe(expectedLabelKey);
});
});
describe('getDashPath', () => { describe('getDashPath', () => {
it('returns the path following /-/', () => { it('returns the path following /-/', () => {
expect(commonUtils.getDashPath('/some/-/url-with-dashes-/')).toEqual('url-with-dashes-/'); expect(commonUtils.getDashPath('/some/-/url-with-dashes-/')).toEqual('url-with-dashes-/');
......
import { cloneDeep } from 'lodash';
import * as types from '~/vue_shared/components/sidebar/labels_select_vue/store/mutation_types'; import * as types from '~/vue_shared/components/sidebar/labels_select_vue/store/mutation_types';
import mutations from '~/vue_shared/components/sidebar/labels_select_vue/store/mutations'; import mutations from '~/vue_shared/components/sidebar/labels_select_vue/store/mutations';
...@@ -153,47 +154,40 @@ describe('LabelsSelect Mutations', () => { ...@@ -153,47 +154,40 @@ describe('LabelsSelect Mutations', () => {
}); });
describe(`${types.UPDATE_SELECTED_LABELS}`, () => { describe(`${types.UPDATE_SELECTED_LABELS}`, () => {
let labels; const labels = [
beforeEach(() => {
labels = [
{ id: 1, title: 'scoped' }, { id: 1, title: 'scoped' },
{ id: 2, title: 'scoped::one', set: false }, { id: 2, title: 'scoped::label::one', set: false },
{ id: 3, title: 'scoped::test', set: true }, { id: 3, title: 'scoped::label::two', set: false },
{ id: 4, title: '' }, { id: 4, title: 'scoped::label::three', set: true },
{ id: 5, title: 'scoped::one', set: false },
{ id: 6, title: 'scoped::two', set: false },
{ id: 7, title: 'scoped::three', set: true },
{ id: 8, title: '' },
]; ];
});
it('updates `state.labels` to include `touched` and `set` props based on provided `labels` param', () => {
const updatedLabelIds = [2];
const state = {
labels,
};
mutations[types.UPDATE_SELECTED_LABELS](state, { labels: [{ id: 2 }] });
state.labels.forEach((label) => {
if (updatedLabelIds.includes(label.id)) {
expect(label.touched).toBe(true);
expect(label.set).toBe(true);
}
});
});
describe('when label is scoped', () => {
it('unsets the currently selected scoped label and sets the current label', () => {
const state = {
labels,
};
mutations[types.UPDATE_SELECTED_LABELS](state, {
labels: [{ id: 2, title: 'scoped::one' }],
});
expect(state.labels).toEqual([ it.each`
{ id: 1, title: 'scoped' }, label | labelGroupIds
{ id: 2, title: 'scoped::one', set: true, touched: true }, ${labels[0]} | ${[]}
{ id: 3, title: 'scoped::test', set: false }, ${labels[1]} | ${[labels[2], labels[3]]}
{ id: 4, title: '' }, ${labels[2]} | ${[labels[1], labels[3]]}
]); ${labels[3]} | ${[labels[1], labels[2]]}
${labels[4]} | ${[labels[5], labels[6]]}
${labels[5]} | ${[labels[4], labels[6]]}
${labels[6]} | ${[labels[4], labels[5]]}
${labels[7]} | ${[]}
`('updates `touched` and `set` props for $label.title', ({ label, labelGroupIds }) => {
const state = { labels: cloneDeep(labels) };
mutations[types.UPDATE_SELECTED_LABELS](state, { labels: [{ id: label.id }] });
expect(state.labels[label.id - 1]).toMatchObject({
touched: true,
set: !labels[label.id - 1].set,
});
labelGroupIds.forEach((l) => {
expect(state.labels[l.id - 1].touched).toBeFalsy();
expect(state.labels[l.id - 1].set).toBe(false);
}); });
}); });
}); });
......
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