diff options
Diffstat (limited to 'spec/frontend/registry')
16 files changed, 703 insertions, 469 deletions
diff --git a/spec/frontend/registry/explorer/components/details_page/partial_cleanup_alert_spec.js b/spec/frontend/registry/explorer/components/details_page/partial_cleanup_alert_spec.js new file mode 100644 index 00000000000..17821d8be31 --- /dev/null +++ b/spec/frontend/registry/explorer/components/details_page/partial_cleanup_alert_spec.js @@ -0,0 +1,71 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlAlert, GlSprintf } from '@gitlab/ui'; +import component from '~/registry/explorer/components/details_page/partial_cleanup_alert.vue'; +import { DELETE_ALERT_TITLE, DELETE_ALERT_LINK_TEXT } from '~/registry/explorer/constants'; + +describe('Partial Cleanup alert', () => { + let wrapper; + + const findAlert = () => wrapper.find(GlAlert); + const findRunLink = () => wrapper.find('[data-testid="run-link"'); + const findHelpLink = () => wrapper.find('[data-testid="help-link"'); + + const mountComponent = () => { + wrapper = shallowMount(component, { + stubs: { GlSprintf }, + propsData: { + runCleanupPoliciesHelpPagePath: 'foo', + cleanupPoliciesHelpPagePath: 'bar', + }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + it(`gl-alert has the correct properties`, () => { + mountComponent(); + + expect(findAlert().props()).toMatchObject({ + title: DELETE_ALERT_TITLE, + variant: 'warning', + }); + }); + + it('has the right text', () => { + mountComponent(); + + expect(wrapper.text()).toMatchInterpolatedText(DELETE_ALERT_LINK_TEXT); + }); + + it('contains run link', () => { + mountComponent(); + + const link = findRunLink(); + expect(link.exists()).toBe(true); + expect(link.attributes()).toMatchObject({ + href: 'foo', + target: '_blank', + }); + }); + + it('contains help link', () => { + mountComponent(); + + const link = findHelpLink(); + expect(link.exists()).toBe(true); + expect(link.attributes()).toMatchObject({ + href: 'bar', + target: '_blank', + }); + }); + + it('GlAlert dismiss event triggers a dismiss event', () => { + mountComponent(); + + findAlert().vm.$emit('dismiss'); + expect(wrapper.emitted('dismiss')).toEqual([[]]); + }); +}); diff --git a/spec/frontend/registry/explorer/components/list_page/image_list_row_spec.js b/spec/frontend/registry/explorer/components/list_page/image_list_row_spec.js index c5b4b3fa5d8..ce446e6d93e 100644 --- a/spec/frontend/registry/explorer/components/list_page/image_list_row_spec.js +++ b/spec/frontend/registry/explorer/components/list_page/image_list_row_spec.js @@ -9,6 +9,8 @@ import { ROW_SCHEDULED_FOR_DELETION, LIST_DELETE_BUTTON_DISABLED, REMOVE_REPOSITORY_LABEL, + ASYNC_DELETE_IMAGE_ERROR_MESSAGE, + CLEANUP_TIMED_OUT_ERROR_MESSAGE, } from '~/registry/explorer/constants'; import { RouterLink } from '../../stubs'; import { imagesListResponse } from '../../mock_data'; @@ -21,6 +23,7 @@ describe('Image List Row', () => { const findTagsCount = () => wrapper.find('[data-testid="tagsCount"]'); const findDeleteBtn = () => wrapper.find(DeleteButton); const findClipboardButton = () => wrapper.find(ClipboardButton); + const findWarningIcon = () => wrapper.find('[data-testid="warning-icon"]'); const mountComponent = props => { wrapper = shallowMount(Component, { @@ -74,6 +77,26 @@ describe('Image List Row', () => { expect(button.props('text')).toBe(item.location); expect(button.props('title')).toBe(item.location); }); + + describe('warning icon', () => { + it.each` + failedDelete | cleanup_policy_started_at | shown | title + ${true} | ${true} | ${true} | ${ASYNC_DELETE_IMAGE_ERROR_MESSAGE} + ${false} | ${true} | ${true} | ${CLEANUP_TIMED_OUT_ERROR_MESSAGE} + ${false} | ${false} | ${false} | ${''} + `( + 'when failedDelete is $failedDelete and cleanup_policy_started_at is $cleanup_policy_started_at', + ({ cleanup_policy_started_at, failedDelete, shown, title }) => { + mountComponent({ item: { ...item, failedDelete, cleanup_policy_started_at } }); + const icon = findWarningIcon(); + expect(icon.exists()).toBe(shown); + if (shown) { + const tooltip = getBinding(icon.element, 'gl-tooltip'); + expect(tooltip.value.title).toBe(title); + } + }, + ); + }); }); describe('delete button', () => { diff --git a/spec/frontend/registry/explorer/components/list_page/registry_header_spec.js b/spec/frontend/registry/explorer/components/list_page/registry_header_spec.js index 7a27f8fa431..3c997093d46 100644 --- a/spec/frontend/registry/explorer/components/list_page/registry_header_spec.js +++ b/spec/frontend/registry/explorer/components/list_page/registry_header_spec.js @@ -1,5 +1,5 @@ import { shallowMount } from '@vue/test-utils'; -import { GlSprintf, GlLink } from '@gitlab/ui'; +import { GlSprintf } from '@gitlab/ui'; import Component from '~/registry/explorer/components/list_page/registry_header.vue'; import TitleArea from '~/vue_shared/components/registry/title_area.vue'; import { @@ -19,12 +19,8 @@ describe('registry_header', () => { const findTitleArea = () => wrapper.find(TitleArea); const findCommandsSlot = () => wrapper.find('[data-testid="commands-slot"]'); - const findInfoArea = () => wrapper.find('[data-testid="info-area"]'); - const findIntroText = () => wrapper.find('[data-testid="default-intro"]'); const findImagesCountSubHeader = () => wrapper.find('[data-testid="images-count"]'); const findExpirationPolicySubHeader = () => wrapper.find('[data-testid="expiration-policy"]'); - const findDisabledExpirationPolicyMessage = () => - wrapper.find('[data-testid="expiration-disabled-message"]'); const mountComponent = (propsData, slots) => { wrapper = shallowMount(Component, { @@ -123,44 +119,18 @@ describe('registry_header', () => { }); }); - describe('info area', () => { - it('exists', () => { - mountComponent(); - - expect(findInfoArea().exists()).toBe(true); - }); - + describe('info messages', () => { describe('default message', () => { - beforeEach(() => { - return mountComponent({ helpPagePath: 'bar' }); - }); - - it('exists', () => { - expect(findIntroText().exists()).toBe(true); - }); - - it('has the correct copy', () => { - expect(findIntroText().text()).toMatchInterpolatedText(LIST_INTRO_TEXT); - }); + it('is correctly bound to title_area props', () => { + mountComponent({ helpPagePath: 'foo' }); - it('has the correct link', () => { - expect( - findIntroText() - .find(GlLink) - .attributes('href'), - ).toBe('bar'); + expect(findTitleArea().props('infoMessages')).toEqual([ + { text: LIST_INTRO_TEXT, link: 'foo' }, + ]); }); }); describe('expiration policy info message', () => { - describe('when there are no images', () => { - it('is hidden', () => { - mountComponent(); - - expect(findDisabledExpirationPolicyMessage().exists()).toBe(false); - }); - }); - describe('when there are images', () => { describe('when expiration policy is disabled', () => { beforeEach(() => { @@ -170,43 +140,27 @@ describe('registry_header', () => { imagesCount: 1, }); }); - it('message exist', () => { - expect(findDisabledExpirationPolicyMessage().exists()).toBe(true); - }); - it('has the correct copy', () => { - expect(findDisabledExpirationPolicyMessage().text()).toMatchInterpolatedText( - EXPIRATION_POLICY_DISABLED_MESSAGE, - ); - }); - it('has the correct link', () => { - expect( - findDisabledExpirationPolicyMessage() - .find(GlLink) - .attributes('href'), - ).toBe('foo'); + it('the prop is correctly bound', () => { + expect(findTitleArea().props('infoMessages')).toEqual([ + { text: LIST_INTRO_TEXT, link: '' }, + { text: EXPIRATION_POLICY_DISABLED_MESSAGE, link: 'foo' }, + ]); }); }); - describe('when expiration policy is enabled', () => { + describe.each` + desc | props + ${'when there are no images'} | ${{ expirationPolicy: { enabled: false }, imagesCount: 0 }} + ${'when expiration policy is enabled'} | ${{ expirationPolicy: { enabled: true }, imagesCount: 1 }} + ${'when the expiration policy is completely disabled'} | ${{ expirationPolicy: { enabled: false }, imagesCount: 1, hideExpirationPolicyData: true }} + `('$desc', ({ props }) => { it('message does not exist', () => { - mountComponent({ - expirationPolicy: { enabled: true }, - imagesCount: 1, - }); - - expect(findDisabledExpirationPolicyMessage().exists()).toBe(false); - }); - }); - describe('when the expiration policy is completely disabled', () => { - it('message does not exist', () => { - mountComponent({ - expirationPolicy: { enabled: true }, - imagesCount: 1, - hideExpirationPolicyData: true, - }); + mountComponent(props); - expect(findDisabledExpirationPolicyMessage().exists()).toBe(false); + expect(findTitleArea().props('infoMessages')).toEqual([ + { text: LIST_INTRO_TEXT, link: '' }, + ]); }); }); }); diff --git a/spec/frontend/registry/explorer/pages/details_spec.js b/spec/frontend/registry/explorer/pages/details_spec.js index 66e8a4aea0d..86b52c4f06a 100644 --- a/spec/frontend/registry/explorer/pages/details_spec.js +++ b/spec/frontend/registry/explorer/pages/details_spec.js @@ -3,6 +3,7 @@ import { GlPagination } from '@gitlab/ui'; import Tracking from '~/tracking'; import component from '~/registry/explorer/pages/details.vue'; import DeleteAlert from '~/registry/explorer/components/details_page/delete_alert.vue'; +import PartialCleanupAlert from '~/registry/explorer/components/details_page/partial_cleanup_alert.vue'; import DetailsHeader from '~/registry/explorer/components/details_page/details_header.vue'; import TagsLoader from '~/registry/explorer/components/details_page/tags_loader.vue'; import TagsList from '~/registry/explorer/components/details_page/tags_list.vue'; @@ -30,8 +31,10 @@ describe('Details Page', () => { const findDeleteAlert = () => wrapper.find(DeleteAlert); const findDetailsHeader = () => wrapper.find(DetailsHeader); const findEmptyTagsState = () => wrapper.find(EmptyTagsState); + const findPartialCleanupAlert = () => wrapper.find(PartialCleanupAlert); - const routeId = window.btoa(JSON.stringify({ name: 'foo', tags_path: 'bar' })); + const routeIdGenerator = override => + window.btoa(JSON.stringify({ name: 'foo', tags_path: 'bar', ...override })); const tagsArrayToSelectedTags = tags => tags.reduce((acc, c) => { @@ -39,7 +42,7 @@ describe('Details Page', () => { return acc; }, {}); - const mountComponent = options => { + const mountComponent = ({ options, routeParams } = {}) => { wrapper = shallowMount(component, { store, stubs: { @@ -48,7 +51,7 @@ describe('Details Page', () => { mocks: { $route: { params: { - id: routeId, + id: routeIdGenerator(routeParams), }, }, }, @@ -224,7 +227,7 @@ describe('Details Page', () => { findDeleteModal().vm.$emit('confirmDelete'); expect(dispatchSpy).toHaveBeenCalledWith('requestDeleteTag', { tag: store.state.tags[0], - params: routeId, + params: routeIdGenerator(), }); }); }); @@ -239,7 +242,7 @@ describe('Details Page', () => { findDeleteModal().vm.$emit('confirmDelete'); expect(dispatchSpy).toHaveBeenCalledWith('requestDeleteTags', { ids: store.state.tags.map(t => t.name), - params: routeId, + params: routeIdGenerator(), }); }); }); @@ -273,11 +276,57 @@ describe('Details Page', () => { it('has the correct props', () => { store.commit(SET_INITIAL_STATE, { ...config }); mountComponent({ - data: () => ({ - deleteAlertType, - }), + options: { + data: () => ({ + deleteAlertType, + }), + }, }); expect(findDeleteAlert().props()).toEqual({ ...config, deleteAlertType }); }); }); + + describe('Partial Cleanup Alert', () => { + const config = { + runCleanupPoliciesHelpPagePath: 'foo', + cleanupPoliciesHelpPagePath: 'bar', + }; + + describe('when expiration_policy_started is not null', () => { + const routeParams = { cleanup_policy_started_at: Date.now().toString() }; + + it('exists', () => { + mountComponent({ routeParams }); + + expect(findPartialCleanupAlert().exists()).toBe(true); + }); + + it('has the correct props', () => { + store.commit(SET_INITIAL_STATE, { ...config }); + + mountComponent({ routeParams }); + + expect(findPartialCleanupAlert().props()).toEqual({ ...config }); + }); + + it('dismiss hides the component', async () => { + mountComponent({ routeParams }); + + expect(findPartialCleanupAlert().exists()).toBe(true); + findPartialCleanupAlert().vm.$emit('dismiss'); + + await wrapper.vm.$nextTick(); + + expect(findPartialCleanupAlert().exists()).toBe(false); + }); + }); + + describe('when expiration_policy_started is null', () => { + it('the component is hidden', () => { + mountComponent(); + + expect(findPartialCleanupAlert().exists()).toBe(false); + }); + }); + }); }); diff --git a/spec/frontend/registry/settings/components/__snapshots__/registry_settings_app_spec.js.snap b/spec/frontend/registry/settings/components/__snapshots__/registry_settings_app_spec.js.snap deleted file mode 100644 index 11393c89d06..00000000000 --- a/spec/frontend/registry/settings/components/__snapshots__/registry_settings_app_spec.js.snap +++ /dev/null @@ -1,7 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Registry Settings App renders 1`] = ` -<div> - <settings-form-stub /> -</div> -`; diff --git a/spec/frontend/registry/settings/components/registry_settings_app_spec.js b/spec/frontend/registry/settings/components/registry_settings_app_spec.js index 9551ee72e51..a784396f47a 100644 --- a/spec/frontend/registry/settings/components/registry_settings_app_spec.js +++ b/spec/frontend/registry/settings/components/registry_settings_app_spec.js @@ -1,28 +1,35 @@ -import { shallowMount } from '@vue/test-utils'; +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import VueApollo from 'vue-apollo'; import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui'; +import createMockApollo from 'jest/helpers/mock_apollo_helper'; import component from '~/registry/settings/components/registry_settings_app.vue'; +import expirationPolicyQuery from '~/registry/settings/graphql/queries/get_expiration_policy.graphql'; import SettingsForm from '~/registry/settings/components/settings_form.vue'; -import { createStore } from '~/registry/settings/store/'; -import { SET_SETTINGS, SET_INITIAL_STATE } from '~/registry/settings/store/mutation_types'; import { FETCH_SETTINGS_ERROR_MESSAGE } from '~/registry/shared/constants'; import { UNAVAILABLE_FEATURE_INTRO_TEXT, UNAVAILABLE_USER_FEATURE_TEXT, } from '~/registry/settings/constants'; -import { stringifiedFormOptions } from '../../shared/mock_data'; +import { expirationPolicyPayload, emptyExpirationPolicyPayload } from '../mock_data'; + +const localVue = createLocalVue(); describe('Registry Settings App', () => { let wrapper; - let store; + let fakeApollo; + + const defaultProvidedValues = { + projectPath: 'path', + isAdmin: false, + adminSettingsPath: 'settingsPath', + enableHistoricEntries: false, + }; const findSettingsComponent = () => wrapper.find(SettingsForm); const findAlert = () => wrapper.find(GlAlert); - const mountComponent = ({ dispatchMock = 'mockResolvedValue' } = {}) => { - const dispatchSpy = jest.spyOn(store, 'dispatch'); - dispatchSpy[dispatchMock](); - + const mountComponent = (provide = defaultProvidedValues, config) => { wrapper = shallowMount(component, { stubs: { GlSprintf, @@ -32,71 +39,72 @@ describe('Registry Settings App', () => { show: jest.fn(), }, }, - store, + provide, + ...config, }); }; - beforeEach(() => { - store = createStore(); - }); + const mountComponentWithApollo = ({ provide = defaultProvidedValues, resolver } = {}) => { + localVue.use(VueApollo); + + const requestHandlers = [[expirationPolicyQuery, resolver]]; + + fakeApollo = createMockApollo(requestHandlers); + mountComponent(provide, { + localVue, + apolloProvider: fakeApollo, + }); + + return requestHandlers.map(request => request[1]); + }; afterEach(() => { wrapper.destroy(); }); - it('renders', () => { - store.commit(SET_SETTINGS, { foo: 'bar' }); - mountComponent(); - expect(wrapper.element).toMatchSnapshot(); - }); - - it('call the store function to load the data on mount', () => { - mountComponent(); - expect(store.dispatch).toHaveBeenCalledWith('fetchSettings'); - }); + it('renders the setting form', async () => { + const requests = mountComponentWithApollo({ + resolver: jest.fn().mockResolvedValue(expirationPolicyPayload()), + }); + await Promise.all(requests); - it('renders the setting form', () => { - store.commit(SET_SETTINGS, { foo: 'bar' }); - mountComponent(); expect(findSettingsComponent().exists()).toBe(true); }); describe('the form is disabled', () => { - beforeEach(() => { - store.commit(SET_SETTINGS, undefined); + it('the form is hidden', () => { mountComponent(); - }); - it('the form is hidden', () => { expect(findSettingsComponent().exists()).toBe(false); }); it('shows an alert', () => { + mountComponent(); + const text = findAlert().text(); expect(text).toContain(UNAVAILABLE_FEATURE_INTRO_TEXT); expect(text).toContain(UNAVAILABLE_USER_FEATURE_TEXT); }); describe('an admin is visiting the page', () => { - beforeEach(() => { - store.commit(SET_INITIAL_STATE, { - ...stringifiedFormOptions, - isAdmin: true, - adminSettingsPath: 'foo', - }); - }); - it('shows the admin part of the alert message', () => { + mountComponent({ ...defaultProvidedValues, isAdmin: true }); + const sprintf = findAlert().find(GlSprintf); expect(sprintf.text()).toBe('administration settings'); - expect(sprintf.find(GlLink).attributes('href')).toBe('foo'); + expect(sprintf.find(GlLink).attributes('href')).toBe( + defaultProvidedValues.adminSettingsPath, + ); }); }); }); describe('fetchSettingsError', () => { beforeEach(() => { - mountComponent({ dispatchMock: 'mockRejectedValue' }); + const requests = mountComponentWithApollo({ + resolver: jest.fn().mockRejectedValue(new Error('GraphQL error')), + }); + return Promise.all(requests); }); it('the form is hidden', () => { @@ -107,4 +115,23 @@ describe('Registry Settings App', () => { expect(findAlert().html()).toContain(FETCH_SETTINGS_ERROR_MESSAGE); }); }); + + describe('empty API response', () => { + it.each` + enableHistoricEntries | isShown + ${true} | ${true} + ${false} | ${false} + `('is $isShown that the form is shown', async ({ enableHistoricEntries, isShown }) => { + const requests = mountComponentWithApollo({ + provide: { + ...defaultProvidedValues, + enableHistoricEntries, + }, + resolver: jest.fn().mockResolvedValue(emptyExpirationPolicyPayload()), + }); + await Promise.all(requests); + + expect(findSettingsComponent().exists()).toBe(isShown); + }); + }); }); diff --git a/spec/frontend/registry/settings/components/settings_form_spec.js b/spec/frontend/registry/settings/components/settings_form_spec.js index 6f9518808db..4346cfadcc8 100644 --- a/spec/frontend/registry/settings/components/settings_form_spec.js +++ b/spec/frontend/registry/settings/components/settings_form_spec.js @@ -1,30 +1,37 @@ -import { shallowMount } from '@vue/test-utils'; +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import VueApollo from 'vue-apollo'; +import createMockApollo from 'jest/helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; import Tracking from '~/tracking'; import component from '~/registry/settings/components/settings_form.vue'; import expirationPolicyFields from '~/registry/shared/components/expiration_policy_fields.vue'; -import { createStore } from '~/registry/settings/store/'; +import updateContainerExpirationPolicyMutation from '~/registry/settings/graphql/mutations/update_container_expiration_policy.graphql'; +import expirationPolicyQuery from '~/registry/settings/graphql/queries/get_expiration_policy.graphql'; import { UPDATE_SETTINGS_ERROR_MESSAGE, UPDATE_SETTINGS_SUCCESS_MESSAGE, } from '~/registry/shared/constants'; -import { stringifiedFormOptions } from '../../shared/mock_data'; +import { GlCard, GlLoadingIcon } from '../../shared/stubs'; +import { expirationPolicyPayload, expirationPolicyMutationPayload } from '../mock_data'; + +const localVue = createLocalVue(); describe('Settings Form', () => { let wrapper; - let store; - let dispatchSpy; - - const GlLoadingIcon = { name: 'gl-loading-icon-stub', template: '<svg></svg>' }; - const GlCard = { - name: 'gl-card-stub', - template: ` - <div> - <slot name="header"></slot> - <slot></slot> - <slot name="footer"></slot> - </div> - `, + let fakeApollo; + + const defaultProvidedValues = { + projectPath: 'path', + }; + + const { + data: { + project: { containerExpirationPolicy }, + }, + } = expirationPolicyPayload(); + + const defaultProps = { + value: { ...containerExpirationPolicy }, }; const trackingPayload = { @@ -35,14 +42,21 @@ describe('Settings Form', () => { const findFields = () => wrapper.find(expirationPolicyFields); const findCancelButton = () => wrapper.find({ ref: 'cancel-button' }); const findSaveButton = () => wrapper.find({ ref: 'save-button' }); - const findLoadingIcon = (parent = wrapper) => parent.find(GlLoadingIcon); - const mountComponent = (data = {}) => { + const mountComponent = ({ + props = defaultProps, + data, + config, + provide = defaultProvidedValues, + mocks, + } = {}) => { wrapper = shallowMount(component, { stubs: { GlCard, GlLoadingIcon, }, + propsData: { ...props }, + provide, data() { return { ...data, @@ -52,15 +66,42 @@ describe('Settings Form', () => { $toast: { show: jest.fn(), }, + ...mocks, + }, + ...config, + }); + }; + + const mountComponentWithApollo = ({ provide = defaultProvidedValues, resolver } = {}) => { + localVue.use(VueApollo); + + const requestHandlers = [ + [updateContainerExpirationPolicyMutation, resolver], + [expirationPolicyQuery, jest.fn().mockResolvedValue(expirationPolicyPayload())], + ]; + + fakeApollo = createMockApollo(requestHandlers); + + fakeApollo.defaultClient.cache.writeQuery({ + query: expirationPolicyQuery, + variables: { + projectPath: provide.projectPath, }, - store, + ...expirationPolicyPayload(), }); + + mountComponent({ + provide, + config: { + localVue, + apolloProvider: fakeApollo, + }, + }); + + return requestHandlers.map(resolvers => resolvers[1]); }; beforeEach(() => { - store = createStore(); - store.dispatch('setInitialState', stringifiedFormOptions); - dispatchSpy = jest.spyOn(store, 'dispatch'); jest.spyOn(Tracking, 'event'); }); @@ -72,32 +113,36 @@ describe('Settings Form', () => { it('v-model change update the settings property', () => { mountComponent(); findFields().vm.$emit('input', { newValue: 'foo' }); - expect(dispatchSpy).toHaveBeenCalledWith('updateSettings', { settings: 'foo' }); + expect(wrapper.emitted('input')).toEqual([['foo']]); }); it('v-model change update the api error property', () => { const apiErrors = { baz: 'bar' }; - mountComponent({ apiErrors }); + mountComponent({ data: { apiErrors } }); expect(findFields().props('apiErrors')).toEqual(apiErrors); findFields().vm.$emit('input', { newValue: 'foo', modified: 'baz' }); expect(findFields().props('apiErrors')).toEqual({}); }); - }); - describe('form', () => { - let form; - beforeEach(() => { - mountComponent(); - form = findForm(); - dispatchSpy.mockReturnValue(); + it('shows the default option when none are selected', () => { + mountComponent({ props: { value: {} } }); + expect(findFields().props('value')).toEqual({ + cadence: 'EVERY_DAY', + keepN: 'TEN_TAGS', + olderThan: 'NINETY_DAYS', + }); }); + }); + describe('form', () => { describe('form reset event', () => { beforeEach(() => { - form.trigger('reset'); + mountComponent(); + + findForm().trigger('reset'); }); it('calls the appropriate function', () => { - expect(dispatchSpy).toHaveBeenCalledWith('resetSettings'); + expect(wrapper.emitted('reset')).toEqual([[]]); }); it('tracks the reset event', () => { @@ -108,54 +153,96 @@ describe('Settings Form', () => { describe('form submit event ', () => { it('save has type submit', () => { mountComponent(); + expect(findSaveButton().attributes('type')).toBe('submit'); }); - it('dispatches the saveSettings action', () => { - dispatchSpy.mockResolvedValue(); - form.trigger('submit'); - expect(dispatchSpy).toHaveBeenCalledWith('saveSettings'); + it('dispatches the correct apollo mutation', async () => { + const [expirationPolicyMutationResolver] = mountComponentWithApollo({ + resolver: jest.fn().mockResolvedValue(expirationPolicyMutationPayload()), + }); + + findForm().trigger('submit'); + await expirationPolicyMutationResolver(); + expect(expirationPolicyMutationResolver).toHaveBeenCalled(); }); it('tracks the submit event', () => { - dispatchSpy.mockResolvedValue(); - form.trigger('submit'); + mountComponentWithApollo({ + resolver: jest.fn().mockResolvedValue(expirationPolicyMutationPayload()), + }); + + findForm().trigger('submit'); + expect(Tracking.event).toHaveBeenCalledWith(undefined, 'submit_form', trackingPayload); }); it('show a success toast when submit succeed', async () => { - dispatchSpy.mockResolvedValue(); - form.trigger('submit'); - await waitForPromises(); + const handlers = mountComponentWithApollo({ + resolver: jest.fn().mockResolvedValue(expirationPolicyMutationPayload()), + }); + + findForm().trigger('submit'); + await Promise.all(handlers); + await wrapper.vm.$nextTick(); + expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(UPDATE_SETTINGS_SUCCESS_MESSAGE, { type: 'success', }); }); describe('when submit fails', () => { - it('shows an error', async () => { - dispatchSpy.mockRejectedValue({ response: {} }); - form.trigger('submit'); - await waitForPromises(); - expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(UPDATE_SETTINGS_ERROR_MESSAGE, { - type: 'error', + describe('user recoverable errors', () => { + it('when there is an error is shown in a toast', async () => { + const handlers = mountComponentWithApollo({ + resolver: jest + .fn() + .mockResolvedValue(expirationPolicyMutationPayload({ errors: ['foo'] })), + }); + + findForm().trigger('submit'); + await Promise.all(handlers); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.$toast.show).toHaveBeenCalledWith('foo', { + type: 'error', + }); }); }); + describe('global errors', () => { + it('shows an error', async () => { + const handlers = mountComponentWithApollo({ + resolver: jest.fn().mockRejectedValue(expirationPolicyMutationPayload()), + }); - it('parses the error messages', async () => { - dispatchSpy.mockRejectedValue({ - response: { - data: { - message: { - foo: 'bar', - 'container_expiration_policy.name': ['baz'], + findForm().trigger('submit'); + await Promise.all(handlers); + await wrapper.vm.$nextTick(); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(UPDATE_SETTINGS_ERROR_MESSAGE, { + type: 'error', + }); + }); + + it('parses the error messages', async () => { + const mutate = jest.fn().mockRejectedValue({ + graphQLErrors: [ + { + extensions: { + problems: [{ path: ['name'], message: 'baz' }], + }, }, - }, - }, + ], + }); + mountComponent({ mocks: { $apollo: { mutate } } }); + + findForm().trigger('submit'); + await waitForPromises(); + await wrapper.vm.$nextTick(); + + expect(findFields().props('apiErrors')).toEqual({ name: 'baz' }); }); - form.trigger('submit'); - await waitForPromises(); - expect(findFields().props('apiErrors')).toEqual({ name: 'baz' }); }); }); }); @@ -163,51 +250,78 @@ describe('Settings Form', () => { describe('form actions', () => { describe('cancel button', () => { - beforeEach(() => { - store.commit('SET_SETTINGS', { foo: 'bar' }); + it('has type reset', () => { mountComponent(); - }); - it('has type reset', () => { expect(findCancelButton().attributes('type')).toBe('reset'); }); - it('is disabled when isEdited is false', () => - wrapper.vm.$nextTick().then(() => { - expect(findCancelButton().attributes('disabled')).toBe('true'); - })); - - it('is disabled isLoading is true', () => { - store.commit('TOGGLE_LOADING'); - store.commit('UPDATE_SETTINGS', { settings: { foo: 'baz' } }); - return wrapper.vm.$nextTick().then(() => { - expect(findCancelButton().attributes('disabled')).toBe('true'); - store.commit('TOGGLE_LOADING'); - }); - }); + it.each` + isLoading | isEdited | mutationLoading | isDisabled + ${true} | ${true} | ${true} | ${true} + ${false} | ${true} | ${true} | ${true} + ${false} | ${false} | ${true} | ${true} + ${true} | ${false} | ${false} | ${true} + ${false} | ${false} | ${false} | ${true} + ${false} | ${true} | ${false} | ${false} + `( + 'when isLoading is $isLoading and isEdited is $isEdited and mutationLoading is $mutationLoading is $isDisabled that the is disabled', + ({ isEdited, isLoading, mutationLoading, isDisabled }) => { + mountComponent({ + props: { ...defaultProps, isEdited, isLoading }, + data: { mutationLoading }, + }); - it('is enabled when isLoading is false and isEdited is true', () => { - store.commit('UPDATE_SETTINGS', { settings: { foo: 'baz' } }); - return wrapper.vm.$nextTick().then(() => { - expect(findCancelButton().attributes('disabled')).toBe(undefined); - }); - }); + const expectation = isDisabled ? 'true' : undefined; + expect(findCancelButton().attributes('disabled')).toBe(expectation); + }, + ); }); - describe('when isLoading is true', () => { - beforeEach(() => { - store.commit('TOGGLE_LOADING'); + describe('submit button', () => { + it('has type submit', () => { mountComponent(); - }); - afterEach(() => { - store.commit('TOGGLE_LOADING'); - }); - it('submit button is disabled and shows a spinner', () => { - const button = findSaveButton(); - expect(button.attributes('disabled')).toBeTruthy(); - expect(findLoadingIcon(button).exists()).toBe(true); + expect(findSaveButton().attributes('type')).toBe('submit'); }); + it.each` + isLoading | fieldsAreValid | mutationLoading | isDisabled + ${true} | ${true} | ${true} | ${true} + ${false} | ${true} | ${true} | ${true} + ${false} | ${false} | ${true} | ${true} + ${true} | ${false} | ${false} | ${true} + ${false} | ${false} | ${false} | ${true} + ${false} | ${true} | ${false} | ${false} + `( + 'when isLoading is $isLoading and fieldsAreValid is $fieldsAreValid and mutationLoading is $mutationLoading is $isDisabled that the is disabled', + ({ fieldsAreValid, isLoading, mutationLoading, isDisabled }) => { + mountComponent({ + props: { ...defaultProps, isLoading }, + data: { mutationLoading, fieldsAreValid }, + }); + + const expectation = isDisabled ? 'true' : undefined; + expect(findSaveButton().attributes('disabled')).toBe(expectation); + }, + ); + + it.each` + isLoading | mutationLoading | showLoading + ${true} | ${true} | ${true} + ${true} | ${false} | ${true} + ${false} | ${true} | ${true} + ${false} | ${false} | ${false} + `( + 'when isLoading is $isLoading and mutationLoading is $mutationLoading is $showLoading that the loading icon is shown', + ({ isLoading, mutationLoading, showLoading }) => { + mountComponent({ + props: { ...defaultProps, isLoading }, + data: { mutationLoading }, + }); + + expect(findSaveButton().props('loading')).toBe(showLoading); + }, + ); }); }); }); diff --git a/spec/frontend/registry/settings/graphql/cache_updated_spec.js b/spec/frontend/registry/settings/graphql/cache_updated_spec.js new file mode 100644 index 00000000000..e5f69a08285 --- /dev/null +++ b/spec/frontend/registry/settings/graphql/cache_updated_spec.js @@ -0,0 +1,56 @@ +import { updateContainerExpirationPolicy } from '~/registry/settings/graphql/utils/cache_update'; +import expirationPolicyQuery from '~/registry/settings/graphql/queries/get_expiration_policy.graphql'; + +describe('Registry settings cache update', () => { + let client; + + const payload = { + data: { + updateContainerExpirationPolicy: { + containerExpirationPolicy: { + enabled: true, + }, + }, + }, + }; + + const cacheMock = { + project: { + containerExpirationPolicy: { + enabled: false, + }, + }, + }; + + const queryAndVariables = { + query: expirationPolicyQuery, + variables: { projectPath: 'foo' }, + }; + + beforeEach(() => { + client = { + readQuery: jest.fn().mockReturnValue(cacheMock), + writeQuery: jest.fn(), + }; + }); + describe('Registry settings cache update', () => { + it('calls readQuery', () => { + updateContainerExpirationPolicy('foo')(client, payload); + expect(client.readQuery).toHaveBeenCalledWith(queryAndVariables); + }); + + it('writes the correct result in the cache', () => { + updateContainerExpirationPolicy('foo')(client, payload); + expect(client.writeQuery).toHaveBeenCalledWith({ + ...queryAndVariables, + data: { + project: { + containerExpirationPolicy: { + enabled: true, + }, + }, + }, + }); + }); + }); +}); diff --git a/spec/frontend/registry/settings/mock_data.js b/spec/frontend/registry/settings/mock_data.js new file mode 100644 index 00000000000..7f3772ce7fe --- /dev/null +++ b/spec/frontend/registry/settings/mock_data.js @@ -0,0 +1,40 @@ +export const expirationPolicyPayload = override => ({ + data: { + project: { + containerExpirationPolicy: { + cadence: 'EVERY_DAY', + enabled: true, + keepN: 'TEN_TAGS', + nameRegex: 'asdasdssssdfdf', + nameRegexKeep: 'sss', + olderThan: 'FOURTEEN_DAYS', + ...override, + }, + }, + }, +}); + +export const emptyExpirationPolicyPayload = () => ({ + data: { + project: { + containerExpirationPolicy: {}, + }, + }, +}); + +export const expirationPolicyMutationPayload = ({ override, errors = [] } = {}) => ({ + data: { + updateContainerExpirationPolicy: { + containerExpirationPolicy: { + cadence: 'EVERY_DAY', + enabled: true, + keepN: 'TEN_TAGS', + nameRegex: 'asdasdssssdfdf', + nameRegexKeep: 'sss', + olderThan: 'FOURTEEN_DAYS', + ...override, + }, + errors, + }, + }, +}); diff --git a/spec/frontend/registry/settings/store/actions_spec.js b/spec/frontend/registry/settings/store/actions_spec.js deleted file mode 100644 index 51b89f96ef2..00000000000 --- a/spec/frontend/registry/settings/store/actions_spec.js +++ /dev/null @@ -1,90 +0,0 @@ -import testAction from 'helpers/vuex_action_helper'; -import Api from '~/api'; -import * as actions from '~/registry/settings/store/actions'; -import * as types from '~/registry/settings/store/mutation_types'; - -describe('Actions Registry Store', () => { - describe.each` - actionName | mutationName | payload - ${'setInitialState'} | ${types.SET_INITIAL_STATE} | ${'foo'} - ${'updateSettings'} | ${types.UPDATE_SETTINGS} | ${'foo'} - ${'toggleLoading'} | ${types.TOGGLE_LOADING} | ${undefined} - ${'resetSettings'} | ${types.RESET_SETTINGS} | ${undefined} - `( - '$actionName invokes $mutationName with payload $payload', - ({ actionName, mutationName, payload }) => { - it('should set state', done => { - testAction(actions[actionName], payload, {}, [{ type: mutationName, payload }], [], done); - }); - }, - ); - - describe('receiveSettingsSuccess', () => { - it('calls SET_SETTINGS', () => { - testAction( - actions.receiveSettingsSuccess, - 'foo', - {}, - [{ type: types.SET_SETTINGS, payload: 'foo' }], - [], - ); - }); - }); - - describe('fetchSettings', () => { - const state = { - projectId: 'bar', - }; - - const payload = { - data: { - container_expiration_policy: 'foo', - }, - }; - - it('should fetch the data from the API', done => { - Api.project = jest.fn().mockResolvedValue(payload); - testAction( - actions.fetchSettings, - null, - state, - [], - [ - { type: 'toggleLoading' }, - { type: 'receiveSettingsSuccess', payload: payload.data.container_expiration_policy }, - { type: 'toggleLoading' }, - ], - done, - ); - }); - }); - - describe('saveSettings', () => { - const state = { - projectId: 'bar', - settings: 'baz', - }; - - const payload = { - data: { - tag_expiration_policies: 'foo', - }, - }; - - it('should fetch the data from the API', done => { - Api.updateProject = jest.fn().mockResolvedValue(payload); - testAction( - actions.saveSettings, - null, - state, - [], - [ - { type: 'toggleLoading' }, - { type: 'receiveSettingsSuccess', payload: payload.data.container_expiration_policy }, - { type: 'toggleLoading' }, - ], - done, - ); - }); - }); -}); diff --git a/spec/frontend/registry/settings/store/getters_spec.js b/spec/frontend/registry/settings/store/getters_spec.js deleted file mode 100644 index b781d09466c..00000000000 --- a/spec/frontend/registry/settings/store/getters_spec.js +++ /dev/null @@ -1,72 +0,0 @@ -import * as getters from '~/registry/settings/store/getters'; -import * as utils from '~/registry/shared/utils'; -import { formOptions } from '../../shared/mock_data'; - -describe('Getters registry settings store', () => { - const settings = { - enabled: true, - cadence: 'foo', - keep_n: 'bar', - older_than: 'baz', - name_regex: 'name-foo', - name_regex_keep: 'name-keep-bar', - }; - - describe.each` - getter | variable | formOption - ${'getCadence'} | ${'cadence'} | ${'cadence'} - ${'getKeepN'} | ${'keep_n'} | ${'keepN'} - ${'getOlderThan'} | ${'older_than'} | ${'olderThan'} - `('Options getter', ({ getter, variable, formOption }) => { - beforeEach(() => { - utils.findDefaultOption = jest.fn(); - }); - - it(`${getter} returns ${variable} when ${variable} exists in settings`, () => { - expect(getters[getter]({ settings })).toBe(settings[variable]); - }); - - it(`${getter} calls findDefaultOption when ${variable} does not exists in settings`, () => { - getters[getter]({ settings: {}, formOptions }); - expect(utils.findDefaultOption).toHaveBeenCalledWith(formOptions[formOption]); - }); - }); - - describe('getSettings', () => { - it('returns the content of settings', () => { - const computedGetters = { - getCadence: settings.cadence, - getOlderThan: settings.older_than, - getKeepN: settings.keep_n, - }; - expect(getters.getSettings({ settings }, computedGetters)).toEqual(settings); - }); - }); - - describe('getIsEdited', () => { - it('returns false when original is equal to settings', () => { - const same = { foo: 'bar' }; - expect(getters.getIsEdited({ original: same, settings: same })).toBe(false); - }); - - it('returns true when original is different from settings', () => { - expect(getters.getIsEdited({ original: { foo: 'bar' }, settings: { foo: 'baz' } })).toBe( - true, - ); - }); - }); - - describe('getIsDisabled', () => { - it.each` - original | enableHistoricEntries | result - ${undefined} | ${false} | ${true} - ${{ foo: 'bar' }} | ${undefined} | ${false} - ${{}} | ${false} | ${false} - `( - 'returns $result when original is $original and enableHistoricEntries is $enableHistoricEntries', - ({ original, enableHistoricEntries, result }) => { - expect(getters.getIsDisabled({ original, enableHistoricEntries })).toBe(result); - }, - ); - }); -}); diff --git a/spec/frontend/registry/settings/store/mutations_spec.js b/spec/frontend/registry/settings/store/mutations_spec.js deleted file mode 100644 index 1d85e38eb36..00000000000 --- a/spec/frontend/registry/settings/store/mutations_spec.js +++ /dev/null @@ -1,80 +0,0 @@ -import mutations from '~/registry/settings/store/mutations'; -import * as types from '~/registry/settings/store/mutation_types'; -import createState from '~/registry/settings/store/state'; -import { formOptions, stringifiedFormOptions } from '../../shared/mock_data'; - -describe('Mutations Registry Store', () => { - let mockState; - - beforeEach(() => { - mockState = createState(); - }); - - describe('SET_INITIAL_STATE', () => { - it('should set the initial state', () => { - const payload = { - projectId: 'foo', - enableHistoricEntries: false, - adminSettingsPath: 'foo', - isAdmin: true, - }; - const expectedState = { ...mockState, ...payload, formOptions }; - mutations[types.SET_INITIAL_STATE](mockState, { - ...payload, - ...stringifiedFormOptions, - }); - - expect(mockState).toEqual(expectedState); - }); - }); - - describe('UPDATE_SETTINGS', () => { - it('should update the settings', () => { - mockState.settings = { foo: 'bar' }; - const payload = { foo: 'baz' }; - const expectedState = { ...mockState, settings: payload }; - mutations[types.UPDATE_SETTINGS](mockState, { settings: payload }); - expect(mockState.settings).toEqual(expectedState.settings); - }); - }); - - describe('SET_SETTINGS', () => { - it('should set the settings and original', () => { - const payload = { foo: 'baz' }; - const expectedState = { ...mockState, settings: payload }; - mutations[types.SET_SETTINGS](mockState, payload); - expect(mockState.settings).toEqual(expectedState.settings); - expect(mockState.original).toEqual(expectedState.settings); - }); - - it('should keep the default state when settings is not present', () => { - const originalSettings = { ...mockState.settings }; - mutations[types.SET_SETTINGS](mockState); - expect(mockState.settings).toEqual(originalSettings); - expect(mockState.original).toEqual(undefined); - }); - }); - - describe('RESET_SETTINGS', () => { - it('should copy original over settings', () => { - mockState.settings = { foo: 'bar' }; - mockState.original = { foo: 'baz' }; - mutations[types.RESET_SETTINGS](mockState); - expect(mockState.settings).toEqual(mockState.original); - }); - - it('if original is undefined it should initialize to empty object', () => { - mockState.settings = { foo: 'bar' }; - mockState.original = undefined; - mutations[types.RESET_SETTINGS](mockState); - expect(mockState.settings).toEqual({}); - }); - }); - - describe('TOGGLE_LOADING', () => { - it('should toggle the loading', () => { - mutations[types.TOGGLE_LOADING](mockState); - expect(mockState.isLoading).toEqual(true); - }); - }); -}); diff --git a/spec/frontend/registry/shared/__snapshots__/utils_spec.js.snap b/spec/frontend/registry/shared/__snapshots__/utils_spec.js.snap new file mode 100644 index 00000000000..032007bba51 --- /dev/null +++ b/spec/frontend/registry/shared/__snapshots__/utils_spec.js.snap @@ -0,0 +1,101 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Utils formOptionsGenerator returns an object containing cadence 1`] = ` +Array [ + Object { + "default": true, + "key": "EVERY_DAY", + "label": "Every day", + }, + Object { + "default": false, + "key": "EVERY_WEEK", + "label": "Every week", + }, + Object { + "default": false, + "key": "EVERY_TWO_WEEKS", + "label": "Every two weeks", + }, + Object { + "default": false, + "key": "EVERY_MONTH", + "label": "Every month", + }, + Object { + "default": false, + "key": "EVERY_THREE_MONTHS", + "label": "Every three months", + }, +] +`; + +exports[`Utils formOptionsGenerator returns an object containing keepN 1`] = ` +Array [ + Object { + "default": false, + "key": "ONE_TAG", + "label": "1 tag per image name", + "variable": 1, + }, + Object { + "default": false, + "key": "FIVE_TAGS", + "label": "5 tags per image name", + "variable": 5, + }, + Object { + "default": true, + "key": "TEN_TAGS", + "label": "10 tags per image name", + "variable": 10, + }, + Object { + "default": false, + "key": "TWENTY_FIVE_TAGS", + "label": "25 tags per image name", + "variable": 25, + }, + Object { + "default": false, + "key": "FIFTY_TAGS", + "label": "50 tags per image name", + "variable": 50, + }, + Object { + "default": false, + "key": "ONE_HUNDRED_TAGS", + "label": "100 tags per image name", + "variable": 100, + }, +] +`; + +exports[`Utils formOptionsGenerator returns an object containing olderThan 1`] = ` +Array [ + Object { + "default": false, + "key": "SEVEN_DAYS", + "label": "7 days until tags are automatically removed", + "variable": 7, + }, + Object { + "default": false, + "key": "FOURTEEN_DAYS", + "label": "14 days until tags are automatically removed", + "variable": 14, + }, + Object { + "default": false, + "key": "THIRTY_DAYS", + "label": "30 days until tags are automatically removed", + "variable": 30, + }, + Object { + "default": true, + "key": "NINETY_DAYS", + "label": "90 days until tags are automatically removed", + "variable": 90, + }, +] +`; diff --git a/spec/frontend/registry/shared/components/expiration_policy_fields_spec.js b/spec/frontend/registry/shared/components/expiration_policy_fields_spec.js index ee765ffd1c0..bee9bca5369 100644 --- a/spec/frontend/registry/shared/components/expiration_policy_fields_spec.js +++ b/spec/frontend/registry/shared/components/expiration_policy_fields_spec.js @@ -40,13 +40,13 @@ describe('Expiration Policy Form', () => { }); describe.each` - elementName | modelName | value | disabledByToggle - ${'toggle'} | ${'enabled'} | ${true} | ${'not disabled'} - ${'interval'} | ${'older_than'} | ${'foo'} | ${'disabled'} - ${'schedule'} | ${'cadence'} | ${'foo'} | ${'disabled'} - ${'latest'} | ${'keep_n'} | ${'foo'} | ${'disabled'} - ${'name-matching'} | ${'name_regex'} | ${'foo'} | ${'disabled'} - ${'keep-name'} | ${'name_regex_keep'} | ${'bar'} | ${'disabled'} + elementName | modelName | value | disabledByToggle + ${'toggle'} | ${'enabled'} | ${true} | ${'not disabled'} + ${'interval'} | ${'olderThan'} | ${'foo'} | ${'disabled'} + ${'schedule'} | ${'cadence'} | ${'foo'} | ${'disabled'} + ${'latest'} | ${'keepN'} | ${'foo'} | ${'disabled'} + ${'name-matching'} | ${'nameRegex'} | ${'foo'} | ${'disabled'} + ${'keep-name'} | ${'nameRegexKeep'} | ${'bar'} | ${'disabled'} `( `${FORM_ELEMENTS_ID_PREFIX}-$elementName form element`, ({ elementName, modelName, value, disabledByToggle }) => { @@ -128,9 +128,9 @@ describe('Expiration Policy Form', () => { }); describe.each` - modelName | elementName - ${'name_regex'} | ${'name-matching'} - ${'name_regex_keep'} | ${'keep-name'} + modelName | elementName + ${'nameRegex'} | ${'name-matching'} + ${'nameRegexKeep'} | ${'keep-name'} `('regex textarea validation', ({ modelName, elementName }) => { const invalidString = new Array(NAME_REGEX_LENGTH + 2).join(','); diff --git a/spec/frontend/registry/shared/stubs.js b/spec/frontend/registry/shared/stubs.js new file mode 100644 index 00000000000..f6b88d70e49 --- /dev/null +++ b/spec/frontend/registry/shared/stubs.js @@ -0,0 +1,11 @@ +export const GlLoadingIcon = { name: 'gl-loading-icon-stub', template: '<svg></svg>' }; +export const GlCard = { + name: 'gl-card-stub', + template: ` +<div> + <slot name="header"></slot> + <slot></slot> + <slot name="footer"></slot> +</div> +`, +}; diff --git a/spec/frontend/registry/shared/utils_spec.js b/spec/frontend/registry/shared/utils_spec.js new file mode 100644 index 00000000000..edb0c3261be --- /dev/null +++ b/spec/frontend/registry/shared/utils_spec.js @@ -0,0 +1,37 @@ +import { + formOptionsGenerator, + optionLabelGenerator, + olderThanTranslationGenerator, +} from '~/registry/shared/utils'; + +describe('Utils', () => { + describe('optionLabelGenerator', () => { + it('returns an array with a set label', () => { + const result = optionLabelGenerator( + [{ variable: 1 }, { variable: 2 }], + olderThanTranslationGenerator, + ); + expect(result).toEqual([ + { variable: 1, label: '1 day until tags are automatically removed' }, + { variable: 2, label: '2 days until tags are automatically removed' }, + ]); + }); + }); + + describe('formOptionsGenerator', () => { + it('returns an object containing olderThan', () => { + expect(formOptionsGenerator().olderThan).toBeDefined(); + expect(formOptionsGenerator().olderThan).toMatchSnapshot(); + }); + + it('returns an object containing cadence', () => { + expect(formOptionsGenerator().cadence).toBeDefined(); + expect(formOptionsGenerator().cadence).toMatchSnapshot(); + }); + + it('returns an object containing keepN', () => { + expect(formOptionsGenerator().keepN).toBeDefined(); + expect(formOptionsGenerator().keepN).toMatchSnapshot(); + }); + }); +}); |