Commit 355f2c6b authored by Jiaan Louw's avatar Jiaan Louw Committed by Paul Slaughter

Remove form logic from audit event components

https://gitlab.com/gitlab-org/gitlab/-/merge_requests/33741
parent d3c5b084
<script>
import { mapActions, mapState } from 'vuex';
import AuditEventsFilter from './audit_events_filter.vue';
import DateRangeField from './date_range_field.vue';
import SortingField from './sorting_field.vue';
......@@ -12,10 +13,6 @@ export default {
AuditEventsTable,
},
props: {
formPath: {
type: String,
required: true,
},
events: {
type: Array,
required: false,
......@@ -41,16 +38,11 @@ export default {
default: undefined,
},
},
data() {
return {
formElement: null,
};
computed: {
...mapState(['filterValue', 'startDate', 'endDate', 'sortBy']),
},
mounted() {
// Passing the form to child components is only temporary
// and should be changed when this issue is completed:
// https://gitlab.com/gitlab-org/gitlab/-/issues/217759
this.formElement = this.$refs.form;
methods: {
...mapActions(['setDateRange', 'setFilterValue', 'setSortBy', 'searchForAuditEvents']),
},
};
</script>
......@@ -58,25 +50,34 @@ export default {
<template>
<div>
<div class="row-content-block second-block pb-0">
<form
ref="form"
method="GET"
:path="formPath"
class="filter-form d-flex justify-content-between audit-controls row"
>
<div class="d-flex justify-content-between audit-controls row">
<div class="col-lg-auto flex-fill form-group align-items-lg-center pr-lg-8">
<audit-events-filter v-bind="{ enabledTokenTypes, qaSelector: filterQaSelector }" />
<audit-events-filter
:enabled-token-types="enabledTokenTypes"
:qa-selector="filterQaSelector"
:value="filterValue"
@selected="setFilterValue"
@submit="searchForAuditEvents"
/>
</div>
<div class="d-flex col-lg-auto flex-wrap pl-lg-0">
<div
class="audit-controls d-flex align-items-lg-center flex-column flex-lg-row col-lg-auto px-0"
>
<date-range-field v-if="formElement" :form-element="formElement" />
<sorting-field />
<date-range-field
:start-date="startDate"
:end-date="endDate"
@selected="setDateRange"
/>
<sorting-field :sort-by="sortBy" @selected="setSortBy" />
</div>
</div>
</form>
</div>
</div>
<audit-events-table v-bind="{ events, isLastPage, qaSelector: tableQaSelector }" />
<audit-events-table
:events="events"
:is-last-page="isLastPage"
:qa-selector="tableQaSelector"
/>
</div>
</template>
<script>
import { GlFilteredSearch } from '@gitlab/ui';
import { queryToObject } from '~/lib/utils/url_utility';
import { FILTER_TOKENS, AVAILABLE_TOKEN_TYPES } from '../constants';
import { availableTokensValidator } from '../validators';
......@@ -9,6 +8,11 @@ export default {
GlFilteredSearch,
},
props: {
value: {
type: Array,
required: false,
default: () => [],
},
enabledTokenTypes: {
type: Array,
required: false,
......@@ -21,14 +25,9 @@ export default {
default: undefined,
},
},
data() {
return {
searchTerms: [],
};
},
computed: {
searchTerm() {
return this.searchTerms.find(term => AVAILABLE_TOKEN_TYPES.includes(term.type));
return this.value.find(term => AVAILABLE_TOKEN_TYPES.includes(term.type));
},
enabledTokens() {
return FILTER_TOKENS.filter(token => this.enabledTokenTypes.includes(token.type));
......@@ -36,39 +35,23 @@ export default {
filterTokens() {
// This limits the user to search by only one of the available tokens
const { enabledTokens, searchTerm } = this;
if (searchTerm?.type) {
return enabledTokens.map(token => ({
...token,
disabled: searchTerm.type !== token.type,
}));
}
return enabledTokens;
},
id() {
return this.searchTerm?.value?.data;
},
type() {
return this.searchTerm?.type;
},
},
created() {
this.setSearchTermsFromQuery();
},
methods: {
// The form logic here will be removed once all the audit
// components are migrated into a single Vue application.
// https://gitlab.com/gitlab-org/gitlab/-/issues/215363
getFormElement() {
return this.$refs.input.form;
onSubmit() {
this.$emit('submit');
},
setSearchTermsFromQuery() {
const { entity_type: type, entity_id: value } = queryToObject(window.location.search);
if (type && value) {
this.searchTerms = [{ type, value: { data: value, operator: '=' } }];
}
},
filteredSearchSubmit() {
this.getFormElement().submit();
onInput(val) {
this.$emit('selected', val);
},
},
};
......@@ -81,16 +64,14 @@ export default {
:data-qa-selector="qaSelector"
>
<gl-filtered-search
v-model="searchTerms"
:value="value"
:placeholder="__('Search')"
:clear-button-title="__('Clear')"
:close-button-title="__('Close')"
:available-tokens="filterTokens"
class="gl-h-32 w-100"
@submit="filteredSearchSubmit"
@submit="onSubmit"
@input="onInput"
/>
<input ref="input" v-model="type" type="hidden" name="entity_type" />
<input v-model="id" type="hidden" name="entity_id" />
</div>
</template>
<script>
import { GlDaterangePicker } from '@gitlab/ui';
import { parsePikadayDate, pikadayToString } from '~/lib/utils/datetime_utility';
import { queryToObject } from '~/lib/utils/url_utility';
export default {
components: {
GlDaterangePicker,
},
props: {
formElement: {
type: HTMLFormElement,
required: true,
startDate: {
type: Date,
required: false,
default: null,
},
},
data() {
const data = {
startDate: null,
endDate: null,
};
const { created_after: initialStartDate, created_before: initialEndDate } = queryToObject(
window.location.search,
);
if (initialStartDate) {
data.startDate = parsePikadayDate(initialStartDate);
}
if (initialEndDate) {
data.endDate = parsePikadayDate(initialEndDate);
}
return data;
},
computed: {
createdAfter() {
return this.startDate ? pikadayToString(this.startDate) : '';
},
createdBefore() {
return this.endDate ? pikadayToString(this.endDate) : '';
endDate: {
type: Date,
required: false,
default: null,
},
},
methods: {
handleInput(dates) {
this.startDate = dates.startDate;
this.endDate = dates.endDate;
this.$nextTick(() => this.formElement.submit());
onInput(dates) {
this.$emit('selected', dates);
},
},
};
</script>
<template>
<div>
<gl-daterange-picker
class="d-flex flex-wrap flex-sm-nowrap"
:default-start-date="startDate"
:default-end-date="endDate"
start-picker-class="form-group align-items-lg-center mr-0 mr-sm-1 d-flex flex-column flex-lg-row"
end-picker-class="form-group align-items-lg-center mr-0 mr-sm-2 d-flex flex-column flex-lg-row"
@input="handleInput"
/>
<input type="hidden" name="created_after" :value="createdAfter" />
<input type="hidden" name="created_before" :value="createdBefore" />
</div>
<gl-daterange-picker
class="d-flex flex-wrap flex-sm-nowrap"
:default-start-date="startDate"
:default-end-date="endDate"
start-picker-class="form-group align-items-lg-center mr-0 mr-sm-1 d-flex flex-column flex-lg-row"
end-picker-class="form-group align-items-lg-center mr-0 mr-sm-2 d-flex flex-column flex-lg-row"
@input="onInput"
/>
</template>
<script>
import { GlNewDropdown, GlNewDropdownHeader, GlNewDropdownItem } from '@gitlab/ui';
import { setUrlParams, queryToObject } from '~/lib/utils/url_utility';
import { s__ } from '~/locale';
const SORTING_TITLE = s__('SortOptions|Sort by:');
......@@ -22,24 +20,24 @@ export default {
GlNewDropdownHeader,
GlNewDropdownItem,
},
data() {
const { sort: selectedOption } = queryToObject(window.location.search);
return {
selectedOption: selectedOption || SORTING_OPTIONS[0].key,
};
props: {
sortBy: {
type: String,
required: false,
default: null,
},
},
computed: {
selectedOptionText() {
return SORTING_OPTIONS.find(option => option.key === this.selectedOption).text;
selectedOption() {
return SORTING_OPTIONS.find(option => option.key === this.sortBy) || SORTING_OPTIONS[0];
},
},
methods: {
getItemLink(key) {
return setUrlParams({ sort: key });
onItemClick(option) {
this.$emit('selected', option);
},
isChecked(key) {
return key === this.selectedOption;
return key === this.selectedOption.key;
},
},
SORTING_TITLE,
......@@ -49,23 +47,17 @@ export default {
<template>
<div>
<gl-new-dropdown
v-model="selectedOption"
:text="selectedOptionText"
class="w-100 flex-column flex-lg-row form-group"
>
<gl-new-dropdown :text="selectedOption.text" class="w-100 flex-column flex-lg-row form-group">
<gl-new-dropdown-header> {{ $options.SORTING_TITLE }}</gl-new-dropdown-header>
<gl-new-dropdown-item
v-for="option in $options.SORTING_OPTIONS"
:key="option.key"
:is-check-item="true"
:is-checked="isChecked(option.key)"
:href="getItemLink(option.key)"
@click="onItemClick(option.key)"
>
{{ option.text }}
</gl-new-dropdown-item>
</gl-new-dropdown>
<input type="hidden" name="sort" :value="selectedOption" />
</div>
</template>
import Vue from 'vue';
import { parseBoolean } from '~/lib/utils/common_utils';
import AuditEventsApp from './components/audit_events_app.vue';
import createStore from './store';
export default selector => {
const el = document.querySelector(selector);
const {
events,
isLastPage,
formPath,
enabledTokenTypes,
filterQaSelector,
tableQaSelector,
} = el.dataset;
const { events, isLastPage, enabledTokenTypes, filterQaSelector, tableQaSelector } = el.dataset;
const store = createStore();
store.dispatch('initializeAuditEvents');
return new Vue({
el,
store,
render: createElement =>
createElement(AuditEventsApp, {
props: {
events: JSON.parse(events),
isLastPage: parseBoolean(isLastPage),
enabledTokenTypes: JSON.parse(enabledTokenTypes),
formPath,
filterQaSelector,
tableQaSelector,
},
......
......@@ -18,9 +18,8 @@ export const setDateRange = ({ commit, dispatch }, { startDate, endDate }) => {
dispatch('searchForAuditEvents');
};
export const setFilterValue = ({ commit, dispatch }, { id, type }) => {
commit(types.SET_FILTER_VALUE, { id, type });
dispatch('searchForAuditEvents');
export const setFilterValue = ({ commit }, filterValue) => {
commit(types.SET_FILTER_VALUE, filterValue);
};
export const setSortBy = ({ commit, dispatch }, sortBy) => {
......
......@@ -11,14 +11,14 @@ export default {
sort: sortBy = null,
} = {},
) {
state.filterValue = { id, type };
state.filterValue = type && id ? [{ type, value: { data: id, operator: '=' } }] : [];
state.startDate = startDate;
state.endDate = endDate;
state.sortBy = sortBy;
},
[types.SET_FILTER_VALUE](state, { id, type }) {
state.filterValue = { id, type };
[types.SET_FILTER_VALUE](state, filterValue) {
state.filterValue = filterValue;
},
[types.SET_DATE_RANGE](state, { startDate, endDate }) {
......
export default () => ({
filterValue: {
id: null,
type: null,
},
filterValue: [],
startDate: null,
endDate: null,
......
import { parsePikadayDate, pikadayToString } from '~/lib/utils/datetime_utility';
import { AVAILABLE_TOKEN_TYPES } from './constants';
export const isNumeric = str => {
return !Number.isNaN(parseInt(str, 10), 10);
......@@ -14,10 +15,16 @@ export const parseAuditEventSearchQuery = ({
created_before: createdBefore ? parsePikadayDate(createdBefore) : null,
});
export const createAuditEventSearchQuery = ({ filterValue, startDate, endDate, sortBy }) => ({
entity_id: filterValue.id,
entity_type: filterValue.type,
created_after: startDate ? pikadayToString(startDate) : null,
created_before: endDate ? pikadayToString(endDate) : null,
sort: sortBy,
});
export const createAuditEventSearchQuery = ({ filterValue, startDate, endDate, sortBy }) => {
const entityValue = filterValue.find(value => AVAILABLE_TOKEN_TYPES.includes(value.type));
return {
created_after: startDate ? pikadayToString(startDate) : null,
created_before: endDate ? pikadayToString(endDate) : null,
sort: sortBy,
entity_id: entityValue?.value.data,
entity_type: entityValue?.type,
// When changing the search parameters, we should be resetting to the first page
page: null,
};
};
......@@ -162,9 +162,10 @@ RSpec.describe 'Admin::AuditLogs', :js do
end
def filter_for(type, name)
within '[data-qa-selector="admin_audit_log_filter"]' do
find('input').click
filter_container = '[data-testid="audit-events-filter"]'
find(filter_container).click
within filter_container do
click_link type
click_link name
......
......@@ -5,10 +5,8 @@ exports[`AuditEventsApp when initialized matches the snapshot 1`] = `
<div
class="row-content-block second-block pb-0"
>
<form
class="filter-form d-flex justify-content-between audit-controls row"
method="GET"
path="form/path"
<div
class="d-flex justify-content-between audit-controls row"
>
<div
class="col-lg-auto flex-fill form-group align-items-lg-center pr-lg-8"
......@@ -24,17 +22,7 @@ exports[`AuditEventsApp when initialized matches the snapshot 1`] = `
clearbuttontitle="Clear"
close-button-title="Close"
placeholder="Search"
value=""
/>
<input
name="entity_type"
type="hidden"
/>
<input
name="entity_id"
type="hidden"
value="[object Object]"
/>
</div>
</div>
......@@ -46,13 +34,16 @@ exports[`AuditEventsApp when initialized matches the snapshot 1`] = `
class="audit-controls d-flex align-items-lg-center flex-column flex-lg-row col-lg-auto px-0"
>
<date-range-field-stub
formelement="[object HTMLFormElement]"
enddate="Sun Feb 02 2020 00:00:00 GMT+0000 (Greenwich Mean Time)"
startdate="Wed Jan 01 2020 00:00:00 GMT+0000 (Greenwich Mean Time)"
/>
<sorting-field-stub />
<sorting-field-stub
sortby="created_asc"
/>
</div>
</div>
</form>
</div>
</div>
<audit-events-table-stub
......
......@@ -2,12 +2,20 @@ import { shallowMount } from '@vue/test-utils';
import AuditEventsApp from 'ee/audit_events/components/audit_events_app.vue';
import DateRangeField from 'ee/audit_events/components/date_range_field.vue';
import SortingField from 'ee/audit_events/components/sorting_field.vue';
import AuditEventsTable from 'ee/audit_events/components/audit_events_table.vue';
import AuditEventsFilter from 'ee/audit_events/components/audit_events_filter.vue';
import { AVAILABLE_TOKEN_TYPES } from 'ee/audit_events/constants';
import createStore from 'ee/audit_events/store';
const TEST_SORT_BY = 'created_asc';
const TEST_START_DATE = new Date('2020-01-01');
const TEST_END_DATE = new Date('2020-02-02');
const TEST_FILTER_VALUE = [{ id: 50, type: 'User' }];
describe('AuditEventsApp', () => {
let wrapper;
let store;
const events = [{ foo: 'bar' }];
const enabledTokenTypes = AVAILABLE_TOKEN_TYPES;
......@@ -16,8 +24,8 @@ describe('AuditEventsApp', () => {
const initComponent = (props = {}) => {
wrapper = shallowMount(AuditEventsApp, {
store,
propsData: {
formPath: 'form/path',
isLastPage: true,
filterQaSelector,
tableQaSelector,
......@@ -31,9 +39,20 @@ describe('AuditEventsApp', () => {
});
};
beforeEach(() => {
store = createStore();
Object.assign(store.state, {
startDate: TEST_START_DATE,
endDate: TEST_END_DATE,
sortBy: TEST_SORT_BY,
filterValue: TEST_FILTER_VALUE,
});
});
afterEach(() => {
wrapper.destroy();
wrapper = null;
store = null;
});
describe('when initialized', () => {
......@@ -45,25 +64,51 @@ describe('AuditEventsApp', () => {
expect(wrapper.element).toMatchSnapshot();
});
it('sets the form element on the date range field', () => {
const { element } = wrapper.find('form');
expect(wrapper.find(DateRangeField).props('formElement')).toEqual(element);
it('renders audit events table', () => {
expect(wrapper.find(AuditEventsTable).props()).toEqual({
events,
qaSelector: tableQaSelector,
isLastPage: true,
});
});
it('renders audit events filter', () => {
expect(wrapper.find(AuditEventsFilter).props()).toEqual({
enabledTokenTypes,
qaSelector: filterQaSelector,
value: TEST_FILTER_VALUE,
});
});
it('passes its events property to the logs table', () => {
expect(wrapper.find(AuditEventsTable).props('events')).toEqual(events);
it('renders date range field', () => {
expect(wrapper.find(DateRangeField).props()).toEqual({
startDate: TEST_START_DATE,
endDate: TEST_END_DATE,
});
});
it('passes the tables QA selector to the logs table', () => {
expect(wrapper.find(AuditEventsTable).props('qaSelector')).toEqual(tableQaSelector);
it('renders sorting field', () => {
expect(wrapper.find(SortingField).props()).toEqual({ sortBy: TEST_SORT_BY });
});
});
it('passes its available token types to the logs filter', () => {
expect(wrapper.find(AuditEventsFilter).props('enabledTokenTypes')).toEqual(enabledTokenTypes);
describe('when a field is selected', () => {
beforeEach(() => {
jest.spyOn(store, 'dispatch').mockImplementation();
initComponent();
});
it('passes the filters QA selector to the logs filter', () => {
expect(wrapper.find(AuditEventsFilter).props('qaSelector')).toEqual(filterQaSelector);
it.each`
name | field | action | payload
${'date range'} | ${DateRangeField} | ${'setDateRange'} | ${'test'}
${'sort by'} | ${SortingField} | ${'setSortBy'} | ${'test'}
${'events filter'} | ${AuditEventsFilter} | ${'setFilterValue'} | ${'test'}
`('for $name, it calls $handler', ({ field, action, payload }) => {
expect(store.dispatch).not.toHaveBeenCalled();
wrapper.find(field).vm.$emit('selected', payload);
expect(store.dispatch).toHaveBeenCalledWith(action, payload);
});
});
});
......@@ -6,9 +6,8 @@ import { AVAILABLE_TOKEN_TYPES } from 'ee/audit_events/constants';
describe('AuditEventsFilter', () => {
let wrapper;
const formElement = document.createElement('form');
formElement.submit = jest.fn();
const value = [{ type: 'Project', value: { data: 1, operator: '=' } }];
const findFilteredSearch = () => wrapper.find(GlFilteredSearch);
const getAvailableTokens = () => findFilteredSearch().props('availableTokens');
const getAvailableTokenProps = type =>
......@@ -19,9 +18,6 @@ describe('AuditEventsFilter', () => {
propsData: {
...props,
},
methods: {
getFormElement: () => formElement,
},
});
};
......@@ -46,74 +42,59 @@ describe('AuditEventsFilter', () => {
});
});
describe('when the URL query has a search term', () => {
const type = 'User';
const id = '1';
describe('when the default token value is set', () => {
beforeEach(() => {
delete window.location;
window.location = { search: `entity_type=${type}&entity_id=${id}` };
initComponent();
initComponent({ value });
});
it('sets the filtered searched token', () => {
expect(findFilteredSearch().props('value')).toMatchObject([
{
type,
value: {
data: id,
},
},
]);
expect(findFilteredSearch().props('value')).toEqual(value);
});
});
describe('when the URL query is empty', () => {
beforeEach(() => {
delete window.location;
window.location = { search: '' };
initComponent();
it('only one token matching the selected token type is enabled', () => {
expect(getAvailableTokenProps('Project').disabled).toEqual(false);
expect(getAvailableTokenProps('Group').disabled).toEqual(true);
expect(getAvailableTokenProps('User').disabled).toEqual(true);
});
it('has an empty search value', () => {
expect(findFilteredSearch().vm.value).toEqual([]);
describe('and the user submits the search field', () => {
beforeEach(() => {
findFilteredSearch().vm.$emit('submit');
});
it('should emit the "submit" event', () => {
expect(wrapper.emitted().submit).toHaveLength(1);
});
});
});
describe('when submitting the filtered search', () => {
describe('when the default token value is not set', () => {
beforeEach(() => {
initComponent();
findFilteredSearch().vm.$emit('submit');
});
it("calls submit on this component's FORM element", () => {
expect(formElement.submit).toHaveBeenCalledWith();
it('has an empty search value', () => {
expect(findFilteredSearch().vm.value).toEqual([]);
});
});
describe('when a search token has been selected', () => {
const searchTerm = {
value: { data: '1' },
type: 'Project',
};
beforeEach(() => {
initComponent();
wrapper.setData({
searchTerms: [searchTerm],
describe('and the user inputs nothing into the search field', () => {
beforeEach(() => {
findFilteredSearch().vm.$emit('input', []);
});
});
it('only one token matching the selected type is available', () => {
expect(getAvailableTokenProps('Project').disabled).toEqual(false);
expect(getAvailableTokenProps('Group').disabled).toEqual(true);
expect(getAvailableTokenProps('User').disabled).toEqual(true);
});
it('should emit the "selected" event with empty values', () => {
expect(wrapper.emitted().selected[0]).toEqual([[]]);
});
describe('and the user submits the search field', () => {
beforeEach(() => {
findFilteredSearch().vm.$emit('submit');
});
it('sets the input values according to the search term', () => {
expect(wrapper.find('input[name="entity_type"]').attributes().value).toEqual(searchTerm.type);
expect(wrapper.find('input[name="entity_id"]').attributes().value).toEqual(
searchTerm.value.data,
);
it('should emit the "submit" event', () => {
expect(wrapper.emitted().submit).toHaveLength(1);
});
});
});
});
......
......@@ -5,81 +5,61 @@ import DateRangeField from 'ee/audit_events/components/date_range_field.vue';
import { parsePikadayDate } from '~/lib/utils/datetime_utility';
describe('DateRangeField component', () => {
const DATE = '1970-01-01';
let wrapper;
const createComponent = (props = {}) => {
const formElement = document.createElement('form');
document.body.appendChild(formElement);
const startDate = parsePikadayDate('2020-03-13');
const endDate = parsePikadayDate('2020-03-14');
return shallowMount(DateRangeField, {
propsData: { formElement, ...props },
const createComponent = (props = {}) => {
wrapper = shallowMount(DateRangeField, {
propsData: { ...props },
});
};
beforeEach(() => {
delete window.location;
window.location = { search: '' };
});
afterEach(() => {
document.querySelector('form').remove();
wrapper.destroy();
wrapper = null;
});
it('should populate the initial start date if passed in the query string', () => {
window.location.search = `?created_after=${DATE}`;
wrapper = createComponent();
it('passes the startDate to the date picker as defaultStartDate', () => {
createComponent({ startDate });
expect(wrapper.find(GlDaterangePicker).props()).toMatchObject({
defaultStartDate: parsePikadayDate(DATE),
defaultStartDate: startDate,
defaultEndDate: null,
});
});
it('should populate the initial end date if passed in the query string', () => {
window.location.search = `?created_before=${DATE}`;
wrapper = createComponent();
it('passes the endDate to the date picker as defaultEndDate', () => {
createComponent({ endDate });
expect(wrapper.find(GlDaterangePicker).props()).toMatchObject({
defaultStartDate: null,
defaultEndDate: parsePikadayDate(DATE),
defaultEndDate: endDate,
});
});
it('should populate both the initial start and end dates if passed in the query string', () => {
window.location.search = `?created_after=${DATE}&created_before=${DATE}`;
wrapper = createComponent();
it('passes both startDate and endDate to the date picker as default dates', () => {
createComponent({ startDate, endDate });
expect(wrapper.find(GlDaterangePicker).props()).toMatchObject({
defaultStartDate: parsePikadayDate(DATE),
defaultEndDate: parsePikadayDate(DATE),
defaultStartDate: startDate,
defaultEndDate: endDate,
});
});
it('should populate the date hidden fields on input', () => {
wrapper = createComponent();
wrapper
.find(GlDaterangePicker)
.vm.$emit('input', { startDate: parsePikadayDate(DATE), endDate: parsePikadayDate(DATE) });
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.find('input[name="created_after"]').attributes().value).toEqual(DATE);
expect(wrapper.find('input[name="created_before"]').attributes().value).toEqual(DATE);
});
});
it('should submit the form on input change', () => {
wrapper = createComponent();
const spy = jest.spyOn(wrapper.props().formElement, 'submit');
wrapper
.find(GlDaterangePicker)
.vm.$emit('input', { startDate: parsePikadayDate(DATE), endDate: parsePikadayDate(DATE) });
return wrapper.vm.$nextTick().then(() => {
expect(spy).toHaveBeenCalledTimes(1);
it('should emit the "selected" event with startDate and endDate on input change', () => {
createComponent();
wrapper.find(GlDaterangePicker).vm.$emit('input', { startDate, endDate });
return wrapper.vm.$nextTick(() => {
expect(wrapper.emitted().selected).toBeTruthy();
expect(wrapper.emitted().selected[0]).toEqual([
{
startDate,
endDate,
},
]);
});
});
});
import { shallowMount } from '@vue/test-utils';
import { GlNewDropdownItem } from '@gitlab/ui';
import * as urlUtils from '~/lib/utils/url_utility';
import SortingField from 'ee/audit_events/components/sorting_field.vue';
describe('SortingField component', () => {
let wrapper;
const DUMMY_URL = 'https://localhost';
const createComponent = () =>
shallowMount(SortingField, { stubs: { GlNewDropdown: true, GlNewDropdownItem: true } });
const initComponent = (props = {}) => {
wrapper = shallowMount(SortingField, {
propsData: { ...props },
stubs: {
GlNewDropdown: true,
GlNewDropdownItem: true,
},
});
};
const getCheckedOptions = () =>
wrapper.findAll(GlNewDropdownItem).filter(item => item.props().isChecked);
const getCheckedOptionHref = () => {
return getCheckedOptions()
.at(0)
.attributes().href;
};
beforeEach(() => {
urlUtils.setUrlParams = jest.fn(({ sort }) => `${DUMMY_URL}/?sort=${sort}`);
wrapper = createComponent();
initComponent();
});
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
describe('Sorting behaviour', () => {
describe('when initialized', () => {
it('should have sorting options', () => {
expect(wrapper.findAll(GlNewDropdownItem)).toHaveLength(2);
});
it('should set the sorting option to `created_desc` by default', () => {
expect(getCheckedOptions()).toHaveLength(1);
expect(getCheckedOptionHref()).toBe(`${DUMMY_URL}/?sort=created_desc`);
});
it('should get the sorting option from the URL', () => {
urlUtils.queryToObject = jest.fn(() => ({ sort: 'created_asc' }));
wrapper = createComponent();
describe('with a sortBy value', () => {
beforeEach(() => {
initComponent({
sortBy: 'created_asc',
});
});
expect(getCheckedOptions()).toHaveLength(1);
expect(getCheckedOptionHref()).toBe(`${DUMMY_URL}/?sort=created_asc`);
it('should set the sorting option accordingly', () => {
expect(getCheckedOptions()).toHaveLength(1);
expect(
getCheckedOptions()
.at(0)
.text(),
).toEqual('Oldest created');
});
});
});
it('should retain other params when creating the option URL', () => {
urlUtils.setUrlParams = jest.fn(({ sort }) => `${DUMMY_URL}/?abc=defg&sort=${sort}`);
urlUtils.queryToObject = jest.fn(() => ({ sort: 'created_desc', abc: 'defg' }));
wrapper = createComponent();
describe('when the user clicks on a option', () => {
beforeEach(() => {
initComponent();
wrapper
.findAll(GlNewDropdownItem)
.at(1)
.vm.$emit('click');
});
expect(getCheckedOptionHref()).toBe(`${DUMMY_URL}/?abc=defg&sort=created_desc`);
it('should emit the "selected" event with clicked option', () => {
expect(wrapper.emitted().selected).toBeTruthy();
expect(wrapper.emitted().selected[0]).toEqual(['created_asc']);
});
});
});
......@@ -18,10 +18,9 @@ describe('Audit Event actions', () => {
});
it.each`
action | type | payload
${'setDateRange'} | ${types.SET_DATE_RANGE} | ${{ startDate, endDate }}
${'setFilterValue'} | ${types.SET_FILTER_VALUE} | ${{ id: '1', type: 'user' }}
${'setSortBy'} | ${types.SET_SORT_BY} | ${'created_asc'}
action | type | payload
${'setDateRange'} | ${types.SET_DATE_RANGE} | ${{ startDate, endDate }}
${'setSortBy'} | ${types.SET_SORT_BY} | ${'created_asc'}
`(
'$action should commit $type with $payload and dispatches "searchForAuditEvents"',
({ action, type, payload }) => {
......@@ -40,6 +39,11 @@ describe('Audit Event actions', () => {
},
);
it('setFilterValue action should commit to the store', () => {
const payload = [{ type: 'User', value: { data: 1, operator: '=' } }];
testAction(actions.setFilterValue, payload, state, [{ type: types.SET_FILTER_VALUE, payload }]);
});
describe('searchForAuditEvents', () => {
let spy;
......
......@@ -15,10 +15,10 @@ describe('Audit Event mutations', () => {
});
it.each`
mutation | payload | expectedState
${types.SET_FILTER_VALUE} | ${{ id: '1', type: 'user' }} | ${{ filterValue: { id: '1', type: 'user' } }}
${types.SET_DATE_RANGE} | ${{ startDate, endDate }} | ${{ startDate, endDate }}
${types.SET_SORT_BY} | ${'created_asc'} | ${{ sortBy: 'created_asc' }}
mutation | payload | expectedState
${types.SET_FILTER_VALUE} | ${[{ type: 'User', value: { data: 1, operator: '=' } }]} | ${{ filterValue: [{ type: 'User', value: { data: 1, operator: '=' } }] }}
${types.SET_DATE_RANGE} | ${{ startDate, endDate }} | ${{ startDate, endDate }}
${types.SET_SORT_BY} | ${'created_asc'} | ${{ sortBy: 'created_asc' }}
`(
'$mutation with payload $payload will update state with $expectedState',
({ mutation, payload, expectedState }) => {
......@@ -32,7 +32,7 @@ describe('Audit Event mutations', () => {
describe(`${types.INITIALIZE_AUDIT_EVENTS}`, () => {
const payload = {
entity_id: '1',
entity_type: 'user',
entity_type: 'User',
created_after: startDate,
created_before: endDate,
sort: 'created_asc',
......@@ -40,7 +40,7 @@ describe('Audit Event mutations', () => {
it.each`
stateKey | expectedState
${'filterValue'} | ${{ id: payload.entity_id, type: payload.entity_type }}
${'filterValue'} | ${[{ type: payload.entity_type, value: { data: payload.entity_id, operator: '=' } }]}
${'startDate'} | ${payload.created_after}
${'endDate'} | ${payload.created_before}
${'sortBy'} | ${payload.sort}
......
......@@ -8,6 +8,7 @@ describe('Audit Event Utils', () => {
created_before: '2020-04-13',
sortBy: 'created_asc',
};
expect(parseAuditEventSearchQuery(input)).toEqual({
created_after: new Date('2020-03-13'),
created_before: new Date('2020-04-13'),
......@@ -19,20 +20,19 @@ describe('Audit Event Utils', () => {
describe('createAuditEventSearchQuery', () => {
it('returns a query object with remapped keys and stringified dates', () => {
const input = {
filterValue: {
id: '1',
type: 'user',
},
filterValue: [{ type: 'User', value: { data: '1', operator: '=' } }],
startDate: new Date('2020-03-13'),
endDate: new Date('2020-04-13'),
sortBy: 'bar',
};
expect(createAuditEventSearchQuery(input)).toEqual({
entity_id: '1',
entity_type: 'user',
entity_type: 'User',
created_after: '2020-03-13',
created_before: '2020-04-13',
sort: 'bar',
page: null,
});
});
});
......
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