Projects and groups badges settings UI
This commit is contained in:
parent
dd552d06f6
commit
31dd86b636
|
|
@ -0,0 +1,121 @@
|
||||||
|
<script>
|
||||||
|
import Icon from '~/vue_shared/components/icon.vue';
|
||||||
|
import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
|
||||||
|
import Tooltip from '~/vue_shared/directives/tooltip';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Badge',
|
||||||
|
components: {
|
||||||
|
Icon,
|
||||||
|
LoadingIcon,
|
||||||
|
Tooltip,
|
||||||
|
},
|
||||||
|
directives: {
|
||||||
|
Tooltip,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
imageUrl: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
linkUrl: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
hasError: false,
|
||||||
|
isLoading: true,
|
||||||
|
numRetries: 0,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
imageUrlWithRetries() {
|
||||||
|
if (this.numRetries === 0) {
|
||||||
|
return this.imageUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${this.imageUrl}#retries=${this.numRetries}`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
imageUrl() {
|
||||||
|
this.hasError = false;
|
||||||
|
this.isLoading = true;
|
||||||
|
this.numRetries = 0;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onError() {
|
||||||
|
this.isLoading = false;
|
||||||
|
this.hasError = true;
|
||||||
|
},
|
||||||
|
onLoad() {
|
||||||
|
this.isLoading = false;
|
||||||
|
},
|
||||||
|
reloadImage() {
|
||||||
|
this.hasError = false;
|
||||||
|
this.isLoading = true;
|
||||||
|
this.numRetries += 1;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<a
|
||||||
|
v-show="!isLoading && !hasError"
|
||||||
|
:href="linkUrl"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class="project-badge"
|
||||||
|
:src="imageUrlWithRetries"
|
||||||
|
@load="onLoad"
|
||||||
|
@error="onError"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<loading-icon
|
||||||
|
v-show="isLoading"
|
||||||
|
:inline="true"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-show="hasError"
|
||||||
|
class="btn-group"
|
||||||
|
>
|
||||||
|
<div class="btn btn-default btn-xs disabled">
|
||||||
|
<icon
|
||||||
|
class="prepend-left-8 append-right-8"
|
||||||
|
name="doc_image"
|
||||||
|
:size="16"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="btn btn-default btn-xs disabled"
|
||||||
|
>
|
||||||
|
<span class="prepend-left-8 append-right-8">{{ s__('Badges|No badge image') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
v-show="hasError"
|
||||||
|
class="btn btn-transparent btn-xs text-primary"
|
||||||
|
type="button"
|
||||||
|
v-tooltip
|
||||||
|
:title="s__('Badges|Reload badge image')"
|
||||||
|
@click="reloadImage"
|
||||||
|
>
|
||||||
|
<icon
|
||||||
|
name="retry"
|
||||||
|
:size="16"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,219 @@
|
||||||
|
<script>
|
||||||
|
import _ from 'underscore';
|
||||||
|
import { mapActions, mapState } from 'vuex';
|
||||||
|
import createFlash from '~/flash';
|
||||||
|
import { s__, sprintf } from '~/locale';
|
||||||
|
import LoadingButton from '~/vue_shared/components/loading_button.vue';
|
||||||
|
import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
|
||||||
|
import createEmptyBadge from '../empty_badge';
|
||||||
|
import Badge from './badge.vue';
|
||||||
|
|
||||||
|
const badgePreviewDelayInMilliseconds = 1500;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'BadgeForm',
|
||||||
|
components: {
|
||||||
|
Badge,
|
||||||
|
LoadingButton,
|
||||||
|
LoadingIcon,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
isEditing: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState([
|
||||||
|
'badgeInAddForm',
|
||||||
|
'badgeInEditForm',
|
||||||
|
'docsUrl',
|
||||||
|
'isRendering',
|
||||||
|
'isSaving',
|
||||||
|
'renderedBadge',
|
||||||
|
]),
|
||||||
|
badge() {
|
||||||
|
if (this.isEditing) {
|
||||||
|
return this.badgeInEditForm;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.badgeInAddForm;
|
||||||
|
},
|
||||||
|
canSubmit() {
|
||||||
|
return (
|
||||||
|
this.badge !== null &&
|
||||||
|
this.badge.imageUrl &&
|
||||||
|
this.badge.imageUrl.trim() !== '' &&
|
||||||
|
this.badge.linkUrl &&
|
||||||
|
this.badge.linkUrl.trim() !== '' &&
|
||||||
|
!this.isSaving
|
||||||
|
);
|
||||||
|
},
|
||||||
|
helpText() {
|
||||||
|
const placeholders = ['project_path', 'project_id', 'default_branch', 'commit_sha']
|
||||||
|
.map(placeholder => `<code>%{${placeholder}}</code>`)
|
||||||
|
.join(', ');
|
||||||
|
return sprintf(
|
||||||
|
s__('Badges|The %{docsLinkStart}variables%{docsLinkEnd} GitLab supports: %{placeholders}'),
|
||||||
|
{
|
||||||
|
docsLinkEnd: '</a>',
|
||||||
|
docsLinkStart: `<a href="${_.escape(this.docsUrl)}">`,
|
||||||
|
placeholders,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
renderedImageUrl() {
|
||||||
|
return this.renderedBadge ? this.renderedBadge.renderedImageUrl : '';
|
||||||
|
},
|
||||||
|
renderedLinkUrl() {
|
||||||
|
return this.renderedBadge ? this.renderedBadge.renderedLinkUrl : '';
|
||||||
|
},
|
||||||
|
imageUrl: {
|
||||||
|
get() {
|
||||||
|
return this.badge ? this.badge.imageUrl : '';
|
||||||
|
},
|
||||||
|
set(imageUrl) {
|
||||||
|
const badge = this.badge || createEmptyBadge();
|
||||||
|
this.updateBadgeInForm({
|
||||||
|
...badge,
|
||||||
|
imageUrl,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
linkUrl: {
|
||||||
|
get() {
|
||||||
|
return this.badge ? this.badge.linkUrl : '';
|
||||||
|
},
|
||||||
|
set(linkUrl) {
|
||||||
|
const badge = this.badge || createEmptyBadge();
|
||||||
|
this.updateBadgeInForm({
|
||||||
|
...badge,
|
||||||
|
linkUrl,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
submitButtonLabel() {
|
||||||
|
if (this.isEditing) {
|
||||||
|
return s__('Badges|Save changes');
|
||||||
|
}
|
||||||
|
return s__('Badges|Add badge');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions(['addBadge', 'renderBadge', 'saveBadge', 'stopEditing', 'updateBadgeInForm']),
|
||||||
|
debouncedPreview: _.debounce(function preview() {
|
||||||
|
this.renderBadge();
|
||||||
|
}, badgePreviewDelayInMilliseconds),
|
||||||
|
onCancel() {
|
||||||
|
this.stopEditing();
|
||||||
|
},
|
||||||
|
onSubmit() {
|
||||||
|
if (!this.canSubmit) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isEditing) {
|
||||||
|
return this.saveBadge()
|
||||||
|
.then(() => {
|
||||||
|
createFlash(s__('Badges|The badge was saved.'), 'notice');
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
createFlash(
|
||||||
|
s__('Badges|Saving the badge failed, please check the entered URLs and try again.'),
|
||||||
|
);
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.addBadge()
|
||||||
|
.then(() => {
|
||||||
|
createFlash(s__('Badges|A new badge was added.'), 'notice');
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
createFlash(
|
||||||
|
s__('Badges|Adding the badge failed, please check the entered URLs and try again.'),
|
||||||
|
);
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
badgeImageUrlPlaceholder:
|
||||||
|
'https://example.gitlab.com/%{project_path}/badges/%{default_branch}/<badge>.svg',
|
||||||
|
badgeLinkUrlPlaceholder: 'https://example.gitlab.com/%{project_path}',
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<form
|
||||||
|
class="prepend-top-default append-bottom-default"
|
||||||
|
@submit.prevent.stop="onSubmit"
|
||||||
|
>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="badge-link-url">{{ s__('Badges|Link') }}</label>
|
||||||
|
<input
|
||||||
|
id="badge-link-url"
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
v-model="linkUrl"
|
||||||
|
:placeholder="$options.badgeLinkUrlPlaceholder"
|
||||||
|
@input="debouncedPreview"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class="help-block"
|
||||||
|
v-html="helpText"
|
||||||
|
></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="badge-image-url">{{ s__('Badges|Badge image URL') }}</label>
|
||||||
|
<input
|
||||||
|
id="badge-image-url"
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
v-model="imageUrl"
|
||||||
|
:placeholder="$options.badgeImageUrlPlaceholder"
|
||||||
|
@input="debouncedPreview"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class="help-block"
|
||||||
|
v-html="helpText"
|
||||||
|
></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="badge-preview">{{ s__('Badges|Badge image preview') }}</label>
|
||||||
|
<badge
|
||||||
|
id="badge-preview"
|
||||||
|
v-show="renderedBadge && !isRendering"
|
||||||
|
:image-url="renderedImageUrl"
|
||||||
|
:link-url="renderedLinkUrl"
|
||||||
|
/>
|
||||||
|
<p v-show="isRendering">
|
||||||
|
<loading-icon
|
||||||
|
:inline="true"
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
v-show="!renderedBadge && !isRendering"
|
||||||
|
class="disabled-content"
|
||||||
|
>{{ s__('Badges|No image to preview') }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row-content-block">
|
||||||
|
<loading-button
|
||||||
|
type="submit"
|
||||||
|
container-class="btn btn-success"
|
||||||
|
:disabled="!canSubmit"
|
||||||
|
:loading="isSaving"
|
||||||
|
:label="submitButtonLabel"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
class="btn btn-cancel"
|
||||||
|
type="button"
|
||||||
|
v-if="isEditing"
|
||||||
|
@click="onCancel"
|
||||||
|
>{{ __('Cancel') }}</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
<script>
|
||||||
|
import { mapState } from 'vuex';
|
||||||
|
import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
|
||||||
|
import BadgeListRow from './badge_list_row.vue';
|
||||||
|
import { GROUP_BADGE } from '../constants';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'BadgeList',
|
||||||
|
components: {
|
||||||
|
BadgeListRow,
|
||||||
|
LoadingIcon,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState(['badges', 'isLoading', 'kind']),
|
||||||
|
hasNoBadges() {
|
||||||
|
return !this.isLoading && (!this.badges || !this.badges.length);
|
||||||
|
},
|
||||||
|
isGroupBadge() {
|
||||||
|
return this.kind === GROUP_BADGE;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
{{ s__('Badges|Your badges') }}
|
||||||
|
<span
|
||||||
|
v-show="!isLoading"
|
||||||
|
class="badge"
|
||||||
|
>{{ badges.length }}</span>
|
||||||
|
</div>
|
||||||
|
<loading-icon
|
||||||
|
v-show="isLoading"
|
||||||
|
class="panel-body"
|
||||||
|
size="2"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
v-if="hasNoBadges"
|
||||||
|
class="panel-body"
|
||||||
|
>
|
||||||
|
<span v-if="isGroupBadge">{{ s__('Badges|This group has no badges') }}</span>
|
||||||
|
<span v-else>{{ s__('Badges|This project has no badges') }}</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="panel-body"
|
||||||
|
>
|
||||||
|
<badge-list-row
|
||||||
|
v-for="badge in badges"
|
||||||
|
:key="badge.id"
|
||||||
|
:badge="badge"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,89 @@
|
||||||
|
<script>
|
||||||
|
import { mapActions, mapState } from 'vuex';
|
||||||
|
import { s__ } from '~/locale';
|
||||||
|
import Icon from '~/vue_shared/components/icon.vue';
|
||||||
|
import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
|
||||||
|
import { PROJECT_BADGE } from '../constants';
|
||||||
|
import Badge from './badge.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'BadgeListRow',
|
||||||
|
components: {
|
||||||
|
Badge,
|
||||||
|
Icon,
|
||||||
|
LoadingIcon,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
badge: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState(['kind']),
|
||||||
|
badgeKindText() {
|
||||||
|
if (this.badge.kind === PROJECT_BADGE) {
|
||||||
|
return s__('Badges|Project Badge');
|
||||||
|
}
|
||||||
|
|
||||||
|
return s__('Badges|Group Badge');
|
||||||
|
},
|
||||||
|
canEditBadge() {
|
||||||
|
return this.badge.kind === this.kind;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions(['editBadge', 'updateBadgeInModal']),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="gl-responsive-table-row-layout gl-responsive-table-row">
|
||||||
|
<badge
|
||||||
|
class="table-section section-30"
|
||||||
|
:image-url="badge.renderedImageUrl"
|
||||||
|
:link-url="badge.renderedLinkUrl"
|
||||||
|
/>
|
||||||
|
<span class="table-section section-50 str-truncated">{{ badge.linkUrl }}</span>
|
||||||
|
<div class="table-section section-10">
|
||||||
|
<span class="badge">{{ badgeKindText }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="table-section section-10 table-button-footer">
|
||||||
|
<div
|
||||||
|
v-if="canEditBadge"
|
||||||
|
class="table-action-buttons">
|
||||||
|
<button
|
||||||
|
class="btn btn-default append-right-8"
|
||||||
|
type="button"
|
||||||
|
:disabled="badge.isDeleting"
|
||||||
|
@click="editBadge(badge)"
|
||||||
|
>
|
||||||
|
<icon
|
||||||
|
name="pencil"
|
||||||
|
:size="16"
|
||||||
|
:aria-label="__('Edit')"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn btn-danger"
|
||||||
|
type="button"
|
||||||
|
data-toggle="modal"
|
||||||
|
data-target="#delete-badge-modal"
|
||||||
|
:disabled="badge.isDeleting"
|
||||||
|
@click="updateBadgeInModal(badge)"
|
||||||
|
>
|
||||||
|
<icon
|
||||||
|
name="remove"
|
||||||
|
:size="16"
|
||||||
|
:aria-label="__('Delete')"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<loading-icon
|
||||||
|
v-show="badge.isDeleting"
|
||||||
|
:inline="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
<script>
|
||||||
|
import { mapState, mapActions } from 'vuex';
|
||||||
|
import createFlash from '~/flash';
|
||||||
|
import { s__ } from '~/locale';
|
||||||
|
import GlModal from '~/vue_shared/components/gl_modal.vue';
|
||||||
|
import Badge from './badge.vue';
|
||||||
|
import BadgeForm from './badge_form.vue';
|
||||||
|
import BadgeList from './badge_list.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'BadgeSettings',
|
||||||
|
components: {
|
||||||
|
Badge,
|
||||||
|
BadgeForm,
|
||||||
|
BadgeList,
|
||||||
|
GlModal,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState(['badgeInModal', 'isEditing']),
|
||||||
|
deleteModalText() {
|
||||||
|
return s__(
|
||||||
|
'Badges|You are going to delete this badge. Deleted badges <strong>cannot</strong> be restored.',
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions(['deleteBadge']),
|
||||||
|
onSubmitModal() {
|
||||||
|
this.deleteBadge(this.badgeInModal)
|
||||||
|
.then(() => {
|
||||||
|
createFlash(s__('Badges|The badge was deleted.'), 'notice');
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
createFlash(s__('Badges|Deleting the badge failed, please try again.'));
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="badge-settings">
|
||||||
|
<gl-modal
|
||||||
|
id="delete-badge-modal"
|
||||||
|
:header-title-text="s__('Badges|Delete badge?')"
|
||||||
|
footer-primary-button-variant="danger"
|
||||||
|
:footer-primary-button-text="s__('Badges|Delete badge')"
|
||||||
|
@submit="onSubmitModal">
|
||||||
|
<div class="well">
|
||||||
|
<badge
|
||||||
|
:image-url="badgeInModal ? badgeInModal.renderedImageUrl : ''"
|
||||||
|
:link-url="badgeInModal ? badgeInModal.renderedLinkUrl : ''"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p v-html="deleteModalText"></p>
|
||||||
|
</gl-modal>
|
||||||
|
|
||||||
|
<badge-form
|
||||||
|
v-show="isEditing"
|
||||||
|
:is-editing="true"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<badge-form
|
||||||
|
v-show="!isEditing"
|
||||||
|
:is-editing="false"
|
||||||
|
/>
|
||||||
|
<badge-list v-show="!isEditing" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
export const GROUP_BADGE = 'group';
|
||||||
|
export const PROJECT_BADGE = 'project';
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
export default () => ({
|
||||||
|
imageUrl: '',
|
||||||
|
isDeleting: false,
|
||||||
|
linkUrl: '',
|
||||||
|
renderedImageUrl: '',
|
||||||
|
renderedLinkUrl: '',
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,167 @@
|
||||||
|
import axios from '~/lib/utils/axios_utils';
|
||||||
|
import types from './mutation_types';
|
||||||
|
|
||||||
|
export const transformBackendBadge = badge => ({
|
||||||
|
id: badge.id,
|
||||||
|
imageUrl: badge.image_url,
|
||||||
|
kind: badge.kind,
|
||||||
|
linkUrl: badge.link_url,
|
||||||
|
renderedImageUrl: badge.rendered_image_url,
|
||||||
|
renderedLinkUrl: badge.rendered_link_url,
|
||||||
|
isDeleting: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default {
|
||||||
|
requestNewBadge({ commit }) {
|
||||||
|
commit(types.REQUEST_NEW_BADGE);
|
||||||
|
},
|
||||||
|
receiveNewBadge({ commit }, newBadge) {
|
||||||
|
commit(types.RECEIVE_NEW_BADGE, newBadge);
|
||||||
|
},
|
||||||
|
receiveNewBadgeError({ commit }) {
|
||||||
|
commit(types.RECEIVE_NEW_BADGE_ERROR);
|
||||||
|
},
|
||||||
|
addBadge({ dispatch, state }) {
|
||||||
|
const newBadge = state.badgeInAddForm;
|
||||||
|
const endpoint = state.apiEndpointUrl;
|
||||||
|
dispatch('requestNewBadge');
|
||||||
|
return axios
|
||||||
|
.post(endpoint, {
|
||||||
|
image_url: newBadge.imageUrl,
|
||||||
|
link_url: newBadge.linkUrl,
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
dispatch('receiveNewBadgeError');
|
||||||
|
throw error;
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
dispatch('receiveNewBadge', transformBackendBadge(res.data));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
requestDeleteBadge({ commit }, badgeId) {
|
||||||
|
commit(types.REQUEST_DELETE_BADGE, badgeId);
|
||||||
|
},
|
||||||
|
receiveDeleteBadge({ commit }, badgeId) {
|
||||||
|
commit(types.RECEIVE_DELETE_BADGE, badgeId);
|
||||||
|
},
|
||||||
|
receiveDeleteBadgeError({ commit }, badgeId) {
|
||||||
|
commit(types.RECEIVE_DELETE_BADGE_ERROR, badgeId);
|
||||||
|
},
|
||||||
|
deleteBadge({ dispatch, state }, badge) {
|
||||||
|
const badgeId = badge.id;
|
||||||
|
dispatch('requestDeleteBadge', badgeId);
|
||||||
|
const endpoint = `${state.apiEndpointUrl}/${badgeId}`;
|
||||||
|
return axios
|
||||||
|
.delete(endpoint)
|
||||||
|
.catch(error => {
|
||||||
|
dispatch('receiveDeleteBadgeError', badgeId);
|
||||||
|
throw error;
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
dispatch('receiveDeleteBadge', badgeId);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
editBadge({ commit }, badge) {
|
||||||
|
commit(types.START_EDITING, badge);
|
||||||
|
},
|
||||||
|
|
||||||
|
requestLoadBadges({ commit }, data) {
|
||||||
|
commit(types.REQUEST_LOAD_BADGES, data);
|
||||||
|
},
|
||||||
|
receiveLoadBadges({ commit }, badges) {
|
||||||
|
commit(types.RECEIVE_LOAD_BADGES, badges);
|
||||||
|
},
|
||||||
|
receiveLoadBadgesError({ commit }) {
|
||||||
|
commit(types.RECEIVE_LOAD_BADGES_ERROR);
|
||||||
|
},
|
||||||
|
|
||||||
|
loadBadges({ dispatch, state }, data) {
|
||||||
|
dispatch('requestLoadBadges', data);
|
||||||
|
const endpoint = state.apiEndpointUrl;
|
||||||
|
return axios
|
||||||
|
.get(endpoint)
|
||||||
|
.catch(error => {
|
||||||
|
dispatch('receiveLoadBadgesError');
|
||||||
|
throw error;
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
dispatch('receiveLoadBadges', res.data.map(transformBackendBadge));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
requestRenderedBadge({ commit }) {
|
||||||
|
commit(types.REQUEST_RENDERED_BADGE);
|
||||||
|
},
|
||||||
|
receiveRenderedBadge({ commit }, renderedBadge) {
|
||||||
|
commit(types.RECEIVE_RENDERED_BADGE, renderedBadge);
|
||||||
|
},
|
||||||
|
receiveRenderedBadgeError({ commit }) {
|
||||||
|
commit(types.RECEIVE_RENDERED_BADGE_ERROR);
|
||||||
|
},
|
||||||
|
|
||||||
|
renderBadge({ dispatch, state }) {
|
||||||
|
const badge = state.isEditing ? state.badgeInEditForm : state.badgeInAddForm;
|
||||||
|
const { linkUrl, imageUrl } = badge;
|
||||||
|
if (!linkUrl || linkUrl.trim() === '' || !imageUrl || imageUrl.trim() === '') {
|
||||||
|
return Promise.resolve(badge);
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch('requestRenderedBadge');
|
||||||
|
|
||||||
|
const parameters = [
|
||||||
|
`link_url=${encodeURIComponent(linkUrl)}`,
|
||||||
|
`image_url=${encodeURIComponent(imageUrl)}`,
|
||||||
|
].join('&');
|
||||||
|
const renderEndpoint = `${state.apiEndpointUrl}/render?${parameters}`;
|
||||||
|
return axios
|
||||||
|
.get(renderEndpoint)
|
||||||
|
.catch(error => {
|
||||||
|
dispatch('receiveRenderedBadgeError');
|
||||||
|
throw error;
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
dispatch('receiveRenderedBadge', transformBackendBadge(res.data));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
requestUpdatedBadge({ commit }) {
|
||||||
|
commit(types.REQUEST_UPDATED_BADGE);
|
||||||
|
},
|
||||||
|
receiveUpdatedBadge({ commit }, updatedBadge) {
|
||||||
|
commit(types.RECEIVE_UPDATED_BADGE, updatedBadge);
|
||||||
|
},
|
||||||
|
receiveUpdatedBadgeError({ commit }) {
|
||||||
|
commit(types.RECEIVE_UPDATED_BADGE_ERROR);
|
||||||
|
},
|
||||||
|
|
||||||
|
saveBadge({ dispatch, state }) {
|
||||||
|
const badge = state.badgeInEditForm;
|
||||||
|
const endpoint = `${state.apiEndpointUrl}/${badge.id}`;
|
||||||
|
dispatch('requestUpdatedBadge');
|
||||||
|
return axios
|
||||||
|
.put(endpoint, {
|
||||||
|
image_url: badge.imageUrl,
|
||||||
|
link_url: badge.linkUrl,
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
dispatch('receiveUpdatedBadgeError');
|
||||||
|
throw error;
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
dispatch('receiveUpdatedBadge', transformBackendBadge(res.data));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
stopEditing({ commit }) {
|
||||||
|
commit(types.STOP_EDITING);
|
||||||
|
},
|
||||||
|
|
||||||
|
updateBadgeInForm({ commit }, badge) {
|
||||||
|
commit(types.UPDATE_BADGE_IN_FORM, badge);
|
||||||
|
},
|
||||||
|
|
||||||
|
updateBadgeInModal({ commit }, badge) {
|
||||||
|
commit(types.UPDATE_BADGE_IN_MODAL, badge);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
import Vue from 'vue';
|
||||||
|
import Vuex from 'vuex';
|
||||||
|
import createState from './state';
|
||||||
|
import actions from './actions';
|
||||||
|
import mutations from './mutations';
|
||||||
|
|
||||||
|
Vue.use(Vuex);
|
||||||
|
|
||||||
|
export default new Vuex.Store({
|
||||||
|
state: createState(),
|
||||||
|
actions,
|
||||||
|
mutations,
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
export default {
|
||||||
|
RECEIVE_DELETE_BADGE: 'RECEIVE_DELETE_BADGE',
|
||||||
|
RECEIVE_DELETE_BADGE_ERROR: 'RECEIVE_DELETE_BADGE_ERROR',
|
||||||
|
RECEIVE_LOAD_BADGES: 'RECEIVE_LOAD_BADGES',
|
||||||
|
RECEIVE_LOAD_BADGES_ERROR: 'RECEIVE_LOAD_BADGES_ERROR',
|
||||||
|
RECEIVE_NEW_BADGE: 'RECEIVE_NEW_BADGE',
|
||||||
|
RECEIVE_NEW_BADGE_ERROR: 'RECEIVE_NEW_BADGE_ERROR',
|
||||||
|
RECEIVE_RENDERED_BADGE: 'RECEIVE_RENDERED_BADGE',
|
||||||
|
RECEIVE_RENDERED_BADGE_ERROR: 'RECEIVE_RENDERED_BADGE_ERROR',
|
||||||
|
RECEIVE_UPDATED_BADGE: 'RECEIVE_UPDATED_BADGE',
|
||||||
|
RECEIVE_UPDATED_BADGE_ERROR: 'RECEIVE_UPDATED_BADGE_ERROR',
|
||||||
|
REQUEST_DELETE_BADGE: 'REQUEST_DELETE_BADGE',
|
||||||
|
REQUEST_LOAD_BADGES: 'REQUEST_LOAD_BADGES',
|
||||||
|
REQUEST_NEW_BADGE: 'REQUEST_NEW_BADGE',
|
||||||
|
REQUEST_RENDERED_BADGE: 'REQUEST_RENDERED_BADGE',
|
||||||
|
REQUEST_UPDATED_BADGE: 'REQUEST_UPDATED_BADGE',
|
||||||
|
START_EDITING: 'START_EDITING',
|
||||||
|
STOP_EDITING: 'STOP_EDITING',
|
||||||
|
UPDATE_BADGE_IN_FORM: 'UPDATE_BADGE_IN_FORM',
|
||||||
|
UPDATE_BADGE_IN_MODAL: 'UPDATE_BADGE_IN_MODAL',
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,158 @@
|
||||||
|
import types from './mutation_types';
|
||||||
|
import { PROJECT_BADGE } from '../constants';
|
||||||
|
|
||||||
|
const reorderBadges = badges =>
|
||||||
|
badges.sort((a, b) => {
|
||||||
|
if (a.kind !== b.kind) {
|
||||||
|
return a.kind === PROJECT_BADGE ? 1 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.id - b.id;
|
||||||
|
});
|
||||||
|
|
||||||
|
export default {
|
||||||
|
[types.RECEIVE_NEW_BADGE](state, newBadge) {
|
||||||
|
Object.assign(state, {
|
||||||
|
badgeInAddForm: null,
|
||||||
|
badges: reorderBadges(state.badges.concat(newBadge)),
|
||||||
|
isSaving: false,
|
||||||
|
renderedBadge: null,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[types.RECEIVE_NEW_BADGE_ERROR](state) {
|
||||||
|
Object.assign(state, {
|
||||||
|
isSaving: false,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[types.REQUEST_NEW_BADGE](state) {
|
||||||
|
Object.assign(state, {
|
||||||
|
isSaving: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
[types.RECEIVE_UPDATED_BADGE](state, updatedBadge) {
|
||||||
|
const badges = state.badges.map(badge => {
|
||||||
|
if (badge.id === updatedBadge.id) {
|
||||||
|
return updatedBadge;
|
||||||
|
}
|
||||||
|
return badge;
|
||||||
|
});
|
||||||
|
Object.assign(state, {
|
||||||
|
badgeInEditForm: null,
|
||||||
|
badges,
|
||||||
|
isEditing: false,
|
||||||
|
isSaving: false,
|
||||||
|
renderedBadge: null,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[types.RECEIVE_UPDATED_BADGE_ERROR](state) {
|
||||||
|
Object.assign(state, {
|
||||||
|
isSaving: false,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[types.REQUEST_UPDATED_BADGE](state) {
|
||||||
|
Object.assign(state, {
|
||||||
|
isSaving: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
[types.RECEIVE_LOAD_BADGES](state, badges) {
|
||||||
|
Object.assign(state, {
|
||||||
|
badges: reorderBadges(badges),
|
||||||
|
isLoading: false,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[types.RECEIVE_LOAD_BADGES_ERROR](state) {
|
||||||
|
Object.assign(state, {
|
||||||
|
isLoading: false,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[types.REQUEST_LOAD_BADGES](state, data) {
|
||||||
|
Object.assign(state, {
|
||||||
|
kind: data.kind, // project or group
|
||||||
|
apiEndpointUrl: data.apiEndpointUrl,
|
||||||
|
docsUrl: data.docsUrl,
|
||||||
|
isLoading: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
[types.RECEIVE_DELETE_BADGE](state, badgeId) {
|
||||||
|
const badges = state.badges.filter(badge => badge.id !== badgeId);
|
||||||
|
Object.assign(state, {
|
||||||
|
badges,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[types.RECEIVE_DELETE_BADGE_ERROR](state, badgeId) {
|
||||||
|
const badges = state.badges.map(badge => {
|
||||||
|
if (badge.id === badgeId) {
|
||||||
|
return {
|
||||||
|
...badge,
|
||||||
|
isDeleting: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return badge;
|
||||||
|
});
|
||||||
|
Object.assign(state, {
|
||||||
|
badges,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[types.REQUEST_DELETE_BADGE](state, badgeId) {
|
||||||
|
const badges = state.badges.map(badge => {
|
||||||
|
if (badge.id === badgeId) {
|
||||||
|
return {
|
||||||
|
...badge,
|
||||||
|
isDeleting: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return badge;
|
||||||
|
});
|
||||||
|
Object.assign(state, {
|
||||||
|
badges,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
[types.RECEIVE_RENDERED_BADGE](state, renderedBadge) {
|
||||||
|
Object.assign(state, { isRendering: false, renderedBadge });
|
||||||
|
},
|
||||||
|
[types.RECEIVE_RENDERED_BADGE_ERROR](state) {
|
||||||
|
Object.assign(state, { isRendering: false });
|
||||||
|
},
|
||||||
|
[types.REQUEST_RENDERED_BADGE](state) {
|
||||||
|
Object.assign(state, { isRendering: true });
|
||||||
|
},
|
||||||
|
|
||||||
|
[types.START_EDITING](state, badge) {
|
||||||
|
Object.assign(state, {
|
||||||
|
badgeInEditForm: { ...badge },
|
||||||
|
isEditing: true,
|
||||||
|
renderedBadge: { ...badge },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[types.STOP_EDITING](state) {
|
||||||
|
Object.assign(state, {
|
||||||
|
badgeInEditForm: null,
|
||||||
|
isEditing: false,
|
||||||
|
renderedBadge: null,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
[types.UPDATE_BADGE_IN_FORM](state, badge) {
|
||||||
|
if (state.isEditing) {
|
||||||
|
Object.assign(state, {
|
||||||
|
badgeInEditForm: badge,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Object.assign(state, {
|
||||||
|
badgeInAddForm: badge,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
[types.UPDATE_BADGE_IN_MODAL](state, badge) {
|
||||||
|
Object.assign(state, {
|
||||||
|
badgeInModal: badge,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
export default () => ({
|
||||||
|
apiEndpointUrl: null,
|
||||||
|
badgeInAddForm: null,
|
||||||
|
badgeInEditForm: null,
|
||||||
|
badgeInModal: null,
|
||||||
|
badges: [],
|
||||||
|
docsUrl: null,
|
||||||
|
renderedBadge: null,
|
||||||
|
isEditing: false,
|
||||||
|
isLoading: false,
|
||||||
|
isRendering: false,
|
||||||
|
isSaving: false,
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
import Vue from 'vue';
|
||||||
|
import Translate from '~/vue_shared/translate';
|
||||||
|
import { GROUP_BADGE } from '~/badges/constants';
|
||||||
|
import mountBadgeSettings from '~/pages/shared/mount_badge_settings';
|
||||||
|
|
||||||
|
Vue.use(Translate);
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
mountBadgeSettings(GROUP_BADGE);
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
import Vue from 'vue';
|
||||||
|
import Translate from '~/vue_shared/translate';
|
||||||
|
import { PROJECT_BADGE } from '~/badges/constants';
|
||||||
|
import mountBadgeSettings from '~/pages/shared/mount_badge_settings';
|
||||||
|
|
||||||
|
Vue.use(Translate);
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
mountBadgeSettings(PROJECT_BADGE);
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
import Vue from 'vue';
|
||||||
|
import BadgeSettings from '~/badges/components/badge_settings.vue';
|
||||||
|
import store from '~/badges/store';
|
||||||
|
|
||||||
|
export default kind => {
|
||||||
|
const badgeSettingsElement = document.getElementById('badge-settings');
|
||||||
|
|
||||||
|
store.dispatch('loadBadges', {
|
||||||
|
kind,
|
||||||
|
apiEndpointUrl: badgeSettingsElement.dataset.apiEndpointUrl,
|
||||||
|
docsUrl: badgeSettingsElement.dataset.docsUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
return new Vue({
|
||||||
|
el: badgeSettingsElement,
|
||||||
|
store,
|
||||||
|
components: {
|
||||||
|
BadgeSettings,
|
||||||
|
},
|
||||||
|
render(createElement) {
|
||||||
|
return createElement(BadgeSettings);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
@ -39,7 +39,7 @@
|
||||||
.table-section {
|
.table-section {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
||||||
$section-widths: 10 15 20 25 30 40 100;
|
$section-widths: 10 15 20 25 30 40 50 100;
|
||||||
@each $width in $section-widths {
|
@each $width in $section-widths {
|
||||||
&.section-#{$width} {
|
&.section-#{$width} {
|
||||||
flex: 0 0 #{$width + '%'};
|
flex: 0 0 #{$width + '%'};
|
||||||
|
|
|
||||||
|
|
@ -1143,3 +1143,11 @@ pre.light-well {
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.project-badge {
|
||||||
|
opacity: 0.9;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
module Groups
|
||||||
|
module Settings
|
||||||
|
class BadgesController < Groups::ApplicationController
|
||||||
|
include GrapeRouteHelpers::NamedRouteMatcher
|
||||||
|
|
||||||
|
before_action :authorize_admin_group!
|
||||||
|
|
||||||
|
def index
|
||||||
|
@badge_api_endpoint = api_v4_groups_badges_path(id: @group.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
module Projects
|
||||||
|
module Settings
|
||||||
|
class BadgesController < Projects::ApplicationController
|
||||||
|
include GrapeRouteHelpers::NamedRouteMatcher
|
||||||
|
|
||||||
|
before_action :authorize_admin_project!
|
||||||
|
|
||||||
|
def index
|
||||||
|
@badge_api_endpoint = api_v4_projects_badges_path(id: @project.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
module GroupsHelper
|
module GroupsHelper
|
||||||
def group_nav_link_paths
|
def group_nav_link_paths
|
||||||
%w[groups#projects groups#edit ci_cd#show ldap_group_links#index hooks#index audit_events#index pipeline_quota#index]
|
%w[groups#projects groups#edit badges#index ci_cd#show ldap_group_links#index hooks#index audit_events#index pipeline_quota#index]
|
||||||
end
|
end
|
||||||
|
|
||||||
def group_sidebar_links
|
def group_sidebar_links
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
- breadcrumb_title _('Project Badges')
|
||||||
|
- page_title _('Project Badges')
|
||||||
|
|
||||||
|
= render 'shared/badges/badge_settings'
|
||||||
|
|
@ -112,7 +112,7 @@
|
||||||
%span.nav-item-name
|
%span.nav-item-name
|
||||||
Settings
|
Settings
|
||||||
%ul.sidebar-sub-level-items
|
%ul.sidebar-sub-level-items
|
||||||
= nav_link(path: %w[groups#projects groups#edit ci_cd#show], html_options: { class: "fly-out-top-item" } ) do
|
= nav_link(path: %w[groups#projects groups#edit badges#index ci_cd#show], html_options: { class: "fly-out-top-item" } ) do
|
||||||
= link_to edit_group_path(@group) do
|
= link_to edit_group_path(@group) do
|
||||||
%strong.fly-out-top-item-name
|
%strong.fly-out-top-item-name
|
||||||
#{ _('Settings') }
|
#{ _('Settings') }
|
||||||
|
|
@ -122,6 +122,12 @@
|
||||||
%span
|
%span
|
||||||
General
|
General
|
||||||
|
|
||||||
|
= nav_link(controller: :badges) do
|
||||||
|
= link_to group_settings_badges_path(@group), title: _('Project Badges') do
|
||||||
|
%span
|
||||||
|
= _('Project Badges')
|
||||||
|
|
||||||
|
|
||||||
= nav_link(path: 'groups#projects') do
|
= nav_link(path: 'groups#projects') do
|
||||||
= link_to projects_group_path(@group), title: 'Projects' do
|
= link_to projects_group_path(@group), title: 'Projects' do
|
||||||
%span
|
%span
|
||||||
|
|
|
||||||
|
|
@ -258,7 +258,7 @@
|
||||||
#{ _('Snippets') }
|
#{ _('Snippets') }
|
||||||
|
|
||||||
- if project_nav_tab? :settings
|
- if project_nav_tab? :settings
|
||||||
= nav_link(path: %w[projects#edit project_members#index integrations#show services#edit repository#show ci_cd#show pages#show]) do
|
= nav_link(path: %w[projects#edit project_members#index integrations#show services#edit repository#show ci_cd#show badges#index pages#show]) do
|
||||||
= link_to edit_project_path(@project), class: 'shortcuts-tree' do
|
= link_to edit_project_path(@project), class: 'shortcuts-tree' do
|
||||||
.nav-icon-container
|
.nav-icon-container
|
||||||
= sprite_icon('settings')
|
= sprite_icon('settings')
|
||||||
|
|
@ -268,7 +268,7 @@
|
||||||
%ul.sidebar-sub-level-items
|
%ul.sidebar-sub-level-items
|
||||||
- can_edit = can?(current_user, :admin_project, @project)
|
- can_edit = can?(current_user, :admin_project, @project)
|
||||||
- if can_edit
|
- if can_edit
|
||||||
= nav_link(path: %w[projects#edit project_members#index integrations#show services#edit repository#show ci_cd#show pages#show], html_options: { class: "fly-out-top-item" } ) do
|
= nav_link(path: %w[projects#edit project_members#index integrations#show services#edit repository#show ci_cd#show badges#index pages#show], html_options: { class: "fly-out-top-item" } ) do
|
||||||
= link_to edit_project_path(@project) do
|
= link_to edit_project_path(@project) do
|
||||||
%strong.fly-out-top-item-name
|
%strong.fly-out-top-item-name
|
||||||
#{ _('Settings') }
|
#{ _('Settings') }
|
||||||
|
|
@ -281,6 +281,11 @@
|
||||||
= link_to project_project_members_path(@project), title: 'Members' do
|
= link_to project_project_members_path(@project), title: 'Members' do
|
||||||
%span
|
%span
|
||||||
Members
|
Members
|
||||||
|
- if can_edit
|
||||||
|
= nav_link(controller: :badges) do
|
||||||
|
= link_to project_settings_badges_path(@project), title: _('Badges') do
|
||||||
|
%span
|
||||||
|
= _('Badges')
|
||||||
- if can_edit
|
- if can_edit
|
||||||
= nav_link(controller: [:integrations, :services, :hooks, :hook_logs]) do
|
= nav_link(controller: [:integrations, :services, :hooks, :hook_logs]) do
|
||||||
= link_to project_settings_integrations_path(@project), title: 'Integrations' do
|
= link_to project_settings_integrations_path(@project), title: 'Integrations' do
|
||||||
|
|
|
||||||
|
|
@ -23,11 +23,14 @@
|
||||||
- deleted_message = s_('ForkedFromProjectPath|Forked from %{project_name} (deleted)')
|
- deleted_message = s_('ForkedFromProjectPath|Forked from %{project_name} (deleted)')
|
||||||
= deleted_message % { project_name: fork_source_name(@project) }
|
= deleted_message % { project_name: fork_source_name(@project) }
|
||||||
|
|
||||||
.project-badges
|
.project-badges.prepend-top-default.append-bottom-default
|
||||||
- @project.badges.each do |badge|
|
- @project.badges.each do |badge|
|
||||||
- badge_link_url = badge.rendered_link_url(@project)
|
%a.append-right-8{ href: badge.rendered_link_url(@project),
|
||||||
%a{ href: badge_link_url, target: '_blank', rel: 'noopener noreferrer' }
|
target: '_blank',
|
||||||
%img{ src: badge.rendered_image_url(@project), alt: badge_link_url }
|
rel: 'noopener noreferrer' }>
|
||||||
|
%img.project-badge{ src: badge.rendered_image_url(@project),
|
||||||
|
'aria-hidden': true,
|
||||||
|
alt: '' }>
|
||||||
|
|
||||||
.project-repo-buttons
|
.project-repo-buttons
|
||||||
.count-buttons
|
.count-buttons
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
- breadcrumb_title _('Badges')
|
||||||
|
- page_title _('Badges')
|
||||||
|
|
||||||
|
= render 'shared/badges/badge_settings'
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
#badge-settings{ data: { api_endpoint_url: @badge_api_endpoint,
|
||||||
|
docs_url: help_page_path('user/project/badges')} }
|
||||||
|
.text-center.prepend-top-default
|
||||||
|
= icon('spinner spin 2x')
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Projects and groups badges settings UI
|
||||||
|
merge_request: 17114
|
||||||
|
author:
|
||||||
|
type: added
|
||||||
|
|
@ -39,7 +39,7 @@ module.exports = function(config) {
|
||||||
frameworks: ['jasmine'],
|
frameworks: ['jasmine'],
|
||||||
files: [
|
files: [
|
||||||
{ pattern: 'spec/javascripts/test_bundle.js', watched: false },
|
{ pattern: 'spec/javascripts/test_bundle.js', watched: false },
|
||||||
{ pattern: 'spec/javascripts/fixtures/**/*@(.json|.html|.html.raw)', included: false },
|
{ pattern: 'spec/javascripts/fixtures/**/*@(.json|.html|.html.raw|.png)', included: false },
|
||||||
],
|
],
|
||||||
preprocessors: {
|
preprocessors: {
|
||||||
'spec/javascripts/**/*.js': ['webpack', 'sourcemap'],
|
'spec/javascripts/**/*.js': ['webpack', 'sourcemap'],
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
|
||||||
constraints: { group_id: Gitlab::PathRegex.full_namespace_route_regex }) do
|
constraints: { group_id: Gitlab::PathRegex.full_namespace_route_regex }) do
|
||||||
namespace :settings do
|
namespace :settings do
|
||||||
resource :ci_cd, only: [:show], controller: 'ci_cd'
|
resource :ci_cd, only: [:show], controller: 'ci_cd'
|
||||||
|
resources :badges, only: [:index]
|
||||||
end
|
end
|
||||||
|
|
||||||
resource :variables, only: [:show, :update]
|
resource :variables, only: [:show, :update]
|
||||||
|
|
|
||||||
|
|
@ -435,6 +435,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
|
||||||
resource :repository, only: [:show], controller: :repository do
|
resource :repository, only: [:show], controller: :repository do
|
||||||
post :create_deploy_token, path: 'deploy_token/create'
|
post :create_deploy_token, path: 'deploy_token/create'
|
||||||
end
|
end
|
||||||
|
resources :badges, only: [:index]
|
||||||
end
|
end
|
||||||
|
|
||||||
# Since both wiki and repository routing contains wildcard characters
|
# Since both wiki and repository routing contains wildcard characters
|
||||||
|
|
|
||||||
|
|
@ -127,6 +127,7 @@ module API
|
||||||
end
|
end
|
||||||
|
|
||||||
destroy_conditionally!(badge)
|
destroy_conditionally!(badge)
|
||||||
|
body false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,124 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
feature 'Group Badges' do
|
||||||
|
include WaitForRequests
|
||||||
|
|
||||||
|
let(:user) { create(:user) }
|
||||||
|
let(:group) { create(:group) }
|
||||||
|
let(:badge_link_url) { 'https://gitlab.com/gitlab-org/gitlab-ee/commits/master'}
|
||||||
|
let(:badge_image_url) { 'https://gitlab.com/gitlab-org/gitlab-ee/badges/master/build.svg'}
|
||||||
|
let!(:badge_1) { create(:group_badge, group: group) }
|
||||||
|
let!(:badge_2) { create(:group_badge, group: group) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
group.add_owner(user)
|
||||||
|
sign_in(user)
|
||||||
|
|
||||||
|
visit(group_settings_badges_path(group))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'shows a list of badges', :js do
|
||||||
|
page.within '.badge-settings' do
|
||||||
|
wait_for_requests
|
||||||
|
|
||||||
|
rows = all('.panel-body > div')
|
||||||
|
expect(rows.length).to eq 2
|
||||||
|
expect(rows[0]).to have_content badge_1.link_url
|
||||||
|
expect(rows[1]).to have_content badge_2.link_url
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'adding a badge', :js do
|
||||||
|
it 'user can preview a badge' do
|
||||||
|
page.within '.badge-settings form' do
|
||||||
|
fill_in 'badge-link-url', with: badge_link_url
|
||||||
|
fill_in 'badge-image-url', with: badge_image_url
|
||||||
|
within '#badge-preview' do
|
||||||
|
expect(find('a')[:href]).to eq badge_link_url
|
||||||
|
expect(find('a img')[:src]).to eq badge_image_url
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it do
|
||||||
|
page.within '.badge-settings' do
|
||||||
|
fill_in 'badge-link-url', with: badge_link_url
|
||||||
|
fill_in 'badge-image-url', with: badge_image_url
|
||||||
|
|
||||||
|
click_button 'Add badge'
|
||||||
|
wait_for_requests
|
||||||
|
|
||||||
|
within '.panel-body' do
|
||||||
|
expect(find('a')[:href]).to eq badge_link_url
|
||||||
|
expect(find('a img')[:src]).to eq badge_image_url
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'editing a badge', :js do
|
||||||
|
it 'form is shown when clicking edit button in list' do
|
||||||
|
page.within '.badge-settings' do
|
||||||
|
wait_for_requests
|
||||||
|
rows = all('.panel-body > div')
|
||||||
|
expect(rows.length).to eq 2
|
||||||
|
rows[1].find('[aria-label="Edit"]').click
|
||||||
|
|
||||||
|
within 'form' do
|
||||||
|
expect(find('#badge-link-url').value).to eq badge_2.link_url
|
||||||
|
expect(find('#badge-image-url').value).to eq badge_2.image_url
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'updates a badge when submitting the edit form' do
|
||||||
|
page.within '.badge-settings' do
|
||||||
|
wait_for_requests
|
||||||
|
rows = all('.panel-body > div')
|
||||||
|
expect(rows.length).to eq 2
|
||||||
|
rows[1].find('[aria-label="Edit"]').click
|
||||||
|
within 'form' do
|
||||||
|
fill_in 'badge-link-url', with: badge_link_url
|
||||||
|
fill_in 'badge-image-url', with: badge_image_url
|
||||||
|
|
||||||
|
click_button 'Save changes'
|
||||||
|
wait_for_requests
|
||||||
|
end
|
||||||
|
|
||||||
|
rows = all('.panel-body > div')
|
||||||
|
expect(rows.length).to eq 2
|
||||||
|
expect(rows[1]).to have_content badge_link_url
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'deleting a badge', :js do
|
||||||
|
def click_delete_button(badge_row)
|
||||||
|
badge_row.find('[aria-label="Delete"]').click
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'shows a modal when deleting a badge' do
|
||||||
|
wait_for_requests
|
||||||
|
rows = all('.panel-body > div')
|
||||||
|
expect(rows.length).to eq 2
|
||||||
|
|
||||||
|
click_delete_button(rows[1])
|
||||||
|
|
||||||
|
expect(find('.modal .modal-title')).to have_content 'Delete badge?'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'deletes a badge when confirming the modal' do
|
||||||
|
wait_for_requests
|
||||||
|
rows = all('.panel-body > div')
|
||||||
|
expect(rows.length).to eq 2
|
||||||
|
click_delete_button(rows[1])
|
||||||
|
|
||||||
|
find('.modal .btn-danger').click
|
||||||
|
wait_for_requests
|
||||||
|
|
||||||
|
rows = all('.panel-body > div')
|
||||||
|
expect(rows.length).to eq 1
|
||||||
|
expect(rows[0]).to have_content badge_1.link_url
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,125 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
feature 'Project Badges' do
|
||||||
|
include WaitForRequests
|
||||||
|
|
||||||
|
let(:user) { create(:user) }
|
||||||
|
let(:group) { create(:group) }
|
||||||
|
let(:project) { create(:project, namespace: group) }
|
||||||
|
let(:badge_link_url) { 'https://gitlab.com/gitlab-org/gitlab-ee/commits/master'}
|
||||||
|
let(:badge_image_url) { 'https://gitlab.com/gitlab-org/gitlab-ee/badges/master/build.svg'}
|
||||||
|
let!(:project_badge) { create(:project_badge, project: project) }
|
||||||
|
let!(:group_badge) { create(:group_badge, group: group) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
group.add_master(user)
|
||||||
|
sign_in(user)
|
||||||
|
|
||||||
|
visit(project_settings_badges_path(project))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'shows a list of badges', :js do
|
||||||
|
page.within '.badge-settings' do
|
||||||
|
wait_for_requests
|
||||||
|
|
||||||
|
rows = all('.panel-body > div')
|
||||||
|
expect(rows.length).to eq 2
|
||||||
|
expect(rows[0]).to have_content group_badge.link_url
|
||||||
|
expect(rows[1]).to have_content project_badge.link_url
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'adding a badge', :js do
|
||||||
|
it 'user can preview a badge' do
|
||||||
|
page.within '.badge-settings form' do
|
||||||
|
fill_in 'badge-link-url', with: badge_link_url
|
||||||
|
fill_in 'badge-image-url', with: badge_image_url
|
||||||
|
within '#badge-preview' do
|
||||||
|
expect(find('a')[:href]).to eq badge_link_url
|
||||||
|
expect(find('a img')[:src]).to eq badge_image_url
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it do
|
||||||
|
page.within '.badge-settings' do
|
||||||
|
fill_in 'badge-link-url', with: badge_link_url
|
||||||
|
fill_in 'badge-image-url', with: badge_image_url
|
||||||
|
|
||||||
|
click_button 'Add badge'
|
||||||
|
wait_for_requests
|
||||||
|
|
||||||
|
within '.panel-body' do
|
||||||
|
expect(find('a')[:href]).to eq badge_link_url
|
||||||
|
expect(find('a img')[:src]).to eq badge_image_url
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'editing a badge', :js do
|
||||||
|
it 'form is shown when clicking edit button in list' do
|
||||||
|
page.within '.badge-settings' do
|
||||||
|
wait_for_requests
|
||||||
|
rows = all('.panel-body > div')
|
||||||
|
expect(rows.length).to eq 2
|
||||||
|
rows[1].find('[aria-label="Edit"]').click
|
||||||
|
|
||||||
|
within 'form' do
|
||||||
|
expect(find('#badge-link-url').value).to eq project_badge.link_url
|
||||||
|
expect(find('#badge-image-url').value).to eq project_badge.image_url
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'updates a badge when submitting the edit form' do
|
||||||
|
page.within '.badge-settings' do
|
||||||
|
wait_for_requests
|
||||||
|
rows = all('.panel-body > div')
|
||||||
|
expect(rows.length).to eq 2
|
||||||
|
rows[1].find('[aria-label="Edit"]').click
|
||||||
|
within 'form' do
|
||||||
|
fill_in 'badge-link-url', with: badge_link_url
|
||||||
|
fill_in 'badge-image-url', with: badge_image_url
|
||||||
|
|
||||||
|
click_button 'Save changes'
|
||||||
|
wait_for_requests
|
||||||
|
end
|
||||||
|
|
||||||
|
rows = all('.panel-body > div')
|
||||||
|
expect(rows.length).to eq 2
|
||||||
|
expect(rows[1]).to have_content badge_link_url
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'deleting a badge', :js do
|
||||||
|
def click_delete_button(badge_row)
|
||||||
|
badge_row.find('[aria-label="Delete"]').click
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'shows a modal when deleting a badge' do
|
||||||
|
wait_for_requests
|
||||||
|
rows = all('.panel-body > div')
|
||||||
|
expect(rows.length).to eq 2
|
||||||
|
|
||||||
|
click_delete_button(rows[1])
|
||||||
|
|
||||||
|
expect(find('.modal .modal-title')).to have_content 'Delete badge?'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'deletes a badge when confirming the modal' do
|
||||||
|
wait_for_requests
|
||||||
|
rows = all('.panel-body > div')
|
||||||
|
expect(rows.length).to eq 2
|
||||||
|
click_delete_button(rows[1])
|
||||||
|
|
||||||
|
find('.modal .btn-danger').click
|
||||||
|
wait_for_requests
|
||||||
|
|
||||||
|
rows = all('.panel-body > div')
|
||||||
|
expect(rows.length).to eq 1
|
||||||
|
expect(rows[0]).to have_content group_badge.link_url
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,171 @@
|
||||||
|
import Vue from 'vue';
|
||||||
|
import store from '~/badges/store';
|
||||||
|
import BadgeForm from '~/badges/components/badge_form.vue';
|
||||||
|
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
|
||||||
|
import { createDummyBadge } from '../dummy_badge';
|
||||||
|
|
||||||
|
describe('BadgeForm component', () => {
|
||||||
|
const Component = Vue.extend(BadgeForm);
|
||||||
|
let vm;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
setFixtures(`
|
||||||
|
<div id="dummy-element"></div>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vm.$destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('methods', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vm = mountComponentWithStore(Component, {
|
||||||
|
el: '#dummy-element',
|
||||||
|
store,
|
||||||
|
props: {
|
||||||
|
isEditing: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('onCancel', () => {
|
||||||
|
it('calls stopEditing', () => {
|
||||||
|
spyOn(vm, 'stopEditing');
|
||||||
|
|
||||||
|
vm.onCancel();
|
||||||
|
|
||||||
|
expect(vm.stopEditing).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('onSubmit', () => {
|
||||||
|
describe('if isEditing is true', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOn(vm, 'saveBadge').and.returnValue(Promise.resolve());
|
||||||
|
store.replaceState({
|
||||||
|
...store.state,
|
||||||
|
isSaving: false,
|
||||||
|
badgeInEditForm: createDummyBadge(),
|
||||||
|
});
|
||||||
|
vm.isEditing = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns immediately if imageUrl is empty', () => {
|
||||||
|
store.state.badgeInEditForm.imageUrl = '';
|
||||||
|
|
||||||
|
vm.onSubmit();
|
||||||
|
|
||||||
|
expect(vm.saveBadge).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns immediately if linkUrl is empty', () => {
|
||||||
|
store.state.badgeInEditForm.linkUrl = '';
|
||||||
|
|
||||||
|
vm.onSubmit();
|
||||||
|
|
||||||
|
expect(vm.saveBadge).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns immediately if isSaving is true', () => {
|
||||||
|
store.state.isSaving = true;
|
||||||
|
|
||||||
|
vm.onSubmit();
|
||||||
|
|
||||||
|
expect(vm.saveBadge).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls saveBadge', () => {
|
||||||
|
vm.onSubmit();
|
||||||
|
|
||||||
|
expect(vm.saveBadge).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('if isEditing is false', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOn(vm, 'addBadge').and.returnValue(Promise.resolve());
|
||||||
|
store.replaceState({
|
||||||
|
...store.state,
|
||||||
|
isSaving: false,
|
||||||
|
badgeInAddForm: createDummyBadge(),
|
||||||
|
});
|
||||||
|
vm.isEditing = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns immediately if imageUrl is empty', () => {
|
||||||
|
store.state.badgeInAddForm.imageUrl = '';
|
||||||
|
|
||||||
|
vm.onSubmit();
|
||||||
|
|
||||||
|
expect(vm.addBadge).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns immediately if linkUrl is empty', () => {
|
||||||
|
store.state.badgeInAddForm.linkUrl = '';
|
||||||
|
|
||||||
|
vm.onSubmit();
|
||||||
|
|
||||||
|
expect(vm.addBadge).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns immediately if isSaving is true', () => {
|
||||||
|
store.state.isSaving = true;
|
||||||
|
|
||||||
|
vm.onSubmit();
|
||||||
|
|
||||||
|
expect(vm.addBadge).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls addBadge', () => {
|
||||||
|
vm.onSubmit();
|
||||||
|
|
||||||
|
expect(vm.addBadge).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('if isEditing is false', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vm = mountComponentWithStore(Component, {
|
||||||
|
el: '#dummy-element',
|
||||||
|
store,
|
||||||
|
props: {
|
||||||
|
isEditing: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders one button', () => {
|
||||||
|
const buttons = vm.$el.querySelectorAll('.row-content-block button');
|
||||||
|
expect(buttons.length).toBe(1);
|
||||||
|
const buttonAddElement = buttons[0];
|
||||||
|
expect(buttonAddElement).toBeVisible();
|
||||||
|
expect(buttonAddElement).toHaveText('Add badge');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('if isEditing is true', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vm = mountComponentWithStore(Component, {
|
||||||
|
el: '#dummy-element',
|
||||||
|
store,
|
||||||
|
props: {
|
||||||
|
isEditing: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders two buttons', () => {
|
||||||
|
const buttons = vm.$el.querySelectorAll('.row-content-block button');
|
||||||
|
expect(buttons.length).toBe(2);
|
||||||
|
const buttonSaveElement = buttons[0];
|
||||||
|
expect(buttonSaveElement).toBeVisible();
|
||||||
|
expect(buttonSaveElement).toHaveText('Save changes');
|
||||||
|
const buttonCancelElement = buttons[1];
|
||||||
|
expect(buttonCancelElement).toBeVisible();
|
||||||
|
expect(buttonCancelElement).toHaveText('Cancel');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,97 @@
|
||||||
|
import $ from 'jquery';
|
||||||
|
import Vue from 'vue';
|
||||||
|
import { GROUP_BADGE, PROJECT_BADGE } from '~/badges/constants';
|
||||||
|
import store from '~/badges/store';
|
||||||
|
import BadgeListRow from '~/badges/components/badge_list_row.vue';
|
||||||
|
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
|
||||||
|
import { createDummyBadge } from '../dummy_badge';
|
||||||
|
|
||||||
|
describe('BadgeListRow component', () => {
|
||||||
|
const Component = Vue.extend(BadgeListRow);
|
||||||
|
let badge;
|
||||||
|
let vm;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
setFixtures(`
|
||||||
|
<div id="delete-badge-modal" class="modal"></div>
|
||||||
|
<div id="dummy-element"></div>
|
||||||
|
`);
|
||||||
|
store.replaceState({
|
||||||
|
...store.state,
|
||||||
|
kind: PROJECT_BADGE,
|
||||||
|
});
|
||||||
|
badge = createDummyBadge();
|
||||||
|
vm = mountComponentWithStore(Component, {
|
||||||
|
el: '#dummy-element',
|
||||||
|
store,
|
||||||
|
props: { badge },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vm.$destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the badge', () => {
|
||||||
|
const badgeElement = vm.$el.querySelector('.project-badge');
|
||||||
|
expect(badgeElement).not.toBeNull();
|
||||||
|
expect(badgeElement.getAttribute('src')).toBe(badge.renderedImageUrl);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the badge link', () => {
|
||||||
|
expect(vm.$el).toContainText(badge.linkUrl);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the badge kind', () => {
|
||||||
|
expect(vm.$el).toContainText('Project Badge');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows edit and delete buttons', () => {
|
||||||
|
const buttons = vm.$el.querySelectorAll('.table-button-footer button');
|
||||||
|
expect(buttons).toHaveLength(2);
|
||||||
|
const buttonEditElement = buttons[0];
|
||||||
|
expect(buttonEditElement).toBeVisible();
|
||||||
|
expect(buttonEditElement).toHaveSpriteIcon('pencil');
|
||||||
|
const buttonDeleteElement = buttons[1];
|
||||||
|
expect(buttonDeleteElement).toBeVisible();
|
||||||
|
expect(buttonDeleteElement).toHaveSpriteIcon('remove');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls editBadge when clicking then edit button', () => {
|
||||||
|
spyOn(vm, 'editBadge');
|
||||||
|
|
||||||
|
const editButton = vm.$el.querySelector('.table-button-footer button:first-of-type');
|
||||||
|
editButton.click();
|
||||||
|
|
||||||
|
expect(vm.editBadge).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls updateBadgeInModal and shows modal when clicking then delete button', done => {
|
||||||
|
spyOn(vm, 'updateBadgeInModal');
|
||||||
|
$('#delete-badge-modal').on('shown.bs.modal', () => done());
|
||||||
|
|
||||||
|
const deleteButton = vm.$el.querySelector('.table-button-footer button:last-of-type');
|
||||||
|
deleteButton.click();
|
||||||
|
|
||||||
|
expect(vm.updateBadgeInModal).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('for a group badge', () => {
|
||||||
|
beforeEach(done => {
|
||||||
|
badge.kind = GROUP_BADGE;
|
||||||
|
|
||||||
|
Vue.nextTick()
|
||||||
|
.then(done)
|
||||||
|
.catch(done.fail);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the badge kind', () => {
|
||||||
|
expect(vm.$el).toContainText('Group Badge');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('hides edit and delete buttons', () => {
|
||||||
|
const buttons = vm.$el.querySelectorAll('.table-button-footer button');
|
||||||
|
expect(buttons).toHaveLength(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
import Vue from 'vue';
|
||||||
|
import { GROUP_BADGE, PROJECT_BADGE } from '~/badges/constants';
|
||||||
|
import store from '~/badges/store';
|
||||||
|
import BadgeList from '~/badges/components/badge_list.vue';
|
||||||
|
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
|
||||||
|
import { createDummyBadge } from '../dummy_badge';
|
||||||
|
|
||||||
|
describe('BadgeList component', () => {
|
||||||
|
const Component = Vue.extend(BadgeList);
|
||||||
|
const numberOfDummyBadges = 3;
|
||||||
|
let vm;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
setFixtures('<div id="dummy-element"></div>');
|
||||||
|
const badges = [];
|
||||||
|
for (let id = 0; id < numberOfDummyBadges; id += 1) {
|
||||||
|
badges.push({ id, ...createDummyBadge() });
|
||||||
|
}
|
||||||
|
store.replaceState({
|
||||||
|
...store.state,
|
||||||
|
badges,
|
||||||
|
kind: PROJECT_BADGE,
|
||||||
|
isLoading: false,
|
||||||
|
});
|
||||||
|
vm = mountComponentWithStore(Component, {
|
||||||
|
el: '#dummy-element',
|
||||||
|
store,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vm.$destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders a header with the badge count', () => {
|
||||||
|
const header = vm.$el.querySelector('.panel-heading');
|
||||||
|
expect(header).toHaveText(new RegExp(`Your badges\\s+${numberOfDummyBadges}`));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders a row for each badge', () => {
|
||||||
|
const rows = vm.$el.querySelectorAll('.gl-responsive-table-row');
|
||||||
|
expect(rows).toHaveLength(numberOfDummyBadges);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders a message if no badges exist', done => {
|
||||||
|
store.state.badges = [];
|
||||||
|
|
||||||
|
Vue.nextTick()
|
||||||
|
.then(() => {
|
||||||
|
expect(vm.$el).toContainText('This project has no badges');
|
||||||
|
})
|
||||||
|
.then(done)
|
||||||
|
.catch(done.fail);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows a loading icon when loading', done => {
|
||||||
|
store.state.isLoading = true;
|
||||||
|
|
||||||
|
Vue.nextTick()
|
||||||
|
.then(() => {
|
||||||
|
const loadingIcon = vm.$el.querySelector('.fa-spinner');
|
||||||
|
expect(loadingIcon).toBeVisible();
|
||||||
|
})
|
||||||
|
.then(done)
|
||||||
|
.catch(done.fail);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('for group badges', () => {
|
||||||
|
beforeEach(done => {
|
||||||
|
store.state.kind = GROUP_BADGE;
|
||||||
|
|
||||||
|
Vue.nextTick()
|
||||||
|
.then(done)
|
||||||
|
.catch(done.fail);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders a message if no badges exist', done => {
|
||||||
|
store.state.badges = [];
|
||||||
|
|
||||||
|
Vue.nextTick()
|
||||||
|
.then(() => {
|
||||||
|
expect(vm.$el).toContainText('This group has no badges');
|
||||||
|
})
|
||||||
|
.then(done)
|
||||||
|
.catch(done.fail);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
import $ from 'jquery';
|
||||||
|
import Vue from 'vue';
|
||||||
|
import store from '~/badges/store';
|
||||||
|
import BadgeSettings from '~/badges/components/badge_settings.vue';
|
||||||
|
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
|
||||||
|
import { createDummyBadge } from '../dummy_badge';
|
||||||
|
|
||||||
|
describe('BadgeSettings component', () => {
|
||||||
|
const Component = Vue.extend(BadgeSettings);
|
||||||
|
let vm;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
setFixtures(`
|
||||||
|
<div id="dummy-element"></div>
|
||||||
|
<button
|
||||||
|
id="dummy-modal-button"
|
||||||
|
type="button"
|
||||||
|
data-toggle="modal"
|
||||||
|
data-target="#delete-badge-modal"
|
||||||
|
>Show modal</button>
|
||||||
|
`);
|
||||||
|
vm = mountComponentWithStore(Component, {
|
||||||
|
el: '#dummy-element',
|
||||||
|
store,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vm.$destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('displays modal if button is clicked', done => {
|
||||||
|
const badge = createDummyBadge();
|
||||||
|
store.state.badgeInModal = badge;
|
||||||
|
const modal = vm.$el.querySelector('#delete-badge-modal');
|
||||||
|
const button = document.getElementById('dummy-modal-button');
|
||||||
|
|
||||||
|
$(modal).on('shown.bs.modal', () => {
|
||||||
|
expect(modal).toContainText('Delete badge?');
|
||||||
|
const badgeElement = modal.querySelector('img.project-badge');
|
||||||
|
expect(badgeElement).not.toBe(null);
|
||||||
|
expect(badgeElement.getAttribute('src')).toBe(badge.renderedImageUrl);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
Vue.nextTick()
|
||||||
|
.then(() => {
|
||||||
|
button.click();
|
||||||
|
})
|
||||||
|
.catch(done.fail);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('displays a form to add a badge', () => {
|
||||||
|
const form = vm.$el.querySelector('form:nth-of-type(2)');
|
||||||
|
expect(form).not.toBe(null);
|
||||||
|
const button = form.querySelector('.btn-success');
|
||||||
|
expect(button).not.toBe(null);
|
||||||
|
expect(button).toHaveText(/Add badge/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('displays badge list', () => {
|
||||||
|
const badgeListElement = vm.$el.querySelector('.panel');
|
||||||
|
expect(badgeListElement).not.toBe(null);
|
||||||
|
expect(badgeListElement).toBeVisible();
|
||||||
|
expect(badgeListElement).toContainText('Your badges');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when editing', () => {
|
||||||
|
beforeEach(done => {
|
||||||
|
store.state.isEditing = true;
|
||||||
|
|
||||||
|
Vue.nextTick()
|
||||||
|
.then(done)
|
||||||
|
.catch(done.fail);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('displays a form to edit a badge', () => {
|
||||||
|
const form = vm.$el.querySelector('form:nth-of-type(1)');
|
||||||
|
expect(form).not.toBe(null);
|
||||||
|
const submitButton = form.querySelector('.btn-success');
|
||||||
|
expect(submitButton).not.toBe(null);
|
||||||
|
expect(submitButton).toHaveText(/Save changes/);
|
||||||
|
const cancelButton = form.querySelector('.btn-cancel');
|
||||||
|
expect(cancelButton).not.toBe(null);
|
||||||
|
expect(cancelButton).toHaveText(/Cancel/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('displays no badge list', () => {
|
||||||
|
const badgeListElement = vm.$el.querySelector('.panel');
|
||||||
|
expect(badgeListElement).toBeHidden();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('methods', () => {
|
||||||
|
describe('onSubmitModal', () => {
|
||||||
|
it('triggers ', () => {
|
||||||
|
spyOn(vm, 'deleteBadge').and.callFake(() => Promise.resolve());
|
||||||
|
const modal = vm.$el.querySelector('#delete-badge-modal');
|
||||||
|
const deleteButton = modal.querySelector('.btn-danger');
|
||||||
|
|
||||||
|
deleteButton.click();
|
||||||
|
|
||||||
|
const badge = store.state.badgeInModal;
|
||||||
|
expect(vm.deleteBadge).toHaveBeenCalledWith(badge);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,147 @@
|
||||||
|
import Vue from 'vue';
|
||||||
|
import Badge from '~/badges/components/badge.vue';
|
||||||
|
import mountComponent from 'spec/helpers/vue_mount_component_helper';
|
||||||
|
import { DUMMY_IMAGE_URL, TEST_HOST } from 'spec/test_constants';
|
||||||
|
|
||||||
|
describe('Badge component', () => {
|
||||||
|
const Component = Vue.extend(Badge);
|
||||||
|
const dummyProps = {
|
||||||
|
imageUrl: DUMMY_IMAGE_URL,
|
||||||
|
linkUrl: `${TEST_HOST}/badge/link/url`,
|
||||||
|
};
|
||||||
|
let vm;
|
||||||
|
|
||||||
|
const findElements = () => {
|
||||||
|
const buttons = vm.$el.querySelectorAll('button');
|
||||||
|
return {
|
||||||
|
badgeImage: vm.$el.querySelector('img.project-badge'),
|
||||||
|
loadingIcon: vm.$el.querySelector('.fa-spinner'),
|
||||||
|
reloadButton: buttons[buttons.length - 1],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const createComponent = (props, el = null) => {
|
||||||
|
vm = mountComponent(Component, props, el);
|
||||||
|
const { badgeImage } = findElements();
|
||||||
|
return new Promise(resolve => badgeImage.addEventListener('load', resolve)).then(() =>
|
||||||
|
Vue.nextTick(),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vm.$destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('watchers', () => {
|
||||||
|
describe('imageUrl', () => {
|
||||||
|
it('sets isLoading and resets numRetries and hasError', done => {
|
||||||
|
const props = { ...dummyProps };
|
||||||
|
createComponent(props)
|
||||||
|
.then(() => {
|
||||||
|
expect(vm.isLoading).toBe(false);
|
||||||
|
vm.hasError = true;
|
||||||
|
vm.numRetries = 42;
|
||||||
|
|
||||||
|
vm.imageUrl = `${props.imageUrl}#something/else`;
|
||||||
|
|
||||||
|
return Vue.nextTick();
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
expect(vm.isLoading).toBe(true);
|
||||||
|
expect(vm.numRetries).toBe(0);
|
||||||
|
expect(vm.hasError).toBe(false);
|
||||||
|
})
|
||||||
|
.then(done)
|
||||||
|
.catch(done.fail);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('methods', () => {
|
||||||
|
beforeEach(done => {
|
||||||
|
createComponent({ ...dummyProps })
|
||||||
|
.then(done)
|
||||||
|
.catch(done.fail);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('onError resets isLoading and sets hasError', () => {
|
||||||
|
vm.hasError = false;
|
||||||
|
vm.isLoading = true;
|
||||||
|
|
||||||
|
vm.onError();
|
||||||
|
|
||||||
|
expect(vm.hasError).toBe(true);
|
||||||
|
expect(vm.isLoading).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('onLoad sets isLoading', () => {
|
||||||
|
vm.isLoading = true;
|
||||||
|
|
||||||
|
vm.onLoad();
|
||||||
|
|
||||||
|
expect(vm.isLoading).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('reloadImage resets isLoading and hasError and increases numRetries', () => {
|
||||||
|
vm.hasError = true;
|
||||||
|
vm.isLoading = false;
|
||||||
|
vm.numRetries = 0;
|
||||||
|
|
||||||
|
vm.reloadImage();
|
||||||
|
|
||||||
|
expect(vm.hasError).toBe(false);
|
||||||
|
expect(vm.isLoading).toBe(true);
|
||||||
|
expect(vm.numRetries).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('behavior', () => {
|
||||||
|
beforeEach(done => {
|
||||||
|
setFixtures('<div id="dummy-element"></div>');
|
||||||
|
createComponent({ ...dummyProps }, '#dummy-element')
|
||||||
|
.then(done)
|
||||||
|
.catch(done.fail);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows a badge image after loading', () => {
|
||||||
|
expect(vm.isLoading).toBe(false);
|
||||||
|
expect(vm.hasError).toBe(false);
|
||||||
|
const { badgeImage, loadingIcon, reloadButton } = findElements();
|
||||||
|
expect(badgeImage).toBeVisible();
|
||||||
|
expect(loadingIcon).toBeHidden();
|
||||||
|
expect(reloadButton).toBeHidden();
|
||||||
|
expect(vm.$el.innerText).toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows a loading icon when loading', done => {
|
||||||
|
vm.isLoading = true;
|
||||||
|
|
||||||
|
Vue.nextTick()
|
||||||
|
.then(() => {
|
||||||
|
const { badgeImage, loadingIcon, reloadButton } = findElements();
|
||||||
|
expect(badgeImage).toBeHidden();
|
||||||
|
expect(loadingIcon).toBeVisible();
|
||||||
|
expect(reloadButton).toBeHidden();
|
||||||
|
expect(vm.$el.innerText).toBe('');
|
||||||
|
})
|
||||||
|
.then(done)
|
||||||
|
.catch(done.fail);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows an error and reload button if loading failed', done => {
|
||||||
|
vm.hasError = true;
|
||||||
|
|
||||||
|
Vue.nextTick()
|
||||||
|
.then(() => {
|
||||||
|
const { badgeImage, loadingIcon, reloadButton } = findElements();
|
||||||
|
expect(badgeImage).toBeHidden();
|
||||||
|
expect(loadingIcon).toBeHidden();
|
||||||
|
expect(reloadButton).toBeVisible();
|
||||||
|
expect(reloadButton).toHaveSpriteIcon('retry');
|
||||||
|
expect(vm.$el.innerText.trim()).toBe('No badge image');
|
||||||
|
})
|
||||||
|
.then(done)
|
||||||
|
.catch(done.fail);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { PROJECT_BADGE } from '~/badges/constants';
|
||||||
|
import { DUMMY_IMAGE_URL, TEST_HOST } from 'spec/test_constants';
|
||||||
|
|
||||||
|
export const createDummyBadge = () => {
|
||||||
|
const id = Math.floor(1000 * Math.random());
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
imageUrl: `${TEST_HOST}/badges/${id}/image/url`,
|
||||||
|
isDeleting: false,
|
||||||
|
linkUrl: `${TEST_HOST}/badges/${id}/link/url`,
|
||||||
|
kind: PROJECT_BADGE,
|
||||||
|
renderedImageUrl: `${DUMMY_IMAGE_URL}?id=${id}`,
|
||||||
|
renderedLinkUrl: `${TEST_HOST}/badges/${id}/rendered/link/url`,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createDummyBadgeResponse = () => ({
|
||||||
|
image_url: `${TEST_HOST}/badge/image/url`,
|
||||||
|
link_url: `${TEST_HOST}/badge/link/url`,
|
||||||
|
kind: PROJECT_BADGE,
|
||||||
|
rendered_image_url: DUMMY_IMAGE_URL,
|
||||||
|
rendered_link_url: `${TEST_HOST}/rendered/badge/link/url`,
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,607 @@
|
||||||
|
import axios from '~/lib/utils/axios_utils';
|
||||||
|
import MockAdapter from 'axios-mock-adapter';
|
||||||
|
import actions, { transformBackendBadge } from '~/badges/store/actions';
|
||||||
|
import mutationTypes from '~/badges/store/mutation_types';
|
||||||
|
import createState from '~/badges/store/state';
|
||||||
|
import { TEST_HOST } from 'spec/test_constants';
|
||||||
|
import testAction from 'spec/helpers/vuex_action_helper';
|
||||||
|
import { createDummyBadge, createDummyBadgeResponse } from '../dummy_badge';
|
||||||
|
|
||||||
|
describe('Badges store actions', () => {
|
||||||
|
const dummyEndpointUrl = `${TEST_HOST}/badges/endpoint`;
|
||||||
|
const dummyBadges = [{ ...createDummyBadge(), id: 5 }, { ...createDummyBadge(), id: 6 }];
|
||||||
|
|
||||||
|
let axiosMock;
|
||||||
|
let badgeId;
|
||||||
|
let state;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
axiosMock = new MockAdapter(axios);
|
||||||
|
state = {
|
||||||
|
...createState(),
|
||||||
|
apiEndpointUrl: dummyEndpointUrl,
|
||||||
|
badges: dummyBadges,
|
||||||
|
};
|
||||||
|
badgeId = state.badges[0].id;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
axiosMock.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('requestNewBadge', () => {
|
||||||
|
it('commits REQUEST_NEW_BADGE', done => {
|
||||||
|
testAction(
|
||||||
|
actions.requestNewBadge,
|
||||||
|
null,
|
||||||
|
state,
|
||||||
|
[{ type: mutationTypes.REQUEST_NEW_BADGE }],
|
||||||
|
[],
|
||||||
|
done,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('receiveNewBadge', () => {
|
||||||
|
it('commits RECEIVE_NEW_BADGE', done => {
|
||||||
|
const newBadge = createDummyBadge();
|
||||||
|
testAction(
|
||||||
|
actions.receiveNewBadge,
|
||||||
|
newBadge,
|
||||||
|
state,
|
||||||
|
[{ type: mutationTypes.RECEIVE_NEW_BADGE, payload: newBadge }],
|
||||||
|
[],
|
||||||
|
done,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('receiveNewBadgeError', () => {
|
||||||
|
it('commits RECEIVE_NEW_BADGE_ERROR', done => {
|
||||||
|
testAction(
|
||||||
|
actions.receiveNewBadgeError,
|
||||||
|
null,
|
||||||
|
state,
|
||||||
|
[{ type: mutationTypes.RECEIVE_NEW_BADGE_ERROR }],
|
||||||
|
[],
|
||||||
|
done,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('addBadge', () => {
|
||||||
|
let badgeInAddForm;
|
||||||
|
let dispatch;
|
||||||
|
let endpointMock;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
endpointMock = axiosMock.onPost(dummyEndpointUrl);
|
||||||
|
dispatch = jasmine.createSpy('dispatch');
|
||||||
|
badgeInAddForm = createDummyBadge();
|
||||||
|
state = {
|
||||||
|
...state,
|
||||||
|
badgeInAddForm,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('dispatches requestNewBadge and receiveNewBadge for successful response', done => {
|
||||||
|
const dummyResponse = createDummyBadgeResponse();
|
||||||
|
|
||||||
|
endpointMock.replyOnce(req => {
|
||||||
|
expect(req.data).toBe(
|
||||||
|
JSON.stringify({
|
||||||
|
image_url: badgeInAddForm.imageUrl,
|
||||||
|
link_url: badgeInAddForm.linkUrl,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(dispatch.calls.allArgs()).toEqual([['requestNewBadge']]);
|
||||||
|
dispatch.calls.reset();
|
||||||
|
return [200, dummyResponse];
|
||||||
|
});
|
||||||
|
|
||||||
|
const dummyBadge = transformBackendBadge(dummyResponse);
|
||||||
|
actions
|
||||||
|
.addBadge({ state, dispatch })
|
||||||
|
.then(() => {
|
||||||
|
expect(dispatch.calls.allArgs()).toEqual([['receiveNewBadge', dummyBadge]]);
|
||||||
|
})
|
||||||
|
.then(done)
|
||||||
|
.catch(done.fail);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('dispatches requestNewBadge and receiveNewBadgeError for error response', done => {
|
||||||
|
endpointMock.replyOnce(req => {
|
||||||
|
expect(req.data).toBe(
|
||||||
|
JSON.stringify({
|
||||||
|
image_url: badgeInAddForm.imageUrl,
|
||||||
|
link_url: badgeInAddForm.linkUrl,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(dispatch.calls.allArgs()).toEqual([['requestNewBadge']]);
|
||||||
|
dispatch.calls.reset();
|
||||||
|
return [500, ''];
|
||||||
|
});
|
||||||
|
|
||||||
|
actions
|
||||||
|
.addBadge({ state, dispatch })
|
||||||
|
.then(() => done.fail('Expected Ajax call to fail!'))
|
||||||
|
.catch(() => {
|
||||||
|
expect(dispatch.calls.allArgs()).toEqual([['receiveNewBadgeError']]);
|
||||||
|
})
|
||||||
|
.then(done)
|
||||||
|
.catch(done.fail);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('requestDeleteBadge', () => {
|
||||||
|
it('commits REQUEST_DELETE_BADGE', done => {
|
||||||
|
testAction(
|
||||||
|
actions.requestDeleteBadge,
|
||||||
|
badgeId,
|
||||||
|
state,
|
||||||
|
[{ type: mutationTypes.REQUEST_DELETE_BADGE, payload: badgeId }],
|
||||||
|
[],
|
||||||
|
done,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('receiveDeleteBadge', () => {
|
||||||
|
it('commits RECEIVE_DELETE_BADGE', done => {
|
||||||
|
testAction(
|
||||||
|
actions.receiveDeleteBadge,
|
||||||
|
badgeId,
|
||||||
|
state,
|
||||||
|
[{ type: mutationTypes.RECEIVE_DELETE_BADGE, payload: badgeId }],
|
||||||
|
[],
|
||||||
|
done,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('receiveDeleteBadgeError', () => {
|
||||||
|
it('commits RECEIVE_DELETE_BADGE_ERROR', done => {
|
||||||
|
testAction(
|
||||||
|
actions.receiveDeleteBadgeError,
|
||||||
|
badgeId,
|
||||||
|
state,
|
||||||
|
[{ type: mutationTypes.RECEIVE_DELETE_BADGE_ERROR, payload: badgeId }],
|
||||||
|
[],
|
||||||
|
done,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('deleteBadge', () => {
|
||||||
|
let dispatch;
|
||||||
|
let endpointMock;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
endpointMock = axiosMock.onDelete(`${dummyEndpointUrl}/${badgeId}`);
|
||||||
|
dispatch = jasmine.createSpy('dispatch');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('dispatches requestDeleteBadge and receiveDeleteBadge for successful response', done => {
|
||||||
|
endpointMock.replyOnce(() => {
|
||||||
|
expect(dispatch.calls.allArgs()).toEqual([['requestDeleteBadge', badgeId]]);
|
||||||
|
dispatch.calls.reset();
|
||||||
|
return [200, ''];
|
||||||
|
});
|
||||||
|
|
||||||
|
actions
|
||||||
|
.deleteBadge({ state, dispatch }, { id: badgeId })
|
||||||
|
.then(() => {
|
||||||
|
expect(dispatch.calls.allArgs()).toEqual([['receiveDeleteBadge', badgeId]]);
|
||||||
|
})
|
||||||
|
.then(done)
|
||||||
|
.catch(done.fail);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('dispatches requestDeleteBadge and receiveDeleteBadgeError for error response', done => {
|
||||||
|
endpointMock.replyOnce(() => {
|
||||||
|
expect(dispatch.calls.allArgs()).toEqual([['requestDeleteBadge', badgeId]]);
|
||||||
|
dispatch.calls.reset();
|
||||||
|
return [500, ''];
|
||||||
|
});
|
||||||
|
|
||||||
|
actions
|
||||||
|
.deleteBadge({ state, dispatch }, { id: badgeId })
|
||||||
|
.then(() => done.fail('Expected Ajax call to fail!'))
|
||||||
|
.catch(() => {
|
||||||
|
expect(dispatch.calls.allArgs()).toEqual([['receiveDeleteBadgeError', badgeId]]);
|
||||||
|
})
|
||||||
|
.then(done)
|
||||||
|
.catch(done.fail);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('editBadge', () => {
|
||||||
|
it('commits START_EDITING', done => {
|
||||||
|
const dummyBadge = createDummyBadge();
|
||||||
|
testAction(
|
||||||
|
actions.editBadge,
|
||||||
|
dummyBadge,
|
||||||
|
state,
|
||||||
|
[{ type: mutationTypes.START_EDITING, payload: dummyBadge }],
|
||||||
|
[],
|
||||||
|
done,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('requestLoadBadges', () => {
|
||||||
|
it('commits REQUEST_LOAD_BADGES', done => {
|
||||||
|
const dummyData = 'this is not real data';
|
||||||
|
testAction(
|
||||||
|
actions.requestLoadBadges,
|
||||||
|
dummyData,
|
||||||
|
state,
|
||||||
|
[{ type: mutationTypes.REQUEST_LOAD_BADGES, payload: dummyData }],
|
||||||
|
[],
|
||||||
|
done,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('receiveLoadBadges', () => {
|
||||||
|
it('commits RECEIVE_LOAD_BADGES', done => {
|
||||||
|
const badges = dummyBadges;
|
||||||
|
testAction(
|
||||||
|
actions.receiveLoadBadges,
|
||||||
|
badges,
|
||||||
|
state,
|
||||||
|
[{ type: mutationTypes.RECEIVE_LOAD_BADGES, payload: badges }],
|
||||||
|
[],
|
||||||
|
done,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('receiveLoadBadgesError', () => {
|
||||||
|
it('commits RECEIVE_LOAD_BADGES_ERROR', done => {
|
||||||
|
testAction(
|
||||||
|
actions.receiveLoadBadgesError,
|
||||||
|
null,
|
||||||
|
state,
|
||||||
|
[{ type: mutationTypes.RECEIVE_LOAD_BADGES_ERROR }],
|
||||||
|
[],
|
||||||
|
done,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('loadBadges', () => {
|
||||||
|
let dispatch;
|
||||||
|
let endpointMock;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
endpointMock = axiosMock.onGet(dummyEndpointUrl);
|
||||||
|
dispatch = jasmine.createSpy('dispatch');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('dispatches requestLoadBadges and receiveLoadBadges for successful response', done => {
|
||||||
|
const dummyData = 'this is just some data';
|
||||||
|
const dummyReponse = [
|
||||||
|
createDummyBadgeResponse(),
|
||||||
|
createDummyBadgeResponse(),
|
||||||
|
createDummyBadgeResponse(),
|
||||||
|
];
|
||||||
|
endpointMock.replyOnce(() => {
|
||||||
|
expect(dispatch.calls.allArgs()).toEqual([['requestLoadBadges', dummyData]]);
|
||||||
|
dispatch.calls.reset();
|
||||||
|
return [200, dummyReponse];
|
||||||
|
});
|
||||||
|
|
||||||
|
actions
|
||||||
|
.loadBadges({ state, dispatch }, dummyData)
|
||||||
|
.then(() => {
|
||||||
|
const badges = dummyReponse.map(transformBackendBadge);
|
||||||
|
expect(dispatch.calls.allArgs()).toEqual([['receiveLoadBadges', badges]]);
|
||||||
|
})
|
||||||
|
.then(done)
|
||||||
|
.catch(done.fail);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('dispatches requestLoadBadges and receiveLoadBadgesError for error response', done => {
|
||||||
|
const dummyData = 'this is just some data';
|
||||||
|
endpointMock.replyOnce(() => {
|
||||||
|
expect(dispatch.calls.allArgs()).toEqual([['requestLoadBadges', dummyData]]);
|
||||||
|
dispatch.calls.reset();
|
||||||
|
return [500, ''];
|
||||||
|
});
|
||||||
|
|
||||||
|
actions
|
||||||
|
.loadBadges({ state, dispatch }, dummyData)
|
||||||
|
.then(() => done.fail('Expected Ajax call to fail!'))
|
||||||
|
.catch(() => {
|
||||||
|
expect(dispatch.calls.allArgs()).toEqual([['receiveLoadBadgesError']]);
|
||||||
|
})
|
||||||
|
.then(done)
|
||||||
|
.catch(done.fail);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('requestRenderedBadge', () => {
|
||||||
|
it('commits REQUEST_RENDERED_BADGE', done => {
|
||||||
|
testAction(
|
||||||
|
actions.requestRenderedBadge,
|
||||||
|
null,
|
||||||
|
state,
|
||||||
|
[{ type: mutationTypes.REQUEST_RENDERED_BADGE }],
|
||||||
|
[],
|
||||||
|
done,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('receiveRenderedBadge', () => {
|
||||||
|
it('commits RECEIVE_RENDERED_BADGE', done => {
|
||||||
|
const dummyBadge = createDummyBadge();
|
||||||
|
testAction(
|
||||||
|
actions.receiveRenderedBadge,
|
||||||
|
dummyBadge,
|
||||||
|
state,
|
||||||
|
[{ type: mutationTypes.RECEIVE_RENDERED_BADGE, payload: dummyBadge }],
|
||||||
|
[],
|
||||||
|
done,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('receiveRenderedBadgeError', () => {
|
||||||
|
it('commits RECEIVE_RENDERED_BADGE_ERROR', done => {
|
||||||
|
testAction(
|
||||||
|
actions.receiveRenderedBadgeError,
|
||||||
|
null,
|
||||||
|
state,
|
||||||
|
[{ type: mutationTypes.RECEIVE_RENDERED_BADGE_ERROR }],
|
||||||
|
[],
|
||||||
|
done,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('renderBadge', () => {
|
||||||
|
let dispatch;
|
||||||
|
let endpointMock;
|
||||||
|
let badgeInForm;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
badgeInForm = createDummyBadge();
|
||||||
|
state = {
|
||||||
|
...state,
|
||||||
|
badgeInAddForm: badgeInForm,
|
||||||
|
};
|
||||||
|
const urlParameters = [
|
||||||
|
`link_url=${encodeURIComponent(badgeInForm.linkUrl)}`,
|
||||||
|
`image_url=${encodeURIComponent(badgeInForm.imageUrl)}`,
|
||||||
|
].join('&');
|
||||||
|
endpointMock = axiosMock.onGet(`${dummyEndpointUrl}/render?${urlParameters}`);
|
||||||
|
dispatch = jasmine.createSpy('dispatch');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns immediately if imageUrl is empty', done => {
|
||||||
|
spyOn(axios, 'get');
|
||||||
|
badgeInForm.imageUrl = '';
|
||||||
|
|
||||||
|
actions
|
||||||
|
.renderBadge({ state, dispatch })
|
||||||
|
.then(() => {
|
||||||
|
expect(axios.get).not.toHaveBeenCalled();
|
||||||
|
})
|
||||||
|
.then(done)
|
||||||
|
.catch(done.fail);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns immediately if linkUrl is empty', done => {
|
||||||
|
spyOn(axios, 'get');
|
||||||
|
badgeInForm.linkUrl = '';
|
||||||
|
|
||||||
|
actions
|
||||||
|
.renderBadge({ state, dispatch })
|
||||||
|
.then(() => {
|
||||||
|
expect(axios.get).not.toHaveBeenCalled();
|
||||||
|
})
|
||||||
|
.then(done)
|
||||||
|
.catch(done.fail);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('escapes user input', done => {
|
||||||
|
spyOn(axios, 'get').and.callFake(() => Promise.resolve({ data: createDummyBadgeResponse() }));
|
||||||
|
badgeInForm.imageUrl = '&make-sandwhich=true';
|
||||||
|
badgeInForm.linkUrl = '<script>I am dangerous!</script>';
|
||||||
|
|
||||||
|
actions
|
||||||
|
.renderBadge({ state, dispatch })
|
||||||
|
.then(() => {
|
||||||
|
expect(axios.get.calls.count()).toBe(1);
|
||||||
|
const url = axios.get.calls.argsFor(0)[0];
|
||||||
|
expect(url).toMatch(`^${dummyEndpointUrl}/render?`);
|
||||||
|
expect(url).toMatch('\\?link_url=%3Cscript%3EI%20am%20dangerous!%3C%2Fscript%3E&');
|
||||||
|
expect(url).toMatch('&image_url=%26make-sandwhich%3Dtrue$');
|
||||||
|
})
|
||||||
|
.then(done)
|
||||||
|
.catch(done.fail);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('dispatches requestRenderedBadge and receiveRenderedBadge for successful response', done => {
|
||||||
|
const dummyReponse = createDummyBadgeResponse();
|
||||||
|
endpointMock.replyOnce(() => {
|
||||||
|
expect(dispatch.calls.allArgs()).toEqual([['requestRenderedBadge']]);
|
||||||
|
dispatch.calls.reset();
|
||||||
|
return [200, dummyReponse];
|
||||||
|
});
|
||||||
|
|
||||||
|
actions
|
||||||
|
.renderBadge({ state, dispatch })
|
||||||
|
.then(() => {
|
||||||
|
const renderedBadge = transformBackendBadge(dummyReponse);
|
||||||
|
expect(dispatch.calls.allArgs()).toEqual([['receiveRenderedBadge', renderedBadge]]);
|
||||||
|
})
|
||||||
|
.then(done)
|
||||||
|
.catch(done.fail);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('dispatches requestRenderedBadge and receiveRenderedBadgeError for error response', done => {
|
||||||
|
endpointMock.replyOnce(() => {
|
||||||
|
expect(dispatch.calls.allArgs()).toEqual([['requestRenderedBadge']]);
|
||||||
|
dispatch.calls.reset();
|
||||||
|
return [500, ''];
|
||||||
|
});
|
||||||
|
|
||||||
|
actions
|
||||||
|
.renderBadge({ state, dispatch })
|
||||||
|
.then(() => done.fail('Expected Ajax call to fail!'))
|
||||||
|
.catch(() => {
|
||||||
|
expect(dispatch.calls.allArgs()).toEqual([['receiveRenderedBadgeError']]);
|
||||||
|
})
|
||||||
|
.then(done)
|
||||||
|
.catch(done.fail);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('requestUpdatedBadge', () => {
|
||||||
|
it('commits REQUEST_UPDATED_BADGE', done => {
|
||||||
|
testAction(
|
||||||
|
actions.requestUpdatedBadge,
|
||||||
|
null,
|
||||||
|
state,
|
||||||
|
[{ type: mutationTypes.REQUEST_UPDATED_BADGE }],
|
||||||
|
[],
|
||||||
|
done,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('receiveUpdatedBadge', () => {
|
||||||
|
it('commits RECEIVE_UPDATED_BADGE', done => {
|
||||||
|
const updatedBadge = createDummyBadge();
|
||||||
|
testAction(
|
||||||
|
actions.receiveUpdatedBadge,
|
||||||
|
updatedBadge,
|
||||||
|
state,
|
||||||
|
[{ type: mutationTypes.RECEIVE_UPDATED_BADGE, payload: updatedBadge }],
|
||||||
|
[],
|
||||||
|
done,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('receiveUpdatedBadgeError', () => {
|
||||||
|
it('commits RECEIVE_UPDATED_BADGE_ERROR', done => {
|
||||||
|
testAction(
|
||||||
|
actions.receiveUpdatedBadgeError,
|
||||||
|
null,
|
||||||
|
state,
|
||||||
|
[{ type: mutationTypes.RECEIVE_UPDATED_BADGE_ERROR }],
|
||||||
|
[],
|
||||||
|
done,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('saveBadge', () => {
|
||||||
|
let badgeInEditForm;
|
||||||
|
let dispatch;
|
||||||
|
let endpointMock;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
badgeInEditForm = createDummyBadge();
|
||||||
|
state = {
|
||||||
|
...state,
|
||||||
|
badgeInEditForm,
|
||||||
|
};
|
||||||
|
endpointMock = axiosMock.onPut(`${dummyEndpointUrl}/${badgeInEditForm.id}`);
|
||||||
|
dispatch = jasmine.createSpy('dispatch');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('dispatches requestUpdatedBadge and receiveUpdatedBadge for successful response', done => {
|
||||||
|
const dummyResponse = createDummyBadgeResponse();
|
||||||
|
|
||||||
|
endpointMock.replyOnce(req => {
|
||||||
|
expect(req.data).toBe(
|
||||||
|
JSON.stringify({
|
||||||
|
image_url: badgeInEditForm.imageUrl,
|
||||||
|
link_url: badgeInEditForm.linkUrl,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(dispatch.calls.allArgs()).toEqual([['requestUpdatedBadge']]);
|
||||||
|
dispatch.calls.reset();
|
||||||
|
return [200, dummyResponse];
|
||||||
|
});
|
||||||
|
|
||||||
|
const updatedBadge = transformBackendBadge(dummyResponse);
|
||||||
|
actions
|
||||||
|
.saveBadge({ state, dispatch })
|
||||||
|
.then(() => {
|
||||||
|
expect(dispatch.calls.allArgs()).toEqual([['receiveUpdatedBadge', updatedBadge]]);
|
||||||
|
})
|
||||||
|
.then(done)
|
||||||
|
.catch(done.fail);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('dispatches requestUpdatedBadge and receiveUpdatedBadgeError for error response', done => {
|
||||||
|
endpointMock.replyOnce(req => {
|
||||||
|
expect(req.data).toBe(
|
||||||
|
JSON.stringify({
|
||||||
|
image_url: badgeInEditForm.imageUrl,
|
||||||
|
link_url: badgeInEditForm.linkUrl,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(dispatch.calls.allArgs()).toEqual([['requestUpdatedBadge']]);
|
||||||
|
dispatch.calls.reset();
|
||||||
|
return [500, ''];
|
||||||
|
});
|
||||||
|
|
||||||
|
actions
|
||||||
|
.saveBadge({ state, dispatch })
|
||||||
|
.then(() => done.fail('Expected Ajax call to fail!'))
|
||||||
|
.catch(() => {
|
||||||
|
expect(dispatch.calls.allArgs()).toEqual([['receiveUpdatedBadgeError']]);
|
||||||
|
})
|
||||||
|
.then(done)
|
||||||
|
.catch(done.fail);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('stopEditing', () => {
|
||||||
|
it('commits STOP_EDITING', done => {
|
||||||
|
testAction(
|
||||||
|
actions.stopEditing,
|
||||||
|
null,
|
||||||
|
state,
|
||||||
|
[{ type: mutationTypes.STOP_EDITING }],
|
||||||
|
[],
|
||||||
|
done,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('updateBadgeInForm', () => {
|
||||||
|
it('commits UPDATE_BADGE_IN_FORM', done => {
|
||||||
|
const dummyBadge = createDummyBadge();
|
||||||
|
testAction(
|
||||||
|
actions.updateBadgeInForm,
|
||||||
|
dummyBadge,
|
||||||
|
state,
|
||||||
|
[{ type: mutationTypes.UPDATE_BADGE_IN_FORM, payload: dummyBadge }],
|
||||||
|
[],
|
||||||
|
done,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('updateBadgeInModal', () => {
|
||||||
|
it('commits UPDATE_BADGE_IN_MODAL', done => {
|
||||||
|
const dummyBadge = createDummyBadge();
|
||||||
|
testAction(
|
||||||
|
actions.updateBadgeInModal,
|
||||||
|
dummyBadge,
|
||||||
|
state,
|
||||||
|
[{ type: mutationTypes.UPDATE_BADGE_IN_MODAL, payload: dummyBadge }],
|
||||||
|
[],
|
||||||
|
done,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,418 @@
|
||||||
|
import { GROUP_BADGE, PROJECT_BADGE } from '~/badges/constants';
|
||||||
|
import store from '~/badges/store';
|
||||||
|
import types from '~/badges/store/mutation_types';
|
||||||
|
import createState from '~/badges/store/state';
|
||||||
|
import { createDummyBadge } from '../dummy_badge';
|
||||||
|
|
||||||
|
describe('Badges store mutations', () => {
|
||||||
|
let dummyBadge;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
dummyBadge = createDummyBadge();
|
||||||
|
store.replaceState(createState());
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('RECEIVE_DELETE_BADGE', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const badges = [
|
||||||
|
{ ...dummyBadge, id: dummyBadge.id - 1 },
|
||||||
|
dummyBadge,
|
||||||
|
{ ...dummyBadge, id: dummyBadge.id + 1 },
|
||||||
|
];
|
||||||
|
|
||||||
|
store.replaceState({
|
||||||
|
...store.state,
|
||||||
|
badges,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('removes deleted badge', () => {
|
||||||
|
const badgeCount = store.state.badges.length;
|
||||||
|
|
||||||
|
store.commit(types.RECEIVE_DELETE_BADGE, dummyBadge.id);
|
||||||
|
|
||||||
|
expect(store.state.badges.length).toBe(badgeCount - 1);
|
||||||
|
expect(store.state.badges.indexOf(dummyBadge)).toBe(-1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('RECEIVE_DELETE_BADGE_ERROR', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const badges = [
|
||||||
|
{ ...dummyBadge, id: dummyBadge.id - 1, isDeleting: false },
|
||||||
|
{ ...dummyBadge, isDeleting: true },
|
||||||
|
{ ...dummyBadge, id: dummyBadge.id + 1, isDeleting: true },
|
||||||
|
];
|
||||||
|
|
||||||
|
store.replaceState({
|
||||||
|
...store.state,
|
||||||
|
badges,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets isDeleting to false', () => {
|
||||||
|
const badgeCount = store.state.badges.length;
|
||||||
|
|
||||||
|
store.commit(types.RECEIVE_DELETE_BADGE_ERROR, dummyBadge.id);
|
||||||
|
|
||||||
|
expect(store.state.badges.length).toBe(badgeCount);
|
||||||
|
expect(store.state.badges[0].isDeleting).toBe(false);
|
||||||
|
expect(store.state.badges[1].isDeleting).toBe(false);
|
||||||
|
expect(store.state.badges[2].isDeleting).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('RECEIVE_LOAD_BADGES', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
store.replaceState({
|
||||||
|
...store.state,
|
||||||
|
isLoading: 'not false',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets badges and isLoading to false', () => {
|
||||||
|
const badges = [createDummyBadge()];
|
||||||
|
store.commit(types.RECEIVE_LOAD_BADGES, badges);
|
||||||
|
|
||||||
|
expect(store.state.isLoading).toBe(false);
|
||||||
|
expect(store.state.badges).toBe(badges);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('RECEIVE_LOAD_BADGES_ERROR', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
store.replaceState({
|
||||||
|
...store.state,
|
||||||
|
isLoading: 'not false',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets isLoading to false', () => {
|
||||||
|
store.commit(types.RECEIVE_LOAD_BADGES_ERROR);
|
||||||
|
|
||||||
|
expect(store.state.isLoading).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('RECEIVE_NEW_BADGE', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const badges = [
|
||||||
|
{ ...dummyBadge, id: dummyBadge.id - 1, kind: GROUP_BADGE },
|
||||||
|
{ ...dummyBadge, id: dummyBadge.id + 1, kind: GROUP_BADGE },
|
||||||
|
{ ...dummyBadge, id: dummyBadge.id - 1, kind: PROJECT_BADGE },
|
||||||
|
{ ...dummyBadge, id: dummyBadge.id + 1, kind: PROJECT_BADGE },
|
||||||
|
];
|
||||||
|
store.replaceState({
|
||||||
|
...store.state,
|
||||||
|
badgeInAddForm: createDummyBadge(),
|
||||||
|
badges,
|
||||||
|
isSaving: 'dummy value',
|
||||||
|
renderedBadge: createDummyBadge(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resets the add form', () => {
|
||||||
|
store.commit(types.RECEIVE_NEW_BADGE, dummyBadge);
|
||||||
|
|
||||||
|
expect(store.state.badgeInAddForm).toBe(null);
|
||||||
|
expect(store.state.isSaving).toBe(false);
|
||||||
|
expect(store.state.renderedBadge).toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('inserts group badge at correct position', () => {
|
||||||
|
const badgeCount = store.state.badges.length;
|
||||||
|
dummyBadge = { ...dummyBadge, kind: GROUP_BADGE };
|
||||||
|
|
||||||
|
store.commit(types.RECEIVE_NEW_BADGE, dummyBadge);
|
||||||
|
|
||||||
|
expect(store.state.badges.length).toBe(badgeCount + 1);
|
||||||
|
expect(store.state.badges.indexOf(dummyBadge)).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('inserts project badge at correct position', () => {
|
||||||
|
const badgeCount = store.state.badges.length;
|
||||||
|
dummyBadge = { ...dummyBadge, kind: PROJECT_BADGE };
|
||||||
|
|
||||||
|
store.commit(types.RECEIVE_NEW_BADGE, dummyBadge);
|
||||||
|
|
||||||
|
expect(store.state.badges.length).toBe(badgeCount + 1);
|
||||||
|
expect(store.state.badges.indexOf(dummyBadge)).toBe(3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('RECEIVE_NEW_BADGE_ERROR', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
store.replaceState({
|
||||||
|
...store.state,
|
||||||
|
isSaving: 'dummy value',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets isSaving to false', () => {
|
||||||
|
store.commit(types.RECEIVE_NEW_BADGE_ERROR);
|
||||||
|
|
||||||
|
expect(store.state.isSaving).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('RECEIVE_RENDERED_BADGE', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
store.replaceState({
|
||||||
|
...store.state,
|
||||||
|
isRendering: 'dummy value',
|
||||||
|
renderedBadge: 'dummy value',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets renderedBadge', () => {
|
||||||
|
store.commit(types.RECEIVE_RENDERED_BADGE, dummyBadge);
|
||||||
|
|
||||||
|
expect(store.state.isRendering).toBe(false);
|
||||||
|
expect(store.state.renderedBadge).toBe(dummyBadge);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('RECEIVE_RENDERED_BADGE_ERROR', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
store.replaceState({
|
||||||
|
...store.state,
|
||||||
|
isRendering: 'dummy value',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets isRendering to false', () => {
|
||||||
|
store.commit(types.RECEIVE_RENDERED_BADGE_ERROR);
|
||||||
|
|
||||||
|
expect(store.state.isRendering).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('RECEIVE_UPDATED_BADGE', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const badges = [
|
||||||
|
{ ...dummyBadge, id: dummyBadge.id - 1 },
|
||||||
|
dummyBadge,
|
||||||
|
{ ...dummyBadge, id: dummyBadge.id + 1 },
|
||||||
|
];
|
||||||
|
store.replaceState({
|
||||||
|
...store.state,
|
||||||
|
badgeInEditForm: createDummyBadge(),
|
||||||
|
badges,
|
||||||
|
isEditing: 'dummy value',
|
||||||
|
isSaving: 'dummy value',
|
||||||
|
renderedBadge: createDummyBadge(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resets the edit form', () => {
|
||||||
|
store.commit(types.RECEIVE_UPDATED_BADGE, dummyBadge);
|
||||||
|
|
||||||
|
expect(store.state.badgeInAddForm).toBe(null);
|
||||||
|
expect(store.state.isSaving).toBe(false);
|
||||||
|
expect(store.state.renderedBadge).toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('replaces the updated badge', () => {
|
||||||
|
const badgeCount = store.state.badges.length;
|
||||||
|
const badgeIndex = store.state.badges.indexOf(dummyBadge);
|
||||||
|
const newBadge = { id: dummyBadge.id, dummy: 'value' };
|
||||||
|
|
||||||
|
store.commit(types.RECEIVE_UPDATED_BADGE, newBadge);
|
||||||
|
|
||||||
|
expect(store.state.badges.length).toBe(badgeCount);
|
||||||
|
expect(store.state.badges[badgeIndex]).toBe(newBadge);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('RECEIVE_UPDATED_BADGE_ERROR', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
store.replaceState({
|
||||||
|
...store.state,
|
||||||
|
isSaving: 'dummy value',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets isSaving to false', () => {
|
||||||
|
store.commit(types.RECEIVE_NEW_BADGE_ERROR);
|
||||||
|
|
||||||
|
expect(store.state.isSaving).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('REQUEST_DELETE_BADGE', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const badges = [
|
||||||
|
{ ...dummyBadge, id: dummyBadge.id - 1, isDeleting: false },
|
||||||
|
{ ...dummyBadge, isDeleting: false },
|
||||||
|
{ ...dummyBadge, id: dummyBadge.id + 1, isDeleting: true },
|
||||||
|
];
|
||||||
|
|
||||||
|
store.replaceState({
|
||||||
|
...store.state,
|
||||||
|
badges,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets isDeleting to true', () => {
|
||||||
|
const badgeCount = store.state.badges.length;
|
||||||
|
|
||||||
|
store.commit(types.REQUEST_DELETE_BADGE, dummyBadge.id);
|
||||||
|
|
||||||
|
expect(store.state.badges.length).toBe(badgeCount);
|
||||||
|
expect(store.state.badges[0].isDeleting).toBe(false);
|
||||||
|
expect(store.state.badges[1].isDeleting).toBe(true);
|
||||||
|
expect(store.state.badges[2].isDeleting).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('REQUEST_LOAD_BADGES', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
store.replaceState({
|
||||||
|
...store.state,
|
||||||
|
apiEndpointUrl: 'some endpoint',
|
||||||
|
docsUrl: 'some url',
|
||||||
|
isLoading: 'dummy value',
|
||||||
|
kind: 'some kind',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets isLoading to true and initializes the store', () => {
|
||||||
|
const dummyData = {
|
||||||
|
apiEndpointUrl: 'dummy endpoint',
|
||||||
|
docsUrl: 'dummy url',
|
||||||
|
kind: 'dummy kind',
|
||||||
|
};
|
||||||
|
|
||||||
|
store.commit(types.REQUEST_LOAD_BADGES, dummyData);
|
||||||
|
|
||||||
|
expect(store.state.isLoading).toBe(true);
|
||||||
|
expect(store.state.apiEndpointUrl).toBe(dummyData.apiEndpointUrl);
|
||||||
|
expect(store.state.docsUrl).toBe(dummyData.docsUrl);
|
||||||
|
expect(store.state.kind).toBe(dummyData.kind);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('REQUEST_NEW_BADGE', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
store.replaceState({
|
||||||
|
...store.state,
|
||||||
|
isSaving: 'dummy value',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets isSaving to true', () => {
|
||||||
|
store.commit(types.REQUEST_NEW_BADGE);
|
||||||
|
|
||||||
|
expect(store.state.isSaving).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('REQUEST_RENDERED_BADGE', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
store.replaceState({
|
||||||
|
...store.state,
|
||||||
|
isRendering: 'dummy value',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets isRendering to true', () => {
|
||||||
|
store.commit(types.REQUEST_RENDERED_BADGE);
|
||||||
|
|
||||||
|
expect(store.state.isRendering).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('REQUEST_UPDATED_BADGE', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
store.replaceState({
|
||||||
|
...store.state,
|
||||||
|
isSaving: 'dummy value',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets isSaving to true', () => {
|
||||||
|
store.commit(types.REQUEST_NEW_BADGE);
|
||||||
|
|
||||||
|
expect(store.state.isSaving).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('START_EDITING', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
store.replaceState({
|
||||||
|
...store.state,
|
||||||
|
badgeInEditForm: 'dummy value',
|
||||||
|
isEditing: 'dummy value',
|
||||||
|
renderedBadge: 'dummy value',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('initializes the edit form', () => {
|
||||||
|
store.commit(types.START_EDITING, dummyBadge);
|
||||||
|
|
||||||
|
expect(store.state.isEditing).toBe(true);
|
||||||
|
expect(store.state.badgeInEditForm).toEqual(dummyBadge);
|
||||||
|
expect(store.state.renderedBadge).toEqual(dummyBadge);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('STOP_EDITING', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
store.replaceState({
|
||||||
|
...store.state,
|
||||||
|
badgeInEditForm: 'dummy value',
|
||||||
|
isEditing: 'dummy value',
|
||||||
|
renderedBadge: 'dummy value',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resets the edit form', () => {
|
||||||
|
store.commit(types.STOP_EDITING);
|
||||||
|
|
||||||
|
expect(store.state.isEditing).toBe(false);
|
||||||
|
expect(store.state.badgeInEditForm).toBe(null);
|
||||||
|
expect(store.state.renderedBadge).toBe(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('UPDATE_BADGE_IN_FORM', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
store.replaceState({
|
||||||
|
...store.state,
|
||||||
|
badgeInAddForm: 'dummy value',
|
||||||
|
badgeInEditForm: 'dummy value',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets badgeInEditForm if isEditing is true', () => {
|
||||||
|
store.state.isEditing = true;
|
||||||
|
|
||||||
|
store.commit(types.UPDATE_BADGE_IN_FORM, dummyBadge);
|
||||||
|
|
||||||
|
expect(store.state.badgeInEditForm).toBe(dummyBadge);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets badgeInAddForm if isEditing is false', () => {
|
||||||
|
store.state.isEditing = false;
|
||||||
|
|
||||||
|
store.commit(types.UPDATE_BADGE_IN_FORM, dummyBadge);
|
||||||
|
|
||||||
|
expect(store.state.badgeInAddForm).toBe(dummyBadge);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('UPDATE_BADGE_IN_MODAL', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
store.replaceState({
|
||||||
|
...store.state,
|
||||||
|
badgeInModal: 'dummy value',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets badgeInModal', () => {
|
||||||
|
store.commit(types.UPDATE_BADGE_IN_MODAL, dummyBadge);
|
||||||
|
|
||||||
|
expect(store.state.badgeInModal).toBe(dummyBadge);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 68 B |
|
|
@ -3,6 +3,12 @@ export const createComponentWithStore = (Component, store, propsData = {}) => ne
|
||||||
propsData,
|
propsData,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const mountComponentWithStore = (Component, { el, props, store }) =>
|
||||||
|
new Component({
|
||||||
|
store,
|
||||||
|
propsData: props || { },
|
||||||
|
}).$mount(el);
|
||||||
|
|
||||||
export default (Component, props = {}, el = null) => new Component({
|
export default (Component, props = {}, el = null) => new Component({
|
||||||
propsData: props,
|
propsData: props,
|
||||||
}).$mount(el);
|
}).$mount(el);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
export default {
|
||||||
|
toHaveSpriteIcon: () => ({
|
||||||
|
compare(element, iconName) {
|
||||||
|
if (!iconName) {
|
||||||
|
throw new Error('toHaveSpriteIcon is missing iconName argument!');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(element instanceof HTMLElement)) {
|
||||||
|
throw new Error(`${element} is not a DOM element!`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const iconReferences = [].slice.apply(element.querySelectorAll('svg use'));
|
||||||
|
const matchingIcon = iconReferences.find(reference => reference.getAttribute('xlink:href').endsWith(`#${iconName}`));
|
||||||
|
const result = {
|
||||||
|
pass: !!matchingIcon,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (result.pass) {
|
||||||
|
result.message = `${element.outerHTML} contains the sprite icon "${iconName}"!`;
|
||||||
|
} else {
|
||||||
|
result.message = `${element.outerHTML} does not contain the sprite icon "${iconName}"!`;
|
||||||
|
|
||||||
|
const existingIcons = iconReferences.map((reference) => {
|
||||||
|
const iconUrl = reference.getAttribute('xlink:href');
|
||||||
|
return `"${iconUrl.replace(/^.+#/, '')}"`;
|
||||||
|
});
|
||||||
|
if (existingIcons.length > 0) {
|
||||||
|
result.message += ` (only found ${existingIcons.join(',')})`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
@ -7,6 +7,9 @@ import Vue from 'vue';
|
||||||
import VueResource from 'vue-resource';
|
import VueResource from 'vue-resource';
|
||||||
|
|
||||||
import { getDefaultAdapter } from '~/lib/utils/axios_utils';
|
import { getDefaultAdapter } from '~/lib/utils/axios_utils';
|
||||||
|
import { FIXTURES_PATH, TEST_HOST } from './test_constants';
|
||||||
|
|
||||||
|
import customMatchers from './matchers';
|
||||||
|
|
||||||
const isHeadlessChrome = /\bHeadlessChrome\//.test(navigator.userAgent);
|
const isHeadlessChrome = /\bHeadlessChrome\//.test(navigator.userAgent);
|
||||||
Vue.config.devtools = !isHeadlessChrome;
|
Vue.config.devtools = !isHeadlessChrome;
|
||||||
|
|
@ -27,15 +30,17 @@ Vue.config.errorHandler = function (err) {
|
||||||
Vue.use(VueResource);
|
Vue.use(VueResource);
|
||||||
|
|
||||||
// enable test fixtures
|
// enable test fixtures
|
||||||
jasmine.getFixtures().fixturesPath = '/base/spec/javascripts/fixtures';
|
jasmine.getFixtures().fixturesPath = FIXTURES_PATH;
|
||||||
jasmine.getJSONFixtures().fixturesPath = '/base/spec/javascripts/fixtures';
|
jasmine.getJSONFixtures().fixturesPath = FIXTURES_PATH;
|
||||||
|
|
||||||
|
beforeAll(() => jasmine.addMatchers(customMatchers));
|
||||||
|
|
||||||
// globalize common libraries
|
// globalize common libraries
|
||||||
window.$ = window.jQuery = $;
|
window.$ = window.jQuery = $;
|
||||||
|
|
||||||
// stub expected globals
|
// stub expected globals
|
||||||
window.gl = window.gl || {};
|
window.gl = window.gl || {};
|
||||||
window.gl.TEST_HOST = 'http://test.host';
|
window.gl.TEST_HOST = TEST_HOST;
|
||||||
window.gon = window.gon || {};
|
window.gon = window.gon || {};
|
||||||
window.gon.test_env = true;
|
window.gon.test_env = true;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
export const FIXTURES_PATH = '/base/spec/javascripts/fixtures';
|
||||||
|
export const TEST_HOST = 'http://test.host';
|
||||||
|
|
||||||
|
export const DUMMY_IMAGE_URL = `${FIXTURES_PATH}/one_white_pixel.png`;
|
||||||
Loading…
Reference in New Issue