Skip to content

Commit

Permalink
Add some tour tests (#216)
Browse files Browse the repository at this point in the history
* Add some tour tests

* Add more tests
  • Loading branch information
RobbieTheWagner authored Aug 23, 2018
1 parent f1d3c9a commit a9c32a4
Show file tree
Hide file tree
Showing 2 changed files with 209 additions and 62 deletions.
101 changes: 71 additions & 30 deletions src/js/tour.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,27 @@ export class Tour extends Evented {
});
}

addStep(name, step) {
if (_.isUndefined(step)) {
step = name;
/**
* Adds a new step to the tour
* @param {Object|Number|Step|String} arg1
* When arg2 is defined, arg1 can either be a string or number, to use for the `id` for the step
* When arg2 is undefined, arg1 is either an object containing step options or a Step instance
* @param {Object|Step} arg2 An object containing step options or a Step instance
* @returns {Step} The newly added step
*/
addStep(arg1, arg2) {
let name, step;

// If we just have one argument, we can assume it is an object of step options, with an id
if (_.isUndefined(arg2)) {
step = arg1;
} else {
name = arg1;
step = arg2;
}

if (!(step instanceof Step)) {
if (typeof name === 'string' || typeof name === 'number') {
step.id = name.toString();
}
step = Object.assign({}, this.options.defaults, step);
step = new Step(this, step);
step = this.setupStep(step, name);
} else {
step.tour = this;
}
Expand All @@ -57,32 +67,51 @@ export class Tour extends Evented {
return step;
}

/**
* Removes the step from the tour
* @param {String} name The id for the step to remove
*/
removeStep(name) {
const current = this.getCurrentStep();

for (let i = 0; i < this.steps.length; ++i) {
const step = this.steps[i];
// Find the step, destroy it and remove it from this.steps
this.steps.some((step, i) => {
if (step.id === name) {
if (step.isOpen()) {
step.hide();
}

step.destroy();
this.steps.splice(i, 1);
break;

return true;
}
}
});

if (current && current.id === name) {
this.currentStep = undefined;

if (this.steps.length) {
this.show(0);
} else {
this.cancel();
}
// If we have steps left, show the first one, otherwise just cancel the tour
this.steps.length ? this.show(0) : this.cancel();
}
}

/**
* Setup a new step object
* @param {Object} stepOptions The object describing the options for the step
* @param {String|Number} name The string or number to use as the `id` for the step
* @returns {Step}
*/
setupStep(stepOptions, name) {
if (_.isString(name) || _.isNumber(name)) {
stepOptions.id = name.toString();
}

stepOptions = Object.assign({}, this.options.defaults, stepOptions);

return new Step(this, stepOptions);
}

getById(id) {
for (let i = 0; i < this.steps.length; ++i) {
const step = this.steps[i];
Expand All @@ -96,16 +125,9 @@ export class Tour extends Evented {
return this.currentStep;
}

next() {
const index = this.steps.indexOf(this.currentStep);

if (index === this.steps.length - 1) {
this.complete();
} else {
this.show(index + 1, true);
}
}

/**
* Go to the previous step in the tour
*/
back() {
const index = this.steps.indexOf(this.currentStep);
this.show(index - 1, false);
Expand Down Expand Up @@ -136,15 +158,31 @@ export class Tour extends Evented {

this.trigger(event);

Shepherd.activeTour.steps.forEach((step) => {
step.destroy();
});
if (Shepherd.activeTour) {
Shepherd.activeTour.steps.forEach((step) => {
step.destroy();
});
}

Shepherd.activeTour = null;
document.body.classList.remove('shepherd-active');
this.trigger('inactive', { tour: this });
}

/**
* Go to the next step in the tour
* If we are at the end, call `complete`
*/
next() {
const index = this.steps.indexOf(this.currentStep);

if (index === this.steps.length - 1) {
this.complete();
} else {
this.show(index + 1, true);
}
}

show(key = 0, forward = true) {
if (this.currentStep) {
this.currentStep.hide();
Expand Down Expand Up @@ -176,6 +214,9 @@ export class Tour extends Evented {
}
}

/**
* Start the tour
*/
start() {
this.trigger('start');

Expand Down
170 changes: 138 additions & 32 deletions test/test.tour.js
Original file line number Diff line number Diff line change
@@ -1,58 +1,95 @@
/* global window,require,describe,it */
import _ from 'lodash';
import { assert } from 'chai';
import Shepherd from '../src/js/shepherd';
import { Step } from '../src/js/step';
// since importing non UMD, needs assignment
window.Shepherd = Shepherd;

describe('Tour', function() {
let instance;
const defaults = {
classes: 'shepherd-theme-arrows',
scrollTo: true
};

after(function() {
instance.cancel();
});
beforeEach(() => {
instance = new Shepherd.Tour({
defaults
});

instance.addStep('test', {
classes: 'foo',
id: 'test',
title: 'This is a test step for our tour'
});

instance.addStep('test2', {
id: 'test2',
title: 'Another Step'
});

const instance = new Shepherd.Tour({
defaults,
instance.addStep('test3', {
id: 'test3',
title: 'Yet, another test step'
});
});

it('creates a new tour instance', function() {
assert.isOk(instance instanceof Shepherd.Tour);
afterEach(() => {
instance.cancel();
});

it('returns the default options on the instance', function() {
assert.isOk(instance.options);
describe('constructor', function() {
it('creates a new tour instance', function() {
assert.isOk(instance instanceof Shepherd.Tour);
});

it('returns the default options on the instance', function() {
assert.deepEqual(instance.options.defaults, {
classes: 'shepherd-theme-arrows',
scrollTo: true
});
});

it('sets the correct bindings', function() {
const bindings = Object.keys(instance.bindings);
const tourEvents = ['complete', 'cancel', 'start', 'show', 'active', 'inactive'];
// Check that all bindings are included
const difference = _.difference(tourEvents, bindings);
assert.equal(difference.length, 0, 'all tour events bound');
});
});

describe('.addStep()', function() {
it('adds tour steps', function() {
instance.addStep('test', {
id: 'test',
title: 'This is a test step for our tour'
assert.equal(instance.steps.length, 3);
assert.equal(instance.getById('test').options.classes, 'foo', 'classes passed to step options');
});

it('adds steps with only one arg', function() {
const step = instance.addStep({
id: 'one-arg'
});

assert.equal(instance.steps.length, 1);
assert.equal(instance.steps.length, 4);
assert.equal(step.id, 'one-arg', 'id applied to step with just one arg');
});

// this is not working as documented
it('returns the step options', function() {
assert.equal(instance.options.defaults, defaults);
it('adds steps that are already Step instances', function() {
const step = instance.addStep(new Step(instance, {
id: 'already-a-step'
}));

assert.equal(instance.steps.length, 4);
assert.equal(step.id, 'already-a-step', 'id applied to step instance');
assert.equal(step.tour, instance, 'tour is set to `this`');
});
});

describe('.getById()', function() {
it('returns the step by ID with the right title', function() {
instance.addStep('test2', {
id: 'test2',
title: 'Another Step'
});

instance.addStep('test3', {
id: 'test3',
title: 'Yet, another test step'
});
assert.equal(instance.steps.length, 3);
assert.equal(instance.getById('test').options.title, 'This is a test step for our tour');
assert.equal(instance.getById('test3').options.title, 'Yet, another test step');
});

});
Expand All @@ -67,21 +104,90 @@ describe('Tour', function() {

describe('.getCurrentStep()', function() {
it('returns the currently shown step', function() {
instance.start();
assert.equal(instance.getCurrentStep().id, 'test');
});
});

describe('.next()', function() {
it('goes to the next step after next() is invoked', function() {
describe('.next()/.back()', function() {
it('goes to the next/previous steps', function() {
instance.start();
instance.next();
assert.equal(instance.getCurrentStep().id, 'test2');
instance.back();
assert.equal(instance.getCurrentStep().id, 'test');
});

it('next completes tour when on last step', function() {
let completeFired = false;
instance.on('complete', () => {
completeFired = true;
});

instance.start();
instance.show('test3');
assert.equal(instance.getCurrentStep().id, 'test3');
instance.next();
assert.isOk(completeFired, 'complete is called when next is clicked on last step');
});
});

describe('.back()', function() {
it('goes to the previous step after back() is invoked', function() {
instance.back();
assert.equal(instance.getCurrentStep().id, 'test');
describe('.complete()', function() {
it('tears down tour on complete', function() {
let inactiveFired = false;
instance.on('inactive', () => {
inactiveFired = true;
});
instance.start();
assert.equal(instance, Shepherd.activeTour, 'activeTour is set to our tour');
instance.complete();
assert.isNotOk(Shepherd.activeTour, 'activeTour is torn down');
assert.isOk(inactiveFired, 'inactive event fired');
});

it('triggers complete event when complete function is called', function() {
let completeFired = false;
instance.on('complete', () => {
completeFired = true;
});

instance.start();
instance.complete();
assert.isOk(completeFired, 'complete event fired');
});
});

describe('.removeStep()', function() {
it('removes the step when passed the id', function() {
instance.start();
assert.equal(instance.steps.length, 3);
instance.removeStep('test2');
assert.equal(instance.steps.length, 2);
});

it('hides the step before removing', function() {
let hideFired = false;
instance.start();
assert.equal(instance.steps.length, 3);
const step = instance.getById('test');
step.on('hide', () => {
hideFired = true;
});
instance.removeStep('test');
assert.equal(instance.steps.length, 2);
assert.isOk(hideFired, 'hide is fired before step is destroyed');
});
});

describe('.show()', function() {
it('show short circuits if next is not found', function() {
let showFired = false;
instance.start();
instance.on('show', () => {
showFired = true;
});
instance.show('not-a-real-key');
assert.isNotOk(showFired, 'showFired is false because show short circuits');
});
});
});

0 comments on commit a9c32a4

Please sign in to comment.