Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add some tour tests #216

Merged
merged 2 commits into from
Aug 23, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the benefit of renaming the args?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They were not truly name and step. Really, this is behaving as an overloaded method. If there is just one arg, there is no name, but name is the first param. It confused me with the naming, so I renamed them.

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');
});
});
});