2020-04-21 18:09:31 +00:00
|
|
|
import { escape } from 'lodash';
|
2019-12-10 09:07:51 +00:00
|
|
|
import VisualTokenValue from '~/filtered_search/visual_token_value';
|
2019-03-07 23:55:45 +00:00
|
|
|
import AjaxCache from '~/lib/utils/ajax_cache';
|
|
|
|
import UsersCache from '~/lib/utils/users_cache';
|
|
|
|
import DropdownUtils from '~/filtered_search//dropdown_utils';
|
|
|
|
import FilteredSearchSpecHelper from '../helpers/filtered_search_spec_helper';
|
2020-07-16 03:09:12 +00:00
|
|
|
import { TEST_HOST } from 'jest/helpers/test_constants';
|
2019-03-07 23:55:45 +00:00
|
|
|
|
|
|
|
describe('Filtered Search Visual Tokens', () => {
|
|
|
|
const findElements = tokenElement => {
|
|
|
|
const tokenNameElement = tokenElement.querySelector('.name');
|
|
|
|
const tokenValueContainer = tokenElement.querySelector('.value-container');
|
|
|
|
const tokenValueElement = tokenValueContainer.querySelector('.value');
|
2020-01-02 13:03:23 +00:00
|
|
|
const tokenOperatorElement = tokenElement.querySelector('.operator');
|
2019-03-07 23:55:45 +00:00
|
|
|
const tokenType = tokenNameElement.innerText.toLowerCase();
|
|
|
|
const tokenValue = tokenValueElement.innerText;
|
2020-01-02 13:03:23 +00:00
|
|
|
const tokenOperator = tokenOperatorElement.innerText;
|
|
|
|
const subject = new VisualTokenValue(tokenValue, tokenType, tokenOperator);
|
2019-03-07 23:55:45 +00:00
|
|
|
return { subject, tokenValueContainer, tokenValueElement };
|
|
|
|
};
|
|
|
|
|
|
|
|
let tokensContainer;
|
|
|
|
let authorToken;
|
|
|
|
let bugLabelToken;
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
setFixtures(`
|
|
|
|
<ul class="tokens-container">
|
|
|
|
${FilteredSearchSpecHelper.createInputHTML()}
|
|
|
|
</ul>
|
|
|
|
`);
|
|
|
|
tokensContainer = document.querySelector('.tokens-container');
|
|
|
|
|
2020-01-02 13:03:23 +00:00
|
|
|
authorToken = FilteredSearchSpecHelper.createFilterVisualToken('author', '=', '@user');
|
|
|
|
bugLabelToken = FilteredSearchSpecHelper.createFilterVisualToken('label', '=', '~bug');
|
2019-03-07 23:55:45 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
describe('updateUserTokenAppearance', () => {
|
|
|
|
let usersCacheSpy;
|
|
|
|
|
|
|
|
beforeEach(() => {
|
2020-05-07 12:09:46 +00:00
|
|
|
jest.spyOn(UsersCache, 'retrieve').mockImplementation(username => usersCacheSpy(username));
|
2019-03-07 23:55:45 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
it('ignores error if UsersCache throws', done => {
|
2020-05-07 12:09:46 +00:00
|
|
|
jest.spyOn(window, 'Flash').mockImplementation(() => {});
|
2019-03-07 23:55:45 +00:00
|
|
|
const dummyError = new Error('Earth rotated backwards');
|
|
|
|
const { subject, tokenValueContainer, tokenValueElement } = findElements(authorToken);
|
|
|
|
const tokenValue = tokenValueElement.innerText;
|
|
|
|
usersCacheSpy = username => {
|
|
|
|
expect(`@${username}`).toBe(tokenValue);
|
|
|
|
return Promise.reject(dummyError);
|
|
|
|
};
|
|
|
|
|
|
|
|
subject
|
|
|
|
.updateUserTokenAppearance(tokenValueContainer, tokenValueElement, tokenValue)
|
|
|
|
.then(() => {
|
2020-05-07 12:09:46 +00:00
|
|
|
expect(window.Flash.mock.calls.length).toBe(0);
|
2019-03-07 23:55:45 +00:00
|
|
|
})
|
|
|
|
.then(done)
|
|
|
|
.catch(done.fail);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('does nothing if user cannot be found', done => {
|
|
|
|
const { subject, tokenValueContainer, tokenValueElement } = findElements(authorToken);
|
|
|
|
const tokenValue = tokenValueElement.innerText;
|
|
|
|
usersCacheSpy = username => {
|
|
|
|
expect(`@${username}`).toBe(tokenValue);
|
|
|
|
return Promise.resolve(undefined);
|
|
|
|
};
|
|
|
|
|
|
|
|
subject
|
|
|
|
.updateUserTokenAppearance(tokenValueContainer, tokenValueElement, tokenValue)
|
|
|
|
.then(() => {
|
|
|
|
expect(tokenValueElement.innerText).toBe(tokenValue);
|
|
|
|
})
|
|
|
|
.then(done)
|
|
|
|
.catch(done.fail);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('replaces author token with avatar and display name', done => {
|
|
|
|
const dummyUser = {
|
|
|
|
name: 'Important Person',
|
|
|
|
avatar_url: 'https://host.invalid/mypics/avatar.png',
|
|
|
|
};
|
|
|
|
const { subject, tokenValueContainer, tokenValueElement } = findElements(authorToken);
|
|
|
|
const tokenValue = tokenValueElement.innerText;
|
|
|
|
usersCacheSpy = username => {
|
|
|
|
expect(`@${username}`).toBe(tokenValue);
|
|
|
|
return Promise.resolve(dummyUser);
|
|
|
|
};
|
|
|
|
|
|
|
|
subject
|
|
|
|
.updateUserTokenAppearance(tokenValueContainer, tokenValueElement, tokenValue)
|
|
|
|
.then(() => {
|
|
|
|
expect(tokenValueContainer.dataset.originalValue).toBe(tokenValue);
|
|
|
|
expect(tokenValueElement.innerText.trim()).toBe(dummyUser.name);
|
|
|
|
const avatar = tokenValueElement.querySelector('img.avatar');
|
|
|
|
|
2020-05-07 12:09:46 +00:00
|
|
|
expect(avatar.getAttribute('src')).toBe(dummyUser.avatar_url);
|
|
|
|
expect(avatar.getAttribute('alt')).toBe('');
|
2019-03-07 23:55:45 +00:00
|
|
|
})
|
|
|
|
.then(done)
|
|
|
|
.catch(done.fail);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('escapes user name when creating token', done => {
|
|
|
|
const dummyUser = {
|
|
|
|
name: '<script>',
|
2020-07-16 03:09:12 +00:00
|
|
|
avatar_url: `${TEST_HOST}/mypics/avatar.png`,
|
2019-03-07 23:55:45 +00:00
|
|
|
};
|
|
|
|
const { subject, tokenValueContainer, tokenValueElement } = findElements(authorToken);
|
|
|
|
const tokenValue = tokenValueElement.innerText;
|
|
|
|
usersCacheSpy = username => {
|
|
|
|
expect(`@${username}`).toBe(tokenValue);
|
|
|
|
return Promise.resolve(dummyUser);
|
|
|
|
};
|
|
|
|
|
|
|
|
subject
|
|
|
|
.updateUserTokenAppearance(tokenValueContainer, tokenValueElement, tokenValue)
|
|
|
|
.then(() => {
|
|
|
|
expect(tokenValueElement.innerText.trim()).toBe(dummyUser.name);
|
|
|
|
tokenValueElement.querySelector('.avatar').remove();
|
|
|
|
|
2020-04-21 18:09:31 +00:00
|
|
|
expect(tokenValueElement.innerHTML.trim()).toBe(escape(dummyUser.name));
|
2019-03-07 23:55:45 +00:00
|
|
|
})
|
|
|
|
.then(done)
|
|
|
|
.catch(done.fail);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('updateLabelTokenColor', () => {
|
|
|
|
const jsonFixtureName = 'labels/project_labels.json';
|
|
|
|
const dummyEndpoint = '/dummy/endpoint';
|
|
|
|
|
|
|
|
preloadFixtures(jsonFixtureName);
|
|
|
|
|
|
|
|
let labelData;
|
|
|
|
|
|
|
|
beforeAll(() => {
|
|
|
|
labelData = getJSONFixture(jsonFixtureName);
|
|
|
|
});
|
|
|
|
|
|
|
|
const missingLabelToken = FilteredSearchSpecHelper.createFilterVisualToken(
|
|
|
|
'label',
|
2020-01-02 13:03:23 +00:00
|
|
|
'=',
|
2019-03-07 23:55:45 +00:00
|
|
|
'~doesnotexist',
|
|
|
|
);
|
|
|
|
const spaceLabelToken = FilteredSearchSpecHelper.createFilterVisualToken(
|
|
|
|
'label',
|
2020-01-02 13:03:23 +00:00
|
|
|
'=',
|
2019-03-07 23:55:45 +00:00
|
|
|
'~"some space"',
|
|
|
|
);
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(`
|
|
|
|
${bugLabelToken.outerHTML}
|
|
|
|
${missingLabelToken.outerHTML}
|
|
|
|
${spaceLabelToken.outerHTML}
|
|
|
|
`);
|
|
|
|
|
|
|
|
const filteredSearchInput = document.querySelector('.filtered-search');
|
2019-06-24 11:42:33 +00:00
|
|
|
filteredSearchInput.dataset.runnerTagsEndpoint = `${dummyEndpoint}/admin/runners/tag_list`;
|
2019-06-13 23:39:39 +00:00
|
|
|
filteredSearchInput.dataset.labelsEndpoint = `${dummyEndpoint}/-/labels`;
|
|
|
|
filteredSearchInput.dataset.milestonesEndpoint = `${dummyEndpoint}/-/milestones`;
|
2019-03-07 23:55:45 +00:00
|
|
|
|
|
|
|
AjaxCache.internalStorage = {};
|
2019-06-13 23:39:39 +00:00
|
|
|
AjaxCache.internalStorage[`${filteredSearchInput.dataset.labelsEndpoint}.json`] = labelData;
|
2019-03-07 23:55:45 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
const parseColor = color => {
|
|
|
|
const dummyElement = document.createElement('div');
|
|
|
|
dummyElement.style.color = color;
|
|
|
|
return dummyElement.style.color;
|
|
|
|
};
|
|
|
|
|
|
|
|
const expectValueContainerStyle = (tokenValueContainer, label) => {
|
|
|
|
expect(tokenValueContainer.getAttribute('style')).not.toBe(null);
|
|
|
|
expect(tokenValueContainer.style.backgroundColor).toBe(parseColor(label.color));
|
|
|
|
expect(tokenValueContainer.style.color).toBe(parseColor(label.text_color));
|
|
|
|
};
|
|
|
|
|
|
|
|
const findLabel = tokenValue =>
|
|
|
|
labelData.find(label => tokenValue === `~${DropdownUtils.getEscapedText(label.title)}`);
|
|
|
|
|
|
|
|
it('updates the color of a label token', done => {
|
|
|
|
const { subject, tokenValueContainer, tokenValueElement } = findElements(bugLabelToken);
|
|
|
|
const tokenValue = tokenValueElement.innerText;
|
|
|
|
const matchingLabel = findLabel(tokenValue);
|
|
|
|
|
|
|
|
subject
|
|
|
|
.updateLabelTokenColor(tokenValueContainer, tokenValue)
|
|
|
|
.then(() => {
|
|
|
|
expectValueContainerStyle(tokenValueContainer, matchingLabel);
|
|
|
|
})
|
|
|
|
.then(done)
|
|
|
|
.catch(done.fail);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('updates the color of a label token with spaces', done => {
|
|
|
|
const { subject, tokenValueContainer, tokenValueElement } = findElements(spaceLabelToken);
|
|
|
|
const tokenValue = tokenValueElement.innerText;
|
|
|
|
const matchingLabel = findLabel(tokenValue);
|
|
|
|
|
|
|
|
subject
|
|
|
|
.updateLabelTokenColor(tokenValueContainer, tokenValue)
|
|
|
|
.then(() => {
|
|
|
|
expectValueContainerStyle(tokenValueContainer, matchingLabel);
|
|
|
|
})
|
|
|
|
.then(done)
|
|
|
|
.catch(done.fail);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('does not change color of a missing label', done => {
|
|
|
|
const { subject, tokenValueContainer, tokenValueElement } = findElements(missingLabelToken);
|
|
|
|
const tokenValue = tokenValueElement.innerText;
|
|
|
|
const matchingLabel = findLabel(tokenValue);
|
|
|
|
|
|
|
|
expect(matchingLabel).toBe(undefined);
|
|
|
|
|
|
|
|
subject
|
|
|
|
.updateLabelTokenColor(tokenValueContainer, tokenValue)
|
|
|
|
.then(() => {
|
|
|
|
expect(tokenValueContainer.getAttribute('style')).toBe(null);
|
|
|
|
})
|
|
|
|
.then(done)
|
|
|
|
.catch(done.fail);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('setTokenStyle', () => {
|
|
|
|
let originalTextColor;
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
originalTextColor = bugLabelToken.style.color;
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should set backgroundColor', () => {
|
|
|
|
const originalBackgroundColor = bugLabelToken.style.backgroundColor;
|
|
|
|
const token = VisualTokenValue.setTokenStyle(bugLabelToken, 'blue', 'white');
|
|
|
|
|
|
|
|
expect(token.style.backgroundColor).toEqual('blue');
|
|
|
|
expect(token.style.backgroundColor).not.toEqual(originalBackgroundColor);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should set textColor', () => {
|
|
|
|
const token = VisualTokenValue.setTokenStyle(bugLabelToken, 'white', 'black');
|
|
|
|
|
|
|
|
expect(token.style.color).toEqual('black');
|
|
|
|
expect(token.style.color).not.toEqual(originalTextColor);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should add inverted class when textColor is #FFFFFF', () => {
|
|
|
|
const token = VisualTokenValue.setTokenStyle(bugLabelToken, 'black', '#FFFFFF');
|
|
|
|
|
|
|
|
expect(token.style.color).toEqual('rgb(255, 255, 255)');
|
|
|
|
expect(token.style.color).not.toEqual(originalTextColor);
|
|
|
|
expect(token.querySelector('.remove-token').classList.contains('inverted')).toEqual(true);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('render', () => {
|
|
|
|
const setupSpies = subject => {
|
2020-05-07 12:09:46 +00:00
|
|
|
jest.spyOn(subject, 'updateLabelTokenColor').mockImplementation(() => {});
|
2019-03-07 23:55:45 +00:00
|
|
|
const updateLabelTokenColorSpy = subject.updateLabelTokenColor;
|
|
|
|
|
2020-05-07 12:09:46 +00:00
|
|
|
jest.spyOn(subject, 'updateUserTokenAppearance').mockImplementation(() => {});
|
2019-03-07 23:55:45 +00:00
|
|
|
const updateUserTokenAppearanceSpy = subject.updateUserTokenAppearance;
|
|
|
|
|
|
|
|
return { updateLabelTokenColorSpy, updateUserTokenAppearanceSpy };
|
|
|
|
};
|
|
|
|
|
|
|
|
const keywordToken = FilteredSearchSpecHelper.createFilterVisualToken('search');
|
|
|
|
const milestoneToken = FilteredSearchSpecHelper.createFilterVisualToken(
|
|
|
|
'milestone',
|
|
|
|
'upcoming',
|
|
|
|
);
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
tokensContainer.innerHTML = FilteredSearchSpecHelper.createTokensContainerHTML(`
|
|
|
|
${authorToken.outerHTML}
|
|
|
|
${bugLabelToken.outerHTML}
|
|
|
|
${keywordToken.outerHTML}
|
|
|
|
${milestoneToken.outerHTML}
|
|
|
|
`);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('renders a author token value element', () => {
|
|
|
|
const { subject, tokenValueContainer, tokenValueElement } = findElements(authorToken);
|
|
|
|
|
|
|
|
const { updateLabelTokenColorSpy, updateUserTokenAppearanceSpy } = setupSpies(subject);
|
|
|
|
subject.render(tokenValueContainer, tokenValueElement);
|
|
|
|
|
2020-05-07 12:09:46 +00:00
|
|
|
expect(updateUserTokenAppearanceSpy.mock.calls.length).toBe(1);
|
2019-03-07 23:55:45 +00:00
|
|
|
const expectedArgs = [tokenValueContainer, tokenValueElement];
|
|
|
|
|
2020-05-07 12:09:46 +00:00
|
|
|
expect(updateUserTokenAppearanceSpy.mock.calls[0]).toEqual(expectedArgs);
|
|
|
|
expect(updateLabelTokenColorSpy.mock.calls.length).toBe(0);
|
2019-03-07 23:55:45 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
it('renders a label token value element', () => {
|
|
|
|
const { subject, tokenValueContainer, tokenValueElement } = findElements(bugLabelToken);
|
|
|
|
|
|
|
|
const { updateLabelTokenColorSpy, updateUserTokenAppearanceSpy } = setupSpies(subject);
|
|
|
|
subject.render(tokenValueContainer, tokenValueElement);
|
|
|
|
|
2020-05-07 12:09:46 +00:00
|
|
|
expect(updateLabelTokenColorSpy.mock.calls.length).toBe(1);
|
2019-03-07 23:55:45 +00:00
|
|
|
const expectedArgs = [tokenValueContainer];
|
|
|
|
|
2020-05-07 12:09:46 +00:00
|
|
|
expect(updateLabelTokenColorSpy.mock.calls[0]).toEqual(expectedArgs);
|
|
|
|
expect(updateUserTokenAppearanceSpy.mock.calls.length).toBe(0);
|
2019-03-07 23:55:45 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
it('renders a milestone token value element', () => {
|
|
|
|
const { subject, tokenValueContainer, tokenValueElement } = findElements(milestoneToken);
|
|
|
|
|
|
|
|
const { updateLabelTokenColorSpy, updateUserTokenAppearanceSpy } = setupSpies(subject);
|
|
|
|
subject.render(tokenValueContainer, tokenValueElement);
|
|
|
|
|
2020-05-07 12:09:46 +00:00
|
|
|
expect(updateLabelTokenColorSpy.mock.calls.length).toBe(0);
|
|
|
|
expect(updateUserTokenAppearanceSpy.mock.calls.length).toBe(0);
|
2019-03-07 23:55:45 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
it('does not update user token appearance for `none` filter', () => {
|
|
|
|
const { subject, tokenValueContainer, tokenValueElement } = findElements(authorToken);
|
|
|
|
|
2019-03-18 14:19:33 +00:00
|
|
|
subject.tokenValue = 'none';
|
|
|
|
|
|
|
|
const { updateUserTokenAppearanceSpy } = setupSpies(subject);
|
|
|
|
subject.render(tokenValueContainer, tokenValueElement);
|
|
|
|
|
2020-05-07 12:09:46 +00:00
|
|
|
expect(updateUserTokenAppearanceSpy.mock.calls.length).toBe(0);
|
2019-03-18 14:19:33 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
it('does not update user token appearance for `None` filter', () => {
|
|
|
|
const { subject, tokenValueContainer, tokenValueElement } = findElements(authorToken);
|
|
|
|
|
|
|
|
subject.tokenValue = 'None';
|
2019-03-07 23:55:45 +00:00
|
|
|
|
|
|
|
const { updateUserTokenAppearanceSpy } = setupSpies(subject);
|
|
|
|
subject.render(tokenValueContainer, tokenValueElement);
|
|
|
|
|
2020-05-07 12:09:46 +00:00
|
|
|
expect(updateUserTokenAppearanceSpy.mock.calls.length).toBe(0);
|
2019-03-07 23:55:45 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
it('does not update user token appearance for `any` filter', () => {
|
|
|
|
const { subject, tokenValueContainer, tokenValueElement } = findElements(authorToken);
|
|
|
|
|
2019-03-18 14:19:33 +00:00
|
|
|
subject.tokenValue = 'any';
|
2019-03-07 23:55:45 +00:00
|
|
|
|
|
|
|
const { updateUserTokenAppearanceSpy } = setupSpies(subject);
|
|
|
|
subject.render(tokenValueContainer, tokenValueElement);
|
|
|
|
|
2020-05-07 12:09:46 +00:00
|
|
|
expect(updateUserTokenAppearanceSpy.mock.calls.length).toBe(0);
|
2019-03-07 23:55:45 +00:00
|
|
|
});
|
|
|
|
|
2019-03-18 14:19:33 +00:00
|
|
|
it('does not update label token color for `None` filter', () => {
|
|
|
|
const { subject, tokenValueContainer, tokenValueElement } = findElements(bugLabelToken);
|
|
|
|
|
|
|
|
subject.tokenValue = 'None';
|
|
|
|
|
|
|
|
const { updateLabelTokenColorSpy } = setupSpies(subject);
|
|
|
|
subject.render(tokenValueContainer, tokenValueElement);
|
|
|
|
|
2020-05-07 12:09:46 +00:00
|
|
|
expect(updateLabelTokenColorSpy.mock.calls.length).toBe(0);
|
2019-03-18 14:19:33 +00:00
|
|
|
});
|
|
|
|
|
2019-03-07 23:55:45 +00:00
|
|
|
it('does not update label token color for `none` filter', () => {
|
|
|
|
const { subject, tokenValueContainer, tokenValueElement } = findElements(bugLabelToken);
|
|
|
|
|
2019-03-18 14:19:33 +00:00
|
|
|
subject.tokenValue = 'none';
|
2019-03-07 23:55:45 +00:00
|
|
|
|
|
|
|
const { updateLabelTokenColorSpy } = setupSpies(subject);
|
|
|
|
subject.render(tokenValueContainer, tokenValueElement);
|
|
|
|
|
2020-05-07 12:09:46 +00:00
|
|
|
expect(updateLabelTokenColorSpy.mock.calls.length).toBe(0);
|
2019-03-07 23:55:45 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
it('does not update label token color for `any` filter', () => {
|
|
|
|
const { subject, tokenValueContainer, tokenValueElement } = findElements(bugLabelToken);
|
|
|
|
|
2019-03-18 14:19:33 +00:00
|
|
|
subject.tokenValue = 'any';
|
2019-03-07 23:55:45 +00:00
|
|
|
|
|
|
|
const { updateLabelTokenColorSpy } = setupSpies(subject);
|
|
|
|
subject.render(tokenValueContainer, tokenValueElement);
|
|
|
|
|
2020-05-07 12:09:46 +00:00
|
|
|
expect(updateLabelTokenColorSpy.mock.calls.length).toBe(0);
|
2019-03-07 23:55:45 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|