dynamicweb/cms/tests/frontend/unit/cms.structureboard.test.js

2814 lines
113 KiB
JavaScript

'use strict';
import StructureBoard from '../../../static/cms/js/modules/cms.structureboard';
import Plugin from '../../../static/cms/js/modules/cms.plugins';
import CMS, { Helpers, KEYS } from '../../../static/cms/js/modules/cms.base';
import $ from 'jquery';
import keyboard from '../../../static/cms/js/modules/keyboard';
import { rewire, resetRewire } from './helpers/rewire';
vi.mock('../../../static/cms/js/modules/loader', async () => {
const { lazyMock, registerActual } = await import('./helpers/rewire');
registerActual('loader', await vi.importActual('../../../static/cms/js/modules/loader'));
return lazyMock('loader', { showLoader: 'showLoader', hideLoader: 'hideLoader' });
});
vi.mock('../../../static/cms/js/modules/cms.plugins', async () => {
const { lazyMock, registerActual } = await import('./helpers/rewire');
const namespace = lazyMock('cms.plugins', { default: 'Plugin' });
// not awaited on purpose: cms.plugins is part of the plugins/structureboard/
// clipboard import cycle, and awaiting it here deadlocks the mock
vi.importActual('../../../static/cms/js/modules/cms.plugins')
.then(actual => registerActual('cms.plugins', actual));
return namespace;
});
vi.mock('../../../static/cms/js/modules/cms.clipboard', async () => {
const { lazyMock, registerActual } = await import('./helpers/rewire');
const namespace = lazyMock('cms.clipboard', { default: 'Clipboard' });
// not awaited on purpose: cms.clipboard is part of the plugins/structureboard/
// clipboard import cycle, and awaiting it here deadlocks the mock
vi.importActual('../../../static/cms/js/modules/cms.clipboard')
.then(actual => registerActual('cms.clipboard', actual));
return namespace;
});
vi.mock('../../../static/cms/js/modules/dom-diff', async () => {
const { lazyMock, registerActual } = await import('./helpers/rewire');
registerActual('dom-diff', await vi.importActual('../../../static/cms/js/modules/dom-diff'));
return lazyMock('dom-diff', { DiffDOM: 'DiffDOM', nodeToObj: 'nodeToObj' });
});
vi.mock('../../../static/cms/js/modules/preload-images', async () => {
const { lazyMock, registerActual } = await import('./helpers/rewire');
registerActual('preload-images', await vi.importActual('../../../static/cms/js/modules/preload-images'));
return lazyMock('preload-images', { default: 'preloadImagesFromMarkup' });
});
var showLoader;
var hideLoader;
window.CMS = CMS;
CMS.StructureBoard = StructureBoard;
CMS.Plugin = Plugin;
CMS.API = CMS.API || {};
CMS.API.Helpers = Helpers;
CMS.KEYS = KEYS;
CMS.$ = $;
const pluginConstructor = jasmine.createSpy();
// the real class, not the rewirable stand-in: FakePlugin._updateClipboard()
// delegates to it, which would otherwise recurse into itself
const originalPlugin = (await vi.importActual('../../../static/cms/js/modules/cms.plugins')).default;
class FakePlugin {
constructor(container, opts) {
this.options = opts;
const el = $('<div></div>');
el.data('cms', []);
this.ui = {
container: el
};
pluginConstructor(container, opts);
}
static _recalculatePluginPositions() {}
}
FakePlugin._updateRegistry = jasmine.createSpy();
FakePlugin._updateClipboard = jasmine.createSpy().and.callFake(() => {
originalPlugin._updateClipboard();
});
FakePlugin.aliasPluginDuplicatesMap = {};
FakePlugin._refreshPlugins = jasmine.createSpy().and.callFake(() => {
originalPlugin._refreshPlugins();
});
FakePlugin.prototype._setupUI = jasmine.createSpy();
FakePlugin.prototype._ensureData = jasmine.createSpy();
FakePlugin.prototype._setGeneric = jasmine.createSpy();
FakePlugin.prototype._setPlaceholder = jasmine.createSpy();
FakePlugin.prototype._collapsables = jasmine.createSpy();
FakePlugin.prototype._setPluginContentEvents = jasmine.createSpy();
FakePlugin.prototype._setPluginStructureEvents = jasmine.createSpy();
describe('CMS.StructureBoard', function() {
fixture.setBase('cms/tests/frontend/unit/fixtures');
beforeEach(() => {
CMS.API.Clipboard = {
populate: jasmine.createSpy()
};
showLoader = jasmine.createSpy();
hideLoader = jasmine.createSpy();
rewire('showLoader', showLoader);
rewire('hideLoader', hideLoader);
});
afterEach(() => {
FakePlugin.aliasPluginDuplicatesMap = {};
FakePlugin.prototype._setupUI.calls.reset();
FakePlugin.prototype._ensureData.calls.reset();
FakePlugin.prototype._setGeneric.calls.reset();
FakePlugin.prototype._setPlaceholder.calls.reset();
FakePlugin.prototype._collapsables.calls.reset();
FakePlugin.prototype._setPluginContentEvents.calls.reset();
FakePlugin.prototype._setPluginStructureEvents.calls.reset();
FakePlugin._refreshPlugins.calls.reset();
resetRewire('showLoader');
resetRewire('hideLoader');
});
it('creates a StructureBoard class', function() {
expect(CMS.StructureBoard).toBeDefined();
});
it('has public API', function() {
expect(CMS.StructureBoard.prototype.show).toEqual(jasmine.any(Function));
expect(CMS.StructureBoard.prototype.hide).toEqual(jasmine.any(Function));
expect(CMS.StructureBoard.prototype.getId).toEqual(jasmine.any(Function));
expect(CMS.StructureBoard.prototype.getIds).toEqual(jasmine.any(Function));
});
describe('instance', function() {
var board;
beforeEach(function(done) {
fixture.load('plugins.html');
CMS.settings = {
mode: 'edit'
};
CMS.config = {
settings: {
mode: 'edit',
structure: '/structure'
},
mode: 'edit'
};
$(function() {
CMS.StructureBoard._initializeGlobalHandlers();
jasmine.clock().install();
board = new CMS.StructureBoard();
done();
});
});
afterEach(function() {
jasmine.clock().uninstall();
fixture.cleanup();
});
it('has ui', function() {
expect(board.ui).toEqual(jasmine.any(Object));
expect(Object.keys(board.ui)).toContain('container');
expect(Object.keys(board.ui)).toContain('content');
expect(Object.keys(board.ui)).toContain('doc');
expect(Object.keys(board.ui)).toContain('window');
expect(Object.keys(board.ui)).toContain('html');
expect(Object.keys(board.ui)).toContain('toolbar');
expect(Object.keys(board.ui)).toContain('sortables');
expect(Object.keys(board.ui)).toContain('plugins');
expect(Object.keys(board.ui)).toContain('render_model');
expect(Object.keys(board.ui)).toContain('placeholders');
expect(Object.keys(board.ui)).toContain('dragitems');
expect(Object.keys(board.ui)).toContain('dragareas');
expect(Object.keys(board.ui)).toContain('toolbarModeSwitcher');
expect(Object.keys(board.ui)).toContain('toolbarModeLinks');
expect(Object.keys(board.ui).length).toEqual(14);
});
it('has no options', function() {
expect(board.options).not.toBeDefined();
});
it('applies correct classes to empty placeholder dragareas', function() {
$('.cms-dragarea').removeClass('cms-dragarea-empty');
board = new CMS.StructureBoard();
expect('.cms-dragarea-1').not.toHaveClass('cms-dragarea-empty');
expect('.cms-dragarea-2').toHaveClass('cms-dragarea-empty');
expect('.cms-dragarea-10').toHaveClass('cms-dragarea-empty');
});
it('initially shows or hides board based on settings', function() {
spyOn(CMS.StructureBoard.prototype, 'show');
spyOn(CMS.StructureBoard.prototype, 'hide');
expect(CMS.config.settings.mode).toEqual('edit');
board = new CMS.StructureBoard();
expect(board.show).not.toHaveBeenCalled();
expect(board.hide).toHaveBeenCalled();
});
it('initially shows or hides board based on settings 2', function() {
spyOn(CMS.StructureBoard.prototype, 'show');
spyOn(CMS.StructureBoard.prototype, 'hide');
CMS.config.settings.mode = 'structure';
board = new CMS.StructureBoard();
expect(board.show).toHaveBeenCalled();
expect(board.hide).not.toHaveBeenCalled();
});
it('does not show or hide structureboard if there are no dragareas', function() {
board.ui.dragareas.remove();
board = new CMS.StructureBoard();
spyOn(board, 'show');
spyOn(board, 'hide');
expect(board.show).not.toHaveBeenCalled();
expect(board.hide).not.toHaveBeenCalled();
jasmine.clock().tick(200);
expect(board.show).not.toHaveBeenCalled();
expect(board.hide).not.toHaveBeenCalled();
});
it('does not show or hide structureboard if there is no board mode switcher', function() {
board.ui.toolbarModeSwitcher.remove();
board = new CMS.StructureBoard();
spyOn(board, 'show');
spyOn(board, 'hide');
expect(board.show).not.toHaveBeenCalled();
expect(board.hide).not.toHaveBeenCalled();
jasmine.clock().tick(200);
expect(board.show).not.toHaveBeenCalled();
expect(board.hide).not.toHaveBeenCalled();
});
it('enables board mode switcher if there are placeholders', function() {
expect(board.ui.placeholders.length > 0).toEqual(true);
board.ui.toolbarModeSwitcher.find('.cms-btn').addClass('cms-btn-disabled');
new CMS.StructureBoard();
jasmine.clock().tick(100);
expect(board.ui.toolbarModeSwitcher.find('.cms-btn')).not.toHaveClass('cms-btn-disabled');
});
it('does not enable board mode switcher if there are no placeholders', function() {
expect(board.ui.placeholders.length > 0).toEqual(true);
expect(board.ui.dragareas.length > 0).toEqual(true);
board.ui.placeholders.remove();
board.ui.dragareas.remove();
board.ui.toolbarModeSwitcher.find('.cms-btn').addClass('cms-btn-disabled');
board = new CMS.StructureBoard();
expect(board.ui.placeholders.length).toEqual(0);
expect(board.ui.dragareas.length).toEqual(0);
jasmine.clock().tick(100);
expect(board.ui.toolbarModeSwitcher.find('.cms-btn')).toHaveClass('cms-btn-disabled');
});
it('sets loaded content and structure flags if it is a legacy renderer', () => {
CMS.config.settings.legacy_mode = true;
board = new CMS.StructureBoard();
expect(board._loadedStructure).toEqual(true);
expect(board._loadedContent).toEqual(true);
});
});
describe('.show()', function() {
var board;
beforeEach(function(done) {
fixture.load('plugins.html');
CMS.settings = {
mode: 'edit'
};
CMS.API.Toolbar = {
_refreshMarkup: jasmine.createSpy()
};
CMS.config = {
settings: {
mode: 'edit',
structure: 'structure'
},
mode: 'edit'
};
$(function() {
spyOn(CMS.API.Helpers, 'setSettings').and.callFake(function(input) {
return input;
});
CMS.StructureBoard._initializeGlobalHandlers();
board = new CMS.StructureBoard();
spyOn(board, '_loadStructure').and.returnValue(Promise.resolve());
done();
});
});
afterEach(function() {
fixture.cleanup();
});
it('shows the board', function(done) {
spyOn(board, '_showBoard').and.callThrough();
expect(board.ui.container).not.toBeVisible();
board.show().then(function() {
expect(board.ui.container).toBeVisible();
expect(board._showBoard).toHaveBeenCalled();
done();
});
});
it('does not show the board if we are viewing published page', function(done) {
CMS.config.mode = 'live';
spyOn(board, '_showBoard').and.callThrough();
expect(board.ui.container).not.toBeVisible();
board.show().then(r => {
expect(r).toEqual(false);
expect(board.ui.container).not.toBeVisible();
expect(board._showBoard).not.toHaveBeenCalled();
done();
});
});
it('highlights correct trigger', function(done) {
expect(board.ui.toolbarModeLinks).not.toHaveClass('cms-btn-active');
board.show().then(() => {
expect(board.ui.toolbarModeLinks).toHaveClass('cms-btn-active');
done();
});
});
it('adds correct classes to the root of the document', function(done) {
board.ui.html.removeClass('cms-structure-mode-structure');
expect(board.ui.html).not.toHaveClass('cms-structure-mode-structure');
board.show().then(() => {
expect(board.ui.html).toHaveClass('cms-structure-mode-structure');
done();
});
});
it('does set state through settings', function(done) {
CMS.API.Helpers.setSettings.and.callFake(function(input) {
return input;
});
expect(CMS.settings.mode).toEqual('edit');
board.show().then(() => {
expect(CMS.settings.mode).toEqual('structure');
expect(CMS.API.Helpers.setSettings).toHaveBeenCalled();
done();
});
});
it('saves the state in the url');
});
describe('highlights', function() {
var board;
beforeEach(function(done) {
fixture.load('plugins.html', 'clipboard.html');
CMS.settings = {
mode: 'edit'
};
CMS.config = {
settings: {
mode: 'edit',
structure: 'structure'
},
mode: 'edit'
};
$(function() {
spyOn(CMS.API.Helpers, 'setSettings').and.callFake(function(input) {
return input;
});
CMS.API.Tooltip = {
domElem: {
is: function() {
return true;
},
data: function() {
return 1;
}
}
};
CMS.StructureBoard._initializeGlobalHandlers();
board = new CMS.StructureBoard();
spyOn(board, 'show').and.returnValue(Promise.resolve());
spyOn(board, 'hide').and.returnValue(Promise.resolve());
spyOn(Plugin, '_highlightPluginStructure');
spyOn(Plugin, '_highlightPluginContent');
done();
});
});
afterEach(function() {
fixture.cleanup();
});
describe('._showAndHighlightPlugin()', function() {
it('returns false if tooltip does not exist', function(done) {
CMS.API.Tooltip = false;
board._showAndHighlightPlugin().then(r => {
expect(r).toEqual(false);
expect(board.show).not.toHaveBeenCalled();
done();
});
});
it('returns false if in live mode', function(done) {
CMS.config.mode = 'live';
board._showAndHighlightPlugin().then(r => {
expect(r).toEqual(false);
expect(board.show).not.toHaveBeenCalled();
done();
});
});
it('returns false if no plugin is hovered', function(done) {
CMS.API.Tooltip.domElem.is = function() {
return false;
};
board._showAndHighlightPlugin().then(r => {
expect(r).toEqual(false);
expect(board.show).not.toHaveBeenCalled();
done();
});
});
it('shows board if plugin is hovered', function(done) {
jasmine.clock().install();
board._showAndHighlightPlugin().then(() => {
expect(board.show).toHaveBeenCalledTimes(1);
expect(Plugin._highlightPluginStructure).not.toHaveBeenCalled();
jasmine.clock().tick(201);
expect(Plugin._highlightPluginStructure).toHaveBeenCalledTimes(1);
jasmine.clock().uninstall();
done();
});
});
});
});
describe('.hide()', function() {
var board;
beforeEach(function(done) {
fixture.load('plugins.html');
$(function() {
CMS.settings = {
mode: 'edit'
};
CMS.config = {
settings: {
mode: 'edit',
structure: 'structure'
},
mode: 'edit'
};
spyOn(CMS.API.Helpers, 'setSettings').and.callFake(function(input) {
return input;
});
CMS.StructureBoard._initializeGlobalHandlers();
board = new CMS.StructureBoard();
spyOn(board, '_loadContent').and.returnValue(Promise.resolve());
spyOn(board, '_loadStructure').and.returnValue(Promise.resolve());
done();
});
});
afterEach(function() {
fixture.cleanup();
});
it('hides the board', function(done) {
spyOn(board, '_hideBoard').and.callThrough();
board
.show()
.then(() => {
expect(board.ui.container).toBeVisible();
return board.hide();
})
.then(() => {
expect(board.ui.container).not.toBeVisible();
expect(board._hideBoard).toHaveBeenCalled();
done();
});
});
it('does not hide the board if we are viewing published page', function() {
CMS.config.mode = 'live';
spyOn(board, '_hideBoard');
expect(board.ui.container).not.toBeVisible();
expect(board.hide()).toEqual(false);
expect(board.ui.container).not.toBeVisible();
expect(board._hideBoard).not.toHaveBeenCalled();
});
it('deactivates the button', function(done) {
board.hide().then(() => {
expect(board.ui.toolbarModeLinks).not.toHaveClass('cms-btn-active');
done();
});
});
it('does not remember the state in localstorage', function() {
board.show();
expect(CMS.settings.mode).toEqual('structure');
CMS.API.Helpers.setSettings.and.callFake(function(input) {
return input;
});
CMS.API.Helpers.setSettings.calls.reset();
board.hide();
expect(CMS.settings.mode).toEqual('edit');
expect(CMS.API.Helpers.setSettings).not.toHaveBeenCalled();
});
it('remembers the state in the url');
it('triggers `resize` event on the window', function(done) {
var spy = jasmine.createSpy();
board
.show()
.then(() => {
$(window).on('resize', spy);
return board.hide();
})
.then(() => {
expect(spy).toHaveBeenCalledTimes(1);
$(window).off('resize', spy);
done();
});
});
});
describe('.getId()', function() {
var board;
beforeEach(function(done) {
fixture.load('plugins.html');
CMS.settings = {
mode: 'edit'
};
CMS.config = {
settings: {
mode: 'edit',
structure: 'structure'
},
mode: 'edit'
};
$(function() {
CMS.StructureBoard._initializeGlobalHandlers();
board = new CMS.StructureBoard();
done();
});
});
afterEach(function() {
fixture.cleanup();
});
it('returns the id of passed element', function() {
[
{
from: 'cms-plugin cms-plugin-1',
result: '1'
},
{
from: 'cms-plugin cms-plugin-125',
result: '125'
},
{
from: 'cms-draggable cms-draggable-1',
result: '1'
},
{
from: 'cms-draggable cms-draggable-125',
result: '125'
},
{
from: 'cms-placeholder cms-placeholder-1',
result: '1'
},
{
from: 'cms-placeholder cms-placeholder-125',
result: '125'
},
{
from: 'cms-dragbar cms-dragbar-1',
result: '1'
},
{
from: 'cms-dragbar cms-dragbar-125',
result: '125'
},
{
from: 'cms-dragarea cms-dragarea-1',
result: '1'
},
{
from: 'cms-dragarea cms-dragarea-125',
result: '125'
}
].forEach(function(obj) {
expect(board.getId($('<div class="' + obj.from + '"></div>'))).toEqual(obj.result);
});
});
it('returns null if element is of non supported "type"', function() {
[
{
from: 'cannot determine',
result: null
},
{
from: 'cms-not-supported cms-not-supported-1',
result: null
}
].forEach(function(obj) {
expect(board.getId($('<div class="' + obj.from + '"></div>'))).toEqual(obj.result);
});
});
it('returns false if element does not exist', function() {
expect(board.getId()).toEqual(false);
expect(board.getId(null)).toEqual(false);
expect(board.getId($('.non-existent'))).toEqual(false);
expect(board.getId([])).toEqual(false);
});
it('fails if classname string is incorrect', function() {
expect(board.getId.bind(board, $('<div class="cms-plugin"></div>'))).toThrow();
expect(board.getId($('<div class="cms-plugin fail cms-plugin-10"></div>'))).toEqual('fail');
});
});
describe('.getIds()', function() {
var board;
beforeEach(function(done) {
fixture.load('plugins.html');
CMS.settings = {
mode: 'edit'
};
CMS.config = {
settings: {
mode: 'edit',
structure: 'structure'
},
mode: 'edit'
};
$(function() {
CMS.StructureBoard._initializeGlobalHandlers();
board = new CMS.StructureBoard();
done();
});
});
afterEach(function() {
fixture.cleanup();
});
it('returns the array of ids of passed collection', function() {
spyOn(board, 'getId').and.callThrough();
[
{
from: ['cms-plugin cms-plugin-1'],
result: ['1']
},
{
from: ['cms-plugin cms-plugin-125', 'cms-plugin cms-plugin-1'],
result: ['125', '1']
},
{
from: ['cms-plugin cms-plugin-125', 'cms-plugin cms-plugin-1', 'cms-draggable cms-draggable-12'],
result: ['125', '1', '12']
},
{
from: ['non-existent', 'cms-plugin cms-plugin-1'],
result: [null, '1']
}
].forEach(function(obj) {
var collection = $();
obj.from.forEach(function(className) {
collection = collection.add($('<div class="' + className + '"></div>'));
});
expect(board.getIds(collection)).toEqual(obj.result);
});
expect(board.getId).toHaveBeenCalled();
});
});
describe('._setupModeSwitcher()', function() {
var board;
beforeEach(function(done) {
fixture.load('plugins.html');
CMS.settings = {
mode: 'edit'
};
CMS.config = {
settings: {
mode: 'edit',
structure: 'structure'
},
mode: 'edit'
};
$(function() {
spyOn(keyboard, 'bind');
CMS.StructureBoard._initializeGlobalHandlers();
board = new CMS.StructureBoard();
spyOn(board, 'show').and.callFake(function() {
CMS.settings.mode = 'structure';
});
spyOn(board, 'hide').and.callFake(function() {
CMS.settings.mode = 'edit';
});
done();
});
});
afterEach(function() {
board.ui.doc.off('keydown.cms.structureboard.switcher');
fixture.cleanup();
});
it('sets up click handler to show board', function() {
var showTrigger = board.ui.toolbarModeLinks;
expect(showTrigger).toHandle(board.click);
CMS.settings.mode = 'structure';
showTrigger.trigger(board.click);
expect(board.show).not.toHaveBeenCalled();
CMS.settings.mode = 'edit';
showTrigger.trigger(board.click);
expect(board.show).toHaveBeenCalledTimes(1);
});
it('sets up click handler to hide board', function() {
var hideTrigger = board.ui.toolbarModeLinks;
expect(hideTrigger).toHandle(board.click);
CMS.settings.mode = 'edit';
hideTrigger.trigger(board.click);
expect(board.hide).not.toHaveBeenCalled();
CMS.settings.mode = 'structure';
hideTrigger.trigger(board.click);
expect(board.hide).toHaveBeenCalledTimes(1);
});
it('sets up shortcuts to toggle board', function() {
spyOn(board, '_toggleStructureBoard');
var preventDefaultSpySpace = jasmine.createSpy();
var preventDefaultSpyShiftSpace = jasmine.createSpy();
expect(keyboard.bind).toHaveBeenCalledTimes(2);
expect(keyboard.bind).toHaveBeenCalledWith('space', jasmine.any(Function));
expect(keyboard.bind).toHaveBeenCalledWith('shift+space', jasmine.any(Function));
var calls = keyboard.bind.calls.all();
calls[0].args[1]({ preventDefault: preventDefaultSpySpace });
expect(board._toggleStructureBoard).toHaveBeenCalledTimes(1);
expect(board._toggleStructureBoard).toHaveBeenCalledWith();
expect(preventDefaultSpySpace).toHaveBeenCalledTimes(1);
expect(preventDefaultSpyShiftSpace).not.toHaveBeenCalled();
calls[1].args[1]({ preventDefault: preventDefaultSpyShiftSpace });
expect(board._toggleStructureBoard).toHaveBeenCalledTimes(2);
expect(board._toggleStructureBoard).toHaveBeenCalledWith({
useHoveredPlugin: true
});
expect(preventDefaultSpySpace).toHaveBeenCalledTimes(1);
expect(preventDefaultSpyShiftSpace).toHaveBeenCalledTimes(1);
});
it('does not setup key binds if toggler is not availabe', function() {
keyboard.bind.calls.reset();
board.ui.toolbarModeSwitcher.remove();
CMS.StructureBoard._initializeGlobalHandlers();
board = new CMS.StructureBoard();
spyOn(board, 'show').and.callFake(function() {
CMS.settings.mode = 'structure';
});
spyOn(board, 'hide').and.callFake(function() {
CMS.settings.mode = 'edit';
});
expect(keyboard.bind).not.toHaveBeenCalled();
});
it('does not setup key binds if toggler is not visible (there are no placeholders)', function() {
keyboard.bind.calls.reset();
board.ui.placeholders.remove();
board.ui.dragareas.remove();
board.ui.toolbarModeSwitcher.find('.cms-btn').addClass('cms-btn-disabled');
CMS.StructureBoard._initializeGlobalHandlers();
board = new CMS.StructureBoard();
spyOn(board, 'show').and.callFake(function() {
CMS.settings.mode = 'structure';
});
spyOn(board, 'hide').and.callFake(function() {
CMS.settings.mode = 'edit';
});
expect(keyboard.bind).not.toHaveBeenCalled();
});
});
describe('._toggleStructureBoard()', function() {
var board;
beforeEach(function(done) {
fixture.load('plugins.html');
CMS.settings = {
mode: 'edit'
};
CMS.config = {
settings: {
mode: 'edit',
structure: 'structure'
},
mode: 'edit'
};
$(function() {
spyOn(keyboard, 'bind');
CMS.StructureBoard._initializeGlobalHandlers();
board = new CMS.StructureBoard();
spyOn(board, 'show').and.callFake(function() {
CMS.settings.mode = 'structure';
});
spyOn(board, 'hide').and.callFake(function() {
CMS.settings.mode = 'edit';
});
spyOn(board, '_showAndHighlightPlugin').and.returnValue({
then() {}
});
done();
});
});
it('shows structureboard', function() {
board._toggleStructureBoard();
expect(board.show).toHaveBeenCalledTimes(1);
expect(board.hide).not.toHaveBeenCalled();
});
it('hides strucrueboard', function() {
CMS.settings.mode = 'structure';
board._toggleStructureBoard();
expect(board.show).not.toHaveBeenCalled();
expect(board.hide).toHaveBeenCalledTimes(1);
});
it('shows structureboard and highlights plugin', function() {
CMS.settings.mode = 'edit';
board._toggleStructureBoard({ useHoveredPlugin: true });
expect(board._showAndHighlightPlugin).toHaveBeenCalledTimes(1);
});
it('does not show structureboard and highlights plugin if it is open already', function() {
CMS.settings.mode = 'structure';
board._toggleStructureBoard({ useHoveredPlugin: true });
expect(board._showAndHighlightPlugin).not.toHaveBeenCalled();
});
});
describe('._drag()', function() {
var board;
var options;
beforeEach(function(done) {
fixture.load('plugins.html');
CMS.settings = {
mode: 'structure'
};
CMS.config = {
settings: {
mode: 'structure',
structure: '/structure'
},
mode: 'structure'
};
$(function() {
CMS.StructureBoard._initializeGlobalHandlers();
board = new CMS.StructureBoard();
board._drag();
options = board.ui.sortables.nestedSortable('option');
board.show().then(() => {
done();
});
});
});
afterEach(function() {
board.ui.doc.off('keyup.cms.interrupt');
fixture.cleanup();
});
it('initializes nested sortable', function() {
options = board.ui.sortables.nestedSortable('option');
expect(options).toEqual(
jasmine.objectContaining({
items: '> .cms-draggable:not(.cms-drag-disabled):not(.cms-draggable-disabled .cms-draggable)',
placeholder: 'cms-droppable',
connectWith: '.cms-draggables:not(.cms-hidden)',
appendTo: '.cms-structure-content',
listType: 'div.cms-draggables',
doNotClear: true,
toleranceElement: '> div',
disableNestingClass: 'cms-draggable-disabled',
errorClass: 'cms-draggable-disallowed',
start: jasmine.any(Function),
helper: jasmine.any(Function),
beforeStop: jasmine.any(Function),
update: jasmine.any(Function),
isAllowed: jasmine.any(Function)
})
);
});
it('adds event handler for cms-structure-update to actualize empty placeholders', function() {
if (!CMS.$._data(board.ui.sortables[0]).events['cms-structure-update'][0].handler.name) {
pending();
}
expect(board.ui.sortables).toHandle('cms-structure-update');
// cheating here a bit
expect(CMS.$._data(board.ui.sortables[0]).events['cms-structure-update'][0].handler.name).toEqual(
'actualizePlaceholders'
);
});
it('defines how draggable helper is created', function() {
options = board.ui.sortables.nestedSortable('option');
var helper = options.helper;
var item = $(
'<div class="some class string">' +
'<div class="cms-dragitem">Only this will be cloned</div>' +
'<div class="cms-draggables">' +
'<div class="cms-dragitem">This will not</div>' +
'</div>' +
'</div>'
);
var result = helper(null, item);
expect(result).toHaveClass('some');
expect(result).toHaveClass('class');
expect(result).toHaveClass('string');
expect(result).toHaveText('Only this will be cloned');
expect(result).not.toHaveText('This will not');
});
describe('start', function() {
it('sets touchAction style property', function() {
expect(board.ui.content[0].style.touchAction).toEqual('pan-y');
options.start({}, { item: $('<div></div>'), helper: $('<div></div>') });
expect(board.ui.content[0].style.touchAction).toEqual('none');
});
it('sets dragging state', function() {
expect(board.dragging).toEqual(false);
options.start({}, { item: $('<div></div>'), helper: $('<div></div>') });
expect(board.dragging).toEqual(true);
});
it('actualizes empty placeholders', function() {
var firstPlaceholder = board.ui.dragareas.eq(0);
var firstPlaceholderCopyAll = firstPlaceholder.find(
'.cms-dragbar .cms-submenu-item:has(a[data-rel="copy"]):first'
);
var secondPlaceholder = board.ui.dragareas.eq(1);
var secondPlaceholderCopyAll = secondPlaceholder.find(
'.cms-dragbar .cms-submenu-item:has(a[data-rel="copy"]):first'
);
expect(firstPlaceholder).toHaveClass('cms-dragarea-empty');
expect(firstPlaceholderCopyAll).toHaveClass('cms-submenu-item-disabled');
expect(secondPlaceholder).not.toHaveClass('cms-dragarea-empty');
expect(secondPlaceholderCopyAll).not.toHaveClass('cms-submenu-item-disabled');
secondPlaceholder
.find('> .cms-draggables')
.contents()
.appendTo(firstPlaceholder.find('> .cms-draggables'));
options.start({}, { item: $('<div></div>'), helper: $('<div></div>') });
expect(firstPlaceholder).not.toHaveClass('cms-dragarea-empty');
expect(firstPlaceholderCopyAll).not.toHaveClass('cms-submenu-item-disabled');
expect(secondPlaceholder).toHaveClass('cms-dragarea-empty');
expect(secondPlaceholderCopyAll).toHaveClass('cms-submenu-item-disabled');
// now check that the plugin currently being dragged does not count
// towards "plugins count"
firstPlaceholder
.find('> .cms-draggables')
.contents()
.appendTo(secondPlaceholder.find('> .cms-draggables'));
firstPlaceholder
.find('> .cms-draggables')
.append($('<div class="cms-draggable cms-draggable-is-dragging"></div>'));
options.start({}, { item: $('<div></div>'), helper: $('<div></div>') });
expect(firstPlaceholder).toHaveClass('cms-dragarea-empty');
expect(firstPlaceholderCopyAll).toHaveClass('cms-submenu-item-disabled');
expect(secondPlaceholder).not.toHaveClass('cms-dragarea-empty');
expect(secondPlaceholderCopyAll).not.toHaveClass('cms-submenu-item-disabled');
});
it('hides settings menu', function() {
spyOn(CMS.Plugin, '_hideSettingsMenu');
options.start({}, { item: $('<div></div>'), helper: $('<div></div>') });
expect(CMS.Plugin._hideSettingsMenu).toHaveBeenCalledTimes(1);
});
it('shows all the empty sortables', function() {
expect($('.cms-draggables.cms-hidden').length).toEqual(1);
options.start({}, { item: $('<div></div>'), helper: $('<div></div>') });
expect($('.cms-draggables.cms-hidden').length).toEqual(0);
});
it('adds appropriate classes on item without children and helper', function() {
var item = $('<div class="cms-draggable"><div class="cms-dragitem">Some plugin</div></div>');
var helper = options.helper(null, item);
options.start(
{},
{
item: item,
helper: helper
}
);
expect(item).toHaveClass('cms-is-dragging');
expect(helper).toHaveClass('cms-draggable-is-dragging');
});
it('adds appropriate classes on item with children', function() {
var item = $(
'<div class="cms-draggable">' +
'<div class="cms-dragitem">Some plugin</div>' +
'<div class="cms-draggables">' +
'<div></div>' +
'</div>' +
'</div>'
);
var helper = options.helper(null, item);
options.start(
{},
{
item: item,
helper: helper
}
);
expect(helper).toHaveClass('cms-draggable-stack');
});
it('sets up a handler for interrupting dragging with keyboard', function() {
expect(board.ui.doc).not.toHandle('keyup.cms.interrupt');
options.start({}, { item: $('<div></div>'), helper: $('<div></div>') });
expect(board.ui.doc).toHandle('keyup.cms.interrupt');
var spy = jasmine.createSpy();
board.ui.sortables.on('mouseup', spy);
spyOn($.ui.sortable.prototype, '_mouseStop');
var wrongEvent = new $.Event('keyup.cms.interrupt', { keyCode: 1287926834 });
var correctEvent = new $.Event('keyup.cms.interrupt', { keyCode: CMS.KEYS.ESC });
board.state = 'mock';
board.ui.doc.trigger(wrongEvent);
expect(board.state).toEqual('mock');
expect($.ui.sortable.prototype._mouseStop).not.toHaveBeenCalled();
expect(spy).not.toHaveBeenCalled();
board.state = 'mock';
board.ui.doc.trigger(wrongEvent, [true]);
expect(board.state).toEqual(false);
expect($.ui.sortable.prototype._mouseStop).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledTimes(1 + board.ui.sortables.length);
board.ui.doc.off('keyup.cms.interrupt');
options.start({}, { item: $('<div></div>'), helper: $('<div></div>') });
board.state = 'mock';
board.ui.doc.trigger(correctEvent);
expect(board.state).toEqual(false);
expect($.ui.sortable.prototype._mouseStop).toHaveBeenCalledTimes(2);
expect(spy).toHaveBeenCalledTimes((1 + board.ui.sortables.length) * 2);
});
});
describe('beforeStop', function() {
it('sets dragging state to false', function() {
board.dragging = true;
options.beforeStop(null, { item: $('<div></div>') });
expect(board.dragging).toEqual(false);
});
it('removes classes', function() {
var item = $('<div class="cms-is-dragging cms-draggable-stack"></div>');
options.beforeStop(null, { item: item });
expect(item).not.toHaveClass('cms-is-dragging');
expect(item).not.toHaveClass('cms-draggable-stack');
});
it('unbinds interrupt event', function() {
var spy = jasmine.createSpy();
board.ui.doc.on('keyup.cms.interrupt', spy);
options.beforeStop(null, { item: $('<div></div>') });
board.ui.doc.trigger('keyup.cms.interrupt');
expect(spy).not.toHaveBeenCalled();
expect(board.ui.doc).not.toHandle('keyup.cms.interrupt');
});
it('resets touchAction style property', function() {
board.ui.content[0].style.touchAction = '';
options.beforeStop(null, { item: $('<div></div>') });
expect(board.ui.content[0].style.touchAction).toEqual('pan-y');
});
});
describe('update', function() {
it('returns false if it is not possible to update', function() {
board.state = false;
expect(options.update()).toEqual(false);
});
it('actualizes collapsible status', function() {
var textPlugin = $('.cms-draggable-1');
var randomPlugin = $('.cms-draggable-2');
var helper = options.helper(null, textPlugin);
// we need to start first to set a private variable original container
options.start(null, { item: textPlugin, helper: helper });
board.state = true;
expect(randomPlugin.find('> .cms-dragitem')).not.toHaveClass('cms-dragitem-collapsable');
expect(randomPlugin.find('> .cms-dragitem')).not.toHaveClass('cms-dragitem-expanded');
textPlugin.appendTo(randomPlugin.find('.cms-draggables'));
options.update(null, { item: textPlugin, helper: helper });
expect(randomPlugin.find('> .cms-dragitem')).toHaveClass('cms-dragitem-collapsable');
expect(randomPlugin.find('> .cms-dragitem')).toHaveClass('cms-dragitem-expanded');
// and back
options.start(null, { item: textPlugin, helper: helper });
board.state = true;
textPlugin.appendTo($('.cms-dragarea-1').find('> .cms-draggables'));
options.update(null, { item: textPlugin, helper: helper });
expect(randomPlugin.find('> .cms-dragitem')).not.toHaveClass('cms-dragitem-collapsable');
expect(randomPlugin.find('> .cms-dragitem')).toHaveClass('cms-dragitem-expanded');
});
it('returns false if we moved plugin inside same container and the event is fired on the container', () => {
var textPlugin = $('.cms-draggable-1');
var helper = options.helper(null, textPlugin);
var placeholderDraggables = $('.cms-dragarea-1').find('> .cms-draggables');
// and one more time
options.start(null, { item: textPlugin, helper: helper });
board.state = true;
textPlugin.prependTo(placeholderDraggables);
expect(options.update.bind(textPlugin)(null, { item: textPlugin, helper: helper })).toEqual(false);
});
it('triggers event on the plugin when necessary', function() {
var textPlugin = $('.cms-draggable-1');
var randomPlugin = $('.cms-draggable-2');
var helper = options.helper(null, textPlugin);
var placeholderDraggables = $('.cms-dragarea-1').find('> .cms-draggables');
var spy = jasmine.createSpy();
textPlugin.on('cms-plugins-update', spy);
// we need to start first to set a private variable original container
options.start(null, { item: textPlugin, helper: helper });
board.state = true;
textPlugin.appendTo(randomPlugin.find('.cms-draggables'));
options.update(null, { item: textPlugin, helper: helper });
expect(spy).toHaveBeenCalledTimes(1);
// and back
options.start(null, { item: textPlugin, helper: helper });
board.state = true;
textPlugin.appendTo($('.cms-dragarea-1').find('> .cms-draggables'));
options.update(null, { item: textPlugin, helper: helper });
expect(spy).toHaveBeenCalledTimes(2);
// and one more time
options.start(null, { item: textPlugin, helper: helper });
board.state = true;
textPlugin.prependTo(placeholderDraggables);
options.update.bind(placeholderDraggables)(null, { item: textPlugin, helper: helper });
expect(spy).toHaveBeenCalledTimes(3);
});
it('triggers event on the plugin in clipboard', function() {
$(fixture.el).prepend(
'<div class="cms-clipboard">' +
'<div class="cms-clipboard-containers cms-draggables"></div>' +
'</div>'
);
var textPlugin = $('.cms-draggable-1');
var randomPlugin = $('.cms-draggable-2');
var helper = options.helper(null, textPlugin);
textPlugin.prependTo('.cms-clipboard-containers');
var pluginSpy = jasmine.createSpy();
var clipboardSpy = jasmine.createSpy();
textPlugin.on('cms-plugins-update', pluginSpy);
textPlugin.on('cms-paste-plugin-update', clipboardSpy);
// we need to start first to set a private variable original container
options.start(null, { item: textPlugin, helper: helper });
board.state = true;
textPlugin.appendTo(randomPlugin.find('.cms-draggables'));
options.update(null, { item: textPlugin, helper: helper });
expect(pluginSpy).not.toHaveBeenCalled();
expect(clipboardSpy).toHaveBeenCalledTimes(1);
});
it('actualizes empty placeholders', function() {
var firstPlaceholder = board.ui.dragareas.eq(0);
var firstPlaceholderCopyAll = firstPlaceholder.find(
'.cms-dragbar .cms-submenu-item:has(a[data-rel="copy"]):first'
);
var secondPlaceholder = board.ui.dragareas.eq(1);
var secondPlaceholderCopyAll = secondPlaceholder.find(
'.cms-dragbar .cms-submenu-item:has(a[data-rel="copy"]):first'
);
expect(firstPlaceholder).toHaveClass('cms-dragarea-empty');
expect(firstPlaceholderCopyAll).toHaveClass('cms-submenu-item-disabled');
expect(secondPlaceholder).not.toHaveClass('cms-dragarea-empty');
expect(secondPlaceholderCopyAll).not.toHaveClass('cms-submenu-item-disabled');
options.start({}, { item: $('<div></div>'), helper: $('<div></div>') });
secondPlaceholder
.find('> .cms-draggables')
.contents()
.appendTo(firstPlaceholder.find('> .cms-draggables'));
board.state = true;
options.update({}, { item: $('<div class="cms-plugin-1"></div>'), helper: $('<div></div>') });
expect(firstPlaceholder).not.toHaveClass('cms-dragarea-empty');
expect(firstPlaceholderCopyAll).not.toHaveClass('cms-submenu-item-disabled');
expect(secondPlaceholder).toHaveClass('cms-dragarea-empty');
expect(secondPlaceholderCopyAll).toHaveClass('cms-submenu-item-disabled');
// now check that the plugin currently being dragged does not count
// towards "plugins count"
firstPlaceholder
.find('> .cms-draggables')
.contents()
.appendTo(secondPlaceholder.find('> .cms-draggables'));
firstPlaceholder
.find('> .cms-draggables')
.append($('<div class="cms-draggable cms-draggable-is-dragging"></div>'));
options.update({}, { item: $('<div class="cms-plugin-1"></div>'), helper: $('<div></div>') });
expect(firstPlaceholder).toHaveClass('cms-dragarea-empty');
expect(firstPlaceholderCopyAll).toHaveClass('cms-submenu-item-disabled');
expect(secondPlaceholder).not.toHaveClass('cms-dragarea-empty');
expect(secondPlaceholderCopyAll).not.toHaveClass('cms-submenu-item-disabled');
});
it('hides empty sortables', function() {
var textPlugin = $('.cms-draggable-1');
var randomPlugin = $('.cms-draggable-2');
var helper = options.helper(null, textPlugin);
options.start(null, { item: textPlugin, helper: helper });
board.state = true;
expect($('.cms-draggables.cms-hidden').length).toEqual(0);
textPlugin.appendTo(randomPlugin.find('.cms-draggables'));
options.update(null, { item: textPlugin, helper: helper });
expect($('.cms-draggables.cms-hidden').length).toEqual(0);
options.start(null, { item: textPlugin, helper: helper });
board.state = true;
expect($('.cms-draggables.cms-hidden').length).toEqual(0);
textPlugin.appendTo($('.cms-dragarea-1').find('> .cms-draggables'));
options.update(null, { item: textPlugin, helper: helper });
expect($('.cms-draggables.cms-hidden').length).toEqual(1);
});
});
describe('isAllowed', function() {
it('returns false if CMS.API is locked', function() {
CMS.API.locked = true;
board.state = 'mock';
expect(options.isAllowed()).toEqual(false);
expect(board.state).toEqual('mock');
});
it('returns false if there is no item', function() {
CMS.API.locked = false;
board.state = 'mock';
expect(options.isAllowed()).toEqual(false);
expect(board.state).toEqual('mock');
});
it('returns false if item has no settings', function() {
board.state = 'mock';
expect(options.isAllowed(null, null, $('.cms-draggable-1'))).toEqual(false);
expect(board.state).toEqual('mock');
});
it('returns false if parent cannot have children', function() {
board.state = 'mock';
var pluginStructure = $('.cms-draggable-1');
var pluginEdit = $('.cms-plugin-1');
var placeholder = $('.cms-draggables').eq(0);
placeholder.parent().addClass('cms-draggable-disabled');
$('.cms-placeholder-1').remove();
pluginEdit.data('cms', {});
expect(options.isAllowed(placeholder, null, pluginStructure)).toEqual(false);
expect(board.state).toEqual('mock');
});
it('returns false if parent is a clipboard', function() {
board.state = 'mock';
var pluginStructure = $('.cms-draggable-1');
var pluginEdit = $('.cms-plugin-1');
var placeholder = $('.cms-draggables').eq(0);
placeholder.parent().addClass('cms-clipboard-containers');
$('.cms-placeholder-1').remove();
pluginEdit.data('cms', {});
expect(options.isAllowed(placeholder, null, pluginStructure)).toEqual(false);
expect(board.state).toEqual('mock');
});
describe('bounds of a place we put current plugin in', function() {
it('uses placeholder bounds', function() {
board.state = 'mock';
var pluginStructure = $('.cms-draggable-1');
var placeholder = $('.cms-dragarea-1 > .cms-draggables');
var placeholderEdit = $('.cms-placeholder-1');
pluginStructure.data('cms', {});
placeholderEdit.data('cms', { plugin_restriction: ['OnlyThisPlugin'] });
expect(options.isAllowed(placeholder, null, pluginStructure)).toEqual(false);
expect(board.state).toEqual(false);
});
it('uses placeholder bounds', function() {
board.state = 'mock';
var pluginStructure = $('.cms-draggable-1');
var placeholder = $('.cms-dragarea-1 > .cms-draggables');
var placeholderEdit = $('.cms-placeholder-1');
pluginStructure.data('cms', { plugin_type: 'OnlyThisPlugin' });
placeholderEdit.data('cms', { plugin_restriction: ['OnlyThisPlugin'] });
expect(options.isAllowed(placeholder, null, pluginStructure)).toEqual(true);
expect(board.state).toEqual(true);
});
it('uses plugin bounds if pasted into the plugin', function() {
board.state = 'mock';
var pluginStructure = $('.cms-draggable-1');
var parentPluginStructure = $('.cms-draggable-2');
var placeholder = $('.cms-draggable-2 > .cms-draggables');
var placeholderEdit = $('.cms-placeholder-1');
pluginStructure.appendTo(parentPluginStructure.find('> .cms-draggables'));
pluginStructure.data('cms', { plugin_type: 'OtherPlugin' });
parentPluginStructure.data('cms', { plugin_restriction: ['OnlyThisPlugin'] });
placeholderEdit.data('cms', { plugin_restriction: ['OnlyThisPlugin'] });
expect(options.isAllowed(placeholder, null, $('.cms-draggable-1'))).toEqual(false);
expect(board.state).toEqual(false);
});
it('uses plugin bounds if pasted into the plugin', function() {
board.state = 'mock';
var pluginStructure = $('.cms-draggable-1');
var parentPluginStructure = $('.cms-draggable-2');
var placeholder = $('.cms-draggable-2 > .cms-draggables');
var placeholderEdit = $('.cms-placeholder-1');
pluginStructure.appendTo(parentPluginStructure.find('> .cms-draggables'));
pluginStructure.data('cms', { plugin_type: 'OtherPlugin' });
parentPluginStructure.data('cms', { plugin_restriction: ['OnlyThisPlugin'] });
placeholderEdit.data('cms', { plugin_restriction: ['OtherPlugin'] });
expect(options.isAllowed(placeholder, null, $('.cms-draggable-1'))).toEqual(false);
expect(board.state).toEqual(false);
});
it('uses plugin bounds if pasted into the plugin', function() {
board.state = 'mock';
var pluginStructure = $('.cms-draggable-1');
var parentPluginStructure = $('.cms-draggable-2');
var placeholder = $('.cms-draggable-2 > .cms-draggables');
var placeholderEdit = $('.cms-placeholder-1');
pluginStructure.appendTo(parentPluginStructure.find('> .cms-draggables'));
pluginStructure.data('cms', { plugin_type: 'OtherPlugin' });
parentPluginStructure.data('cms', { plugin_restriction: [] });
placeholderEdit.data('cms', { plugin_restriction: ['OnlyThisPlugin'] });
expect(options.isAllowed(placeholder, null, $('.cms-draggable-1'))).toEqual(true);
expect(board.state).toEqual(true);
});
it('uses placeholderParent bounds', function() {
board.state = 'mock';
var pluginStructure = $('.cms-draggable-1');
var parentPluginStructure = $('.cms-draggable-2');
var placeholder = $('.cms-draggable-2 > .cms-draggables');
var placeholderEdit = $('.cms-placeholder-1');
pluginStructure.appendTo(parentPluginStructure.find('> .cms-draggables'));
pluginStructure.data('cms', { plugin_type: 'OtherPlugin' });
parentPluginStructure.data('cms', { plugin_restriction: [] });
placeholderEdit.data('cms', { plugin_restriction: ['OnlyThisPlugin'] });
// it's important that placeholder is used, and not .cms-draggable-1
expect(options.isAllowed($('.cms-draggable-1'), placeholder, $('.cms-draggable-1'))).toEqual(true);
expect(board.state).toEqual(true);
});
});
it('blocks a plugin that the target placeholder does not allow at its root', function() {
// A plugin that requires a parent is excluded from the placeholder's root list
// server-side, so it is blocked at the root purely by the bounds (child list) check.
board.state = 'mock';
var pluginStructure = $('.cms-draggable-1');
var placeholder = $('.cms-dragarea-1 > .cms-draggables');
var placeholderEdit = $('.cms-placeholder-1');
pluginStructure.data('cms', { plugin_type: 'OtherPlugin' });
placeholderEdit.data('cms', { plugin_restriction: ['OnlyRootPlugin'] });
expect(options.isAllowed(placeholder, null, pluginStructure)).toEqual(false);
expect(board.state).toEqual(false);
});
});
});
describe('invalidateState', () => {
let board;
beforeEach(done => {
fixture.load('plugins.html');
board = new CMS.StructureBoard();
rewire('Plugin', FakePlugin);
spyOn(StructureBoard, 'actualizePluginCollapseStatus');
spyOn(StructureBoard, 'actualizePlaceholders');
spyOn(StructureBoard, 'actualizePluginsCollapsibleStatus');
spyOn(Plugin, '_updateClipboard');
spyOn(board, '_drag');
setTimeout(() => {
done();
}, 20);
});
afterEach(() => {
fixture.cleanup();
resetRewire('Plugin');
});
describe('itself', () => {
beforeEach(() => {
spyOn(CMS.API.Helpers, 'reloadBrowser');
CMS.API.Toolbar = {
_refreshMarkup: jasmine.createSpy()
};
spyOn(board, 'handleAddPlugin');
spyOn(board, 'handleEditPlugin');
spyOn(board, 'handleDeletePlugin');
spyOn(board, 'handleClearPlaceholder');
spyOn(board, 'handleCopyPlugin');
spyOn(board, 'handleMovePlugin');
spyOn(board, 'handleCutPlugin');
spyOn(board, '_loadToolbar').and.returnValue({
done() {
return {
fail() {}
};
}
});
spyOn(board, '_requestMode').and.returnValue({
done() {
return {
fail() {}
};
}
});
spyOn(board, 'refreshContent');
});
it('delegates to correct methods', () => {
board.invalidateState('ADD', { randomData: 1 });
expect(board.handleAddPlugin).toHaveBeenCalledWith({ randomData: 1 });
board.invalidateState('MOVE', { randomData: 1 });
expect(board.handleMovePlugin).toHaveBeenCalledWith({ randomData: 1 });
board.invalidateState('COPY', { randomData: 1 });
expect(board.handleCopyPlugin).toHaveBeenCalledWith({ randomData: 1 });
board.invalidateState('PASTE', { randomData: 1 });
expect(board.handleMovePlugin).toHaveBeenCalledWith({ randomData: 1 });
board.invalidateState('CUT', { randomData: 1 });
expect(board.handleCutPlugin).toHaveBeenCalledWith({ randomData: 1 });
board.invalidateState('EDIT', { randomData: 1 });
expect(board.handleEditPlugin).toHaveBeenCalledWith({ randomData: 1 });
board.invalidateState('DELETE', { randomData: 1 });
expect(board.handleDeletePlugin).toHaveBeenCalledWith({ randomData: 1 });
board.invalidateState('CLEAR_PLACEHOLDER', { randomData: 1 });
expect(board.handleClearPlaceholder).toHaveBeenCalledWith({ randomData: 1 });
expect(board.handleAddPlugin).toHaveBeenCalledTimes(1);
expect(board.handleCopyPlugin).toHaveBeenCalledTimes(1);
expect(board.handleMovePlugin).toHaveBeenCalledTimes(2);
expect(board.handleCutPlugin).toHaveBeenCalledTimes(1);
expect(board.handleEditPlugin).toHaveBeenCalledTimes(1);
expect(board.handleDeletePlugin).toHaveBeenCalledTimes(1);
expect(board.handleClearPlaceholder).toHaveBeenCalledTimes(1);
});
it('reloads browser if there was no action', () => {
board.invalidateState();
expect(CMS.API.Helpers.reloadBrowser).toHaveBeenCalledWith();
expect(board._loadToolbar).not.toHaveBeenCalled();
});
it('reloads toolbar if there is an action', () => {
board.invalidateState('ADD', { randomData: 1 });
board.invalidateState('MOVE', { randomData: 1 });
board.invalidateState('COPY', { randomData: 1 });
board.invalidateState('PASTE', { randomData: 1 });
board.invalidateState('CUT', { randomData: 1 });
board.invalidateState('EDIT', { randomData: 1 });
board.invalidateState('DELETE', { randomData: 1 });
board.invalidateState('CLEAR_PLACEHOLDER', { randomData: 1 });
expect(board._loadToolbar).toHaveBeenCalledTimes(8);
board.invalidateState('x');
expect(board._loadToolbar).toHaveBeenCalledTimes(9);
});
it('refreshes markup if loading markup succeeds', () => {
board._loadToolbar.and.returnValue({
done(fn) {
fn();
return {
fail() {}
};
}
});
board.invalidateState('x');
expect(CMS.API.Toolbar._refreshMarkup).toHaveBeenCalled();
});
it('reloads if fails', () => {
board._loadToolbar.and.returnValue({
done() {
return {
fail(fn) {
fn();
}
};
}
});
board.invalidateState('x');
expect(CMS.API.Helpers.reloadBrowser).toHaveBeenCalled();
});
it('resets the requested content', () => {
const args = ['random', 'stuff'];
expect(CMS.settings.mode).toEqual('structure');
board._loadedContent = false;
board._requestMode.and.callFake(mode => {
expect(mode).toEqual('content');
return {
done(fn) {
fn(...args);
return { fail() {} };
}
};
});
board._requestcontent = '1';
board.invalidateState('x');
expect(board._requestcontent).toEqual(null);
expect(board.refreshContent).not.toHaveBeenCalled();
});
it('refreshes content mode if needed', () => {
const args = ['random', 'stuff'];
expect(CMS.settings.mode).toEqual('structure');
board._loadedContent = true;
board._requestMode.and.callFake(mode => {
expect(mode).toEqual('content');
return {
done(fn) {
fn(...args);
return { fail() {} };
}
};
});
board.invalidateState('x');
expect(board.refreshContent).toHaveBeenCalledWith('random');
});
it('reloads if it cannot refresh content', () => {
const args = ['random', 'stuff'];
expect(CMS.settings.mode).toEqual('structure');
board._loadedContent = true;
board._requestMode.and.callFake(mode => {
expect(mode).toEqual('content');
return {
done() {
return {
fail(fn) {
fn(...args);
}
};
}
};
});
board.invalidateState('x');
expect(CMS.API.Helpers.reloadBrowser).toHaveBeenCalledWith();
});
it('refreshes content if in content mode', () => {
const args = ['random', 'stuff'];
CMS.settings.mode = 'edit';
board._loadedContent = true;
board._requestMode.and.callFake(mode => {
expect(mode).toEqual('content');
expect(board._requestcontent).toEqual(null);
return {
done(fn) {
fn(...args);
return {
fail() {}
};
}
};
});
board.invalidateState('x');
expect(board._requestMode).toHaveBeenCalledWith('content');
expect(board.refreshContent).toHaveBeenCalledWith('random');
});
it('reloads if cant refresh content when in content mode', () => {
const args = ['random', 'stuff'];
CMS.settings.mode = 'edit';
board._loadedContent = false;
board._requestMode.and.callFake(mode => {
expect(mode).toEqual('content');
expect(board._requestcontent).toEqual(null);
return {
done() {
return {
fail(fn) {
fn(...args);
}
};
}
};
});
board.invalidateState('x');
expect(board._requestMode).toHaveBeenCalledWith('content');
expect(CMS.API.Helpers.reloadBrowser).toHaveBeenCalledWith();
});
});
describe('handleMovePlugin', () => {
it('replaces markup with given one', () => {
const data = {
plugin_parent: false,
plugin_id: 1,
html: '<div class="new-draggable"><div class="cms-draggables"></div></div>',
plugins: [{ plugin_id: 1, otherStuff: true }]
};
board.handleMovePlugin(data);
expect($('.cms-draggable-1')).not.toExist();
expect($('.new-draggable')).toExist();
expect(StructureBoard.actualizePlaceholders).toHaveBeenCalled();
expect(FakePlugin._updateRegistry).toHaveBeenCalledWith(data.plugins);
expect(StructureBoard.actualizePluginCollapseStatus).toHaveBeenCalledTimes(1);
expect(StructureBoard.actualizePluginCollapseStatus).toHaveBeenCalledWith(1);
expect(board._drag).toHaveBeenCalled();
expect(board.ui.sortables.filter('.new-draggable .cms-draggables')).toExist();
});
it('replaces markup with given one when parent is also provided', () => {
const data = {
plugin_parent: 2,
plugin_id: 1,
html: `
<div class="new-draggable ">
<div class="cms-draggables"><div class="cms-draggable-1"></div></div>
</div>
`,
plugins: [{ plugin_id: 1, otherStuff: true }]
};
board.handleMovePlugin(data);
expect($('.cms-draggable-1')).toExist();
expect($('.cms-draggable-2')).not.toExist();
expect($('.new-draggable')).toExist();
expect(StructureBoard.actualizePlaceholders).toHaveBeenCalled();
expect(FakePlugin._updateRegistry).toHaveBeenCalledWith(data.plugins);
expect(StructureBoard.actualizePluginCollapseStatus).toHaveBeenCalledTimes(1);
expect(StructureBoard.actualizePluginCollapseStatus).toHaveBeenCalledWith(1);
expect(board._drag).toHaveBeenCalled();
expect(board.ui.sortables.filter('.new-draggable .cms-draggables')).toExist();
});
it('replaces markup with given one when it is copying from language', () => {
const data = {
target_placeholder_id: 2,
html: '<div class="new-draggable"><div class="cms-draggables"></div></div>',
plugins: [{ plugin_id: 1, otherStuff: true }]
};
board.handleMovePlugin(data);
expect($('.cms-draggable-1')).toExist();
expect($('.cms-draggable-2')).toExist();
expect($('.new-draggable')).toExist();
expect(StructureBoard.actualizePlaceholders).toHaveBeenCalled();
expect(FakePlugin._updateRegistry).toHaveBeenCalledWith(data.plugins);
expect(StructureBoard.actualizePluginCollapseStatus).toHaveBeenCalledTimes(1);
expect(StructureBoard.actualizePluginCollapseStatus).toHaveBeenCalledWith(1);
expect(board._drag).toHaveBeenCalled();
expect(board.ui.sortables.filter('.cms-dragarea-2 .new-draggable .cms-draggables')).toExist();
});
it('handles top level move update when in same placeholder', () => {
const data = {
placeholder_id: 1,
plugin_id: 2,
plugin_order: ['2', '1'],
html: `
<div class="cms-draggable cms-draggable-2 new-cms-draggable-2">
</div>
`,
plugins: [{ plugin_id: 2, otherStuff: true }, { plugin_id: 1, otherStuff: true }]
};
board.handleMovePlugin(data);
expect($('.cms-draggable-1')).toExist();
expect($('.cms-draggable-2')).toExist();
expect(StructureBoard.actualizePlaceholders).toHaveBeenCalled();
expect(FakePlugin._updateRegistry).toHaveBeenCalledWith(data.plugins);
expect(StructureBoard.actualizePluginCollapseStatus).toHaveBeenCalledTimes(2);
expect(StructureBoard.actualizePluginCollapseStatus).toHaveBeenCalledWith(1);
expect(StructureBoard.actualizePluginCollapseStatus).toHaveBeenCalledWith(2);
expect(board._drag).toHaveBeenCalled();
expect($('.new-cms-draggable-2')).toExist();
expect($('.cms-draggable-2').index()).toEqual(0); // account for "empty" message
expect($('.cms-draggable-1').index()).toEqual(2);
});
it('handles top level move update when in same placeholder', () => {
const data = {
placeholder_id: 1,
plugin_id: 1,
plugin_order: ['2', '1'],
html: `
<div class="cms-draggable cms-draggable-1 new-cms-draggable-1">
</div>
`,
plugins: [{ plugin_id: 2, otherStuff: true }, { plugin_id: 1, otherStuff: true }]
};
board.handleMovePlugin(data);
expect($('.cms-draggable-1')).toExist();
expect($('.cms-draggable-2')).toExist();
expect(StructureBoard.actualizePlaceholders).toHaveBeenCalled();
expect(FakePlugin._updateRegistry).toHaveBeenCalledWith(data.plugins);
expect(StructureBoard.actualizePluginCollapseStatus).toHaveBeenCalledTimes(2);
expect(StructureBoard.actualizePluginCollapseStatus).toHaveBeenCalledWith(1);
expect(StructureBoard.actualizePluginCollapseStatus).toHaveBeenCalledWith(2);
expect(board._drag).toHaveBeenCalled();
expect($('.new-cms-draggable-1')).toExist();
expect($('.cms-draggable-2').index()).toEqual(1);
expect($('.cms-draggable-1').index()).toEqual(2);
});
});
describe('handleAddPlugin', () => {
it('replaces parent if provided', () => {
const data = {
plugin_parent: 2,
plugin_id: 3,
structure: {
html: `
<div class="cms-draggable-2 new">
<div class="cms-draggables">
<div class="cms-draggable-3 new-draggable">
<div class="cms-draggables"></div>
</div>
</div>
</div>
`,
plugins: [{ plugin_id: 2, otherStuff: true }, { plugin_id: 3, other_stuff: false }]
}
};
board.handleAddPlugin(data);
expect($('.cms-draggable-2')).toHaveClass('new');
expect($('.new-draggable')).toExist();
expect(StructureBoard.actualizePlaceholders).toHaveBeenCalled();
expect(FakePlugin._updateRegistry).toHaveBeenCalledWith(data.structure.plugins);
expect(StructureBoard.actualizePluginCollapseStatus).toHaveBeenCalledTimes(2);
expect(StructureBoard.actualizePluginCollapseStatus).toHaveBeenCalledWith(2);
expect(StructureBoard.actualizePluginCollapseStatus).toHaveBeenCalledWith(3);
expect(board._drag).toHaveBeenCalled();
expect(board.ui.sortables.filter('.new-draggable .cms-draggables')).toExist();
});
it('appends to placeholder if no parent', () => {
const data = {
plugin_parent: null,
placeholder_id: 2,
plugin_id: 3,
structure: {
html: `
<div class="cms-draggable-3 new-draggable">
<div class="cms-draggables"></div>
</div>
`,
plugins: [{ plugin_id: 3, other_stuff: false }]
}
};
board.handleAddPlugin(data);
expect($('.cms-draggable-2')).not.toHaveClass('new');
expect($('.cms-dragarea-2 > .cms-draggables > .new-draggable')).toBeInDOM();
expect(StructureBoard.actualizePlaceholders).toHaveBeenCalled();
expect(FakePlugin._updateRegistry).toHaveBeenCalledWith(data.structure.plugins);
expect(StructureBoard.actualizePluginCollapseStatus).toHaveBeenCalledTimes(1);
expect(StructureBoard.actualizePluginCollapseStatus).toHaveBeenCalledWith(3);
expect(board._drag).toHaveBeenCalled();
expect(board.ui.sortables.filter('.new-draggable .cms-draggables')).toExist();
});
});
describe('handleEditPlugin', () => {
it('replaces edited plugin with new markup', () => {
const data = {
plugin_parent: null,
plugin_id: 2,
structure: {
html: `
<div class="cms-draggable-3 new-draggable">
<div class="cms-draggables"></div>
</div>
`,
plugins: [{ plugin_id: 3, other_stuff: false }]
}
};
board.handleEditPlugin(data);
expect($('.cms-draggable-2')).not.toExist();
expect($('.new-draggable')).toBeInDOM();
expect(StructureBoard.actualizePlaceholders).not.toHaveBeenCalled();
expect(FakePlugin._updateRegistry).toHaveBeenCalledWith(data.structure.plugins);
expect(StructureBoard.actualizePluginCollapseStatus).toHaveBeenCalledTimes(1);
expect(StructureBoard.actualizePluginCollapseStatus).toHaveBeenCalledWith(3);
expect(board._drag).toHaveBeenCalled();
expect(board.ui.sortables.filter('.new-draggable .cms-draggables')).toExist();
});
it('replaces parent of edited plugin if it was passed', () => {
const data = {
plugin_parent: 2,
plugin_id: 3,
structure: {
html: `
<div class="cms-draggable-3 new-draggable">
<div class="cms-draggables"></div>
</div>
`,
plugins: [{ plugin_id: 3, other_stuff: false }]
}
};
board.handleEditPlugin(data);
expect($('.cms-draggable-2')).not.toExist();
expect($('.new-draggable')).toBeInDOM();
expect(StructureBoard.actualizePlaceholders).not.toHaveBeenCalled();
expect(FakePlugin._updateRegistry).toHaveBeenCalledWith(data.structure.plugins);
expect(StructureBoard.actualizePluginCollapseStatus).toHaveBeenCalledTimes(1);
expect(StructureBoard.actualizePluginCollapseStatus).toHaveBeenCalledWith(3);
expect(board._drag).toHaveBeenCalled();
expect(board.ui.sortables.filter('.new-draggable .cms-draggables')).toExist();
});
});
describe('handleDeletePlugin', () => {
it('removes plugin', () => {
CMS._plugins = [['cms-plugin-1', { plugin_id: 1 }], ['cms-plugin-2']];
CMS._instances = [{ options: { plugin_id: 1 } }, { options: { plugin_id: 2 } }];
board.handleDeletePlugin({ plugin_id: 1 });
expect($('.cms-draggable-1')).not.toBeInDOM();
expect(StructureBoard.actualizePluginsCollapsibleStatus).toHaveBeenCalledWith(
$('.cms-dragarea-1 > .cms-draggables')
);
expect(CMS._plugins).toEqual([['cms-plugin-2']]);
expect(CMS._instances).toEqual([{ options: { plugin_id: 2 } }]);
});
it('removes plugin and its children', () => {
CMS._plugins = [['cms-plugin-3', { plugin_id: 3 }], ['cms-plugin-4']];
CMS._instances = [{ options: { plugin_id: 3 } }, { options: { plugin_id: 4 } }];
$('.cms-draggable-2').find('.cms-draggables').append(`
<div class="cms-draggable cms-draggable-3">
<div class="cms-draggables">
<div class="cms-draggable cms-draggable-4">
</div>
</div>
</div>
`);
expect($('.cms-draggable-3')).toBeInDOM();
expect($('.cms-draggable-4')).toBeInDOM();
board.handleDeletePlugin({ plugin_id: 3 });
expect($('.cms-draggable-3')).not.toBeInDOM();
expect($('.cms-draggable-4')).not.toBeInDOM();
expect(StructureBoard.actualizePluginsCollapsibleStatus).toHaveBeenCalledWith(
$('.cms-draggable-2 > .cms-draggables')
);
expect(CMS._plugins).toEqual([]);
expect(CMS._instances).toEqual([]);
});
});
describe('handleClearPlaceholder', () => {
it('clears placeholder', () => {
CMS._plugins = [
['cms-plugin-3'],
['cms-plugin-1', { plugin_id: 1, placeholder_id: 1 }],
['cms-plugin-2', { plugin_id: '2', placeholder_id: 1 }]
];
CMS._instances = [
{ options: { plugin_id: 3 } },
{ options: { plugin_id: 1, placeholder_id: 1 } },
{ options: { plugin_id: '2', placeholder_id: '1' } }
];
expect($('.cms-draggable-1')).toBeInDOM();
expect($('.cms-draggable-2')).toBeInDOM();
board.handleClearPlaceholder({ placeholder_id: 1 });
expect($('.cms-draggable-1')).not.toBeInDOM();
expect($('.cms-draggable-2')).not.toBeInDOM();
expect(StructureBoard.actualizePluginsCollapsibleStatus).not.toHaveBeenCalled();
expect(StructureBoard.actualizePlaceholders).toHaveBeenCalled();
expect(CMS._plugins).toEqual([['cms-plugin-3']]);
expect(CMS._instances).toEqual([{ options: { plugin_id: 3 } }]);
});
});
describe('handleCopyPlugin', () => {
const clipboardConstructor = jasmine.createSpy();
const close = jasmine.createSpy();
// Use a function constructor instead of class to allow prototype mutation in tests
function FakeClipboard(...args) {
clipboardConstructor(...args);
}
// Assign methods without replacing the non-writable ES6 class prototype
Object.assign(FakeClipboard.prototype, {
_isClipboardModalOpen: jasmine.createSpy(),
modal: {
close: jasmine.createSpy()
},
populate: jasmine.createSpy(),
_enableTriggers: jasmine.createSpy()
});
beforeEach(() => {
CMS.API.Clipboard = {
_isClipboardModalOpen: jasmine.createSpy(),
modal: {
close
},
populate: jasmine.createSpy(),
_enableTriggers: jasmine.createSpy()
};
rewire('Clipboard', FakeClipboard);
rewire('Plugin', FakePlugin);
fixture.load('clipboard.html');
});
afterEach(() => {
resetRewire('Clipboard');
resetRewire('Plugin');
});
it('updates the clipboard', () => {
const data = {
plugins: [{ plugin_id: 10, stuff: true }],
html: '<div class="new-clipboard-draggable cms-draggable-from-clipboard"></div>'
};
CMS._plugins = [];
CMS._instances = [];
board.handleCopyPlugin(data);
expect(CMS._plugins).toEqual([['cms-plugin-10', data.plugins[0]]]);
expect(CMS._instances).toEqual([jasmine.any(FakePlugin)]);
expect(FakePlugin._updateClipboard).toHaveBeenCalled();
expect(clipboardConstructor).toHaveBeenCalled();
expect(FakeClipboard.prototype.populate).toHaveBeenCalled();
expect(FakeClipboard.prototype._enableTriggers).toHaveBeenCalled();
expect(board.ui.sortables.find('.new-clipboard-draggable')).toExist();
expect(board._drag).toHaveBeenCalled();
expect(pluginConstructor).toHaveBeenCalledWith('cms-plugin-10', data.plugins[0]);
expect(CMS.API.Clipboard.modal.close).not.toHaveBeenCalled();
});
it('closes clipboard modal if needed', () => {
CMS.API.Clipboard._isClipboardModalOpen.and.returnValue(true);
const data = {
plugins: [{ plugin_id: 10, stuff: true }],
html: '<div class="new-clipboard-draggable cms-draggable-from-clipboard"></div>'
};
board.handleCopyPlugin(data);
expect(close).toHaveBeenCalled();
});
});
describe('handleCutPlugin', () => {
it('calls delete and copy', () => {
spyOn(board, 'handleDeletePlugin');
spyOn(board, 'handleCopyPlugin');
board.handleCutPlugin({ randomData: true });
expect(board.handleDeletePlugin).toHaveBeenCalledWith({ randomData: true });
expect(board.handleCopyPlugin).toHaveBeenCalledWith({ randomData: true });
});
});
});
describe('_loadToolbar', () => {
it('loads toolbar url', () => {
const board = new StructureBoard();
spyOn($, 'ajax');
CMS.config.settings.structure = '/structure';
CMS.config.settings.edit = '/edit';
CMS.config.settings.mode = 'edit';
CMS.config.request = {
toolbar: 'TOOLBAR_URL',
pk: 100,
model: 'cms.page',
language: 'en'
};
board._loadToolbar();
expect($.ajax).toHaveBeenCalledWith({
url: jasmine.stringMatching(
/TOOLBAR_URL\?obj_id=100&obj_type=cms.page&language=en&cms_path=%2Fstructure*/
)
});
});
});
describe('_requestMode', () => {
let board;
let request;
const preloadImages = jasmine.createSpy();
beforeEach(() => {
board = new StructureBoard();
rewire('preloadImagesFromMarkup', preloadImages);
});
afterEach(() => {
resetRewire('preloadImagesFromMarkup');
preloadImages.calls.reset();
});
it('requests content', () => {
request = {
then(fn) {
fn('markup');
return request;
}
};
spyOn($, 'ajax').and.callFake(req => {
expect(req.method).toEqual('GET');
expect(req.url).toEqual('/edit');
return request;
});
expect(board._requestcontent).not.toBeDefined();
board._requestMode('content');
expect(preloadImages).toHaveBeenCalledWith('markup');
expect(board._requestcontent).toBe(request);
});
it('requests structure', () => {
request = {
then(fn) {
fn('markup');
return request;
}
};
spyOn($, 'ajax').and.callFake(req => {
expect(req.method).toEqual('GET');
expect(req.url).toEqual('/structure');
return request;
});
expect(board._requeststructure).not.toBeDefined();
board._requestMode('structure');
expect(preloadImages).toHaveBeenCalledWith('markup');
expect(board._requeststructure).toBe(request);
});
it('reuses same promise if it was not reset', () => {
request = {
then(fn) {
fn('markup');
return request;
}
};
spyOn($, 'ajax').and.callFake(req => {
expect(req.method).toEqual('GET');
expect(req.url).toEqual('/edit');
return request;
});
expect(board._requestcontent).not.toBeDefined();
board._requestMode('content');
expect(preloadImages).toHaveBeenCalledWith('markup');
expect(board._requestcontent).toBe(request);
board._requestMode('content');
expect(preloadImages).toHaveBeenCalledTimes(1);
expect(board._requestcontent).toBe(request);
});
});
describe('_loadContent', () => {
let board;
let response = '';
let requestSucceeded;
beforeEach(() => {
requestSucceeded = jasmine.createSpy();
CMS.API.Toolbar = {
_refreshMarkup: jasmine.createSpy()
};
board = new StructureBoard();
CMS.config = {
settings: {
mode: 'structure'
}
};
spyOn(board, '_requestMode').and.returnValue({
done(fn) {
requestSucceeded();
fn(response);
return {
fail() {
return {
then(callback) {
callback();
}
};
}
};
}
});
rewire('Plugin', FakePlugin);
rewire('Plugin', FakePlugin);
});
afterEach(() => {
resetRewire('Plugin');
resetRewire('Plugin', FakePlugin);
});
it('resolves immediately when content mode is already loaded', done => {
CMS.config.settings.mode = 'edit';
board._loadContent().then(() => {
expect(requestSucceeded).not.toHaveBeenCalled();
done();
});
CMS.config.settings.mode = 'structure';
});
it('resolves immediately when content mode is already loaded', done => {
board._loadedContent = true;
board._loadContent().then(() => {
expect(requestSucceeded).not.toHaveBeenCalled();
done();
});
CMS.config.settings.mode = 'structure';
});
it('requests content', done => {
response = `
<html attr0="a" attr1="b">
<head>
<title>i am a new title</title>
</head>
<body attr2="x" attr3="y">
<p class="new-content">New body content yay</p>
</body>
</html>
`;
CMS._instances = [
new FakePlugin('cms-plugin-1', { plugin_id: 1, type: 'plugin' }),
new FakePlugin('cms-placeholder-1', { placeholder_id: 1, type: 'placeholder' }),
new FakePlugin('cms-plugin-existing-generic-1', { plugin_id: '1', type: 'generic' })
];
CMS._plugins = [
['cms-plugin-1', { plugin_id: 1, type: 'plugin' }],
['cms-plugin-1', { plugin_id: 1, type: 'plugin' }],
['cms-placeholder-1', { placeholder_id: 1, type: 'placeholder' }],
['cms-plugin-existing-generic-1', { plugin_id: 1, type: 'generic' }],
['cms-plugin-new-generic-2', { plugin_id: 2, type: 'generic' }]
];
expect(board._loadedContent).not.toBeDefined();
pluginConstructor.calls.reset();
// FakePlugin._refreshPlugins() delegates to the real implementation,
// and that one instantiates cms.plugins' own reference to itself -
// something vi.mock cannot swap, so the instances it creates are real
// plugins and the spies have to sit on the real prototype.
spyOn(originalPlugin.prototype, '_setupUI').and.callFake(function() {
const el = $('<div></div>');
el.data('cms', []);
this.ui = { container: el };
});
spyOn(originalPlugin.prototype, '_ensureData');
spyOn(originalPlugin.prototype, '_setGeneric');
spyOn(originalPlugin.prototype, '_setPluginContentEvents');
const instancesBefore = CMS._instances.length;
board._loadContent().then(() => {
expect(showLoader).toHaveBeenCalled();
expect(hideLoader).toHaveBeenCalled();
expect(CMS.API.Toolbar._refreshMarkup).toHaveBeenCalled();
expect($('html').attr('attr0')).toEqual('a');
expect($('html').attr('attr1')).toEqual('b');
expect($('body').attr('attr2')).toEqual('x');
expect($('body').attr('attr3')).toEqual('y');
expect($('.new-content')).toBeInDOM();
expect(FakePlugin.prototype._ensureData).toHaveBeenCalledTimes(3);
expect(originalPlugin.prototype._ensureData).toHaveBeenCalledTimes(1);
expect(originalPlugin.prototype._setGeneric).toHaveBeenCalledTimes(1);
expect(FakePlugin.prototype._setPluginContentEvents).toHaveBeenCalledTimes(1);
expect(CMS._instances.length).toEqual(instancesBefore + 1);
$('.new-content').remove();
expect(board._loadedContent).toBe(true);
done();
});
});
});
describe('_loadStructure', () => {
let board;
let response = '';
let requestSucceeded;
beforeEach(() => {
requestSucceeded = jasmine.createSpy();
CMS.API.Toolbar = {
_refreshMarkup: jasmine.createSpy()
};
board = new StructureBoard();
CMS.config = {
settings: {
mode: 'edit'
}
};
spyOn(StructureBoard, '_initializeGlobalHandlers');
spyOn(StructureBoard, 'actualizePlaceholders');
spyOn(StructureBoard, '_initializeDragItemsStates');
spyOn(board, '_drag');
spyOn(board, '_requestMode').and.returnValue({
done(fn) {
requestSucceeded();
fn(response);
return {
fail() {
return {
then(callback) {
callback();
}
};
}
};
}
});
rewire('Plugin', FakePlugin);
});
afterEach(() => {
resetRewire('Plugin');
});
it('resolves immediately when structure mode is already loaded', done => {
CMS.config.settings.mode = 'structure';
board._loadStructure().then(() => {
expect(requestSucceeded).not.toHaveBeenCalled();
done();
});
CMS.config.settings.mode = 'edit';
});
it('resolves immediately when structure mode is already loaded', done => {
board._loadedStructure = true;
board._loadStructure().then(() => {
expect(requestSucceeded).not.toHaveBeenCalled();
done();
});
CMS.config.settings.mode = 'edit';
});
it('requests structure', done => {
response = `
<html attr0="a" attr1="b">
<head>
<title>i am a new title</title>
</head>
<body attr2="x" attr3="y">
<p class="new-content">New body content yay</p>
</body>
</html>
`;
CMS._instances = [
new FakePlugin('cms-plugin-1', { plugin_id: 1, type: 'plugin' }),
new FakePlugin('cms-placeholder-1', { placeholder_id: 1, type: 'placeholder' }),
new FakePlugin('cms-plugin-existing-generic-1', { plugin_id: '1', type: 'generic' })
];
expect(board._loadedStructure).not.toBeDefined();
pluginConstructor.calls.reset();
board._loadStructure().then(() => {
expect(showLoader).toHaveBeenCalled();
expect(hideLoader).toHaveBeenCalled();
expect(CMS.API.Toolbar._refreshMarkup).toHaveBeenCalled();
expect(FakePlugin.prototype._setPlaceholder).toHaveBeenCalledTimes(1);
expect(FakePlugin.prototype._setPluginStructureEvents).toHaveBeenCalledTimes(1);
expect(FakePlugin.prototype._setGeneric).not.toHaveBeenCalled();
expect(pluginConstructor).not.toHaveBeenCalled();
expect(board._drag).toHaveBeenCalled();
expect(StructureBoard._initializeDragItemsStates).toHaveBeenCalled();
expect(StructureBoard._initializeGlobalHandlers).toHaveBeenCalled();
expect(StructureBoard.actualizePlaceholders).toHaveBeenCalled();
expect(board._loadedStructure).toBe(true);
done();
});
});
});
describe('_getPluginDataFromMarkup()', () => {
const wrap = markup => $(`<div>${markup}</div>`)[0];
[
{
args: [wrap(''), [1, 2, 3]],
expected: []
},
{
args: [wrap('whatever'), []],
expected: []
},
{
args: [wrap('<script id="cms-plugin-4">{"plugin_id":"4"}</script>'), [1, 2, 3]],
expected: []
},
{
args: [wrap('<script id="cms-plugin-4">{"plugin_id":"4"}</script>'), [1, 2, 4]],
expected: [{ plugin_id: '4' }]
},
{
args: [
wrap('<script id="cms-plugin-4">{"plugin_id":"4"}</script>' +
'<script id="cms-plugin-10">{"plugin_id":"meh"}</script>'),
[1, 2, 10]],
expected: [{ plugin_id: 'meh' }]
},
{
args: [wrap('<script id="cms-plugin-4">{plugin_id:"4"}</script>'), [4]],
expected: []
},
{
args: [wrap('<script id="cms-plugin-4">not a json :(]</script>'), [4]],
expected: []
},
{
args: [wrap('<script id="cms-plugin-4">{"something":1}</script>'), [4]],
expected: [{ something: 1 }]
}
].forEach((test, i) => {
it(`extracts plugin data from markup ${i}`, () => {
expect(StructureBoard._getPluginDataFromMarkup(...test.args)).toEqual(test.expected);
});
});
});
describe('_extractMessages()', () => {
let board;
beforeEach(() => {
board = new StructureBoard();
});
it('extracts messages', () => {
expect(
board._extractMessages(
$(`
<div>
<div data-cms-messages-container>
<div data-cms-message data-cms-message-tags="error">
Error
</div>
<div data-cms-message data-cms-message-tags="invalid">
Normal
</div>
<div data-cms-message>
</div>
</div>
</div>
`)
)
).toEqual([{ message: 'Error', error: true }, { message: 'Normal', error: false }]);
expect(
board._extractMessages(
$(`
<div>
<ul class="messagelist"></ul>
<div data-cms-messages-container>
<div data-cms-message data-cms-message-tags="error">
Error1
</div>
<div data-cms-message data-cms-message-tags="invalid">
Normal1
</div>
</div>
</div>
`)
)
).toEqual([{ message: 'Error1', error: true }, { message: 'Normal1', error: false }]);
expect(
board._extractMessages(
$(`
<div>
<ul class="messagelist">
<li class="whatever">normal message</li>
<li class="error">error message</li>
</ul>
</div>
`)
)
).toEqual([{ message: 'normal message', error: false }, { message: 'error message', error: true }]);
expect(
board._extractMessages(
$(`
<div>
</div>
`)
)
).toEqual([]);
});
});
describe('_refreshContent()', () => {
let board;
const diffDOMConstructor = jasmine.createSpy();
const newDoc = `
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<div class="new-markup">new markup</div>
</body>
</html>
`;
class DiffDOM {
constructor() {
diffDOMConstructor();
}
}
DiffDOM.prototype.diff = jasmine.createSpy();
DiffDOM.prototype.apply = jasmine.createSpy();
let originalDOMParser;
class FakeDOMParser {}
FakeDOMParser.prototype.parseFromString = jasmine.createSpy().and.returnValue({
head: 'fakeNewHead',
body: 'fakeNewBody'
});
beforeEach(() => {
rewire('DiffDOM', DiffDOM);
// DOMParser is a browser global in the module under test
originalDOMParser = window.DOMParser;
window.DOMParser = FakeDOMParser;
board = new StructureBoard();
spyOn(board, '_replaceBodyWithHTML');
CMS.API.Messages = {
open: jasmine.createSpy()
};
CMS.API.Toolbar = {
_refreshMarkup: jasmine.createSpy()
};
});
afterEach(() => {
resetRewire('DiffDOM');
window.DOMParser = originalDOMParser;
});
it('resets loaded content flag', () => {
CMS._instances = [];
board._requestcontent = {};
board._loadedContent = false;
board.refreshContent(newDoc);
expect(board._requestcontent).toBe(null);
expect(board._loadedContent).toBe(true);
});
it('shows messages', done => {
CMS._instances = [];
spyOn(board, '_extractMessages').and.returnValue([{ message: 'hello' }]);
board.refreshContent(newDoc);
setTimeout(() => {
expect(CMS.API.Messages.open).toHaveBeenCalledWith({ message: 'hello' });
done();
}, 20);
});
it('resets plugin instances', () => {
CMS._instances = [
new FakePlugin('cms-plugin-1', { plugin_id: 1, type: 'plugin' }),
new FakePlugin('cms-placeholder-1', { placeholder_id: 1, type: 'placeholder' })
];
board.refreshContent(newDoc);
expect(FakePlugin.prototype._setupUI).toHaveBeenCalledTimes(2);
expect(FakePlugin.prototype._setupUI).toHaveBeenCalledWith('cms-plugin-1');
expect(FakePlugin.prototype._setupUI).toHaveBeenCalledWith('cms-placeholder-1');
expect(FakePlugin.prototype._ensureData).toHaveBeenCalledTimes(2);
expect(FakePlugin.prototype._setPlaceholder).toHaveBeenCalledTimes(1);
expect(FakePlugin.prototype._setPluginContentEvents).toHaveBeenCalledTimes(1);
});
});
describe('_updateContentFromDataBridge', () => {
beforeEach(() => {
CMS.API.StructureBoard = new StructureBoard();
});
it('should return true if data is missing or incomplete', () => {
const incompleteData = {};
expect(CMS.API.StructureBoard._updateContentFromDataBridge(incompleteData)).toBe(true);
const missingContent = { content: [] };
expect(CMS.API.StructureBoard._updateContentFromDataBridge(missingContent)).toBe(true);
const missingPluginIds = { content: [{ html: '<div></div>' }] };
expect(CMS.API.StructureBoard._updateContentFromDataBridge(missingPluginIds)).toBe(true);
});
it('should return true if source placeholder is not found', () => {
const data = {
source_placeholder_id: 1,
content: {
pluginIds: [1],
html: '<div></div>'
}
};
spyOn(CMS._instances, 'some').and.returnValue(false);
expect(CMS.API.StructureBoard._updateContentFromDataBridge(data)).toBe(true);
});
it('should update content and return false if data is valid', () => {
const data = {
content: [{
pluginIds: [1],
html: '<template class="cms-plugin cms-plugin-1 cms-plugin-start"></template>' +
'<div>My new plugin</div>' +
'<template class="cms-plugin cms-plugin-1 cms-plugin-end"></template>',
placeholder_id: 1,
position: 0
}]
};
spyOn(CMS.API.StructureBoard, '_findNextElement').and.returnValue($('<div></div>'));
spyOn(CMS.API.StructureBoard, '_updateSekizai');
spyOn(CMS.API.StructureBoard, '_contentChanged');
expect(CMS.API.StructureBoard._updateContentFromDataBridge(data)).toBe(false);
expect(CMS.API.StructureBoard._updateSekizai).toHaveBeenCalledWith(data.content[0], 'css');
expect(CMS.API.StructureBoard._updateSekizai).toHaveBeenCalledWith(data.content[0], 'js');
expect(CMS.API.StructureBoard._contentChanged).toHaveBeenCalledWith(undefined);
});
it('should remove previous content and update Sekizai blocks', () => {
const data = {
content: [{
pluginIds: [1, 2],
html: '<div class="cms-plugin cms-plugin-1 cms-plugin-start"></div>',
placeholder_id: 1,
position: 0,
css: '<link rel="stylesheet" href="style.css">',
js: '<script src="script.js"></script>'
}]
};
spyOn(CMS.API.StructureBoard, '_findNextElement').and.returnValue($('<div></div>'));
spyOn(CMS.API.StructureBoard, '_updateSekizai');
spyOn(CMS.API.StructureBoard, '_contentChanged');
$('body').append('<div class="cms-plugin cms-plugin-1"></div>');
$('body').append('<div class="cms-plugin cms-plugin-2"></div>');
$('body').append('<script data-cms-plugin id="cms-plugin-1"></script>');
$('body').append('<script data-cms-plugin id="cms-plugin-2"></script>');
expect(CMS.API.StructureBoard._updateContentFromDataBridge(data)).toBe(false);
expect($('.cms-plugin.cms-plugin-1').length).toBe(0);
expect($('.cms-plugin.cms-plugin-2').length).toBe(0);
expect($('script[data-cms-plugin]#cms-plugin-1').length).toBe(0);
expect($('script[data-cms-plugin]#cms-plugin-2').length).toBe(0);
expect(CMS.API.StructureBoard._updateSekizai).toHaveBeenCalledWith(data.content[0], 'css');
expect(CMS.API.StructureBoard._updateSekizai).toHaveBeenCalledWith(data.content[0], 'js');
expect(CMS.API.StructureBoard._contentChanged).toHaveBeenCalledWith(undefined);
});
});
describe('_preloadOppositeMode', () => {
['content', 'structure'].forEach(mode => {
it(`preloads the opposite mode (${mode})`, () => {
const div = document.createElement('div');
const _getWindow = jasmine.createSpy().and.returnValue(div);
spyOn(Helpers, '_getWindow').and.callFake(_getWindow);
const board = new StructureBoard();
spyOn(board, '_requestMode');
jasmine.clock().install();
if (mode === 'content') {
board._loadedStructure = false;
} else {
board._loadedStructure = true;
}
board._preloadOppositeMode();
expect(board._requestMode).not.toHaveBeenCalled();
$(div).trigger('load');
expect(board._requestMode).not.toHaveBeenCalled();
jasmine.clock().tick(2001);
expect(board._requestMode).toHaveBeenCalledTimes(1);
expect(board._requestMode).toHaveBeenCalledWith(mode === 'content' ? 'structure' : 'content');
jasmine.clock().uninstall();
});
});
it('does not preload the opposite mode (legacy renderer)', () => {
const div = document.createElement('div');
const _getWindow = jasmine.createSpy().and.returnValue(div);
spyOn(Helpers, '_getWindow').and.callFake(_getWindow);
const board = new StructureBoard();
spyOn(board, '_requestMode');
jasmine.clock().install();
CMS.config.settings.legacy_mode = true;
board._loadedContent = true;
board._loadedStructure = true;
board._preloadOppositeMode();
expect(board._requestMode).not.toHaveBeenCalled();
$(div).trigger('load');
expect(board._requestMode).not.toHaveBeenCalled();
jasmine.clock().tick(2001);
expect(board._requestMode).not.toHaveBeenCalled();
jasmine.clock().uninstall();
});
});
describe('actualizePluginCollapseStatus', () => {
it('works', () => {
$('#fixture_container').append(`
<div class="cms-draggable-1">
<div class="cms-collapsable-container cms-hidden"></div>
<div class="cms-dragitem"></div>
<div class="cms-draggables"></div>
</div>
`);
CMS.settings = {
states: ['1\n']
};
StructureBoard.actualizePluginCollapseStatus(1);
expect('.cms-draggable-1 .cms-collapsable-container').not.toHaveClass('cms-hidden');
expect('.cms-draggable-1 .cms-dragitem').toHaveClass('cms-dragitem-expanded');
$('#fixture_container').empty();
});
});
});