bespoke
Version:
DIY Presentation Micro-Framework
785 lines (575 loc) • 22.1 kB
JavaScript
(function() {
"use strict";
describe("bespoke", function() {
['selector', 'element'].forEach(function(fromType) {
describe("from " + fromType, function() {
var PARENT_TAG = 'article',
SLIDE_TAG = 'section',
NO_OF_SLIDES = 10,
article,
slides,
decks = [],
deck;
beforeEach(function() {
slides = [];
article = document.createElement(PARENT_TAG);
for (var i = 0; i < NO_OF_SLIDES; i++) {
slides.push(document.createElement(SLIDE_TAG));
article.appendChild(slides[i]);
// Ensure script tags are ignored
article.appendChild(document.createElement('script'));
}
document.body.appendChild(article);
var from = {
selector: PARENT_TAG,
element: article
};
decks.push((deck = bespoke.from(from[fromType])));
});
afterEach(function() {
document.body.removeChild(article);
});
describe("classes", function() {
it("should add a 'bespoke' class to the container", function() {
expect(article.className).toMatch(/bespoke-parent/);
});
it("should add a 'bespoke-slide' class to the slides", function() {
slides.forEach(function(slide) {
expect(slide.className).toMatch(/bespoke-slide(\s|$)/);
});
});
describe("bespoke-active", function() {
it("should add a 'bespoke-active' class to the active slide", function() {
deck.slide(3);
expect(slides[3].className).toMatch(/bespoke-active(\s|$)/);
});
it("should not add a 'bespoke-active' class to all inactive slides", function() {
slides = slides.reverse().slice(0, slides.length - 2).reverse();
slides.forEach(function(slide) {
expect(slide.className).not.toMatch(/bespoke-active(\s|$)/);
});
});
});
describe("bespoke-inactive", function() {
it("should add a 'bespoke-inactive' class to all inactive slides", function() {
slides = slides.reverse().slice(0, slides.length - 2).reverse();
slides.forEach(function(slide) {
expect(slide.className).toMatch(/bespoke-inactive(\s|$)/);
});
});
it("should not add a 'bespoke-inactive' class to the active slide", function() {
expect(slides[0].className).not.toMatch(/bespoke-inactive(\s|$)/);
});
});
describe("bespoke-before", function() {
it("should add a 'bespoke-before' class to all slides before active slide", function() {
deck.slide(5);
var beforeSlides = slides.slice(0, 4);
beforeSlides.forEach(function(slide) {
expect(slide.className).toMatch(/bespoke-before(\s|$)/);
});
});
it("should not add a 'bespoke-before' class to all slides after active slide", function() {
deck.slide(5);
var notBeforeSlides = slides.slice(5, 9);
notBeforeSlides.forEach(function(slide) {
expect(slide.className).not.toMatch(/bespoke-before(\s|$)/);
});
});
});
describe("bespoke-before", function() {
it("should add a 'bespoke-after' class to all slides after active slide", function() {
deck.slide(5);
var afterSlides = slides.slice(6);
afterSlides.forEach(function(slide) {
expect(slide.className).toMatch(/bespoke-after(\s|$)/);
});
});
it("should not add a 'bespoke-after' class to all slides before active slide", function() {
deck.slide(5);
var notAfterSlides = slides.slice(0, 5);
notAfterSlides.forEach(function(slide) {
expect(slide.className).not.toMatch(/bespoke-after(\s|$)/);
});
});
});
});
describe("API", function() {
describe("global 'bespoke' object", function() {
describe("next", function() {
it("should call 'next' on all deck instances", function() {
decks.forEach(function(deck) {
deck.next = sinon.spy();
});
bespoke.next();
decks.forEach(function(deck) {
expect(deck.next.called).toBe(true);
});
});
it("should call 'next' with custom event data on all deck instances", function() {
var customEventData = { foo: 'bar' };
decks.forEach(function(deck) {
deck.next = sinon.spy();
});
bespoke.next(customEventData);
decks.forEach(function(deck) {
expect(deck.next.calledWith(customEventData)).toBe(true);
});
});
});
describe("prev", function() {
it("should call 'prev' on all deck instances", function() {
decks.forEach(function(deck) {
deck.prev = sinon.spy();
});
bespoke.prev();
decks.forEach(function(deck) {
expect(deck.prev.called).toBe(true);
});
});
it("should call 'prev' with custom event data on all deck instances", function() {
var customEventData = { foo: 'bar' };
decks.forEach(function(deck) {
deck.prev = sinon.spy();
});
bespoke.prev(customEventData);
decks.forEach(function(deck) {
expect(deck.prev.calledWith(customEventData)).toBe(true);
});
});
});
describe("slide", function() {
it("should call 'slide' on all deck instances", function() {
decks.forEach(function(deck) {
deck.slide = sinon.spy();
});
bespoke.slide(0);
decks.forEach(function(deck) {
expect(deck.slide.calledWith(0)).toBe(true);
});
});
it("should call 'slide' with custom event data on all deck instances", function() {
var customEventData = { foo: 'bar' };
decks.forEach(function(deck) {
deck.slide = sinon.spy();
});
bespoke.slide(0, customEventData);
decks.forEach(function(deck) {
expect(deck.slide.calledWith(0, customEventData)).toBe(true);
});
});
});
});
describe("deck instances", function() {
describe("next", function() {
it("should go to the next slide when not last slide", function() {
deck.next();
expect(slides[1].className).toMatch(/bespoke-active(\s|$)/);
});
it("should do nothing when on last slide", function() {
deck.slide(9);
deck.next();
deck.next();
expect(slides[9].className).toMatch(/bespoke-active(\s|$)/);
});
it("should do nothing when on last slide and not change any state", function() {
deck.slide(9);
deck.next();
deck.next();
deck.prev();
expect(slides[8].className).toMatch(/bespoke-active(\s|$)/);
});
it("shouldn't activate the next slide if event handler activates an earlier slide while on last slide", function() {
var activateAnotherSlide = function() { deck.slide(5); };
deck.slide(deck.slides.length - 1);
var off = deck.on("next", activateAnotherSlide);
deck.next();
expect(deck.slides[5].classList.contains('bespoke-active')).toBe(true);
off();
});
it("should merge the custom user payload with the event object", function() {
var event;
deck.on("next", function(e) {
event = e;
});
deck.next({ foo: "bar" });
expect(event.foo).toBe("bar");
});
});
describe("prev", function() {
it("should go to the previous slide when not first slide", function() {
deck.slide(1);
deck.prev();
expect(slides[0].className).toMatch(/bespoke-active(\s|$)/);
});
it("should do nothing when on first slide", function() {
deck.prev();
deck.prev();
expect(slides[0].className).toMatch(/bespoke-active(\s|$)/);
});
it("should do nothing when on first slide and not change any state", function() {
deck.prev();
deck.next();
expect(slides[1].className).toMatch(/bespoke-active(\s|$)/);
});
it("shouldn't activate the previous slide if event handler activates a later slide while on first slide", function() {
var activateAnotherSlide = function() { deck.slide(5); };
var off = deck.on("prev", activateAnotherSlide);
deck.prev();
expect(deck.slides[5].classList.contains('bespoke-active')).toBe(true);
off();
});
it("should merge the custom user payload with the event object", function() {
var event;
deck.on("prev", function(e) {
event = e;
});
deck.prev({ foo: "bar" });
expect(event.foo).toBe("bar");
});
});
describe("on", function() {
it("should return a function to unbind the event", function() {
var callback = sinon.spy();
var off = deck.on("foo", callback);
deck.fire("foo");
expect(callback.callCount).toBe(1);
off();
deck.fire("foo");
expect(callback.callCount).toBe(1);
});
it("should allow multiple events to be bound", function() {
var callback1 = sinon.spy(),
callback2 = sinon.spy();
deck.on("bar", callback1);
deck.on("bar", callback2);
deck.fire("bar");
expect(callback1.called).toBe(true);
expect(callback2.called).toBe(true);
});
describe("activate", function() {
it("should call handler when slide is activated", function() {
var callback = sinon.spy();
deck.on("activate", callback);
deck.next();
expect(callback.called).toBe(true);
});
it("should pass payload to 'activate' handler when slide is activated", function() {
var callback = sinon.spy(),
SLIDE_INDEX = 0,
ACTIVATED_SLIDE = deck.slides[SLIDE_INDEX];
deck.on("activate", callback);
deck.slide(SLIDE_INDEX);
expect(callback.calledWith({
slide: ACTIVATED_SLIDE,
index: SLIDE_INDEX
})).toBe(true);
});
it("should pass merged payload to 'activate' handler when next slide is activated with user payload", function() {
var event;
deck.on("activate", function(e) {
event = e;
});
deck.next({ foo: "bar" });
expect(event.foo).toBe("bar");
});
it("should pass merged payload to 'activate' handler when previous slide is activated with user payload", function() {
var event;
deck.slide(1);
deck.on("activate", function(e) {
event = e;
});
deck.prev({ foo: "bar" });
expect(event.foo).toBe("bar");
});
it("should pass merged payload to 'deactivate' handler when specific slide is activated with user payload", function() {
var event;
deck.on("activate", function(e) {
event = e;
});
deck.slide(1, { foo: "bar" });
expect(event.foo).toBe("bar");
});
});
describe("deactivate", function() {
it("should call handler when slide is deactivated", function() {
var callback = sinon.spy();
deck.on("deactivate", callback);
deck.next();
expect(callback.called).toBe(true);
});
it("should pass payload to 'deactivate' handler once when slide is activated", function() {
var callback = sinon.spy(),
SLIDE_INDEX = 0,
DEACTIVATED_SLIDE = deck.slides[SLIDE_INDEX];
deck.on("deactivate", callback);
deck.slide(1);
expect(callback.calledWith({
slide: DEACTIVATED_SLIDE,
index: SLIDE_INDEX
})).toBe(true);
expect(callback.callCount).toBe(1);
});
it("should pass merged payload to 'deactivate' handler when next slide is activated with user payload", function() {
var event;
deck.on("deactivate", function(e) {
event = e;
});
deck.next({ foo: "bar" });
expect(event.foo).toBe("bar");
});
it("should pass merged payload to 'deactivate' handler when previous slide is activated with user payload", function() {
var event;
deck.slide(1);
deck.on("deactivate", function(e) {
event = e;
});
deck.prev({ foo: "bar" });
expect(event.foo).toBe("bar");
});
it("should pass merged payload to 'deactivate' handler when specific slide is activated with user payload", function() {
var event;
deck.on("deactivate", function(e) {
event = e;
});
deck.slide(1, { foo: "bar" });
expect(event.foo).toBe("bar");
});
});
describe("next", function() {
it("should call handler when next slide is requested", function() {
var callback = sinon.spy();
deck.on("next", callback);
deck.next();
expect(callback.callCount).toBe(1);
});
it("should call handler when next slide is requested while on last slide", function() {
var callback = sinon.spy();
deck.slide(deck.slides.length - 1);
deck.on("next", callback);
deck.next();
expect(callback.called).toBe(true);
});
it("should pass payload to 'next' handler when next slide is requested", function() {
var callback = sinon.spy(),
ACTIVE_SLIDE_INDEX = 0,
ACTIVE_SLIDE = deck.slides[ACTIVE_SLIDE_INDEX];
deck.on("next", callback);
deck.slide(ACTIVE_SLIDE_INDEX);
deck.next();
expect(callback.calledWith({
slide: ACTIVE_SLIDE,
index: ACTIVE_SLIDE_INDEX
})).toBe(true);
expect(callback.callCount).toBe(1);
});
it("should not activate next slide if an event handler returns false", function() {
var activateCallback = sinon.spy();
deck.on("activate", activateCallback);
deck.on("next", function() {
return false;
});
deck.next();
expect(activateCallback.called).toBe(false);
});
it("should not call subsequent 'next' handlers if an earlier event handler returns false", function() {
var nextCallback = sinon.spy();
deck.on("next", function() {
return false;
});
deck.on("next", nextCallback);
deck.next();
expect(nextCallback.called).toBe(false);
});
it("should activate next slide if event handler returns true", function() {
var activateCallback = sinon.spy();
deck.on("activate", activateCallback);
deck.on("next", function() {
return true;
});
deck.next();
expect(activateCallback.called).toBe(true);
});
});
describe("prev", function() {
it("should call handler when previous slide is requested", function() {
var callback = sinon.spy();
deck.slide(1);
deck.on("prev", callback);
deck.prev();
expect(callback.callCount).toBe(1);
});
it("should call handler when previous slide is requested while on first slide", function() {
var callback = sinon.spy();
deck.on("prev", callback);
deck.prev();
expect(callback.called).toBe(true);
});
it("should pass payload to 'prev' handler when previous slide is requested", function() {
var callback = sinon.spy(),
ACTIVE_SLIDE_INDEX = 1,
ACTIVE_SLIDE = deck.slides[ACTIVE_SLIDE_INDEX];
deck.on("prev", callback);
deck.slide(ACTIVE_SLIDE_INDEX);
deck.prev();
expect(callback.calledWith({
slide: ACTIVE_SLIDE,
index: ACTIVE_SLIDE_INDEX
})).toBe(true);
expect(callback.callCount).toBe(1);
});
it("should not activate previous slide if an event handler returns false", function() {
var activateCallback = sinon.spy();
deck.on("activate", activateCallback);
deck.on("prev", function() {
return false;
});
deck.prev();
expect(activateCallback.called).toBe(false);
});
it("should not call subsequent 'prev' handlers if an earlier event handler returns false", function() {
var prevCallback = sinon.spy();
deck.slide(1);
deck.on("prev", function() {
return false;
});
deck.on("prev", prevCallback);
deck.prev();
expect(prevCallback.called).toBe(false);
});
it("should activate previous slide if event handler returns true", function() {
var activateCallback = sinon.spy();
deck.slide(1);
deck.on("activate", activateCallback);
deck.on("prev", function() {
return true;
});
deck.prev();
expect(activateCallback.called).toBe(true);
});
});
describe("slide", function() {
it("should return the current slide index when called without arguments", function() {
deck.slide(1);
expect(deck.slide()).toBe(1);
deck.slide(2);
expect(deck.slide()).toBe(2);
});
it("should call handler when specific slide is requested", function() {
var callback = sinon.spy();
deck.on("slide", callback);
deck.slide(1);
expect(callback.callCount).toBe(1);
});
it("should pass payload to 'slide' handler when specific slide is requested", function() {
var callback = sinon.spy(),
ACTIVE_SLIDE_INDEX = 1,
ACTIVE_SLIDE = deck.slides[ACTIVE_SLIDE_INDEX];
deck.on("slide", callback);
deck.slide(ACTIVE_SLIDE_INDEX);
expect(callback.calledWith({
slide: ACTIVE_SLIDE,
index: ACTIVE_SLIDE_INDEX
})).toBe(true);
});
it("should not activate requested slide if an event handler returns false", function() {
var activateCallback = sinon.spy();
deck.on("activate", activateCallback);
deck.on("slide", function() {
return false;
});
deck.slide(1);
expect(activateCallback.called).toBe(false);
});
it("should merge the custom user payload with the event object", function() {
var event;
deck.on("slide", function(e) {
event = e;
});
deck.slide(0, { foo: "bar" });
expect(event.foo).toBe("bar");
});
});
});
describe("fire", function() {
it("should allow custom events to be triggered", function() {
var customEventHandler = sinon.spy(),
payload = { foo: 'bar' };
deck.on('custom-event', customEventHandler);
deck.fire('custom-event', payload);
expect(customEventHandler.calledWith(payload)).toBe(true);
});
});
describe("parent", function() {
it("should refer to the parent element", function() {
expect(deck.parent).toBe(article);
});
});
describe("slides", function() {
it("should be an array", function() {
expect(Array.isArray(deck.slides)).toBe(true);
});
it("should have the same number of items as elements", function() {
expect(deck.slides.length).toBe(NO_OF_SLIDES);
});
it("should match all slide elements in the DOM", function() {
[].slice.call(document.querySelectorAll(SLIDE_TAG), 0).forEach(function(domSlide, i) {
expect(domSlide).toBe(deck.slides[i]);
});
});
});
describe("plugins", function() {
describe("custom", function() {
var testPlugin,
onActivate;
beforeEach(function() {
bespoke.plugins.testPlugin = testPlugin = sinon.spy();
bespoke.plugins.onActivatePlugin = function(deck) {
onActivate = sinon.spy();
deck.on('activate', onActivate);
};
});
afterEach(function() {
delete bespoke.plugins.testPlugin;
delete bespoke.plugins.onActivatePlugin;
});
it("should pass the deck instance as the first parameter", function() {
var deck = bespoke.from("article", { testPlugin: true });
expect(testPlugin.calledWith(deck)).toBe(true);
});
it("should pass 'true' as the second parameter if option is 'true'", function() {
var deck = bespoke.from("article", { testPlugin: true });
expect(testPlugin.calledWith(deck, true)).toBe(true);
});
it("should pass the options hash as the second parameter", function() {
var deck = bespoke.from("article", { testPlugin: { foo: 'bar' } });
expect(testPlugin.calledWith(deck, { foo: 'bar' })).toBe(true);
});
it("should not run the plugin if option is 'false'", function() {
bespoke.from("article", { testPlugin: false });
expect(testPlugin.called).toBe(false);
});
it("should call any 'activate' event handlers immediately", function() {
bespoke.from("article", { onActivatePlugin: true });
expect(onActivate.called).toBe(true);
});
it("should throw an error if a plugin isn't found", function() {
var createDeckWithMissingPlugin = function() {
bespoke.from("article", { foobar: true });
};
expect(createDeckWithMissingPlugin).toThrow("Missing plugin: bespoke-foobar");
});
it("should throw an error if a plugin isn't found even if the plugin is disabled", function() {
var createDeckWithMissingDisabledPlugin = function() {
bespoke.from("article", { foobar: false });
};
expect(createDeckWithMissingDisabledPlugin).toThrow("Missing plugin: bespoke-foobar");
});
});
});
});
});
});
});
});
}());