Commit 951e9a74 authored by Martin Wortschack's avatar Martin Wortschack Committed by Phil Hughes

Add app component

- Add specs for app component
- Add Vuex store and specs
- Mount app and setup store
- Add styles for popover
- Add tour titles and exit tour content
- Add empty tour parts
- Update PO file
- Add skeleton for action popover utils
parent 269c1a39
import { s__, sprintf } from '~/locale';
export const ONBOARDING_DISMISSED_COOKIE_NAME = 'onboarding_dismissed';
export const STORAGE_KEY = 'onboarding_state';
......@@ -8,6 +10,15 @@ export const AVAILABLE_TOURS = {
INVITE_COLLEAGUES_TOUR: 3,
};
export const TOUR_TITLES = [
{ id: AVAILABLE_TOURS.GUIDED_GITLAB_TOUR, title: s__('UserOnboardingTour|Guided GitLab Tour') },
{ id: AVAILABLE_TOURS.CREATE_PROJECT_TOUR, title: s__('UserOnboardingTour|Create a project') },
{
id: AVAILABLE_TOURS.INVITE_COLLEAGUES_TOUR,
title: s__('UserOnboardingTour|Invite colleagues'),
},
];
export const ONBOARDING_PROPS_DEFAULTS = {
tourKey: AVAILABLE_TOURS.GUIDED_GITLAB_TOUR,
lastStepIndex: -1,
......@@ -19,3 +30,17 @@ export const ACCEPTING_MR_LABEL_TEXT = 'Accepting merge requests';
export const LABEL_SEARCH_QUERY = `scope=all&state=opened&label_name[]=${encodeURIComponent(
ACCEPTING_MR_LABEL_TEXT,
)}`;
export const EXIT_TOUR_CONTENT = {
text: sprintf(
s__(
'UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right.',
),
{
emphasisStart: '<strong>',
emphasisEnd: '</strong>',
},
false,
),
buttons: [{ text: s__('UserOnboardingTour|Got it'), btnClass: 'btn-primary', exitTour: true }],
};
const renderPopover = () => {
// TODO: implementation will be added in a separate MR
};
const actionPopoverUtils = {
renderPopover,
};
export default actionPopoverUtils;
<script>
export default {};
import _ from 'underscore';
import { mapState, mapActions, mapGetters } from 'vuex';
import { redirectTo } from '~/lib/utils/url_utility';
import OnboardingHelper from './onboarding_helper.vue';
import actionPopoverUtils from './../action_popover_utils';
import eventHub from '../event_hub';
export default {
components: {
OnboardingHelper,
},
props: {
tourTitles: {
type: Array,
required: true,
},
exitTourContent: {
type: Object,
required: true,
},
goldenTanukiSvgPath: {
type: String,
required: true,
},
},
data() {
return {
showStepContent: false,
initialShowPopover: false,
dismissPopover: false,
};
},
computed: {
...mapState([
'projectName',
'tourKey',
'tourData',
'lastStepIndex',
'helpContentIndex',
'exitTour',
'dismissed',
]),
...mapGetters([
'stepIndex',
'stepContent',
'helpContent',
'totalTourPartSteps',
'percentageCompleted',
'actionPopover',
]),
helpContentData() {
if (this.showStepContent) {
return this.exitTour ? this.exitTourContent : this.helpContent;
}
return null;
},
completedSteps() {
return Math.max(this.lastStepIndex, 0);
},
},
mounted() {
this.init();
},
methods: {
...mapActions([
'setTourKey',
'setLastStepIndex',
'setHelpContentIndex',
'switchTourPart',
'setExitTour',
'setDismissed',
]),
init() {
// ensure we show help content on consecutive pages only
if (this.tourKey) {
const nextStepIndex = this.lastStepIndex + 1;
// show help content when the current was the last visited page (e.g., user navigates away and comes back to current page)
if (this.lastStepIndex === this.stepIndex) {
this.showStepContent = true;
this.initActionPopover();
// show help content when this is the upcoming page in the content list (otherwise don't show the help content)
// and update the lastStepIndex
} else if (nextStepIndex === this.stepIndex) {
this.setLastStepIndex(nextStepIndex);
this.showStepContent = true;
this.initActionPopover();
}
}
},
initActionPopover() {
if (this.actionPopover) {
const { selector, text, placement } = this.actionPopover;
// immediately show the action popover if there's not helpContent for this step
const showPopover = !this.helpContent && !_.isUndefined(selector);
actionPopoverUtils.renderPopover(selector, text, placement, showPopover);
}
},
showActionPopover() {
eventHub.$emit('onboardingHelper.showActionPopover');
},
hideActionPopover() {
eventHub.$emit('onboardingHelper.hideActionPopover');
},
handleRestartStep() {
this.showExitTourContent(false);
eventHub.$emit('onboardingHelper.hideActionPopover');
},
handleSkipStep() {
if (this.actionPopover) {
const { selector } = this.actionPopover;
const popoverEl = selector ? document.querySelector(selector) : null;
if (popoverEl) {
popoverEl.click();
}
}
},
handleClickPopoverButton(button) {
const { showExitTourContent, exitTour, redirectPath, nextPart, dismissPopover } = button;
const helpContentItems = this.stepContent.getHelpContent({ projectName: this.projectName });
const showNextContentItem =
helpContentItems.length > 1 && this.helpContentIndex < helpContentItems.length - 1;
// display exit tour content
if (showExitTourContent) {
this.showExitTourContent(true);
return;
}
// quit tour
if (exitTour) {
this.handleExitTour();
return;
}
// dismiss popover if necessary
if (_.isUndefined(dismissPopover) || dismissPopover === true) {
this.dismissPopover = true;
}
// redirect to redirectPath
if (redirectPath) {
redirectTo(redirectPath);
return;
}
// switch to the next tour part
if (!_.isUndefined(nextPart)) {
this.switchTourPart(nextPart);
this.initActionPopover();
return;
}
// switch to next content item
if (showNextContentItem) {
this.setHelpContentIndex(this.helpContentIndex + 1);
return;
}
// show action popover
this.showActionPopover();
},
showExitTourContent(showExitTour) {
this.dismissPopover = false;
this.setExitTour(showExitTour);
},
handleExitTour() {
this.hideActionPopover();
this.setDismissed(true);
// remove popover event handlers
eventHub.$emit('onboardingHelper.destroyActionPopover');
},
afterAppearHook() {
this.initialShowPopover = true;
},
},
};
</script>
<template>
<div></div>
<transition appear name="slide-in-fwd-bottom" @after-appear="afterAppearHook">
<onboarding-helper
v-if="!dismissed"
:tour-titles="tourTitles"
:active-tour="tourKey"
:completed-steps="completedSteps"
:help-content="helpContentData"
:percentage-completed="percentageCompleted"
:total-steps-for-tour="totalTourPartSteps"
:initial-show="initialShowPopover"
:dismiss-popover="dismissPopover"
:golden-tanuki-svg-path="goldenTanukiSvgPath"
@clickPopoverButton="handleClickPopoverButton"
@restartStep="handleRestartStep"
@skipStep="handleSkipStep"
@showExitTourContent="showExitTourContent"
@exitTour="handleExitTour"
/>
</transition>
</template>
import Vue from 'vue';
import { mapActions } from 'vuex';
import OnboardingApp from './components/app.vue';
import createStore from './store';
import onboardingUtils from './../utils';
import { TOUR_TITLES, EXIT_TOUR_CONTENT } from './../constants';
import TOUR_PARTS from './../tour_parts';
export default function() {
const el = document.getElementById('js-onboarding-helper');
......@@ -8,15 +13,45 @@ export default function() {
return false;
}
const { projectFullPath, projectName } = el.dataset;
const tourData = onboardingUtils.getOnboardingLocalStorageState();
const url = window.location.href;
if (!tourData) {
return false;
}
const { tourKey, lastStepIndex, createdProjectPath } = tourData;
const store = createStore();
return new Vue({
el,
store,
components: {
OnboardingApp,
},
created() {
if (tourKey) {
this.setInitialData({
url,
projectFullPath,
projectName,
tourData: TOUR_PARTS,
tourKey,
lastStepIndex,
createdProjectPath,
});
}
},
methods: {
...mapActions(['setInitialData']),
},
render(h) {
return h(OnboardingApp, {
props: {},
props: {
tourTitles: TOUR_TITLES,
exitTourContent: EXIT_TOUR_CONTENT,
},
});
},
});
......
import Cookies from 'js-cookie';
import * as types from './mutation_types';
import { ONBOARDING_DISMISSED_COOKIE_NAME } from '../../constants';
import onboardingUtils from '../../utils';
export const setInitialData = ({ commit }, data) => {
commit(types.SET_INITIAL_DATA, data);
};
export const setTourKey = ({ commit }, tourKey) => {
commit(types.SET_TOUR_KEY, tourKey);
onboardingUtils.updateLocalStorage({ tourKey });
};
export const setLastStepIndex = ({ commit }, lastStepIndex) => {
commit(types.SET_LAST_STEP_INDEX, lastStepIndex);
onboardingUtils.updateLocalStorage({ lastStepIndex });
};
export const setHelpContentIndex = ({ commit }, helpContentIndex) => {
commit(types.SET_HELP_CONTENT_INDEX, helpContentIndex);
};
export const switchTourPart = ({ dispatch }, tourKey) => {
dispatch('setTourKey', tourKey);
dispatch('setLastStepIndex', 0);
dispatch('setHelpContentIndex', 0);
};
export const setExitTour = ({ commit }, exitTour) => {
commit(types.SET_EXIT_TOUR, exitTour);
};
export const setDismissed = ({ commit }, dismissed) => {
commit(types.SET_DISMISSED, dismissed);
Cookies.set(ONBOARDING_DISMISSED_COOKIE_NAME, dismissed);
};
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
export const stepIndex = state => {
const { tourData, tourKey, url, projectFullPath, createdProjectPath } = state;
let idx = -1;
if (tourData && tourData[tourKey] && url !== '') {
idx = tourData[tourKey].findIndex(item =>
item.forUrl({ projectFullPath, createdProjectPath }).test(state.url),
);
}
return idx !== -1 ? idx : null;
};
export const stepContent = (state, getters) => {
const { tourData, tourKey } = state;
if (!tourData || !tourData[tourKey] || getters.stepIndex === null) {
return null;
}
return tourData[tourKey][getters.stepIndex] ? tourData[tourKey][getters.stepIndex] : null;
};
export const helpContent = (state, getters) => {
const { projectName, helpContentIndex } = state;
if (getters.stepContent === null) {
return null;
}
return getters.stepContent.getHelpContent
? getters.stepContent.getHelpContent({ projectName })[helpContentIndex]
: null;
};
export const totalTourPartSteps = state => {
if (state.tourData && state.tourKey && state.tourData[state.tourKey]) {
return state.tourData[state.tourKey].length;
}
return 0;
};
export const percentageCompleted = (state, getters) => {
const { tourData, tourKey } = state;
if (getters.stepIndex === null || !tourData || !tourData[tourKey]) {
return 0;
}
return tourData[tourKey][getters.stepIndex]
? tourData[tourKey][getters.stepIndex].percentageCompleted
: 0;
};
export const actionPopover = (state, getters) =>
getters.stepContent !== null && getters.stepContent.actionPopover
? getters.stepContent.actionPopover
: null;
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
import Vue from 'vue';
import Vuex from 'vuex';
import * as actions from './actions';
import * as getters from './getters';
import mutations from './mutations';
import state from './state';
Vue.use(Vuex);
const createStore = () =>
new Vuex.Store({
actions,
getters,
mutations,
state: state(),
});
export default createStore;
export const SET_INITIAL_DATA = 'SET_INITIAL_DATA';
export const SET_TOUR_KEY = 'SET_TOUR_KEY';
export const SET_LAST_STEP_INDEX = 'SET_LAST_STEP_INDEX';
export const SET_HELP_CONTENT_INDEX = 'SET_HELP_CONTENT_INDEX';
export const SET_EXIT_TOUR = 'SET_EXIT_TOUR';
export const SET_DISMISSED = 'SET_DISMISSED';
import * as types from './mutation_types';
export default {
[types.SET_INITIAL_DATA](state, payload) {
Object.assign(state, payload);
},
[types.SET_TOUR_KEY](state, payload) {
state.tourKey = payload;
},
[types.SET_LAST_STEP_INDEX](state, payload) {
state.lastStepIndex = payload;
},
[types.SET_HELP_CONTENT_INDEX](state, payload) {
state.helpContentIndex = payload;
},
[types.SET_EXIT_TOUR](state, payload) {
state.exitTour = payload;
},
[types.SET_DISMISSED](state, payload) {
state.dismissed = payload;
},
};
import { AVAILABLE_TOURS } from '../../constants';
export default () => ({
url: '',
projectFullPath: '',
projectName: '',
tourData: [],
tourKey: AVAILABLE_TOURS.GUIDED_GITLAB_TOUR,
helpContentIndex: 0,
lastStepIndex: -1,
dismissed: false,
createdProjectPath: '',
exitTour: false,
});
import Vue from 'vue';
import OnboardingHelperApp from 'ee/onboarding/onboarding_helper/components/app.vue';
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import eventHub from 'ee/onboarding/onboarding_helper/event_hub';
import createStore from 'ee/onboarding/onboarding_helper/store';
import actionPopoverUtils from 'ee/onboarding/onboarding_helper/action_popover_utils';
import { mockTourData } from '../mock_data';
describe('User onboarding helper app', () => {
let vm;
let store;
const initialData = {
url: 'http://gitlab-org/gitlab-test/foo',
projectFullPath: 'http://gitlab-org/gitlab-test',
projectName: 'Mock Project',
tourData: mockTourData,
tourKey: 1,
lastStepIndex: -1,
createdProjectPath: '',
};
const tourTitles = [{ id: 1, title: 'First tour' }, { id: 2, title: 'Second tour' }];
const exitTourContent = {
text: 'exit tour content',
buttons: [{ text: 'OK', btnClass: 'btn-primary' }],
};
const defaultProps = {
tourTitles,
exitTourContent,
goldenTanukiSvgPath: 'illustrations/golden_tanuki.svg',
};
const createComponent = ({ props = defaultProps } = {}) => {
const Component = Vue.extend(OnboardingHelperApp);
store = createStore();
store.dispatch('setInitialData', initialData);
return mountComponentWithStore(Component, { props, store });
};
beforeEach(() => {
vm = createComponent();
spyOn(vm, 'init');
vm.$mount();
});
afterEach(() => {
vm.$destroy();
});
describe('computed', () => {
describe('helpContentData', () => {
it('returns an object containing the help content data', () => {
const helpContent = mockTourData[initialData.tourKey][0].getHelpContent()[0];
expect(vm.helpContentData).toEqual(helpContent);
});
it('returns null if showStepContent is false', () => {
vm.showStepContent = false;
expect(vm.helpContentData).toBeNull();
});
it('returns an object containing exit tour content if exitTour is true', () => {
store.dispatch('setExitTour', true);
expect(vm.helpContentData).toEqual(exitTourContent);
});
});
describe('completedSteps', () => {
it('returns 3 if the lastStepIndex is 1', () => {
vm.$store.state.lastStepIndex = 3;
expect(vm.completedSteps).toBe(3);
});
it('returns 0 if the lastStepIndex is -1', () => {
vm.$store.state.lastStepIndex = -1;
expect(vm.completedSteps).toBe(0);
});
});
});
describe('mounted', () => {
it('calls the init method', () => {
expect(vm.init).toHaveBeenCalled();
expect(vm.showStepContent).toBe(true);
});
});
describe('methods', () => {
describe('initActionPopover', () => {
it('calls renderPopover with the correct data', () => {
spyOn(actionPopoverUtils, 'renderPopover');
const expected = {
selector: '.popup-trigger',
text: 'foo',
placement: 'top',
showPopover: false,
};
const { selector, text, placement, showPopover } = expected;
vm.initActionPopover();
expect(actionPopoverUtils.renderPopover).toHaveBeenCalledWith(
selector,
text,
placement,
showPopover,
);
});
it('calls renderPopover with showPopover=true if there is no helpContent data and no popover selector for the current url', () => {
spyOn(actionPopoverUtils, 'renderPopover');
vm.$store.state.url = 'http://gitlab-org/gitlab-test/xyz';
const expected = {
selector: null,
text: 'foo',
placement: 'top',
showPopover: true,
};
const { selector, text, placement, showPopover } = expected;
vm.initActionPopover();
expect(actionPopoverUtils.renderPopover).toHaveBeenCalledWith(
selector,
text,
placement,
showPopover,
);
});
});
describe('showActionPopover', () => {
it('emits the "onboardingHelper.showActionPopover" event', () => {
spyOn(eventHub, '$emit');
vm.showActionPopover();
expect(eventHub.$emit).toHaveBeenCalledWith('onboardingHelper.showActionPopover');
});
});
describe('hideActionPopover', () => {
it('emits the "onboardingHelper.hideActionPopover" event', () => {
spyOn(eventHub, '$emit');
vm.hideActionPopover();
expect(eventHub.$emit).toHaveBeenCalledWith('onboardingHelper.hideActionPopover');
});
});
describe('handleRestartStep', () => {
it('calls the "showExitTourContent" method', () => {
spyOn(vm, 'showExitTourContent');
vm.handleRestartStep();
expect(vm.showExitTourContent).toHaveBeenCalledWith(false);
});
it('emits the "onboardingHelper.hideActionPopover" event', () => {
spyOn(eventHub, '$emit');
vm.handleRestartStep();
expect(eventHub.$emit).toHaveBeenCalledWith('onboardingHelper.hideActionPopover');
});
});
describe('handleSkipStep', () => {
it('calls the click method on given popover selector if there is a stepContent', () => {
const {
actionPopover: { selector },
} = vm.stepContent;
const fakeLink = {
click: () => {},
};
spyOn(document, 'querySelector').and.returnValue(fakeLink);
spyOn(fakeLink, 'click');
vm.handleSkipStep();
expect(document.querySelector).toHaveBeenCalledWith(`${selector}`);
expect(fakeLink.click).toHaveBeenCalled();
});
});
describe('handleClickPopoverButton', () => {
it('shows the exitTour content', () => {
spyOn(vm, 'showExitTourContent');
const button = {
showExitTourContent: true,
};
vm.handleClickPopoverButton(button);
expect(vm.showExitTourContent).toHaveBeenCalledWith(true);
});
it('quits the tour', () => {
spyOn(vm, 'handleExitTour');
const button = {
exitTour: true,
};
vm.handleClickPopoverButton(button);
expect(vm.handleExitTour).toHaveBeenCalled();
});
it('sets dismissPopover to true when true/undefined on button config', () => {
let button = {
dismissPopover: true,
};
vm.handleClickPopoverButton(button);
expect(vm.dismissPopover).toBe(true);
button = {};
vm.handleClickPopoverButton(button);
expect(vm.dismissPopover).toBe(true);
});
it('does not set dismissPopover to true when false on button config', () => {
const button = {
dismissPopover: false,
};
vm.handleClickPopoverButton(button);
expect(vm.dismissPopover).toBe(false);
});
it('redirects to the redirectPath', () => {
const redirectSpy = spyOnDependency(OnboardingHelperApp, 'redirectTo');
const button = {
redirectPath: 'my-redirect/path',
};
vm.handleClickPopoverButton(button);
expect(redirectSpy).toHaveBeenCalledWith(button.redirectPath);
});
it('switches to the next tour part and calls initActionPopover', () => {
spyOn(vm.$store, 'dispatch');
spyOn(vm, 'initActionPopover');
const nextPart = 2;
const button = {
nextPart,
};
vm.handleClickPopoverButton(button);
expect(vm.$store.dispatch).toHaveBeenCalledWith('switchTourPart', nextPart);
expect(vm.initActionPopover).toHaveBeenCalled();
});
it('shows the next content item', () => {
spyOn(vm.$store, 'dispatch');
const button = {};
vm.$store.state.url = 'http://gitlab-org/gitlab-test/foo';
vm.$store.state.lastStepIndex = 0;
vm.handleClickPopoverButton(button);
expect(vm.$store.dispatch).toHaveBeenCalledWith('setHelpContentIndex', 1);
});
});
describe('showExitTourContent', () => {
it('sets the "dismissPopover" prop to false', () => {
vm.showExitTourContent(true);
expect(vm.dismissPopover).toBeFalsy();
});
it('calls the "setExitTour" method', () => {
spyOn(vm.$store, 'dispatch');
vm.showExitTourContent(true);
expect(vm.$store.dispatch).toHaveBeenCalledWith('setExitTour', true);
});
});
describe('handleExitTour', () => {
it('calls the "hideActionPopover" method', () => {
spyOn(vm, 'hideActionPopover');
vm.handleExitTour();
expect(vm.hideActionPopover).toHaveBeenCalled();
});
it('calls the "setDismissed" method with true', () => {
spyOn(vm.$store, 'dispatch');
vm.handleExitTour();
expect(vm.$store.dispatch).toHaveBeenCalledWith('setDismissed', true);
});
it('emits the "onboardingHelper.destroyActionPopover" event', () => {
spyOn(eventHub, '$emit');
vm.handleExitTour();
expect(eventHub.$emit).toHaveBeenCalledWith('onboardingHelper.destroyActionPopover');
});
});
});
});
export const mockTourData = {
1: [
{
forUrl: ({ projectFullPath }) => new RegExp(`${projectFullPath}/foo$`, ''),
getHelpContent: () => [
{
text: 'foo',
buttons: [{ text: 'button', btnClass: 'btn-primary' }],
},
{
text: 'next content item',
buttons: [{ text: 'button', btnClass: 'btn-primary' }],
},
],
actionPopover: {
selector: '.popup-trigger',
text: 'foo',
placement: 'top',
},
percentageCompleted: 10,
},
{
forUrl: ({ projectFullPath }) => new RegExp(`${projectFullPath}/foo/bar$`, ''),
getHelpContent: ({ projectName }) => [
{
text: `This is the ${projectName}`,
buttons: [{ text: 'button', btnClass: 'btn-primary' }],
},
],
actionPopover: {
selector: '',
text: 'bar',
},
percentageCompleted: 20,
},
{
forUrl: ({ projectFullPath }) => new RegExp(`${projectFullPath}/xyz`, ''),
getHelpContent: null,
actionPopover: {
selector: null,
text: 'foo',
placement: 'top',
},
percentageCompleted: 30,
},
],
};
export const mockData = {
url: 'http://gitlab-org/gitlab-test/foo',
projectFullPath: 'http://gitlab-org/gitlab-test',
projectName: 'Mock Project',
tourData: mockTourData,
tourKey: 1,
helpContentIndex: 0,
lastStepIndex: -1,
createdProjectPath: '',
};
import Cookies from 'js-cookie';
import testAction from 'spec/helpers/vuex_action_helper';
import createState from 'ee/onboarding/onboarding_helper/store/state';
import * as types from 'ee/onboarding/onboarding_helper/store/mutation_types';
import {
setInitialData,
setTourKey,
setLastStepIndex,
setHelpContentIndex,
switchTourPart,
setExitTour,
setDismissed,
} from 'ee/onboarding/onboarding_helper/store/actions';
import { ONBOARDING_DISMISSED_COOKIE_NAME } from 'ee/onboarding/constants';
import onboardingUtils from 'ee/onboarding/utils';
import mockData from './../mock_data';
describe('User onboarding helper store actions', () => {
let state;
beforeEach(() => {
state = createState();
});
describe('setInitialData', () => {
it(`commits ${types.SET_INITIAL_DATA} mutation`, done => {
const initialData = mockData;
testAction(
setInitialData,
initialData,
state,
[{ type: types.SET_INITIAL_DATA, payload: initialData }],
[],
done,
);
});
});
describe('setTourKey', () => {
beforeEach(() => {
spyOn(onboardingUtils, 'updateLocalStorage').and.stub();
});
it(`commits ${types.SET_TOUR_KEY} mutation`, done => {
const tourKey = 2;
testAction(
setTourKey,
tourKey,
state,
[{ type: types.SET_TOUR_KEY, payload: tourKey }],
[],
done,
);
});
it('updates localStorage with the tourKey', () => {
const tourKey = 2;
setTourKey({ commit() {} }, tourKey);
expect(onboardingUtils.updateLocalStorage).toHaveBeenCalledWith({ tourKey });
});
});
describe('setLastStepIndex', () => {
beforeEach(() => {
spyOn(onboardingUtils, 'updateLocalStorage').and.stub();
});
it(`commits ${types.SET_LAST_STEP_INDEX} mutation`, done => {
const lastStepIndex = 1;
testAction(
setLastStepIndex,
lastStepIndex,
state,
[{ type: types.SET_LAST_STEP_INDEX, payload: lastStepIndex }],
[],
done,
);
});
it('updates localStorage with the lastStepIndex', () => {
const lastStepIndex = 1;
setLastStepIndex({ commit() {} }, lastStepIndex);
expect(onboardingUtils.updateLocalStorage).toHaveBeenCalledWith({ lastStepIndex });
});
});
describe('setHelpContentIndex', () => {
it(`commits ${types.SET_HELP_CONTENT_INDEX} mutation`, done => {
const helpContentIndex = 1;
testAction(
setHelpContentIndex,
helpContentIndex,
state,
[{ type: types.SET_HELP_CONTENT_INDEX, payload: helpContentIndex }],
[],
done,
);
});
});
describe('switchTourPart', () => {
it('should dispatch setTourKey, setLastStepIndex and', done => {
const nextPart = 2;
testAction(
switchTourPart,
nextPart,
state,
[],
[
{ type: 'setTourKey', payload: nextPart },
{ type: 'setLastStepIndex', payload: 0 },
{ type: 'setHelpContentIndex', payload: 0 },
],
done,
);
});
});
describe('setExitTour', () => {
it(`commits ${types.SET_EXIT_TOUR} mutation`, done => {
const exitTour = true;
testAction(
setExitTour,
exitTour,
state,
[{ type: types.SET_EXIT_TOUR, payload: exitTour }],
[],
done,
);
});
});
describe('setDismissed', () => {
it(`commits ${types.SET_DISMISSED} mutation`, done => {
const dismissed = true;
testAction(
setDismissed,
dismissed,
state,
[{ type: types.SET_DISMISSED, payload: dismissed }],
[],
() => {
setTimeout(() => {
expect(Cookies.get(ONBOARDING_DISMISSED_COOKIE_NAME)).toEqual(`${dismissed}`);
done();
}, 0);
},
);
});
});
});
import * as getters from 'ee/onboarding/onboarding_helper/store/getters';
import createStore from 'ee/onboarding/onboarding_helper/store/state';
import { mockTourData } from './../mock_data';
describe('User onboarding store getters', () => {
let localState;
beforeEach(() => {
localState = createStore();
localState.projectFullPath = 'http://gitlab-org/gitlab-test';
localState.tourData = mockTourData;
localState.tourKey = 1;
localState.url = 'http://gitlab-org/gitlab-test/foo/bar';
});
describe('stepIndex', () => {
it('returns the current step index if the url matches the data at a given tour key', () => {
expect(getters.stepIndex(localState)).toBe(1);
});
it('returns null if there is no tour data', () => {
localState.tourData = [];
expect(getters.stepIndex(localState)).toBe(null);
});
it('returns null if there is no tour key', () => {
localState.tourKey = null;
expect(getters.stepIndex(localState)).toBe(null);
});
it("returns null if the url doesn't match any data at a given tour key", () => {
localState.url = 'http://not-matching/url';
expect(getters.stepIndex(localState)).toBe(null);
});
it("returns null if the url doesn't match any data due to a different project full path", () => {
localState.projectFullPath = 'http://my-path/does/not/match';
expect(getters.stepIndex(localState)).toBe(null);
});
});
describe('stepContent', () => {
it('returns the correct step content for the active tour step', () => {
const tourKey = 1;
const stepIndex = 1;
const localGetters = {
stepIndex,
};
expect(getters.stepContent(localState, localGetters)).toBe(mockTourData[tourKey][stepIndex]);
});
it('returns null if there is no tour data', () => {
localState.tourData = [];
const localGetters = {
stepIndex: 1,
};
expect(getters.stepContent(localState, localGetters)).toBe(null);
});
it('returns null if there is no step index', () => {
const localGetters = {
stepIndex: null,
};
expect(getters.stepContent(localState, localGetters)).toBe(null);
});
});
describe('helpContent', () => {
it('returns the help content for a given index', () => {
const helpContentIndex = 0;
const stepContent = {
getHelpContent: () => [
{
text: 'foo',
buttons: [{ text: 'button', btnClass: 'btn-primary' }],
},
],
};
const localGetters = {
stepContent,
};
localState.helpContentIndex = helpContentIndex;
expect(getters.helpContent(localState, localGetters)).toEqual(
stepContent.getHelpContent()[helpContentIndex],
);
});
it('displays the project name in the help content text', () => {
const helpContentIndex = 0;
const stepContent = {
getHelpContent: ({ projectName }) => [
{
text: `This is the ${projectName}`,
buttons: [{ text: 'button', btnClass: 'btn-primary' }],
},
],
};
const localGetters = {
stepContent,
};
localState.helpContentIndex = helpContentIndex;
localState.projectName = 'Mock Project';
const helpContent = getters.helpContent(localState, localGetters);
expect(helpContent.text).toBe('This is the Mock Project');
});
it('returns null if there is no step content', () => {
const localGetters = {
stepContent: null,
};
localState.helpContentIndex = 0;
expect(getters.helpContent(localState, localGetters)).toBe(null);
});
it('returns null if there is no getHelpContent property on the step content', () => {
const stepContent = {
getHelpContent: null,
};
const localGetters = {
stepContent,
};
expect(getters.helpContent(localState, localGetters)).toBe(null);
});
});
describe('totalTourPartSteps', () => {
it('returns the correct number of total tour steps for the tour with key "1"', () => {
expect(getters.totalTourPartSteps(localState)).toBe(3);
});
it('returns 0 if there is no tour data', () => {
localState.tourData = [];
expect(getters.totalTourPartSteps(localState)).toBe(0);
});
it('returns 0 if there is no tour key', () => {
localState.tourKey = null;
expect(getters.totalTourPartSteps(localState)).toBe(0);
});
it('returns 0 if there is no data at a given tour key', () => {
localState.tourKey = 10;
expect(getters.totalTourPartSteps(localState)).toBe(0);
});
});
describe('percentageCompleted', () => {
it('returns the percentage completed for the current step', () => {
const localGetters = {
stepIndex: 1,
};
expect(getters.percentageCompleted(localState, localGetters)).toBe(20);
});
it('returns the 0 if there is no step index', () => {
const localGetters = {
stepIndex: null,
};
expect(getters.percentageCompleted(localState, localGetters)).toBe(0);
});
it('returns the 0 if there is no data for a given step index', () => {
const localGetters = {
stepIndex: 10,
};
expect(getters.percentageCompleted(localState, localGetters)).toBe(0);
});
});
describe('actionPopover', () => {
it("returns the step content's action popover if the step content exists", () => {
const stepContent = {
actionPopover: {
selector: '.popover-selector',
text: 'Some action popover content',
},
};
const localGetters = {
stepContent,
};
expect(getters.actionPopover(localState, localGetters)).toEqual(stepContent.actionPopover);
});
it('returns null if there is no step content', () => {
const localGetters = {
stepContent: null,
};
expect(getters.actionPopover(localState, localGetters)).toBeNull();
});
});
});
import createState from 'ee/onboarding/onboarding_helper/store/state';
import mutations from 'ee/onboarding/onboarding_helper/store/mutations';
import * as types from 'ee/onboarding/onboarding_helper/store/mutation_types';
import { mockTourData } from './../mock_data';
describe('User onboarding helper store mutations', () => {
let state;
beforeEach(() => {
state = createState();
});
describe('SET_INITIAL_DATA', () => {
it('sets all inital data', () => {
const initialData = {
url: 'http://gitlab-org/gitlab-test/foo',
projectFullPath: 'http://gitlab-org/gitlab-test',
projectName: 'Mock Project',
tourData: mockTourData,
tourKey: 1,
helpContentIndex: 0,
lastStepIndex: -1,
dismissed: false,
createdProjectPath: '',
exitTour: false,
};
mutations[types.SET_INITIAL_DATA](state, initialData);
expect(state).toEqual(initialData);
});
});
describe('SET_TOUR_KEY', () => {
it('sets the tour key', () => {
const tourKey = 2;
mutations[types.SET_TOUR_KEY](state, tourKey);
expect(state.tourKey).toEqual(tourKey);
});
});
describe('SET_LAST_STEP_INDEX', () => {
it('sets the last step index', () => {
const lastStepIndex = 1;
mutations[types.SET_LAST_STEP_INDEX](state, lastStepIndex);
expect(state.lastStepIndex).toEqual(lastStepIndex);
});
});
describe('SET_HELP_CONTENT_INDEX', () => {
it('sets the help content index', () => {
const helpContentIndex = 1;
mutations[types.SET_HELP_CONTENT_INDEX](state, helpContentIndex);
expect(state.helpContentIndex).toEqual(helpContentIndex);
});
});
describe('SET_EXIT_TOUR', () => {
it('sets the exitTour property to true', () => {
mutations[types.SET_EXIT_TOUR](state, true);
expect(state.exitTour).toBeTruthy();
});
});
describe('SET_DISMISSED', () => {
it('sets the dismissed property to true', () => {
mutations[types.SET_DISMISSED](state, true);
expect(state.dismissed).toBeTruthy();
});
});
});
......@@ -14589,12 +14589,18 @@ msgstr ""
msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch."
msgstr ""
msgid "UserOnboardingTour|Create a project"
msgstr ""
msgid "UserOnboardingTour|Exit 'Learn GitLab'"
msgstr ""
msgid "UserOnboardingTour|Got it"
msgstr ""
msgid "UserOnboardingTour|Guided GitLab Tour"
msgstr ""
msgid "UserOnboardingTour|Here you can compare the changes of this branch to another one. Changes are divided by files so that it's easier to see what was changed where."
msgstr ""
......@@ -14613,6 +14619,9 @@ msgstr ""
msgid "UserOnboardingTour|Here's an overview of branches in the %{emphasisStart}%{projectName}%{emphasisEnd} project. They're split into Active and Stale.%{lineBreak}%{lineBreak}From here, you can create a new merge request, from a branch or compare the branch to any other branch in the project. By default, it will compare it to the master branch."
msgstr ""
msgid "UserOnboardingTour|Invite colleagues"
msgstr ""
msgid "UserOnboardingTour|Issues are great for communicating and keeping track of progess in GitLab. These are all issues that are open in the %{emphasisStart}%{projectName}%{emphasisEnd}.%{lineBreak}%{lineBreak}You can help us improve GitLab by contributing work to issues that are labeled <span class=\"badge color-label accept-mr-label\">Accepting merge requests</span>.%{lineBreak}%{lineBreak}This list can be filtered by labels, milestones, assignees, authors... We'll show you how it looks like when the list is filtered by a label."
msgstr ""
......@@ -14652,6 +14661,9 @@ msgstr ""
msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started."
msgstr ""
msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right."
msgstr ""
msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}."
msgstr ""
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment