first commit

This commit is contained in:
2025-09-16 01:40:08 +03:00
commit d9969e1394
252 changed files with 41184 additions and 0 deletions

View File

@@ -0,0 +1,101 @@
<html>
<head>
<link rel="stylesheet" href="styles.css"/>
<title>MixItUp Demo</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<menu>
<h2>Native Controls</h2>
<h3>Filters</h3>
<button type="button" class="control mixitup-control" data-filter="all">All</button>
<button type="button" class="control mixitup-control" data-filter="none">None</button>
<button type="button" class="control mixitup-control" data-filter=".cat-1">Category A</button>
<button type="button" class="control mixitup-control" data-filter=".cat-2">Category B</button>
<button type="button" class="control mixitup-control" data-filter=".cat-3">Category C</button>
<button type="button" class="control mixitup-control" data-filter=".cat-x">Category X</button>
<h3>Filter Toggles</h3>
<button type="button" class="control mixitup-control" data-toggle=".cat-1">Category A</button>
<button type="button" class="control mixitup-control" data-toggle=".cat-2">Category B</button>
<button type="button" class="control mixitup-control" data-toggle=".cat-3">Category C</button>
<h3>Sorts</h3>
<button type="button" class="control mixitup-control" data-sort="default:asc">Ascending</button>
<button type="button" class="control mixitup-control" data-sort="default:desc">Descending</button> |
<button type="button" class="control mixitup-control" data-sort="order:asc">Custom Ascending</button>
<button type="button" class="control mixitup-control" data-sort="order:desc">Custom Descending</button> |
<button type="button" class="control mixitup-control" data-sort="date:asc order:asc">Multi Custom Ascending Ascending</button>
<button type="button" class="control mixitup-control" data-sort="date:asc order:desc">Multi Custom Ascending Descending</button> |
<button type="button" class="control mixitup-control" data-sort="random">Random</button>
<h3>MultiMix</h3>
<button type="button" class="control mixitup-control" data-filter=".cat-1" data-sort="default:asc">Category A / Ascending</button>
<button type="button" class="control mixitup-control" data-filter=".cat-3" data-sort="order:desc">Category C / Custom Descending</button>
<h2>API Methods</h2>
<div class="control js-append">Append</div>
<div class="control js-prepend">Prepend</div>
<div class="control js-insert-at-index">Insert at index (5)</div>
<div class="control js-remove-via-element">Remove by element reference (0)</div>
<div class="control js-remove-via-index">Remove by index (3)</div>
<div class="control js-insert-multiple-via-markup">Insert Multiple via markup</div>
<div class="control js-insert-multiple-via-elements">Insert Multiple via elements</div><br><br>
<div class="control js-api-filter">Filter Category B</div>
<div class="control js-api-filter-compound">Filter Category B + C</div>
<div class="control js-api-sort">Sort Custom Ascending</div>
<br><br>
<div class="control js-api-limit-3">3 per page</div>
<div class="control js-api-limit-inf">Infinite per page</div>
<div class="control js-api-limit-10">10 per page</div>
</menu>
<section class="sandbox" id="sandbox-1">
<div class="mix cat-1" data-order="5" data-date="1">A 5</div>
<div class="mix cat-1" data-order="1" data-date="2">A 1</div>
<div class="mix cat-2" data-order="4" data-date="3">B 4</div>
<div class="mix cat-3" data-order="3" data-date="3">C 3</div>
<div class="mix cat-3" data-order="2" data-date="4">C 2</div>
<div class="mix cat-3" data-order="6" data-date="6">C 6</div>
<div class="gap"></div>
<div class="gap"></div>
<div class="gap"></div>
<div class="gap"></div>
<div class="gap"></div>
<div class="gap"></div>
</section>
<span class="mixitup-page-list"></span>
<span class="mixitup-page-stats"></span>
<section class="sandbox" id="sandbox-2">
<div class="mix" id="12"></div>
<div class="gap"></div>
<div class="gap"></div>
<div class="gap"></div>
<div class="gap"></div>
<div class="gap"></div>
<div class="gap"></div>
</section>
<script src="../../dist/mixitup.js"></script>
<script src="../../../mixitup-pagination/dist/mixitup-pagination.js"></script>
<script src="../../../mixitup-dragndrop/mixitup-dragndrop.js"></script>
<script src="main.js"></script>
</body>
</html>

View File

@@ -0,0 +1,191 @@
/* global mixitup */
var sandbox = document.querySelector('.sandbox');
var mixer = mixitup('#sandbox-1', {
animation: {
effects: 'fade',
easing: 'cubic-bezier(1, 0, 0, 1)',
duration: 400
},
controls: {
scope: 'global',
live: false
// toggleLogic: 'and'
},
pagination: {
limit: 4,
maxPagers: 6
},
load: {
// filter: 'none',
sort: 'random'
},
// dragndrop: {
// enable: true,
// hidePlaceholder: false,
// debounceDelay: 20,
// detection: 'collision',
// // liveSort: false
// // swap: true
// },
callbacks: {
// onMixLift: function() {
// console.log('lift', this);
// }
}
}, null);
console.log(mixer.getState());
sandbox.addEventListener('mixStart', function(e) {
console.log('mixStart', e.detail);
});
sandbox.addEventListener('mixEnd', function(e) {
console.log('mixEnd', e.detail);
});
sandbox.addEventListener('mixClick', function(e) {
console.log('mixClick', e.detail);
});
sandbox.addEventListener('mixBusy', function(e) {
console.log('mixBusy', e.detail);
});
sandbox.addEventListener('mixFail', function(e) {
console.log('mixFail', e.detail);
});
sandbox.classList.add('sandbox__mixitup');
document.querySelector('.js-append').addEventListener('click', function() {
mixer.append('<div class="mix cat-3" data-order="3">C 3</div>');
});
document.querySelector('.js-prepend').addEventListener('click', function() {
mixer.prepend('<div class="mix cat-1" data-order="2">A 2</div>');
});
document.querySelector('.js-insert-at-index').addEventListener('click', function() {
mixer.insert(4, '<div class="mix cat-4" data-order="5">D 5</div>');
});
document.querySelector('.js-remove-via-element').addEventListener('click', function() {
var state = mixer.getState();
if (state.targets[0]) {
mixer.remove(state.targets[0]);
}
});
document.querySelector('.js-remove-via-index').addEventListener('click', function() {
mixer.remove(3);
});
document.querySelector('.js-insert-multiple-via-markup').addEventListener('click', function() {
mixer.prepend(
'<div class="mix cat-4" data-order="1">D 1</div>' +
'<div class="mix cat-4" data-order="2">D 2</div>' +
'<div class="mix cat-4" data-order="3">D 3</div>'
);
});
document.querySelector('.js-insert-multiple-via-elements').addEventListener('click', function() {
var h = mixitup.h,
el1 = h.createElement('<div class="mix cat-4" data-order="1">D 1</div>').children[0],
el2 = h.createElement('<div class="mix cat-4" data-order="2">D 2</div>').children[0],
el3 = h.createElement('<div class="mix cat-4" data-order="3">D 3</div>').children[0],
elements = [el1, el2, el3];
// Going into the mixer backwards?
mixer.multiMix({
insert: elements
});
});
document.querySelector('.js-api-filter').addEventListener('click', function() {
mixer.filter('.cat-2');
});
document.querySelector('.js-api-filter-compound').addEventListener('click', function() {
mixer.filter('.cat-2, .cat-3');
});
document.querySelector('.js-api-sort').addEventListener('click', function() {
mixer.sort('order:asc');
});
document.querySelector('.js-api-limit-3').addEventListener('click', function() {
mixer.paginate({limit: 3});
});
document.querySelector('.js-api-limit-inf').addEventListener('click', function() {
mixer.paginate({limit: Infinity});
});
document.querySelector('.js-api-limit-10').addEventListener('click', function() {
mixer.paginate({limit: 10});
});
var dataset = [{
id: '12',
category: '1'
}];
var dataMixer = mixitup('#sandbox-2', {
load: {
dataset: dataset
},
data: {
uidKey: 'id',
dirtyCheck: true
},
render: {
target: function(data) {
return `<div class="mix cat-${data.category}" id="${data.id}"></div>`;
}
}
});
dataset.push({
id: '14',
category: '2'
}, {
id: '2',
category: '3'
});
var first;
dataMixer.dataset(dataset)
.then(function() {
dataset.reverse();
return dataMixer.dataset(dataset);
})
.then(function() {
first = dataset.shift();
return dataMixer.dataset(dataset);
})
.then(function() {
dataset.push(first);
return dataMixer.dataset(dataset);
})
.then(function() {
dataset = [dataset[2], dataset[0]];
return dataMixer.dataset(dataset);
})
.then(function() {
dataset[1] = {
id: '14',
category: '4'
};
return dataMixer.dataset(dataset);
});

View File

@@ -0,0 +1,136 @@
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
body {
background: #eee;
}
.mixitup-control,
.mixitup-pager {
cursor: pointer;
display: inline-block;
padding: .5rem;
background: #eee;
}
.mixitup-control-active,
.mixitup-pager-active {
font-weight: bold;
}
.mixitup-pager-disabled {
opacity: .8;
}
.mix {
vertical-align: top;
background: skyblue;
border-radius: 5px;
margin: 2rem;
color: transparent;
transition: opacity 300ms;
-webkit-user-select: none;
}
.mix,
.gap {
display: inline-block;
width: 6%;
margin: 0 2rem;
}
.mix {
margin: 2rem;
}
.mix:before {
content: '';
display: inline-block;
padding-top: 100%;
}
.mixitup-target-placeholder {
background: transparent !important;
border: 2px dashed #ccc;
box-sizing: border-box;
}
.mixitup-target-dragging {
opacity: .5;
}
.mixitup-target-closest {
border: 3px solid orange;
}
.sandbox {
overflow: hidden;
text-align: justify;
background: #333;
min-height: 2rem;
}
.sandbox:after {
content: '';
width: 100%;
display: inline-block;
}
.sandbox__mixitup .mix {
}
.mix.cat-2 {
background: violet;
}
.mix.cat-3 {
background: yellow;
}
.mix.cat-4 {
background: aquamarine;
}

View File

@@ -0,0 +1,38 @@
[
{
"id": "target-1",
"categories": ["a"],
"published": "20161102",
"views": 100
},
{
"id": "target-2",
"categories": ["a"],
"published": "20130501",
"views": 54
},
{
"id": "target-3",
"categories": ["b"],
"published": "20121231",
"views": 3
},
{
"id": "target-4",
"categories": ["b"],
"published": "20160407",
"views": 62
},
{
"id": "target-5",
"categories": ["c"],
"published": "20160820",
"views": 54
},
{
"id": "target-6",
"categories": ["a", "c"],
"published": "20151020",
"views": 95
}
]

View File

@@ -0,0 +1,118 @@
'use strict';
require('jsdom-global')();
const renderElement = (html) => {
const temp = document.createElement('div');
temp.innerHTML = html;
return temp.firstElementChild;
};
module.exports = {
getContainer() {
return renderElement('<div class="mixitup-container" data-ref="container">' +
'<div id="target-1" class="mix category-a" data-ref="mix" data-category="a" data-published="20161102" data-views="100"></div> ' +
'<div id="target-2" class="mix category-a" data-ref="mix" data-category="a" data-published="20130501" data-views="54"></div> ' +
'<div id="target-3" class="mix category-b" data-ref="mix" data-category="b" data-published="20121231" data-views="3"></div> ' +
'<div id="target-4" class="mix category-b" data-ref="mix" data-category="b" data-published="20160407" data-views="62"></div> ' +
'<div id="target-5" class="mix category-c" data-ref="mix" data-category="c" data-published="20160820" data-views="54"></div> ' +
'<div id="target-6" class="mix category-a category-c" data-ref="mix" data-category="a c" data-published="20151020" data-views="95"></div>' +
'<span class="mixitup-container-gap></span>' +
'</div>');
},
getTarget() {
return renderElement('<div id="7" class="mix category-d" data-ref="mix" data-category="d" data-published="20161222" data-views="132"></div>');
},
getEmptyContainer() {
return renderElement('<div class="container" data-ref="container"></div>');
},
getFilterControls() {
return renderElement('<div class="mixitup-controls">' +
'<div class="mixitup-control" data-filter="all">All</div> ' +
'<div class="mixitup-control" data-filter="none">None</div> ' +
'<div class="mixitup-control" data-filter=".category-a">Category A</div> ' +
'<div class="mixitup-control" data-filter=".category-b">Category B</div> ' +
'<div class="mixitup-control" data-filter=".category-c">Category C</div> ' +
'<div class="mixitup-control" data-filter=".category-d">Category D</div> ' +
'<div class="mixitup-control" data-filter=".category-a, .category-b">Category A OR B</div> ' +
'<div class="mixitup-control" data-filter=".category-a.category-c">Category A AND C</div> ' +
'<div class="mixitup-control mixitup_control__attr-a" data-filter=\'[data-category="a"]\'>Category A (attribute)</div> ' +
'<div class="mixitup-control mixitup_control__attr-a-or-b" data-filter=\'[data-category="a"], [data-category="b"]\'>Category A OR B (attribute)</div> ' +
'<div class="mixitup-control mixitup_control__attr-a-and-c" data-filter=\'[data-category="a"][data-category="c"]\'>Category A AND C (attribute)</div> ' +
'<div class="mixitup-control" data-toggle=".category-a">Category A</div> ' +
'<div class="mixitup-control" data-toggle=".category-b">Category B</div> ' +
'<div class="mixitup-control" data-toggle=".category-c">Category C</div> ' +
'</div>');
},
getFilterControl() {
return renderElement('<div class="mixitup-control" data-filter=".category-d">Category D</div>');
},
getToggleControl() {
return renderElement('<div class="mixitup-control" data-toggle=".category-b">Category B</div>');
},
getSortControl() {
return renderElement('<div class="mixitup-control" data-sort="views:desc published:asc">Views (desc) Published (asc)</div>');
},
getSortControls() {
return renderElement('<div class="mixitup-controls">' +
'<div class="mixitup-control" data-sort="default">Default</div> ' +
'<div class="mixitup-control" data-sort="default:asc">Default Ascending</div> ' +
'<div class="mixitup-control" data-sort="default:desc">Default Descending</div> ' +
'<div class="mixitup-control" data-sort="random">Random</div> ' +
'<div class="mixitup-control" data-sort="published">Published Date</div> ' +
'<div class="mixitup-control" data-sort="views">Views</div> ' +
'<div class="mixitup-control" data-sort="published:asc views:desc">Published (asc) Views (desc)</div> ' +
'</div>');
},
getMultimixControls() {
return renderElement('<div class="mixitup-controls">' +
'<div class="mixitup-control" data-filter="all" data-sort="default:asc">All / Default</div> ' +
'<div class="mixitup-control" data-filter=".category-b" data-sort="published">Category B / Published</div> ' +
'</div>');
},
getTotalWhitespace(html) {
let re = /[>? ]( )[<? ]/g;
let totalWhitespace = 0;
let matches;
while (matches = re.exec(html)) {
totalWhitespace++;
}
return totalWhitespace;
},
Item: class Item {
constructor(data) {
this.id = typeof data !== 'undefined' ? data.id : undefined;
this.categories = Array.prototype.slice.call(data.categories) || [];
this.published = typeof data.published === 'string' ? data.published : '';
this.views = typeof data.views === 'number' ? data.views : 0;
Object.seal(this);
}
get classList() {
return 'mix ' + this.categories.map(category => 'category-' + category).join(' ');
}
get categoryList() {
return this.categories.join(' ');
}
},
ITEM_TEMPLATE: '<div id="${id}" class="${classList}" data-ref="mix" data-category="${categoryList}" data-published="${published}" data-views="${views}"></div>',
ITEM_TEMPLATE_ALT: '<div id="${id}"></div>'
};

View File

@@ -0,0 +1,37 @@
(function(window) {
'use strict';
var mixitupMockExtension = function(mixitup) {
var h = mixitup.h;
if (
!mixitup.CORE_VERSION ||
!h.compareVersions(mixitupMockExtension.REQUIRE_CORE_VERSION, mixitup.CORE_VERSION)
) {
throw new Error(
'[MixItUp-MockExtension] MixItUp MockExtension v' +
mixitupMockExtension.EXTENSION_VERSION +
' requires at least MixItUp v' +
mixitupMockExtension.REQUIRE_CORE_VERSION
);
}
};
mixitupMockExtension.TYPE = 'mixitup-extension';
mixitupMockExtension.NAME = 'mixitup-mock-extension';
mixitupMockExtension.EXTENSION_VERSION = '1.0.0';
mixitupMockExtension.REQUIRE_CORE_VERSION = '3.0.0';
if (typeof exports === 'object' && typeof module === 'object') {
module.exports = mixitupMockExtension;
} else if (typeof define === 'function' && define.amd) {
define(function() {
return mixitupMockExtension;
});
} else if (window.mixitup && typeof window.mixitup === 'function') {
mixitupMockExtension(window.mixitup);
} else {
console.error('[MixItUp-MockExtension] MixItUp core not found');
}
})(window);

View File

@@ -0,0 +1,175 @@
'use strict';
require('jsdom-global')();
const chai = require('chai');
const dom = require('../mock/dom');
const mixitup = require('../../dist/mixitup.js');
describe('mixitup()', () => {
it('should accept an `onMixStart` callback, invoked at the start of operations', () => {
let container = dom.getContainer();
let wasCalled = false;
let mixer = mixitup(container, {
callbacks: {
onMixStart: (state, futureState) => {
chai.assert.instanceOf(state, mixitup.State);
chai.assert.instanceOf(futureState, mixitup.State);
chai.assert.notEqual(state.totalShow, futureState.totalShow);
wasCalled = true;
}
}
});
return mixer.hide()
.then(() => chai.assert.equal(wasCalled, true));
});
it('should accept an `onMixBusy` callback, called if simulataneous operation is rejected', () => {
let container = dom.getContainer();
let wasCalled = false;
let mixer = mixitup(container, {
debug: {
fauxAsync: true
},
animation: {
duration: 200,
queue: false
},
callbacks: {
onMixBusy: (state) => {
chai.assert.instanceOf(state, mixitup.State);
wasCalled = true;
}
}
});
mixer.hide();
return mixer.show()
.then(() => chai.assert.equal(wasCalled, true));
});
it('should accept an `onMixEnd` callback, called at the end of an operation', () => {
let container = dom.getContainer();
let wasCalled = false;
let endState;
let mixer = mixitup(container, {
callbacks: {
onMixEnd: (state) => {
endState = state;
chai.assert.instanceOf(state, mixitup.State);
wasCalled = true;
}
}
});
return mixer.hide()
.then(state => {
chai.assert.equal(state.totalShow, endState.totalShow);
chai.assert.equal(wasCalled, true);
});
});
it('should accept an `onMixFail` callback, called when a filter operation does not match any targets', () => {
let container = dom.getContainer();
let wasCalled = false;
let mixer = mixitup(container, {
callbacks: {
onMixFail: (state) => {
chai.assert.instanceOf(state, mixitup.State);
wasCalled = true;
}
}
});
return mixer.filter('.category-x')
.then(() => {
chai.assert.equal(wasCalled, true);
});
});
it('should accept an `onMixClick` callback invoked when a control is clicked', () => {
let frag = document.createDocumentFragment();
let container = dom.getContainer();
let controls = dom.getFilterControls();
let wasCalled = false;
let filter = controls.querySelector('[data-filter="none"]');
container.insertBefore(controls, container.children[0]);
frag.appendChild(container);
let mixer = mixitup(container, {
controls: {
scope: 'local'
},
callbacks: {
onMixClick: function(state, originalEvent) {
var self = this;
chai.assert.instanceOf(state, mixitup.State);
chai.assert.instanceOf(originalEvent, window.MouseEvent);
chai.assert.equal(self, filter);
wasCalled = true;
}
}
}, frag);
filter.click();
return Promise.resolve()
.then(() => {
chai.assert.equal(wasCalled, true);
mixer.destroy();
});
});
it('should accept an `onMixClick` callback which can be cancelled by returning false', () => {
let frag = document.createDocumentFragment();
let container = dom.getContainer();
let controls = dom.getFilterControls();
let wasCalled = false;
let filter = controls.querySelector('[data-filter="none"]');
container.insertBefore(controls, container.children[0]);
frag.appendChild(container);
let mixer = mixitup(container, {
controls: {
scope: 'local'
},
callbacks: {
onMixClick: () => {
return false;
},
onMixEnd: () => {
// Will not be called
wasCalled = true;
}
}
}, frag);
filter.click();
return Promise.resolve()
.then(() => {
chai.assert.equal(wasCalled, false);
mixer.destroy();
});
});
});

View File

@@ -0,0 +1,201 @@
'use strict';
require('jsdom-global')();
const chai = require('chai');
const dom = require('../mock/dom');
const mixitup = require('../../dist/mixitup.js');
chai.use(require('chai-shallow-deep-equal'));
chai.use(require('chai-as-promised'));
describe('Controls', () => {
describe('Filter', () => {
const frag = document.createDocumentFragment();
const container = dom.getContainer();
const controls = dom.getFilterControls();
container.insertBefore(controls, container.children[0]);
frag.appendChild(container);
const mixer = mixitup(container, {
controls: {
scope: 'local'
}
}, frag);
after(() => mixer.destroy());
it('should detect nested filter controls and set active states upon instantiation', () => {
const filter = controls.querySelector('[data-filter="all"]');
chai.assert.isOk(filter.matches('.mixitup-control-active'));
});
it('should map filter controls with value "none" to the selector ""', () => {
const filter = controls.querySelector('[data-filter="none"]');
filter.click();
const state = mixer.getState();
chai.assert.equal(state.activeFilter.selector, '');
chai.assert.equal(state.totalShow, 0);
chai.assert.isOk(filter.matches('.mixitup-control-active'));
});
it('should map filter controls with value "all" to the target selector', () => {
const filter = controls.querySelector('[data-filter="all"]');
filter.click();
const state = mixer.getState();
chai.assert.equal(state.activeFilter.selector, '.mix');
chai.assert.equal(state.totalHide, 0);
chai.assert.isOk(filter.matches('.mixitup-control-active'));
});
it('should accept filter controls with a selector value', () => {
const filter = controls.querySelector('[data-filter=".category-a"]');
const totalMatching = container.querySelectorAll('.category-a').length;
filter.click();
const state = mixer.getState();
chai.assert.equal(state.activeFilter.selector, '.category-a');
chai.assert.equal(state.totalShow, totalMatching);
chai.assert.isOk(filter.matches('.mixitup-control-active'));
});
it('should accept filter controls with a compound OR selector', () => {
const filter = controls.querySelector('[data-filter=".category-a, .category-b"]');
const totalMatching = container.querySelectorAll('.category-a, .category-b').length;
filter.click();
const state = mixer.getState();
chai.assert.equal(state.activeFilter.selector, '.category-a, .category-b');
chai.assert.equal(state.totalShow, totalMatching);
chai.assert.isOk(filter.matches('.mixitup-control-active'));
});
it('should accept filter controls with a compound AND selector', () => {
const filter = controls.querySelector('[data-filter=".category-a.category-c"]');
const totalMatching = container.querySelectorAll('.category-a.category-c').length;
filter.click();
const state = mixer.getState();
chai.assert.equal(state.activeFilter.selector, '.category-a.category-c');
chai.assert.equal(state.totalShow, totalMatching);
chai.assert.isOk(filter.matches('.mixitup-control-active'));
});
it('should accept filter controls with an attribute selector value', () => {
const filter = controls.querySelector('.mixitup_control__attr-a');
const totalMatching = container.querySelectorAll('[data-category="a"]').length;
filter.click();
const state = mixer.getState();
chai.assert.equal(state.activeFilter.selector, '[data-category="a"]');
chai.assert.equal(state.totalShow, totalMatching);
chai.assert.isOk(filter.matches('.mixitup-control-active'));
});
it('should accept filter controls with an attribute selector value', () => {
const filter = controls.querySelector('.mixitup_control__attr-a');
const totalMatching = container.querySelectorAll('[data-category="a"]').length;
filter.click();
const state = mixer.getState();
chai.assert.equal(state.activeFilter.selector, '[data-category="a"]');
chai.assert.equal(state.totalShow, totalMatching);
chai.assert.isOk(filter.matches('.mixitup-control-active'));
});
it('should accept filter controls with a compound OR attribute selector value', () => {
const filter = controls.querySelector('.mixitup_control__attr-a-or-b');
const totalMatching = container.querySelectorAll('[data-category="a"], [data-category="b"]').length;
filter.click();
const state = mixer.getState();
chai.assert.equal(state.activeFilter.selector, '[data-category="a"], [data-category="b"]');
chai.assert.equal(state.totalShow, totalMatching);
chai.assert.isOk(filter.matches('.mixitup-control-active'));
});
it('should accept filter controls with a compound AND attribute selector value', () => {
const filter = controls.querySelector('.mixitup_control__attr-a-and-c');
const totalMatching = container.querySelectorAll('[data-category="a"][data-category="c"]').length;
filter.click();
const state = mixer.getState();
chai.assert.equal(state.activeFilter.selector, '[data-category="a"][data-category="c"]');
chai.assert.equal(state.totalShow, totalMatching);
chai.assert.isOk(filter.matches('.mixitup-control-active'));
});
it ('should allow a single set of controls to control multiple mixer instance simultanously', () => {
const frag = document.createDocumentFragment();
const container1 = dom.getContainer();
const container2 = dom.getContainer();
const controls = dom.getFilterControls();
frag.appendChild(controls);
frag.appendChild(container1);
frag.appendChild(container2);
const mixer1 = mixitup(container1, {}, frag);
const mixer2 = mixitup(container2, {}, frag);
after(() => {
mixer1.destroy();
mixer2.destroy();
});
const filter = controls.querySelector('[data-filter=".category-a"]');
filter.click();
chai.assert.equal(mixer1.getState().activeFilter.selector, '.category-a');
chai.assert.equal(mixer2.getState().activeFilter.selector, '.category-a');
chai.assert.isOk(filter.matches('.mixitup-control-active'));
});
it ('should activate the appropriate controls on load for a single selector', () => {
const frag = document.createDocumentFragment();
const container = dom.getContainer();
const controls = dom.getFilterControls();
frag.appendChild(controls);
frag.appendChild(container);
const mixer = mixitup(container, {
load: {
filter: '.category-a'
}
}, frag);
after(() => mixer.destroy());
const filter = controls.querySelector('[data-filter=".category-a"]');
chai.assert.isTrue(filter.classList.contains('mixitup-control-active'));
});
});
});

View File

@@ -0,0 +1,156 @@
'use strict';
require('jsdom-global')();
const chai = require('chai');
const dom = require('../mock/dom');
const mixitup = require('../../dist/mixitup.js');
chai.use(require('chai-shallow-deep-equal'));
chai.use(require('chai-as-promised'));
describe('Controls', () => {
describe('Live', () => {
let frag = document.createDocumentFragment();
let container = dom.getContainer();
let filterControls = dom.getFilterControls();
let sortControls = dom.getSortControls();
container.insertBefore(filterControls, container.children[0]);
container.insertBefore(sortControls, filterControls);
frag.appendChild(container);
let mixer = mixitup(container, {
controls: {
scope: 'local',
live: true
}
}, frag);
after(() => mixer.destroy());
it('should detect nested controls and set active states upon instantiation', () => {
let filter = filterControls.querySelector('[data-filter="all"]');
chai.assert.isOk(filter.matches('.mixitup-control-active'));
});
it('should allow new filter controls to be added', () => {
let control = dom.getFilterControl();
let totalMatching = container.querySelectorAll('.category-d').length;
filterControls.appendChild(control);
control.click();
let state = mixer.getState();
chai.assert.equal(state.activeFilter.selector, '.category-d');
chai.assert.equal(state.totalShow, totalMatching);
chai.assert.isOk(control.matches('.mixitup-control-active'));
});
it('should allow new toggle controls to be added', () => {
let control = dom.getToggleControl();
let totalMatching = container.querySelectorAll('.category-b, .category-d').length;
filterControls.appendChild(control);
control.click();
let state = mixer.getState();
chai.assert.equal(state.activeFilter.selector, '.category-d, .category-b');
chai.assert.equal(state.totalShow, totalMatching);
chai.assert.isOk(control.matches('.mixitup-control-active'));
});
it('should allow new sort controls to be added', () => {
let control = dom.getSortControl();
sortControls.appendChild(control);
control.click();
let state = mixer.getState();
chai.assert.equal(state.activeSort.sortString, 'views:desc');
chai.assert.isOk(state.activeSort.next);
chai.assert.equal(state.activeSort.next.sortString, 'published:asc');
chai.assert.isOk(control.matches('.mixitup-control-active'));
});
it ('should allow a single set of filter controls to control multiple mixer instance simultanously', () => {
let frag = document.createDocumentFragment();
let container1 = dom.getContainer();
let container2 = dom.getContainer();
let controls = dom.getFilterControls();
let config = {
controls: {
live: true
}
};
frag.appendChild(controls);
frag.appendChild(container1);
frag.appendChild(container2);
let mixer1 = mixitup(container1, config, frag);
let mixer2 = mixitup(container2, config, frag);
after(() => {
mixer1.destroy();
mixer2.destroy();
});
let filter = controls.querySelector('[data-filter=".category-a"]');
filter.click();
chai.assert.equal(mixer1.getState().activeFilter.selector, '.category-a');
chai.assert.equal(mixer2.getState().activeFilter.selector, '.category-a');
chai.assert.isOk(filter.matches('.mixitup-control-active'));
});
it ('should restrict control clicks to only those matching is a control selector is defined', () => {
let frag = document.createDocumentFragment();
let container = dom.getContainer();
let controls = dom.getFilterControls();
let config = {
controls: {
live: true
},
selectors: {
control: '.mixitup-control-restrict'
}
};
frag.appendChild(controls);
frag.appendChild(container);
let mixer = mixitup(container, config, frag);
after(() => {
mixer.destroy();
});
let filter1 = controls.querySelector('[data-filter=".category-a"]');
filter1.classList.add('mixitup-control-restrict');
filter1.click();
let filter2 = controls.querySelector('[data-filter=".category-b"]');
filter2.click();
chai.assert.equal(mixer.getState().activeFilter.selector, '.category-a');
chai.assert.isOk(filter1.matches('.mixitup-control-active'));
chai.assert.isNotOk(filter2.matches('.mixitup-control-active'));
});
});
});

View File

@@ -0,0 +1,49 @@
'use strict';
require('jsdom-global')();
const chai = require('chai');
const dom = require('../mock/dom');
const mixitup = require('../../dist/mixitup.js');
chai.use(require('chai-shallow-deep-equal'));
chai.use(require('chai-as-promised'));
describe('Controls', () => {
describe('Multimix', () => {
let frag = document.createDocumentFragment();
let container = dom.getContainer();
let controls = dom.getMultimixControls();
container.insertBefore(controls, container.children[0]);
frag.appendChild(container);
let mixer = mixitup(container, {
controls: {
scope: 'local'
}
}, frag);
after(() => mixer.destroy());
it('should detect nested filter controls and set active states upon instantiation', () => {
let control = controls.querySelector('[data-filter="all"][data-sort="default:asc"]');
chai.assert.isOk(control.matches('.mixitup-control-active'));
});
it('should read filter and sort actions simultaneously', () => {
let control = controls.querySelector('[data-filter=".category-b"][data-sort="published"]');
control.click();
let state = mixer.getState();
chai.assert.equal(state.activeFilter.selector, '.category-b');
chai.assert.equal(state.activeSort.sortString, 'published');
chai.assert.isOk(control.matches('.mixitup-control-active'));
});
});
});

View File

@@ -0,0 +1,76 @@
'use strict';
require('jsdom-global')();
const chai = require('chai');
const dom = require('../mock/dom');
const mixitup = require('../../dist/mixitup.js');
chai.use(require('chai-shallow-deep-equal'));
chai.use(require('chai-as-promised'));
describe('Controls', () => {
describe('Sort', () => {
let frag = document.createDocumentFragment();
let container = dom.getContainer();
let controls = dom.getSortControls();
container.insertBefore(controls, container.children[0]);
frag.appendChild(container);
let mixer = mixitup(container, {
controls: {
scope: 'local'
}
}, frag);
after(() => mixer.destroy());
it('should detect nested sort controls and set active states upon instantiation', () => {
let control1 = controls.querySelector('[data-sort="default"]');
let control2 = controls.querySelector('[data-sort="default:asc"]');
chai.assert.isOk(control1.matches('.mixitup-control-active'));
chai.assert.isOk(control2.matches('.mixitup-control-active'));
});
it('should handle sort control clicks with a single sortString value', () => {
let control = controls.querySelector('[data-sort="default:desc"]');
control.click();
let state = mixer.getState();
chai.assert.isOk(control.matches('.mixitup-control-active'));
chai.assert.equal(state.activeSort.sortString, 'default:desc');
chai.assert.equal(state.activeSort.attribute, '');
chai.assert.equal(state.activeSort.order, 'desc');
});
it('should handle sort control clicks with "random" value', () => {
let control = controls.querySelector('[data-sort="random"]');
control.click();
let state = mixer.getState();
chai.assert.isOk(control.matches('.mixitup-control-active'));
chai.assert.equal(state.activeSort.sortString, 'random');
chai.assert.equal(state.activeSort.attribute, '');
chai.assert.equal(state.activeSort.order, 'random');
});
it('should activate buttons in response to matching API calls', () => {
let control = controls.querySelector('[data-sort="published:asc views:desc"]');
return mixer.sort('published:asc views:desc')
.then(state => {
chai.assert.isOk(control.matches('.mixitup-control-active'));
chai.assert.equal(state.activeSort.sortString, 'published:asc');
chai.assert.isOk(state.activeSort.next);
chai.assert.equal(state.activeSort.next.sortString, 'views:desc');
});
});
});
});

View File

@@ -0,0 +1,304 @@
'use strict';
require('jsdom-global')();
const chai = require('chai');
const dom = require('../mock/dom');
const mixitup = require('../../dist/mixitup.js');
chai.use(require('chai-shallow-deep-equal'));
chai.use(require('chai-as-promised'));
describe('Controls', () => {
describe('Toggle', () => {
describe('OR', () => {
let frag = document.createDocumentFragment();
let container = dom.getContainer();
let controls = dom.getFilterControls();
container.insertBefore(controls, container.children[0]);
frag.appendChild(container);
let mixer = mixitup(container, {
controls: {
scope: 'local'
}
});
after(() => mixer.destroy());
it('should accept toggle controls with a selector value', () => {
return mixer.hide()
.then(() => {
let toggle = controls.querySelector('[data-toggle=".category-a"]');
let totalMatching = container.querySelectorAll('.category-a').length;
toggle.click();
let state = mixer.getState();
chai.assert.equal(state.activeFilter.selector, '.category-a');
chai.assert.equal(state.totalShow, totalMatching);
chai.assert.isOk(toggle.matches('.mixitup-control-active'));
});
});
it('should build up a compound selector as toggles are activated', () => {
let toggleA = controls.querySelector('[data-toggle=".category-a"]');
let toggleB = controls.querySelector('[data-toggle=".category-b"]');
let totalMatching = container.querySelectorAll('.category-a, .category-b').length;
toggleB.click();
let state = mixer.getState();
chai.assert.equal(state.activeFilter.selector, '.category-a, .category-b');
chai.assert.equal(state.totalShow, totalMatching);
chai.assert.isOk(toggleA.matches('.mixitup-control-active'));
chai.assert.isOk(toggleB.matches('.mixitup-control-active'));
});
it('should break down a compound selector as toggles are deactivated', () => {
let toggle = controls.querySelector('[data-toggle=".category-a"]');
let totalMatching = container.querySelectorAll('.category-b').length;
toggle.click();
let state = mixer.getState();
chai.assert.equal(state.activeFilter.selector, '.category-b');
chai.assert.equal(state.totalShow, totalMatching);
chai.assert.isNotOk(toggle.matches('.mixitup-control-active'));
});
it('should return to "all" when all toggles are deactivated', () => {
let toggle = controls.querySelector('[data-toggle=".category-b"]');
toggle.click();
let state = mixer.getState();
chai.assert.equal(state.activeFilter.selector, '.mix');
chai.assert.equal(state.totalHide, 0);
chai.assert.isNotOk(toggle.matches('.mixitup-control-active'));
});
it ('should activate the appropriate toggle controls on load for an OR compound selector', () => {
const frag = document.createDocumentFragment();
const container = dom.getContainer();
const controls = dom.getFilterControls();
frag.appendChild(controls);
frag.appendChild(container);
const mixer = mixitup(container, {
load: {
filter: '.category-a, .category-c'
}
}, frag);
after(() => mixer.destroy());
const toggleA = controls.querySelector('[data-toggle=".category-a"]');
const toggleC = controls.querySelector('[data-toggle=".category-c"]');
chai.assert.isTrue(toggleA.classList.contains('mixitup-control-active'));
chai.assert.isTrue(toggleC.classList.contains('mixitup-control-active'));
});
});
describe('AND', () => {
let frag = document.createDocumentFragment();
let container = dom.getContainer();
let controls = dom.getFilterControls();
container.insertBefore(controls, container.children[0]);
frag.appendChild(container);
let mixer = mixitup(container, {
controls: {
scope: 'local',
toggleLogic: 'AND'
}
});
after(() => mixer.destroy());
it('should accept toggle controls with a selector value', () => {
return mixer.hide()
.then(() => {
let toggle = controls.querySelector('[data-toggle=".category-a"]');
let totalMatching = container.querySelectorAll('.category-a').length;
toggle.click();
let state = mixer.getState();
chai.assert.equal(state.activeFilter.selector, '.category-a');
chai.assert.equal(state.totalShow, totalMatching);
chai.assert.isOk(toggle.matches('.mixitup-control-active'));
});
});
it('should build up a compound selector as toggles are activated', () => {
let toggleA = controls.querySelector('[data-toggle=".category-a"]');
let toggleB = controls.querySelector('[data-toggle=".category-c"]');
let totalMatching = container.querySelectorAll('.category-a.category-c').length;
toggleB.click();
let state = mixer.getState();
chai.assert.equal(state.activeFilter.selector, '.category-a.category-c');
chai.assert.equal(state.totalShow, totalMatching);
chai.assert.isOk(toggleA.matches('.mixitup-control-active'));
chai.assert.isOk(toggleB.matches('.mixitup-control-active'));
});
it('should break down a compound selector as toggles are deactivated', () => {
let toggle = controls.querySelector('[data-toggle=".category-a"]');
let totalMatching = container.querySelectorAll('.category-c').length;
toggle.click();
let state = mixer.getState();
chai.assert.equal(state.activeFilter.selector, '.category-c');
chai.assert.equal(state.totalShow, totalMatching);
chai.assert.isNotOk(toggle.matches('.mixitup-control-active'));
});
it('should return to "all" when all toggles are deactivated', () => {
let toggle = controls.querySelector('[data-toggle=".category-c"]');
toggle.click();
let state = mixer.getState();
chai.assert.equal(state.activeFilter.selector, '.mix');
chai.assert.equal(state.totalHide, 0);
chai.assert.isNotOk(toggle.matches('.mixitup-control-active'));
});
it('should allow toggles to activated via the API', () => {
let totalMatching = container.querySelectorAll('.category-a.category-c').length;
mixer.toggleOn('.category-a');
mixer.toggleOn('.category-c');
let state = mixer.getState();
chai.assert.equal(state.activeFilter.selector, '.category-a.category-c');
chai.assert.equal(state.totalShow, totalMatching);
});
it ('should activate the appropriate toggle controls on load for an AND compound selector', () => {
const frag = document.createDocumentFragment();
const container = dom.getContainer();
const controls = dom.getFilterControls();
frag.appendChild(controls);
frag.appendChild(container);
const mixer = mixitup(container, {
controls: {
toggleLogic: 'and'
},
load: {
filter: '.category-a.category-c'
}
}, frag);
after(() => mixer.destroy());
const toggleA = controls.querySelector('[data-toggle=".category-a"]');
const toggleC = controls.querySelector('[data-toggle=".category-c"]');
chai.assert.isTrue(toggleA.classList.contains('mixitup-control-active'));
chai.assert.isTrue(toggleC.classList.contains('mixitup-control-active'));
});
});
describe('Defaults', () => {
it('should default to "none" when all toggles are deactivated and toggleDefault is set to "none"', () => {
let frag = document.createDocumentFragment();
let container = dom.getContainer();
let controls = dom.getFilterControls();
container.insertBefore(controls, container.children[0]);
frag.appendChild(container);
let mixer = mixitup(container, {
controls: {
scope: 'local',
toggleDefault: 'none'
}
});
return mixer.hide()
.then(() => {
let toggle = controls.querySelector('[data-toggle=".category-a"]');
// on
toggle.click();
// off
toggle.click();
let state = mixer.getState();
chai.assert.equal(state.activeFilter.selector, '');
chai.assert.equal(state.totalShow, 0);
chai.assert.isNotOk(toggle.matches('.mixitup-control-active'));
mixer.destroy();
});
});
it('should default to "all" when all toggles are deactivated', () => {
let container = dom.getContainer();
let controls = dom.getFilterControls();
container.insertBefore(controls, container.children[0]);
document.body.appendChild(container);
let mixer = mixitup(container, {
controls: {
scope: 'local',
toggleDefault: 'all'
}
});
return mixer.hide()
.then(() => {
let toggle = controls.querySelector('[data-toggle=".category-a"]');
// on
toggle.click();
// off
toggle.click();
let state = mixer.getState();
chai.assert.equal(state.activeFilter.selector, '.mix');
chai.assert.equal(state.totalHide, 0);
chai.assert.isNotOk(toggle.matches('.mixitup-control-active'));
});
});
});
});
});

View File

@@ -0,0 +1,19 @@
'use strict';
require('jsdom-global')();
const chai = require('chai');
const dom = require('../mock/dom');
const extension = require('../mock/extension');
const mixitup = require('../../dist/mixitup.js');
chai.use(require('chai-shallow-deep-equal'));
chai.use(require('chai-as-promised'));
describe('Extension', () => {
it('should register itself via the mixitup.use() method', () => {
mixitup.use(extension);
chai.assert.isOk(mixitup.extensions[extension.NAME]);
});
});

View File

@@ -0,0 +1,294 @@
'use strict';
require('jsdom-global')();
const chai = require('chai');
const dom = require('../mock/dom');
const dataset = require('../mock/dataset');
const mixitup = require('../../dist/mixitup.js');
describe('mixitup()', () => {
it('should throw an error if no container reference', () => {
chai.assert.throws(() => mixitup(), Error, mixitup.messages.errorFactoryInvalidContainer());
});
it('should throw an error if a null container reference is passed', () => {
chai.assert.throws(() => mixitup(null), Error, mixitup.messages.errorFactoryInvalidContainer());
});
it('should throw an error if an invalid container reference is passed', () => {
chai.assert.throws(() => mixitup({}), Error, mixitup.messages.errorFactoryInvalidContainer());
});
it('should throw an error if an invalid reference or selector is passed', function() {
chai.assert.throws(() => mixitup(false), Error, mixitup.messages.errorFactoryInvalidContainer());
});
it('should throw an error if an invalid configuration option is passed', function() {
let container = dom.getContainer();
chai.assert.throws(() => {
mixitup(container, {
animations: {}
});
}, TypeError);
});
it('should accept an element reference as a container', () => {
let container = dom.getContainer();
let mixer = mixitup(container);
chai.assert.isOk(mixer);
mixer.destroy();
});
it('should accept a container selector', () => {
let frag = document.createDocumentFragment();
let container = dom.getContainer();
frag.appendChild(container);
let mixer = mixitup('.mixitup-container', {}, frag);
let state = mixer.getState();
chai.assert.isOk(mixer);
chai.assert.equal(state.container, frag.querySelector('.mixitup-container'));
mixer.destroy();
});
it('should accept a container and valid configuration object', function() {
let container = dom.getContainer();
let mixer = mixitup(container, {
selectors: {
target: '[data-ref="mix"]'
},
controls: {
enable: false
}
});
let state = mixer.getState();
chai.assert.isOk(mixer);
chai.assert.equal(state.activeFilter.selector, '[data-ref="mix"]');
mixer.destroy();
});
it('should throw an error if the container selector yields no element', () => {
chai.assert.throws(() => mixitup('.invalid-container-selector'), Error, mixitup.messages.errorFactoryContainerNotFound());
});
it('should return an instance of a facade by default', () => {
let container = dom.getContainer();
let mixer = mixitup(container);
chai.assert.instanceOf(mixer, mixitup.Facade);
mixer.destroy();
});
it('should return an instance of a mixer if debug mode enabled', () => {
let container = dom.getContainer();
let mixer = mixitup(container, {
debug: {
enable: true
},
controls: {
enable: false
}
});
chai.assert.instanceOf(mixer, mixitup.Mixer);
mixer.destroy();
});
it('should return a single instance of a mixer, wrapping the first element if multiple elements passed', () => {
let elementList = [
dom.getContainer(),
dom.getContainer()
];
let mixer = mixitup(elementList, {
debug: {
enable: true
},
controls: {
enable: false
}
});
chai.assert.instanceOf(mixer, mixitup.Mixer);
chai.assert.equal(mixer.getState().container, elementList[0]);
mixer.destroy();
});
it('should return an instance of a collection if multiple elements passed and `returnCollection` specified', () => {
let elementList = [
dom.getContainer(),
dom.getContainer()
];
let collection = mixitup(elementList, void(0), void(0), true);
chai.assert.instanceOf(collection, mixitup.Collection);
chai.assert.instanceOf(collection[0], mixitup.Facade);
chai.assert.instanceOf(collection[1], mixitup.Facade);
collection.mixitup('destroy');
});
it('should add a unique ID to the container if no ID present', () => {
let container = dom.getContainer();
let mixer = mixitup(container);
let state = mixer.getState();
chai.assert.equal(container.id, state.id);
mixer.destroy();
});
it('should use any existing ID on the container as the mixer ID if present', () => {
let container = dom.getContainer();
let id = 'test-id';
container.id = id;
let mixer = mixitup(container);
let state = mixer.getState();
chai.assert.equal(id, state.id);
mixer.destroy();
});
it('should not allow multiple instance to be instantiated on a single container', () => {
let container = dom.getContainer();
let mixer1 = mixitup(container, {
debug: {
enable: true
},
controls: {
enable: false
}
});
let mixer2 = mixitup(container, {
debug: {
enable: true,
showWarnings: false
},
controls: {
enable: false
}
});
let facade = mixitup(container);
chai.assert.equal(mixer1, mixer2);
chai.assert.notEqual(facade, mixer1);
chai.assert.notEqual(facade, mixer2);
mixer1.destroy();
});
it('should respect a `load.filter` configuration option of none', () => {
let container = dom.getContainer();
let mixer = mixitup(container, {
load: {
filter: 'none'
}
});
let state = mixer.getState();
chai.assert.equal(state.activeFilter.selector, '');
chai.assert.equal(state.totalShow, 0);
chai.assert.equal(state.hide[0].style.display, 'none');
mixer.destroy();
});
it('should respect a `load.filter` configuration option of a single selector', () => {
let container = dom.getContainer();
let mixer = mixitup(container, {
load: {
filter: '.category-a'
}
});
let state = mixer.getState();
chai.assert.equal(state.activeFilter.selector, '.category-a');
chai.assert.equal(state.totalShow, 3);
mixer.destroy();
});
it('should respect a `load.filter` configuration option of a compound selector', () => {
let container = dom.getContainer();
let mixer = mixitup(container, {
load: {
filter: '.category-a.category-c'
}
});
let state = mixer.getState();
chai.assert.equal(state.activeFilter.selector, '.category-a.category-c');
chai.assert.equal(state.totalShow, 1);
mixer.destroy();
});
it('should respect a `load.sort` configuration option', () => {
let idsByPublishedDate = dataset.slice().sort((a, b) => {
let dateA = a.published;
let dateB = b.published;
if (dateA < dateB) {
return -1;
}
if (dateA > dateB) {
return 1;
}
return 0;
}).map(item => item.id.toString());
let container = dom.getContainer();
let mixer = mixitup(container, {
load: {
sort: 'published'
}
});
let state = mixer.getState();
let targetIds = state.show.map(el => el.id);
chai.assert.deepEqual(targetIds, idsByPublishedDate);
mixer.destroy();
});
it('should add a `layout.containerClassName` class if specified and be reflected in state', () => {
let container = dom.getContainer();
let mixer = mixitup(container, {
layout: {
containerClassName: 'grid'
}
});
let state = mixer.getState();
chai.assert.equal(state.activeContainerClassName, 'grid');
mixer.destroy();
});
});

View File

@@ -0,0 +1,44 @@
'use strict';
const chai = require('chai');
const mixitup = require('../../dist/mixitup.js');
const h = mixitup.h;
describe('h#compareVersions()', () => {
it('should return true if versions are matching', () => {
let result = h.compareVersions('1.0.0', '1.0.0');
chai.assert.isOk(result);
});
it('should return false if specimen version is less than control', () => {
let result = h.compareVersions('1.0.0', '0.1.2');
chai.assert.isNotOk(result);
});
it('should return true if specimen version is greater than control', () => {
let result = h.compareVersions('1.0.0', '1.1.2');
chai.assert.isOk(result);
});
it('should return true if specimen version is greater than control, with double figures', () => {
let result = h.compareVersions('3.0.0', '10.1.2');
chai.assert.isOk(result);
});
it('should handle semver carat notation', () => {
let result = h.compareVersions('^3.0.0', '2.0.0');
chai.assert.isNotOk(result);
});
it('should handle semver label notation', () => {
let result = h.compareVersions('^3.0.0', '3.0.0-beta');
chai.assert.isOk(result);
});
});

View File

@@ -0,0 +1,20 @@
require('./factory');
require('./mixer-get-state');
require('./mixer-filter');
require('./mixer-toggle-on-off');
require('./mixer-sort');
require('./mixer-dataset');
require('./mixer-insert');
require('./mixer-remove');
require('./mixer-change-layout');
require('./mixer-multimix');
require('./mixer-get-config');
require('./controls-filter');
require('./controls-toggle');
require('./controls-sort');
require('./controls-multimix');
require('./controls-live');
require('./queue');
require('./extension');
require('./callbacks');
require('./h');

View File

@@ -0,0 +1,54 @@
'use strict';
require('jsdom-global')();
const chai = require('chai');
const dom = require('../mock/dom');
const mixitup = require('../../dist/mixitup.js');
chai.use(require('chai-shallow-deep-equal'));
chai.use(require('chai-as-promised'));
describe('mixitup.Mixer', () => {
let container = dom.getContainer();
let newClass = 'mixitup-container__display-rows';
let mixer = mixitup(container);
describe('#changeLayout()', () => {
it('should add a new class name to the container', () => {
return mixer.changeLayout(newClass)
.then(state => {
chai.assert.equal(state.activeContainerClassName, newClass);
chai.assert.isOk(container.matches('.' + newClass));
});
});
it('should remove the class name from the container', () => {
return mixer.changeLayout('')
.then(state => {
chai.assert.equal(state.activeContainerClassName, '');
chai.assert.notOk(container.matches('.' + newClass));
});
});
it('should accept a callback function which is invoked after filtering', () => {
let promise = new Promise(resolve => mixer.changeLayout(newClass, resolve));
chai.assert.isFulfilled(promise);
return promise
.then(state => {
chai.assert.equal(state.activeContainerClassName, newClass);
chai.assert.isOk(container.matches('.' + newClass));
});
});
it('should accept a boolean allowing toggling off of animation', () => {
return mixer.changeLayout('', false)
.then(state => {
chai.assert.equal(state.activeContainerClassName, '');
chai.assert.notOk(container.matches('.' + newClass));
});
});
});
});

View File

@@ -0,0 +1,301 @@
'use strict';
require('jsdom-global')();
const chai = require('chai');
const dom = require('../mock/dom');
const mixitup = require('../../dist/mixitup.js');
const JSONDataset = require('../mock/dataset');
const dataset = JSONDataset.map(data => new dom.Item(data));
chai.use(require('chai-shallow-deep-equal'));
chai.use(require('chai-as-promised'));
describe('mixitup()', () => {
it('should throw an error if `load.dataset` does not match pre-rendered targets', () => {
const emptyContainer = dom.getEmptyContainer();
chai.assert.throws(() => {
mixitup(emptyContainer, {
load: {
dataset: dataset
}
});
}, mixitup.messages.errorDatasetPrerenderedMismatch());
});
it('should throw an error if UID not provided in dataset API mode', () => {
const container = dom.getContainer();
let mixer;
chai.assert.throws(() => {
mixer = mixitup(container, {
load: {
dataset: dataset
}
});
}, mixitup.messages.errorConfigDataUidKeyNotSet());
});
it('should instantiate in dataset API mode when provided with `load.dataset` and a matching container', () => {
const container = dom.getContainer();
const targets = Array.prototype.slice.call(container.children);
const mixer = mixitup(container, {
data: {
uidKey: 'id'
},
load: {
dataset: dataset
}
});
const state = mixer.getState();
chai.assert.equal(state.activeFilter, null);
chai.assert.equal(state.activeSort, null);
chai.assert.deepEqual(state.activeDataset, dataset);
chai.assert.deepEqual(state.targets, targets);
chai.assert.deepEqual(state.show, targets);
chai.assert.deepEqual(state.matching, []);
mixer.destroy();
});
});
describe('mixitup.Mixer', () => {
describe('#dataset()', () => {
const container = dom.getContainer();
const workingDataset = dataset.slice();
const config = {
data: {
uidKey: 'id',
dirtyCheck: true
},
render: {
target: mixitup.h.template(dom.ITEM_TEMPLATE)
},
load: {
dataset: dataset
}
};
const mixer = mixitup(container, config);
const startTotalWhitespace = dom.getTotalWhitespace(container.outerHTML);
after(() => mixer.destroy());
it('should throw an error if an item is added to the dataset, without a render function defined', () => {
const newDataset = dataset.slice();
const container = dom.getContainer();
const erMixer = mixitup(container, {
data: {
uidKey: 'id'
},
load: {
dataset: dataset
}
});
newDataset.push(new dom.Item({
id: 99,
categories: ['d']
}));
chai.assert.throws(() => {
erMixer.dataset(newDataset);
}, mixitup.messages.errorDatasetRendererNotSet());
});
it('should throw an error if an item is added to the dataset without a valid UID', () => {
const newDataset = dataset.slice();
const container = dom.getContainer();
const erMixer = mixitup(container, config);
newDataset.push(new dom.Item({
categories: ['d']
}));
chai.assert.throws(() => {
erMixer.dataset(newDataset);
}, mixitup.messages.errorDatasetInvalidUidKey({
uidKey: 'id'
}));
});
it('should throw an error if an item with a duplicate UID is added to the dataset', () => {
const newDataset = dataset.slice();
const container = dom.getContainer();
const erMixer = mixitup(container, config);
newDataset.push(new dom.Item({
id: 'target-1',
categories: ['d']
}));
chai.assert.throws(() => {
erMixer.dataset(newDataset);
}, mixitup.messages.errorDatasetDuplicateUid({
uid: 'target-1'
}));
});
it('should insert a target when a new item is added to end of the dataset', () => {
workingDataset.push(new dom.Item({
id: 7,
categories: ['d']
}));
return mixer.dataset(workingDataset)
.then((state) => {
chai.assert.equal(state.totalShow, 7);
chai.assert.equal(state.show[6].id, '7');
chai.assert.isOk(state.show[6].matches('.category-d'));
});
});
it('should insert a target when a new item is added to the start of the dataset', () => {
workingDataset.unshift(new dom.Item({
id: 0,
categories: ['d']
}));
return mixer.dataset(workingDataset)
.then((state) => {
chai.assert.equal(state.totalShow, 8);
chai.assert.equal(state.show[0].id, '0');
chai.assert.isOk(state.show[0].matches('.category-d'));
});
});
it('should insert a target when a new item is added at an arbitrary point in the dataset', () => {
workingDataset.splice(3, 0, new dom.Item({
id: 999,
categories: ['d']
}));
return mixer.dataset(workingDataset)
.then((state) => {
chai.assert.equal(state.totalShow, 9);
chai.assert.equal(state.show[3].id, '999');
chai.assert.isOk(state.show[3].matches('.category-d'));
});
});
it('should remove a target when an item is removed from the end of the dataset', () => {
workingDataset.pop();
return mixer.dataset(workingDataset)
.then((state) => {
chai.assert.equal(state.totalShow, 8);
chai.assert.notEqual(state.show[7].id, '7');
});
});
it('should remove a target when an item is removed from the start of the dataset', () => {
workingDataset.shift();
return mixer.dataset(workingDataset)
.then((state) => {
chai.assert.equal(state.totalShow, 7);
chai.assert.notEqual(state.show[0].id, '0');
});
});
it('should remove a target when an item is removed from an arbitary point in the dataset', () => {
const removed = workingDataset.splice(2, 1);
chai.assert.equal(removed[0].id, 999);
return mixer.dataset(workingDataset)
.then((state) => {
chai.assert.equal(state.totalShow, 6);
chai.assert.notEqual(state.show[2].id, '999');
});
});
it('should sort targets when the dataset is sorted', () => {
workingDataset.reverse();
const ids = workingDataset.map((item) => item.id.toString());
return mixer.dataset(workingDataset)
.then((state) => {
const elIds = state.show.map((el) => el.id);
chai.assert.equal(state.totalShow, 6);
chai.assert.deepEqual(ids, elIds);
});
});
it('should sort rerender targets if their data changes and dirtyChecking is enabled', () => {
workingDataset[0] = new dom.Item(Object.assign({}, workingDataset[0]));
workingDataset[0].categories.push('z');
return mixer.dataset(workingDataset)
.then((state) => {
chai.assert.isOk(state.show[0].matches('.category-z'));
});
});
it('should not insert excessive whitespace after DOM manipulations', () => {
chai.assert.equal(dom.getTotalWhitespace(container.outerHTML), startTotalWhitespace);
});
it('should accept a callback function which is invoked after dataset change', () => {
workingDataset.reverse();
const ids = workingDataset.map((item) => item.id.toString());
const promise = new Promise(resolve => mixer.dataset(workingDataset, resolve));
chai.assert.isFulfilled(promise);
return promise
.then((state) => {
const elIds = state.show.map((el) => el.id);
chai.assert.equal(state.totalShow, 6);
chai.assert.deepEqual(ids, elIds);
});
});
it('should accept a boolean allowing toggling off of animation', () => {
workingDataset.reverse();
const ids = workingDataset.map((item) => item.id.toString());
return mixer.dataset(workingDataset, false)
.then(state => {
const elIds = state.show.map((el) => el.id);
chai.assert.equal(state.totalShow, 6);
chai.assert.deepEqual(ids, elIds);
});
});
it('should re-render targets reflective of template changes when `forceRender` is called', () => {
let firstTarget = mixer.getState().show[0];
chai.assert.equal(firstTarget.outerHTML, '<div id="target-6" class="mix category-a category-c category-z" data-ref="mix" data-category="a c z" data-published="20151020" data-views="95"></div>');
mixer.configure({
render: {
target: mixitup.h.template(dom.ITEM_TEMPLATE_ALT)
}
});
mixer.forceRender();
firstTarget = mixer.getState().show[0];
chai.assert.equal(firstTarget.outerHTML, '<div id="target-6"></div>');
});
});
});

View File

@@ -0,0 +1,268 @@
'use strict';
require('jsdom-global')();
const chai = require('chai');
const dom = require('../mock/dom');
const mixitup = require('../../dist/mixitup.js');
chai.use(require('chai-shallow-deep-equal'));
chai.use(require('chai-as-promised'));
describe('mixitup.Mixer', () => {
describe('#filter()', () => {
let container = dom.getContainer();
let mixer = mixitup(container);
it('should accept a class selector', () => {
let matching = Array.prototype.slice.call(container.querySelectorAll('.category-a'));
return mixer.filter('.category-a')
.then(state => {
chai.assert.equal(state.totalShow, matching.length);
chai.assert.deepEqual(state.show, matching);
chai.assert.deepEqual(state.matching, matching);
});
});
it('should accept an attribute selector', () => {
let matching = Array.prototype.slice.call(container.querySelectorAll('[data-category~="a"]'));
return mixer.filter('[data-category~="a"]')
.then(state => {
chai.assert.equal(state.totalShow, matching.length);
chai.assert.deepEqual(state.show, matching);
chai.assert.deepEqual(state.matching, matching);
});
});
it('should accept a compound OR class selector', () => {
let matching = Array.prototype.slice.call(container.querySelectorAll('.category-a, .category-b'));
return mixer.filter('.category-a, .category-b')
.then(state => {
chai.assert.equal(state.totalShow, matching.length);
chai.assert.deepEqual(state.show, matching);
chai.assert.deepEqual(state.matching, matching);
});
});
it('should accept a compound AND class selector', () => {
let matching = Array.prototype.slice.call(container.querySelectorAll('.category-a.category-c'));
return mixer.filter('.category-a.category-c')
.then(state => {
chai.assert.equal(state.totalShow, matching.length);
chai.assert.deepEqual(state.show, matching);
chai.assert.deepEqual(state.matching, matching);
});
});
it('should accept a compound OR attribute selector', () => {
let matching = Array.prototype.slice.call(container.querySelectorAll('[data-category~="a"], [data-category~="c"]'));
return mixer.filter('[data-category~="a"], [data-category~="c"]')
.then(state => {
chai.assert.equal(state.totalShow, matching.length);
chai.assert.deepEqual(state.show, matching);
chai.assert.deepEqual(state.matching, matching);
});
});
it('should accept a compound AND attribute selector', () => {
let matching = Array.prototype.slice.call(container.querySelectorAll('[data-category~="a"][data-category~="c"]'));
return mixer.filter('[data-category~="a"][data-category~="c"]')
.then(state => {
chai.assert.equal(state.totalShow, matching.length);
chai.assert.equal(state.totalShow, 1);
chai.assert.deepEqual(state.show, matching);
chai.assert.deepEqual(state.matching, matching);
});
});
it('should accept "none"', () => {
return mixer.filter('none')
.then(state => {
chai.assert.equal(state.totalShow, 0);
chai.assert.equal(state.hasFailed, false);
chai.assert.deepEqual(state.hide, Array.prototype.slice.call(container.children));
chai.assert.equal(state.activeFilter.selector, '');
});
});
it('should accept "all"', () => {
return mixer.filter('all')
.then(state => {
chai.assert.deepEqual(state.show, Array.prototype.slice.apply(container.children));
chai.assert.deepEqual(state.show, state.targets);
});
});
it('should fail if queried with a non matching selector', () => {
return mixer.filter('.non-mathing-selector')
.then(state => {
chai.assert.deepEqual(state.show, []);
chai.assert.equal(state.hasFailed, true);
});
});
it('should accept a single element', () => {
let el = container.firstElementChild;
return mixer.filter(el)
.then(state => {
chai.assert.deepEqual(state.show, [el]);
chai.assert.equal(state.activeFilter.selector, '');
chai.assert.deepEqual(state.activeFilter.collection, [el]);
});
});
it('should accept a collection of elements', () => {
let collection = [
container.firstElementChild,
container.lastElementChild
];
return mixer.filter(collection)
.then(state => {
chai.assert.deepEqual(state.show, collection);
chai.assert.equal(state.activeFilter.selector, '');
chai.assert.deepEqual(state.activeFilter.collection, collection);
});
});
it('should interpret `null` as hide all', () => {
return mixer.filter(null)
.then(state => {
chai.assert.deepEqual(state.show, []);
chai.assert.equal(state.activeFilter.selector, '');
chai.assert.deepEqual(state.activeFilter.collection, []);
});
});
it('should interpret `[]` as hide all', () => {
return mixer.filter(null)
.then(state => {
chai.assert.deepEqual(state.show, []);
chai.assert.equal(state.activeFilter.selector, '');
chai.assert.deepEqual(state.activeFilter.collection, []);
});
});
it('should accept a full CommandFilter object, allowing for inverse filtering via selector', () => {
let command = {
selector: '.category-a',
action: 'hide'
};
let collection = Array.prototype.slice.call(container.querySelectorAll(':not(.category-a)'));
return mixer.filter(command)
.then(state => {
chai.assert.deepEqual(state.show, collection);
chai.assert.equal(state.activeFilter.selector, '.category-a');
chai.assert.equal(state.activeFilter.action, 'hide');
});
});
it('should accept a full CommandFilter object, allowing for inverse filtering via a collection', () => {
let el = container.querySelector('.category-a.category-c');
let command = {
collection: [el],
action: 'hide'
};
let collection = Array.prototype.slice.call(container.querySelectorAll(':not(.category-a.category-c)'));
return mixer.filter(command)
.then(state => {
chai.assert.deepEqual(state.show, collection);
chai.assert.deepEqual(state.activeFilter.collection, [el]);
chai.assert.equal(state.activeFilter.action, 'hide');
});
});
it('should accept a callback function which is invoked after filtering', () => {
let matching = Array.prototype.slice.call(container.querySelectorAll('.category-a'));
let promise = new Promise(resolve => mixer.filter('.category-a', resolve));
chai.assert.isFulfilled(promise);
return promise
.then(state => {
chai.assert.equal(state.totalShow, matching.length);
chai.assert.deepEqual(state.show, matching);
chai.assert.deepEqual(state.matching, matching);
});
});
it('should return a promise which is resolved after filtering', () => {
let matching = Array.prototype.slice.call(container.querySelectorAll('.category-a'));
return mixer.filter('.category-a')
.then(state => {
chai.assert.equal(state.totalShow, matching.length);
chai.assert.deepEqual(state.show, matching);
chai.assert.deepEqual(state.matching, matching);
});
});
it('should accept a boolean allowing toggling off of animation', () => {
let matching = Array.prototype.slice.call(container.querySelectorAll('.category-a'));
return mixer.filter('.category-a', false)
.then(state => {
chai.assert.equal(state.totalShow, matching.length);
chai.assert.deepEqual(state.show, matching);
chai.assert.deepEqual(state.matching, matching);
});
});
it('should throw an error if both a selector and a collection are provided', () => {
let command = {
collection: [],
selector: '.selector'
};
chai.assert.throws(() => {
mixer.filter(command);
}, Error, mixitup.messages.errorFilterInvalidArguments());
});
});
});
describe('mixitup.Mixer', () => {
describe('#hide()', () => {
let container = dom.getContainer();
let mixer = mixitup(container);
it('should hide all elements', () => {
return mixer.hide()
.then(state => {
chai.assert.equal(state.totalShow, 0);
chai.assert.equal(state.totalHide, state.targets.length);
chai.assert.equal(state.activeFilter.selector, '');
});
});
});
});
describe('mixitup.Mixer', () => {
describe('#show()', () => {
let container = dom.getContainer();
let mixer = mixitup(container);
it('should show all elements', () => {
return mixer.filter('.category-a')
.then(mixer.show)
.then(state => {
chai.assert.equal(state.totalShow, state.targets.length);
chai.assert.equal(state.totalHide, 0);
chai.assert.equal(state.activeFilter.selector, '.mix');
});
});
});
});

View File

@@ -0,0 +1,62 @@
'use strict';
require('jsdom-global')();
const chai = require('chai');
const dom = require('../mock/dom');
const mixitup = require('../../dist/mixitup.js');
chai.use(require('chai-shallow-deep-equal'));
chai.use(require('chai-as-promised'));
describe('mixitup.Mixer', () => {
let container = dom.getContainer();
let mixer = mixitup(container);
describe('#getConfig()', () => {
it('should retrieve the whole config object if no stringKey passed', () => {
let config = mixer.getConfig();
chai.assert.instanceOf(config, mixitup.Config);
});
it('should retrieve a config sub-object if single prop stringKey passed', () => {
let config = mixer.getConfig('animation');
chai.assert.instanceOf(config, mixitup.ConfigAnimation);
});
it('should retrieve a nested property value if multi-prop stringKey passed', () => {
let config = mixer.getConfig('animation.effects');
chai.assert.equal(typeof config, 'string');
});
it('should retrieve a the current configuration, reflective of any changes', () => {
let newEffects = 'fade translateZ(-100px)';
mixer.configure({
animation: {
effects: newEffects
}
});
let newConfig = mixer.getConfig('animation.effects');
chai.assert.equal(newConfig, newEffects);
});
it('should throw an error if an invalid configuration option is passed', function() {
chai.assert.throws(() => {
mixer.configure({
animations: {}
});
}, TypeError, mixitup.messages.errorConfigInvalidProperty({
erroneous: 'animations',
suggestion: mixitup.messages.errorConfigInvalidPropertySuggestion({
probableMatch: 'animation'
})
}));
});
});
});

View File

@@ -0,0 +1,92 @@
'use strict';
require('jsdom-global')();
const chai = require('chai');
const dom = require('../mock/dom');
const mixitup = require('../../dist/mixitup.js');
chai.use(require('chai-shallow-deep-equal'));
describe('mixitup.Mixer', () => {
describe('#getState()', () => {
let container = dom.getContainer();
let id = container.id = 'test-id';
let mixer = mixitup(container);
let state = mixer.getState();
after(() => mixer.destroy());
it('should contain an id equal to the container id', () => {
chai.assert.equal(state.container.id, id);
});
it('should contain a reference to the container element', () => {
chai.assert.equal(state.container, container);
});
it('should contain a reference to the container element', () => {
chai.assert.equal(state.container, container);
});
it('should contain an activeFilter object with the default selector active', () => {
chai.assert.instanceOf(state.activeFilter, mixitup.CommandFilter);
chai.assert.equal(state.activeFilter.selector, '.mix');
});
it('should contain an activeSort object with the default sort string active', () => {
chai.assert.instanceOf(state.activeSort, mixitup.CommandSort);
chai.assert.equal(state.activeSort.sortString, 'default:asc');
});
it('should contain an empty activeContainerClassName string', () => {
chai.assert.equal(state.activeContainerClassName, '');
});
it('should contain a null activeDataset', () => {
chai.assert.deepEqual(state.activeDataset, null);
});
it('should contain a hasFailed boolean, set to false', () => {
chai.assert.deepEqual(state.hasFailed, false);
});
it('should contain a list of targets deeply equaling the contents of the container', () => {
chai.assert.deepEqual(state.targets, Array.prototype.slice.apply(container.children));
});
it('should contain a totalTargets integer, equal to the number of targets in the container', () => {
chai.assert.equal(state.totalTargets, container.children.length);
});
it('should contain a list of targets currently shown', () => {
chai.assert.deepEqual(state.show, Array.prototype.slice.apply(container.children));
chai.assert.deepEqual(state.show, state.targets);
});
it('should contain a totalShow integer, equal to the number of targets shown', () => {
chai.assert.equal(state.totalShow, container.children.length);
});
it('should contain a list of targets matching the active selector', () => {
chai.assert.deepEqual(state.matching, Array.prototype.slice.apply(container.children));
chai.assert.deepEqual(state.matching, state.targets);
});
it('should contain a totalMatching integer, equal to the number of targets matching the active selector', () => {
chai.assert.equal(state.totalMatching, container.children.length);
});
it('should contain a list of targets currently hidden', () => {
chai.assert.deepEqual(state.hide, []);
});
it('should contain a totalShow integer, equal to the number of targets hidden', () => {
chai.assert.equal(state.totalHide, 0);
});
it('should contain a null triggerElement reference', () => {
chai.assert.equal(state.triggerElement, null);
});
});
});

View File

@@ -0,0 +1,395 @@
'use strict';
require('jsdom-global')();
const chai = require('chai');
const dom = require('../mock/dom');
const mixitup = require('../../dist/mixitup.js');
chai.use(require('chai-shallow-deep-equal'));
chai.use(require('chai-as-promised'));
describe('mixitup.Mixer', () => {
describe('#insert()', () => {
it('should accept an element as an argument', () => {
let container = dom.getContainer();
let mixer = mixitup(container);
let newTarget = dom.getTarget();
return mixer.insert(newTarget)
.then(state => {
chai.assert.equal(state.show[0].id, 7);
mixer.destroy();
});
});
it('should accept an element and an index as arguments', () => {
let container = dom.getContainer();
let mixer = mixitup(container);
let newTarget = dom.getTarget();
return mixer.insert(newTarget, 3)
.then(state => {
chai.assert.equal(state.show[3].id, 7);
mixer.destroy();
});
});
it('should accept an html string as an argument', () => {
let container = dom.getContainer();
let mixer = mixitup(container);
let newTarget = dom.getTarget();
return mixer.insert(newTarget.outerHTML)
.then(state => {
chai.assert.equal(state.show[0].id, 7);
mixer.destroy();
});
});
it('should accept an html and an index as arguments', () => {
let container = dom.getContainer();
let mixer = mixitup(container);
let newTarget = dom.getTarget();
return mixer.insert(newTarget.outerHTML, 5)
.then(state => {
chai.assert.equal(state.show[5].id, 7);
mixer.destroy();
});
});
it('should accept accept an element collection as an argument', () => {
let container = dom.getContainer();
let mixer = mixitup(container);
let newTarget1 = dom.getTarget();
let newTarget2 = dom.getTarget();
newTarget2.id = '8';
return mixer.insert([newTarget1, newTarget2])
.then(state => {
chai.assert.equal(state.show[0].id, 7);
chai.assert.equal(state.show[1].id, 8);
mixer.destroy();
});
});
it('should accept accept a document fragment as an argument', () => {
let container = dom.getContainer();
let mixer = mixitup(container);
let newTarget = dom.getTarget();
let frag = document.createDocumentFragment();
frag.appendChild(newTarget);
return mixer.insert(frag)
.then(state => {
chai.assert.equal(state.show[0].id, 7);
mixer.destroy();
});
});
it('should accept accept an element collection and an index as an argument', () => {
let container = dom.getContainer();
let mixer = mixitup(container);
let newTarget1 = dom.getTarget();
let newTarget2 = dom.getTarget();
newTarget2.id = '8';
return mixer.insert([newTarget1, newTarget2], 4)
.then(state => {
chai.assert.equal(state.show[4].id, 7);
chai.assert.equal(state.show[5].id, 8);
mixer.destroy();
});
});
it('should throw an error if an element, index and sibling are passed simultaneously', () => {
let container = dom.getContainer();
let mixer = mixitup(container);
let newTarget = dom.getTarget();
let sibling = container.children[4];
chai.assert.throws(() => {
mixer.insert(newTarget, 4, sibling);
}, Error, mixitup.messages.errorInsertInvalidArguments());
});
it('should accept an element and sibling reference to insert before', () => {
let container = dom.getContainer();
let mixer = mixitup(container);
let newTarget = dom.getTarget();
let sibling = container.children[4];
return mixer.insert(newTarget, sibling)
.then(state => {
chai.assert.equal(state.show[4].id, '7');
mixer.destroy();
});
});
it('should accept an element, sibling reference and position string', () => {
let container = dom.getContainer();
let mixer = mixitup(container);
let newTarget = dom.getTarget();
let sibling = container.children[4];
return mixer.insert(newTarget, sibling, 'after')
.then(state => {
chai.assert.equal(state.show[5].id, '7');
mixer.destroy();
});
});
it('should insert at end if the insertion index is above range', () => {
let container = dom.getContainer();
let mixer = mixitup(container);
let newTarget = dom.getTarget();
return mixer.insert(newTarget, 10)
.then(state => {
chai.assert.equal(state.show[6].id, '7');
mixer.destroy();
});
});
it('should insert at start if the insertion index is below range', () => {
let container = dom.getContainer();
let mixer = mixitup(container);
let newTarget = dom.getTarget();
return mixer.insert(newTarget, -2)
.then(state => {
chai.assert.equal(state.show[0].id, '7');
mixer.destroy();
});
});
it('should throw an error if the element to insert already exists', () => {
let container = dom.getContainer();
let mixer = mixitup(container);
let newTarget = container.children[1];
chai.assert.throws(() => {
mixer.insert(newTarget);
}, Error, mixitup.messages.errorInsertPreexistingElement());
});
it('should allow no elements to be inserted with a warning', () => {
let container = dom.getContainer();
let totalTargets = container.children.length;
let mixer = mixitup(container);
return mixer.insert()
.then(state => {
chai.assert.equal(state.totalShow, totalTargets);
mixer.destroy();
});
});
it('should accept a callback function which is invoked after insertion', () => {
let container = dom.getContainer();
let mixer = mixitup(container);
let newTarget = dom.getTarget();
let promise = new Promise(resolve => mixer.insert(newTarget, resolve));
chai.assert.isFulfilled(promise);
return promise
.then(() => {
chai.assert.equal(newTarget.parentElement, container);
mixer.destroy();
});
});
it('should accept a boolean allowing toggling off of animation', () => {
let container = dom.getContainer();
let mixer = mixitup(container);
let newTarget = dom.getTarget();
return mixer.insert(newTarget, false)
.then(() => {
chai.assert.equal(newTarget.parentElement, container);
mixer.destroy();
});
});
it('should accept a HTML with padding whitespace as an argument', () => {
let container = dom.getContainer();
let mixer = mixitup(container);
let newTarget = ' ' + dom.getTarget().outerHTML + ' ';
return mixer.insert(newTarget)
.then(state => {
chai.assert.equal(state.show[0].id, 7);
mixer.destroy();
});
});
});
describe('#prepend()', () => {
it('should insert an element at the start', () => {
let container = dom.getContainer();
let mixer = mixitup(container);
let newTarget = dom.getTarget();
return mixer.prepend(newTarget)
.then(state => {
chai.assert.equal(state.show[0].id, 7);
mixer.destroy();
});
});
it('should insert a collection of elements at the start', () => {
let container = dom.getContainer();
let mixer = mixitup(container);
let newTarget1 = dom.getTarget();
let newTarget2 = dom.getTarget();
newTarget2.id = '8';
return mixer.prepend([newTarget1, newTarget2])
.then(state => {
chai.assert.equal(state.show[0].id, 7);
chai.assert.equal(state.show[1].id, 8);
mixer.destroy();
});
});
});
describe('#append()', () => {
it('should insert an element at the end', () => {
let container = dom.getContainer();
let mixer = mixitup(container);
let newTarget = dom.getTarget();
return mixer.append(newTarget)
.then(state => {
chai.assert.equal(state.show[6].id, 7);
mixer.destroy();
});
});
it('should insert a collection of elements at the end', () => {
let container = dom.getContainer();
let mixer = mixitup(container);
let newTarget1 = dom.getTarget();
let newTarget2 = dom.getTarget();
newTarget2.id = '8';
return mixer.append([newTarget1, newTarget2])
.then(state => {
chai.assert.equal(state.show[6].id, 7);
chai.assert.equal(state.show[7].id, 8);
mixer.destroy();
});
});
it('should accept accept a document fragment as an argument to append', () => {
let container = dom.getContainer();
let mixer = mixitup(container);
let newTarget = dom.getTarget();
let frag = document.createDocumentFragment();
frag.appendChild(newTarget);
return mixer.append(frag)
.then(state => {
chai.assert.equal(state.show[6].id, 7);
mixer.destroy();
});
});
});
describe('#insertBefore()', () => {
it('should insert an element before the referenced element', () => {
let container = dom.getContainer();
let mixer = mixitup(container);
let newTarget = dom.getTarget();
let sibling = container.children[3];
return mixer.insertBefore(newTarget, sibling)
.then(state => {
chai.assert.equal(state.show[3].id, 7);
mixer.destroy();
});
});
it('should insert a collection of elements before the referenced element', () => {
let container = dom.getContainer();
let mixer = mixitup(container);
let newTarget1 = dom.getTarget();
let newTarget2 = dom.getTarget();
let sibling = container.children[3];
newTarget2.id = '8';
return mixer.insertBefore([newTarget1, newTarget2], sibling)
.then(state => {
chai.assert.equal(state.show[3].id, 7);
chai.assert.equal(state.show[4].id, 8);
mixer.destroy();
});
});
});
describe('#insertAfter()', () => {
it('should insert an element after the referenced element', () => {
let container = dom.getContainer();
let mixer = mixitup(container);
let newTarget = dom.getTarget();
let sibling = container.children[3];
return mixer.insertAfter(newTarget, sibling)
.then(state => {
chai.assert.equal(state.show[4].id, 7);
mixer.destroy();
});
});
it('should insert a collection of elements after the referenced element', () => {
let container = dom.getContainer();
let mixer = mixitup(container);
let newTarget1 = dom.getTarget();
let newTarget2 = dom.getTarget();
let sibling = container.children[3];
newTarget2.id = '8';
return mixer.insertAfter([newTarget1, newTarget2], sibling)
.then(state => {
chai.assert.equal(state.show[4].id, 7);
chai.assert.equal(state.show[5].id, 8);
mixer.destroy();
});
});
});
});

View File

@@ -0,0 +1,17 @@
'use strict';
require('jsdom-global')();
const chai = require('chai');
const dom = require('../mock/dom');
const mixitup = require('../../dist/mixitup.js');
chai.use(require('chai-shallow-deep-equal'));
chai.use(require('chai-as-promised'));
describe('mixitup.Mixer', () => {
describe('#multimix()', () => {
let container = dom.getContainer();
let mixer = mixitup(container);
});
});

View File

@@ -0,0 +1,111 @@
'use strict';
require('jsdom-global')();
const chai = require('chai');
const dom = require('../mock/dom');
const mixitup = require('../../dist/mixitup.js');
chai.use(require('chai-shallow-deep-equal'));
chai.use(require('chai-as-promised'));
describe('mixitup.Mixer', () => {
describe('#remove()', () => {
it('should accept an element as an argument', () => {
const container = dom.getContainer();
const mixer = mixitup(container);
const toRemove = container.children[3];
return mixer.remove(toRemove)
.then(state => {
chai.assert.notEqual(state.show[3].id, 'target-4');
chai.assert.equal(state.show[3].id, 'target-5');
chai.assert.equal(state.totalShow, '5');
mixer.destroy();
});
});
it('should accept a collection of elements as an argument', () => {
const container = dom.getContainer();
const mixer = mixitup(container);
const toRemove = [container.children[3], container.children[0]];
return mixer.remove(toRemove)
.then(state => {
chai.assert.equal(state.show[0].id, 'target-2');
chai.assert.equal(state.show[3].id, 'target-6');
chai.assert.equal(state.totalShow, '4');
mixer.destroy();
});
});
it('should accept an index as an argument', () => {
const container = dom.getContainer();
const mixer = mixitup(container);
return mixer.remove(3)
.then(state => {
chai.assert.equal(state.show[3].id, 'target-5');
chai.assert.equal(state.totalShow, '5');
mixer.destroy();
});
});
it('should accept a selector as an argument', () => {
const container = dom.getContainer();
const mixer = mixitup(container);
return mixer.remove('.category-a')
.then(state => {
chai.assert.equal(state.totalShow, '3');
mixer.destroy();
});
});
it('should allow no elements to be removed with a warning', () => {
const container = dom.getContainer();
const mixer = mixitup(container);
return mixer.remove()
.then(state => {
chai.assert.equal(state.totalShow, '6');
mixer.destroy();
});
});
it('should accept a callback function which is invoked after removal', () => {
const container = dom.getContainer();
const mixer = mixitup(container);
const toRemove = container.children[0];
const promise = new Promise(resolve => mixer.insert(mixer.remove(toRemove), resolve));
chai.assert.isFulfilled(promise);
return promise
.then(() => {
chai.assert.notEqual(toRemove, container);
mixer.destroy();
});
});
it('should accept a boolean allowing toggling off of animation', () => {
const container = dom.getContainer();
const mixer = mixitup(container);
const toRemove = container.children[0];
return mixer.remove(toRemove, false)
.then(() => {
chai.assert.notEqual(toRemove, container);
mixer.destroy();
});
});
});
});

View File

@@ -0,0 +1,257 @@
'use strict';
require('jsdom-global')();
const chai = require('chai');
const dom = require('../mock/dom');
const dataset = require('../mock/dataset');
const mixitup = require('../../dist/mixitup.js');
chai.use(require('chai-shallow-deep-equal'));
chai.use(require('chai-as-promised'));
describe('mixitup.Mixer', () => {
describe('#sort()', () => {
const container = dom.getContainer();
const originalOrder = Array.prototype.slice.call(container.children);
const mixer = mixitup(container);
const idsByPublishedDate = dataset.slice().sort((a, b) => {
const dateA = a.published;
const dateB = b.published;
if (dateA < dateB) {
return -1;
}
if (dateA > dateB) {
return 1;
}
return 0;
}).map(item => item.id.toString());
const idsByViewsThenPublishedDate = dataset.slice().sort((a, b) => {
const viewsA = a.views;
const viewsB = b.views;
const sortByPublishedDate = function(a, b) {
const dateA = a.published;
const dateB = b.published;
if (dateA < dateB) {
return -1;
}
if (dateA > dateB) {
return 1;
}
return 0;
};
if (viewsA < viewsB) {
return -1;
}
if (viewsA > viewsB) {
return 1;
}
sortByPublishedDate(a, b);
}).map(item => item.id.toString());
after(() => mixer.destroy());
it('accepts `default` as a sort string, but should have no effect on the order', () => {
var startOrder = mixer.getState().show;
return mixer.sort('default')
.then(state => {
chai.assert.deepEqual(startOrder, state.show);
chai.assert.equal(state.activeSort.sortString, 'default');
chai.assert.equal(state.activeSort.order, 'asc');
chai.assert.equal(state.activeSort.attribute, '');
});
});
it('accepts `default:asc` as a sort string, but should have no effect on the order', () => {
var startOrder = mixer.getState().show;
return mixer.sort('default:asc')
.then(state => {
chai.assert.deepEqual(startOrder, state.show);
chai.assert.equal(state.activeSort.sortString, 'default:asc');
chai.assert.equal(state.activeSort.order, 'asc');
chai.assert.equal(state.activeSort.attribute, '');
});
});
it('accepts `default:desc` as a sort string, which should reverse the order', () => {
var reversedOrder = mixer.getState().show.slice().reverse();
return mixer.sort('default:desc')
.then(state => {
chai.assert.deepEqual(state.show, reversedOrder);
chai.assert.equal(state.activeSort.sortString, 'default:desc');
chai.assert.equal(state.activeSort.order, 'desc');
chai.assert.equal(state.activeSort.attribute, '');
});
});
it('should return the mixer to its original order if sorted by `default` after previous transformations', () => {
return mixer.sort('default')
.then(state => chai.assert.deepEqual(state.show, originalOrder));
});
it('should accept `random` as a sort string, shuffling the targets', () => {
return mixer.sort('random')
.then(state => {
chai.assert.notDeepEqual(state.show, originalOrder);
chai.assert.equal(state.activeSort.sortString, 'random');
chai.assert.equal(state.activeSort.order, 'random');
chai.assert.equal(state.activeSort.attribute, '');
});
});
it('should accept a data-attribute as a sort string, sorting by the attribute\'s value', () => {
return mixer.sort('published')
.then(state => {
const targetIds = state.show.map(el => el.id);
chai.assert.equal(state.activeSort.sortString, 'published');
chai.assert.equal(state.activeSort.order, 'asc');
chai.assert.equal(state.activeSort.attribute, 'published');
chai.assert.deepEqual(targetIds, idsByPublishedDate);
});
});
it('should accept a data-attribute and an order as sorting, sorting by the attribute\'s value in the defined order', () => {
const idsByPublishedDateDesc = idsByPublishedDate.slice().reverse();
return mixer.sort('published:desc')
.then(state => {
const targetIds = state.show.map(el => el.id);
chai.assert.equal(state.activeSort.sortString, 'published:desc');
chai.assert.equal(state.activeSort.order, 'desc');
chai.assert.equal(state.activeSort.attribute, 'published');
chai.assert.deepEqual(targetIds, idsByPublishedDateDesc);
});
});
it('should accept multiple sort strings for multi attribute sorting', () => {
return mixer.sort('views published')
.then(state => {
const targetIds = state.show.map(el => el.id);
chai.assert.isOk(state.activeSort.next);
chai.assert.instanceOf(state.activeSort.next, mixitup.CommandSort);
chai.assert.equal(state.activeSort.sortString, 'views');
chai.assert.equal(state.activeSort.order, 'asc');
chai.assert.equal(state.activeSort.attribute, 'views');
chai.assert.equal(state.activeSort.next.sortString, 'published');
chai.assert.equal(state.activeSort.next.order, 'asc');
chai.assert.equal(state.activeSort.next.attribute, 'published');
chai.assert.deepEqual(targetIds, idsByViewsThenPublishedDate);
});
});
it('should accept multiple sort strings with orders for multi attribute sorting', () => {
const idsByViewsThenPublishedDateDesc = idsByViewsThenPublishedDate.slice().reverse();
return mixer.sort('views:desc published:desc')
.then(state => {
const targetIds = state.show.map(el => el.id);
chai.assert.deepEqual(targetIds, idsByViewsThenPublishedDateDesc);
});
});
it('should accept multiple sort strings with orders for multi attribute sorting', () => {
const idsByViewsThenPublishedDateDesc = idsByViewsThenPublishedDate.slice().reverse();
return mixer.sort('views:desc published:desc')
.then(state => {
const targetIds = state.show.map(el => el.id);
chai.assert.deepEqual(targetIds, idsByViewsThenPublishedDateDesc);
});
});
it('should accept a collection of elements by which to sort by', () => {
const firstTarget = mixer.getState().targets[0];
const collection = mixer.getState().targets.slice().reverse();
return mixer.sort(collection)
.then(state => {
const lastTarget = state.targets[state.targets.length - 1];
chai.assert.deepEqual(lastTarget, firstTarget);
});
});
it('should error if any element in the collection provided does not exist in the container', () => {
const mixer = mixitup(container);
const collection = [document.createElement('div')];
chai.assert.throws(() => mixer.sort(collection));
});
it('should accept a callback function which is invoked after sorting', () => {
const promise = new Promise(resolve => mixer.sort('random', resolve));
chai.assert.isFulfilled(promise);
return promise
.then(state => chai.assert.equal(state.activeSort.sortString, 'random'));
});
it('should accept a boolean allowing toggling off of animation', () => {
return mixer.sort('random', false)
.then(state => chai.assert.equal(state.activeSort.sortString, 'random'));
});
it('should resort when sorting attributes are dynamically edited, if `behavior.liveSort` is enabled', () => {
const newDate = '20170628';
let startOrder = null;
return mixer.sort('published', false)
.then(state => {
const target4 = container.querySelector('#target-4');
startOrder = state.targets;
target4.setAttribute('data-published', newDate);
return mixer.sort('published', false);
})
.then(state => {
// Order has not changed
chai.assert.deepEqual(state.targets, startOrder);
chai.assert.notEqual(state.targets[5].getAttribute('data-published'), newDate);
mixer.configure({
behavior: {
liveSort: true
}
});
return mixer.sort('published', false);
})
.then(state => {
// Order has changed
chai.assert.notDeepEqual(state.targets, startOrder);
chai.assert.equal(state.targets[5].getAttribute('data-published'), newDate);
});
});
});
});

View File

@@ -0,0 +1,82 @@
'use strict';
require('jsdom-global')();
const chai = require('chai');
const dom = require('../mock/dom');
const mixitup = require('../../dist/mixitup.js');
chai.use(require('chai-shallow-deep-equal'));
chai.use(require('chai-as-promised'));
describe('mixitup.Mixer', () => {
describe('#toggleOn()', () => {
const container = dom.getContainer();
const mixer = mixitup(container);
it('should activate an initial toggle', () => {
const matching = Array.prototype.slice.call(container.querySelectorAll('.category-a'));
return mixer.toggleOn('.category-a')
.then(state => {
chai.assert.equal(state.totalShow, matching.length);
chai.assert.deepEqual(state.show, matching);
chai.assert.deepEqual(state.matching, matching);
});
});
it('should activate a further toggle', () => {
const matching = Array.prototype.slice.call(container.querySelectorAll('.category-a, .category-c'));
return mixer.toggleOn('.category-c')
.then(state => {
chai.assert.equal(state.totalShow, matching.length);
chai.assert.deepEqual(state.show, matching);
chai.assert.deepEqual(state.matching, matching);
});
});
it('should activate a non-existant toggle with no effect', () => {
const matching = Array.prototype.slice.call(container.querySelectorAll('.category-a, .category-c'));
return mixer.toggleOn('.category-z')
.then(state => {
chai.assert.equal(state.totalShow, matching.length);
chai.assert.deepEqual(state.show, matching);
chai.assert.deepEqual(state.matching, matching);
});
});
});
describe('#toggleOff()', () => {
const container = dom.getContainer();
const mixer = mixitup(container, {
load: {
filter: '.category-a, .category-b, .category-c'
}
});
it('should deactivate a toggle', () => {
const matching = Array.prototype.slice.call(container.querySelectorAll('.category-a, .category-b'));
return mixer.toggleOff('.category-c')
.then(state => {
chai.assert.equal(state.totalShow, matching.length);
chai.assert.deepEqual(state.show, matching);
chai.assert.deepEqual(state.matching, matching);
});
});
it('should deactivate a non existent toggle with no effect', () => {
const matching = Array.prototype.slice.call(container.querySelectorAll('.category-a, .category-b'));
return mixer.toggleOff('.category-z')
.then(state => {
chai.assert.equal(state.totalShow, matching.length);
chai.assert.deepEqual(state.show, matching);
chai.assert.deepEqual(state.matching, matching);
});
});
});
});

View File

@@ -0,0 +1,71 @@
'use strict';
require('jsdom-global')();
const chai = require('chai');
const dom = require('../mock/dom');
const mixitup = require('../../dist/mixitup.js');
const JSONDataset = require('../mock/dataset');
const dataset = JSONDataset.map(data => new dom.Item(data));
chai.use(require('chai-shallow-deep-equal'));
chai.use(require('chai-as-promised'));
describe('mixitup.Mixer', () => {
describe('Queue', () => {
it('should warn if too many multimix operations are pushed into the queue', () => {
let container = dom.getContainer();
let mixer = mixitup(container, {
debug: {
fauxAsync: true
},
animation: {
duration: 200
}
});
let promise = Promise.all([
mixer.hide(),
mixer.show(),
mixer.hide(),
mixer.show(),
mixer.hide()
]);
chai.assert.isFulfilled(promise);
return promise;
});
it('should warn if too many dataset operations are pushed into the queue', () => {
let container = dom.getContainer();
let mixer = mixitup(container, {
debug: {
fauxAsync: true
},
animation: {
duration: 200
},
data: {
uidKey: 'id'
},
load: {
dataset: dataset
}
});
let promise = Promise.all([
mixer.dataset([]),
mixer.dataset(dataset),
mixer.dataset([]),
mixer.dataset(dataset),
mixer.dataset([])
]);
chai.assert.isFulfilled(promise);
return promise;
});
});
});