diff --git a/ee/app/assets/javascripts/geo_replicable/components/app.vue b/ee/app/assets/javascripts/geo_replicable/components/app.vue index 1c680187dcb4cd2b49a8ee21c8ba1ba28e42a17d..abea41ed3cfceb44bb0f98fdeebd2046bb29f936 100644 --- a/ee/app/assets/javascripts/geo_replicable/components/app.vue +++ b/ee/app/assets/javascripts/geo_replicable/components/app.vue @@ -41,7 +41,7 @@ export default { <template> <article class="geo-replicable-container"> <!-- Filtering not currently supported via GraphQl --> - <geo-replicable-filter-bar v-if="!useGraphQl" class="mb-3" /> + <geo-replicable-filter-bar v-if="!useGraphQl" /> <gl-loading-icon v-if="isLoading" size="xl" /> <template v-else> <geo-replicable v-if="hasReplicableItems" /> diff --git a/ee/app/assets/javascripts/geo_replicable/components/geo_replicable_filter_bar.vue b/ee/app/assets/javascripts/geo_replicable/components/geo_replicable_filter_bar.vue index b0a2aef9c784c9f454bdc248795c24d9e63791c4..f74ba0af1dd852a15ad48875a54e8e83b65d3584 100644 --- a/ee/app/assets/javascripts/geo_replicable/components/geo_replicable_filter_bar.vue +++ b/ee/app/assets/javascripts/geo_replicable/components/geo_replicable_filter_bar.vue @@ -77,40 +77,37 @@ export default { </script> <template> - <nav class="bg-secondary border-bottom border-secondary-100 p-3"> - <div class="row d-flex flex-column flex-sm-row"> - <div class="col"> - <div class="d-sm-flex mx-n1"> - <gl-dropdown :text="$options.i18n.dropdownTitle" class="px-1 my-1 my-sm-0 w-100"> - <gl-dropdown-item - v-for="(filter, index) in filterOptions" - :key="index" - :class="{ 'bg-secondary-100': index === currentFilterIndex }" - @click="filterChange(index)" + <nav class="gl-bg-gray-50 gl-p-5"> + <div class="gl-display-grid geo-replicable-filter-grid gl-gap-3"> + <div + class="gl-display-flex gl-align-items-center gl-flex-direction-column gl-sm-flex-direction-row" + > + <gl-dropdown :text="$options.i18n.dropdownTitle" class="gl-w-full"> + <gl-dropdown-item + v-for="(filter, index) in filterOptions" + :key="index" + :class="{ 'gl-bg-gray-50': index === currentFilterIndex }" + @click="filterChange(index)" + > + <span v-if="filter === $options.filterStates.ALL" + >{{ filter.label }} {{ replicableTypeName }}</span > - <span v-if="filter === $options.filterStates.ALL" - >{{ filter.label }} {{ replicableTypeName }}</span - > - <span v-else>{{ filter.label }}</span> - </gl-dropdown-item> - </gl-dropdown> - <gl-search-box-by-type - v-model="search" - :debounce="$options.debounce" - class="px-1 my-1 my-sm-0 bg-white w-100" - type="text" - :placeholder="$options.i18n.searchPlaceholder" - /> - </div> + <span v-else>{{ filter.label }}</span> + </gl-dropdown-item> + </gl-dropdown> + <gl-search-box-by-type + v-model="search" + :debounce="$options.debounce" + class="gl-w-full gl-mt-3 gl-ml-0 gl-sm-mt-0 gl-sm-ml-3" + :placeholder="$options.i18n.searchPlaceholder" + /> </div> - <div + <gl-button v-if="hasReplicableItems" - class="col col-sm-5 d-flex justify-content-end my-1 my-sm-0 w-100" + v-gl-modal-directive="$options.RESYNC_MODAL_ID" + class="gl-ml-auto" + >{{ $options.i18n.resyncAll }}</gl-button > - <gl-button v-gl-modal-directive="$options.RESYNC_MODAL_ID">{{ - $options.i18n.resyncAll - }}</gl-button> - </div> </div> <gl-modal :modal-id="$options.RESYNC_MODAL_ID" diff --git a/ee/app/assets/javascripts/geo_replicable/components/geo_replicable_item.vue b/ee/app/assets/javascripts/geo_replicable/components/geo_replicable_item.vue index 8f471f9ff76d34ef92bba84737016223fc6178c0..ebcf3c371074c35818cc4fca2c4659c69b2b62e7 100644 --- a/ee/app/assets/javascripts/geo_replicable/components/geo_replicable_item.vue +++ b/ee/app/assets/javascripts/geo_replicable/components/geo_replicable_item.vue @@ -1,6 +1,7 @@ <script> import { GlLink, GlButton } from '@gitlab/ui'; import { mapActions } from 'vuex'; +import { capitalizeFirstCharacter } from '~/lib/utils/text_utility'; import { __ } from '~/locale'; import { ACTION_TYPES } from '../constants'; import GeoReplicableStatus from './geo_replicable_status.vue'; @@ -49,19 +50,19 @@ export default { return { timeAgoArray: [ { - label: __('Last successful sync'), + label: capitalizeFirstCharacter(this.syncStatus), dateString: this.lastSynced, - defaultText: __('Never'), + defaultText: __('Unknown'), }, { label: __('Last time verified'), dateString: this.lastVerified, - defaultText: __('Not Implemented'), + defaultText: __('Unknown'), }, { label: __('Last time checked'), dateString: this.lastChecked, - defaultText: __('Not Implemented'), + defaultText: __('Unknown'), }, ], }; @@ -79,35 +80,36 @@ export default { </script> <template> - <div class="card"> - <div v-if="hasProject" class="card-header d-flex align-center"> - <gl-link class="font-weight-bold" :href="`/${name}`" target="_blank">{{ name }}</gl-link> - <div class="ml-auto"> + <div class="gl-border-b gl-p-5"> + <div + class="geo-replicable-item-grid gl-display-grid gl-align-items-center gl-pb-4" + data-testid="replicable-item-header" + > + <geo-replicable-status :status="syncStatus" /> + <template v-if="hasProject"> + <gl-link class="gl-font-weight-bold gl-pr-3" :href="`/${name}`" target="_blank">{{ + name + }}</gl-link> <gl-button + class="gl-ml-auto" size="small" @click="initiateReplicableSync({ projectId, name, action: $options.actionTypes.RESYNC })" >{{ __('Resync') }}</gl-button > - </div> + </template> + <template v-else> + <span class="gl-font-weight-bold">{{ name }}</span> + </template> </div> - <div v-else class="card-header"> - <span class="font-weight-bold">{{ name }}</span> - </div> - <div class="card-body"> - <div class="d-flex flex-column flex-md-row"> - <div class="flex-grow-1"> - <label class="text-muted">{{ __('Status') }}</label> - <geo-replicable-status :status="syncStatus" /> - </div> - <geo-replicable-time-ago - v-for="(timeAgo, index) in timeAgoArray" - :key="index" - class="flex-grow-1" - :label="timeAgo.label" - :date-string="timeAgo.dateString" - :default-text="timeAgo.defaultText" - /> - </div> + <div class="gl-display-flex gl-align-items-center gl-flex-wrap"> + <geo-replicable-time-ago + v-for="(timeAgo, index) in timeAgoArray" + :key="index" + :label="timeAgo.label" + :date-string="timeAgo.dateString" + :default-text="timeAgo.defaultText" + :show-divider="index !== timeAgoArray.length - 1" + /> </div> </div> </template> diff --git a/ee/app/assets/javascripts/geo_replicable/components/geo_replicable_status.vue b/ee/app/assets/javascripts/geo_replicable/components/geo_replicable_status.vue index b15f2c21681837fe6330591b003d940a68055cf8..8ed12ef5b44c182d7eb0d34d378f9552d9fb6e03 100644 --- a/ee/app/assets/javascripts/geo_replicable/components/geo_replicable_status.vue +++ b/ee/app/assets/javascripts/geo_replicable/components/geo_replicable_status.vue @@ -1,5 +1,6 @@ <script> import { GlIcon } from '@gitlab/ui'; +import { capitalizeFirstCharacter } from '~/lib/utils/text_utility'; import { STATUS_ICON_NAMES, STATUS_ICON_CLASS, DEFAULT_STATUS } from '../constants'; export default { @@ -14,22 +15,20 @@ export default { default: DEFAULT_STATUS, }, }, - data() { - return { - icon: this.iconProperties(), - }; - }, - methods: { - iconProperties() { + computed: { + capitalizedStatus() { + return capitalizeFirstCharacter(this.status); + }, + styleProperties() { if (STATUS_ICON_NAMES[this.status] && STATUS_ICON_CLASS[this.status]) { return { - name: STATUS_ICON_NAMES[this.status], + iconName: STATUS_ICON_NAMES[this.status], cssClass: STATUS_ICON_CLASS[this.status], }; } return { - name: STATUS_ICON_NAMES[DEFAULT_STATUS], + iconName: STATUS_ICON_NAMES[DEFAULT_STATUS], cssClass: STATUS_ICON_CLASS[DEFAULT_STATUS], }; }, @@ -38,10 +37,12 @@ export default { </script> <template> - <div> - <span class="d-flex align-items-center text-capitalize"> - <gl-icon :name="icon.name" :class="icon.cssClass" class="mr-2" /> - {{ status }} - </span> + <div + class="gl-display-flex align-items-center" + :class="styleProperties.cssClass" + data-testid="replicable-item-status" + > + <gl-icon :name="styleProperties.iconName" class="gl-mr-2" /> + <span class="gl-font-weight-bold gl-font-sm">{{ capitalizedStatus }}</span> </div> </template> diff --git a/ee/app/assets/javascripts/geo_replicable/components/geo_replicable_time_ago.vue b/ee/app/assets/javascripts/geo_replicable/components/geo_replicable_time_ago.vue index bac4fc63083bdb4d37013ced56f4f1b5496a57ce..ccd2499126b005442a5835338c9ab3f91fa299f0 100644 --- a/ee/app/assets/javascripts/geo_replicable/components/geo_replicable_time_ago.vue +++ b/ee/app/assets/javascripts/geo_replicable/components/geo_replicable_time_ago.vue @@ -1,10 +1,16 @@ <script> +import { GlSprintf } from '@gitlab/ui'; +import { s__ } from '~/locale'; import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; export default { name: 'GeoReplicableTimeAgo', + i18n: { + timeAgoString: s__('Geo|%{label} %{timeAgo}'), + }, components: { TimeAgo, + GlSprintf, }, props: { label: { @@ -20,14 +26,27 @@ export default { required: false, default: '', }, + showDivider: { + type: Boolean, + required: false, + default: false, + }, }, }; </script> <template> - <div class="d-flex flex-column"> - <label class="text-muted">{{ label }}</label> - <time-ago v-if="dateString" :time="dateString" tooltip-placement="bottom" /> - <span v-else>{{ defaultText }}</span> + <div class="gl-text-gray-700 gl-font-sm" data-testid="replicable-time-ago"> + <span class="gl-px-2" :class="{ 'gl-border-r-solid gl-border-r-1': showDivider }"> + <gl-sprintf :message="$options.i18n.timeAgoString"> + <template #label> + <span>{{ label }}</span> + </template> + <template #timeAgo> + <time-ago v-if="dateString" :time="dateString" tooltip-placement="top" /> + <span v-else>{{ defaultText }}</span> + </template> + </gl-sprintf> + </span> </div> </template> diff --git a/ee/app/assets/javascripts/geo_replicable/constants.js b/ee/app/assets/javascripts/geo_replicable/constants.js index 589e62cd58c6fdbff8119a566cbff85746da8cc0..c325258b198aa4bb578cd2341ad1804e8c20e865 100644 --- a/ee/app/assets/javascripts/geo_replicable/constants.js +++ b/ee/app/assets/javascripts/geo_replicable/constants.js @@ -22,17 +22,17 @@ export const FILTER_STATES = { export const DEFAULT_STATUS = 'never'; export const STATUS_ICON_NAMES = { - [FILTER_STATES.SYNCED.value]: 'status_closed', - [FILTER_STATES.PENDING.value]: 'status_scheduled', + [FILTER_STATES.SYNCED.value]: 'check-circle-filled', + [FILTER_STATES.PENDING.value]: 'status_pending', [FILTER_STATES.FAILED.value]: 'status_failed', [DEFAULT_STATUS]: 'status_notfound', }; export const STATUS_ICON_CLASS = { - [FILTER_STATES.SYNCED.value]: 'text-success', - [FILTER_STATES.PENDING.value]: 'text-warning', - [FILTER_STATES.FAILED.value]: 'text-danger', - [DEFAULT_STATUS]: 'text-muted', + [FILTER_STATES.SYNCED.value]: 'gl-text-green-500', + [FILTER_STATES.PENDING.value]: 'gl-text-orange-500', + [FILTER_STATES.FAILED.value]: 'gl-text-orange-500', + [DEFAULT_STATUS]: 'gl-text-gray-500', }; export const DEFAULT_SEARCH_DELAY = 500; diff --git a/ee/app/assets/stylesheets/pages/geo.scss b/ee/app/assets/stylesheets/pages/geo.scss index 1aecd4192e04762f6262a84762b3ea9af25f0e21..f2c45748a652036b16d624b0a3dbfeca4217d47a 100644 --- a/ee/app/assets/stylesheets/pages/geo.scss +++ b/ee/app/assets/stylesheets/pages/geo.scss @@ -67,3 +67,20 @@ grid-template-columns: 3fr 1fr; } } + +.geo-replicable-item-grid { + grid-template-columns: 8ch 1fr auto; + grid-gap: 1rem; +} + +.geo-replicable-filter-grid { + grid-template-columns: 1fr; + + @include media-breakpoint-up(md) { + grid-template-columns: 2fr 1fr; + } + + @include media-breakpoint-up(xl) { + grid-template-columns: 1fr 1fr; + } +} diff --git a/ee/spec/frontend/geo_replicable/components/__snapshots__/geo_replicable_time_ago_spec.js.snap b/ee/spec/frontend/geo_replicable/components/__snapshots__/geo_replicable_time_ago_spec.js.snap deleted file mode 100644 index fa881e0efe7be513591535a25359ad1acb47e387..0000000000000000000000000000000000000000 --- a/ee/spec/frontend/geo_replicable/components/__snapshots__/geo_replicable_time_ago_spec.js.snap +++ /dev/null @@ -1,5 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`GeoReplicableTimeAgo template when dateString exists TimeAgo sets innerHTML as 09-23-1994 1`] = `"<time title=\\"Sep 23, 1994 12:00am UTC\\" datetime=\\"09-23-1994\\" class=\\"\\">25 years ago</time>"`; - -exports[`GeoReplicableTimeAgo template when dateString is null DefaultText sets innerHTML as props.defaultText 1`] = `"<span>Default Text</span>"`; diff --git a/ee/spec/frontend/geo_replicable/components/geo_replicable_item_spec.js b/ee/spec/frontend/geo_replicable/components/geo_replicable_item_spec.js index 1e69bd0c8946ca70fc8da66028085f7f3db63bf6..4e7c700cd2f4d694bdaca6e63a38fd1c9560f011 100644 --- a/ee/spec/frontend/geo_replicable/components/geo_replicable_item_spec.js +++ b/ee/spec/frontend/geo_replicable/components/geo_replicable_item_spec.js @@ -1,8 +1,10 @@ import { GlLink, GlButton } from '@gitlab/ui'; -import { mount } from '@vue/test-utils'; import Vue from 'vue'; import Vuex from 'vuex'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import GeoReplicableItem from 'ee/geo_replicable/components/geo_replicable_item.vue'; +import GeoReplicableStatus from 'ee/geo_replicable/components/geo_replicable_status.vue'; +import GeoReplicableTimeAgo from 'ee/geo_replicable/components/geo_replicable_time_ago.vue'; import { ACTION_TYPES } from 'ee/geo_replicable/constants'; import { getStoreConfig } from 'ee/geo_replicable/store'; import { MOCK_BASIC_FETCH_DATA_MAP, MOCK_REPLICABLE_TYPE } from '../mock_data'; @@ -27,13 +29,13 @@ describe('GeoReplicableItem', () => { }; const createComponent = (props = {}) => { - const fakeStore = new Vuex.Store({ + const store = new Vuex.Store({ ...getStoreConfig({ replicableType: MOCK_REPLICABLE_TYPE, graphqlFieldName: null }), actions: actionSpies, }); - wrapper = mount(GeoReplicableItem, { - store: fakeStore, + wrapper = shallowMountExtended(GeoReplicableItem, { + store, propsData: { ...defaultProps, ...props, @@ -45,43 +47,48 @@ describe('GeoReplicableItem', () => { wrapper.destroy(); }); - const findCard = () => wrapper.find('.card'); - const findGlLink = () => findCard().findComponent(GlLink); - const findGlButton = () => findCard().findComponent(GlButton); - const findCardHeader = () => findCard().find('.card-header'); - const findTextTitle = () => findCardHeader().find('span'); - const findCardBody = () => findCard().find('.card-body'); + const findReplicableItemHeader = () => wrapper.findByTestId('replicable-item-header'); + const findReplicableItemSyncStatus = () => + findReplicableItemHeader().findComponent(GeoReplicableStatus); + const findReplicableItemLink = () => findReplicableItemHeader().findComponent(GlLink); + const findResyncButton = () => findReplicableItemHeader().findComponent(GlButton); + const findReplicableItemNoLinkText = () => findReplicableItemHeader().find('span'); + const findReplicableItemTimeAgos = () => wrapper.findAllComponents(GeoReplicableTimeAgo); describe('template', () => { - beforeEach(() => { - createComponent(); - }); - - it('renders card', () => { - expect(findCard().exists()).toBe(true); - }); + describe('by default', () => { + beforeEach(() => { + createComponent(); + }); - it('renders card header', () => { - expect(findCardHeader().exists()).toBe(true); - }); + it('renders GeoReplicableStatus', () => { + expect(findReplicableItemSyncStatus().exists()).toBe(true); + }); - it('renders card body', () => { - expect(findCardBody().exists()).toBe(true); + it('renders GeoReplicableTimeAgo component for each element in timeAgoArray', () => { + expect(findReplicableItemTimeAgos().length).toBe(3); + }); }); describe('with projectId', () => { + beforeEach(() => { + createComponent({ projectId: mockReplicable.projectId }); + }); + it('GlLink renders correctly', () => { - expect(findGlLink().exists()).toBe(true); - expect(findGlLink().text()).toBe(mockReplicable.name); + expect(findReplicableItemLink().exists()).toBe(true); + expect(findReplicableItemLink().text()).toBe(mockReplicable.name); + expect(findReplicableItemLink().attributes('href')).toBe(`/${mockReplicable.name}`); }); - describe('ReSync Button', () => { + describe('Resync Button', () => { it('renders', () => { - expect(findGlButton().exists()).toBe(true); + expect(findResyncButton().exists()).toBe(true); }); it('calls initiateReplicableSync when clicked', () => { - findGlButton().trigger('click'); + findResyncButton().vm.$emit('click'); + expect(actionSpies.initiateReplicableSync).toHaveBeenCalledWith(expect.any(Object), { projectId: mockReplicable.projectId, name: mockReplicable.name, @@ -96,17 +103,21 @@ describe('GeoReplicableItem', () => { createComponent({ projectId: null }); }); + it('renders GeoReplicableStatus', () => { + expect(findReplicableItemSyncStatus().exists()).toBe(true); + }); + it('Text title renders correctly', () => { - expect(findTextTitle().exists()).toBe(true); - expect(findTextTitle().text()).toBe(mockReplicable.name); + expect(findReplicableItemNoLinkText().exists()).toBe(true); + expect(findReplicableItemNoLinkText().text()).toBe(mockReplicable.name); }); it('GlLink does not render', () => { - expect(findGlLink().exists()).toBe(false); + expect(findReplicableItemLink().exists()).toBe(false); }); it('ReSync Button does not render', () => { - expect(findGlButton().exists()).toBe(false); + expect(findResyncButton().exists()).toBe(false); }); }); }); diff --git a/ee/spec/frontend/geo_replicable/components/geo_replicable_status_spec.js b/ee/spec/frontend/geo_replicable/components/geo_replicable_status_spec.js index 0684e98bf5dd525755430634d8e267c566fcf118..3b8e2609629ec36f07d92de2a95e4ffae16bd00c 100644 --- a/ee/spec/frontend/geo_replicable/components/geo_replicable_status_spec.js +++ b/ee/spec/frontend/geo_replicable/components/geo_replicable_status_spec.js @@ -1,30 +1,27 @@ import { GlIcon } from '@gitlab/ui'; -import { mount } from '@vue/test-utils'; -import Vue from 'vue'; -import Vuex from 'vuex'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import GeoReplicableStatus from 'ee/geo_replicable/components/geo_replicable_status.vue'; +import { capitalizeFirstCharacter } from '~/lib/utils/text_utility'; import { FILTER_STATES, STATUS_ICON_NAMES, STATUS_ICON_CLASS, DEFAULT_STATUS, } from 'ee/geo_replicable/constants'; -import createStore from 'ee/geo_replicable/store'; -import { MOCK_REPLICABLE_TYPE } from '../mock_data'; - -Vue.use(Vuex); describe('GeoReplicableStatus', () => { let wrapper; - const propsData = { + const defaultProps = { status: FILTER_STATES.SYNCED.value, }; - const createComponent = () => { - wrapper = mount(GeoReplicableStatus, { - store: createStore({ replicableType: MOCK_REPLICABLE_TYPE, graphqlFieldName: null }), - propsData, + const createComponent = (props = {}) => { + wrapper = shallowMountExtended(GeoReplicableStatus, { + propsData: { + ...defaultProps, + ...props, + }, }); }; @@ -32,46 +29,32 @@ describe('GeoReplicableStatus', () => { wrapper.destroy(); }); - const findGeoReplicableStatusContainer = () => wrapper.find('div'); - const findIcon = () => findGeoReplicableStatusContainer().findComponent(GlIcon); - - describe('template', () => { - beforeEach(() => { - createComponent(); - }); - - it('renders status container', () => { - expect(findGeoReplicableStatusContainer().exists()).toBe(true); - }); - }); + const findStatusWrapper = () => wrapper.findByTestId('replicable-item-status'); + const findStatusIcon = () => findStatusWrapper().findComponent(GlIcon); + const findStatusText = () => findStatusWrapper().find('span'); describe.each` - status | iconName | iconClass - ${FILTER_STATES.SYNCED.value} | ${STATUS_ICON_NAMES[FILTER_STATES.SYNCED.value]} | ${STATUS_ICON_CLASS[FILTER_STATES.SYNCED.value]} - ${FILTER_STATES.PENDING.value} | ${STATUS_ICON_NAMES[FILTER_STATES.PENDING.value]} | ${STATUS_ICON_CLASS[FILTER_STATES.PENDING.value]} - ${FILTER_STATES.FAILED.value} | ${STATUS_ICON_NAMES[FILTER_STATES.FAILED.value]} | ${STATUS_ICON_CLASS[FILTER_STATES.FAILED.value]} - ${DEFAULT_STATUS} | ${STATUS_ICON_NAMES[DEFAULT_STATUS]} | ${STATUS_ICON_CLASS[DEFAULT_STATUS]} - `(`iconProperties`, ({ status, iconName, iconClass }) => { + status + ${FILTER_STATES.SYNCED.value} + ${FILTER_STATES.PENDING.value} + ${FILTER_STATES.FAILED.value} + ${DEFAULT_STATUS} + `('template', ({ status }) => { beforeEach(() => { - propsData.status = status; - createComponent(); + createComponent({ status }); }); - describe(`with filter set to ${status}`, () => { - beforeEach(() => { - wrapper.vm.icon = wrapper.vm.iconProperties(); - }); - - it(`sets icon.name to ${iconName}`, () => { - expect(wrapper.vm.icon.name).toEqual(iconName); + describe(`with status set to ${status}`, () => { + it(`adds ${STATUS_ICON_CLASS[status]} to status wrapper`, () => { + expect(findStatusWrapper().classes(STATUS_ICON_CLASS[status])).toBe(true); }); - it(`sets icon.cssClass to ${iconClass}`, () => { - expect(wrapper.vm.icon.cssClass).toEqual(iconClass); + it(`sets the status icon to ${STATUS_ICON_NAMES[status]}`, () => { + expect(findStatusIcon().props('name')).toBe(STATUS_ICON_NAMES[status]); }); - it(`sets svg to icon '${iconName}'`, () => { - expect(findIcon().attributes('data-testid')).toBe(`${wrapper.vm.icon.name}-icon`); + it(`sets the status text to ${capitalizeFirstCharacter(status)}`, () => { + expect(findStatusText().text()).toBe(capitalizeFirstCharacter(status)); }); }); }); diff --git a/ee/spec/frontend/geo_replicable/components/geo_replicable_time_ago_spec.js b/ee/spec/frontend/geo_replicable/components/geo_replicable_time_ago_spec.js index 92c4e48325a85a3dce0c9b1decb313f71aee1ef6..2e9ff366502a21bd2ded1a86b4b9b8c2559948a3 100644 --- a/ee/spec/frontend/geo_replicable/components/geo_replicable_time_ago_spec.js +++ b/ee/spec/frontend/geo_replicable/components/geo_replicable_time_ago_spec.js @@ -1,27 +1,32 @@ -import { mount } from '@vue/test-utils'; -import Vue from 'vue'; -import Vuex from 'vuex'; +import { GlSprintf } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import GeoReplicableTimeAgo from 'ee/geo_replicable/components/geo_replicable_time_ago.vue'; -import createStore from 'ee/geo_replicable/store'; -import { useFakeDate } from 'helpers/fake_date'; import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; -import { MOCK_REPLICABLE_TYPE } from '../mock_data'; -Vue.use(Vuex); +const MOCK_LABEL = 'Test Label'; +const MOCK_DEFAULT_TEXT = 'Default Text'; +const MOCK_JUST_NOW = new Date().toISOString(); describe('GeoReplicableTimeAgo', () => { let wrapper; - const propsData = { - label: 'Test Label', - dateString: '09-23-1994', - defaultText: 'Default Text', + const defaultProps = { + label: MOCK_LABEL, + dateString: MOCK_JUST_NOW, + defaultText: MOCK_DEFAULT_TEXT, + showDivider: false, }; - const createComponent = () => { - wrapper = mount(GeoReplicableTimeAgo, { - store: createStore({ replicableType: MOCK_REPLICABLE_TYPE, graphqlFieldName: null }), - propsData, + const createComponent = (props = {}) => { + wrapper = shallowMountExtended(GeoReplicableTimeAgo, { + propsData: { + ...defaultProps, + ...props, + }, + stubs: { + GlSprintf, + TimeAgo, + }, }); }; @@ -29,60 +34,27 @@ describe('GeoReplicableTimeAgo', () => { wrapper.destroy(); }); - const findGeoReplicableTimeAgo = () => wrapper.findComponent(GeoReplicableTimeAgo); - const findTimeAgo = () => findGeoReplicableTimeAgo().findComponent(TimeAgo); - const findDefaultText = () => findGeoReplicableTimeAgo().find('span'); - - describe('template', () => { - // ensure consistent output for timeago - useFakeDate(2020, 0, 1); + const findReplicableTimeAgo = () => wrapper.findByTestId('replicable-time-ago'); + describe.each` + dateString | showDivider | expectedText + ${MOCK_JUST_NOW} | ${true} | ${`${MOCK_LABEL} just now`} + ${MOCK_JUST_NOW} | ${false} | ${`${MOCK_LABEL} just now`} + ${null} | ${true} | ${`${MOCK_LABEL} ${MOCK_DEFAULT_TEXT}`} + ${null} | ${false} | ${`${MOCK_LABEL} ${MOCK_DEFAULT_TEXT}`} + `('template', ({ dateString, showDivider, expectedText }) => { beforeEach(() => { - createComponent(); - }); - - it('renders GeoReplicableTimeAgo container', () => { - expect(findGeoReplicableTimeAgo().exists()).toBe(true); + createComponent({ dateString, showDivider }); }); - describe('when dateString exists', () => { - describe('TimeAgo', () => { - it('renders', () => { - expect(findTimeAgo().exists()).toBe(true); - }); - - it('sets time prop', () => { - expect(findTimeAgo().props().time).toBe(propsData.dateString); - }); - - it(`sets innerHTML as ${propsData.dateString}`, () => { - expect(findTimeAgo().html()).toMatchSnapshot(); - }); - }); - - it('hides DefaultText', () => { - expect(findDefaultText().exists()).toBe(false); + describe(`with dateString is ${dateString} and showDivider is ${showDivider}`, () => { + it(`sets Replicable Time Ago text to ${expectedText}`, () => { + expect(findReplicableTimeAgo().text()).toBe(expectedText); }); - }); - - describe('when dateString is null', () => { - beforeEach(() => { - propsData.dateString = null; - createComponent(); - }); - - it('hides TimeAgo', () => { - expect(findTimeAgo().exists()).toBe(false); - }); - - describe('DefaultText', () => { - it('renders', () => { - expect(findDefaultText().exists()).toBe(true); - }); - it('sets innerHTML as props.defaultText', () => { - expect(findDefaultText().html()).toMatchSnapshot(); - }); + it(`does${showDivider ? '' : ' not'} show right border`, () => { + expect(findReplicableTimeAgo().find('span').classes('gl-border-r-1')).toBe(showDivider); + expect(findReplicableTimeAgo().find('span').classes('gl-border-r-solid')).toBe(showDivider); }); }); }); diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 8f0e86b144b051a68a357923c5262b77ff762da4..5ac065c85b6183300caf1766be69c696c2a296dd 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -16898,6 +16898,9 @@ msgstr "" msgid "Geo|%{component} verified" msgstr "" +msgid "Geo|%{label} %{timeAgo}" +msgstr "" + msgid "Geo|%{label} can't be blank" msgstr "" @@ -23019,9 +23022,6 @@ msgstr "" msgid "Last sign-in at:" msgstr "" -msgid "Last successful sync" -msgstr "" - msgid "Last successful update" msgstr "" @@ -26435,9 +26435,6 @@ msgstr "" msgid "Normal text" msgstr "" -msgid "Not Implemented" -msgstr "" - msgid "Not all browsers support U2F devices. Therefore, we require that you set up a two-factor authentication app first. That way you'll always be able to sign in - even when you're using an unsupported browser." msgstr ""