Additionally added tests for #10302 (#10284 (comment)), #10284 (#10217 (comment)), #11729
This commit is contained in:
Alexey Rogachev
2016-12-18 15:06:17 +06:00
committed by Alexander Makarov
parent 24dae19fc8
commit 8d813f7bf1
4 changed files with 1009 additions and 27 deletions

View File

@ -21,7 +21,7 @@ Yii Framework 2 Change Log
- Bug #12822: Fixed `yii\i18n\Formatter::asTimestamp()` to process timestamp with miliseconds correctly (h311ion)
- Bug #12824: Enabled usage of `yii\mutex\FileMutex` on Windows systems (davidsonalencar)
- Bug #12828: Fixed handling of nested arrays, objects in `\yii\grid\GridView::guessColumns` (githubjeka)
- Bug #12836: Fixed `yii\widgets\GridView::filterUrl` to not ignore `#` part of filter URL (cebe)
- Bug #12836: Fixed `yii\widgets\GridView::filterUrl` to not ignore `#` part of filter URL (cebe, arogachev)
- Bug #12856: Fixed `yii\web\XmlResponseFormatter` to use `true` and `false` to represent booleans (samdark)
- Bug #12879: Console progress bar was not working properly in Windows terminals (samdark, kids-return)
- Bug #12880: Fixed `yii\behaviors\AttributeTypecastBehavior` marks attributes with `null` value as 'dirty' (klimov-paul)
@ -36,6 +36,8 @@ Yii Framework 2 Change Log
- Bug #13159: Fixed `destroy` method in `yii.captcha.js` which did not work as expected (arogachev)
- Bug #13198: Fixed order of checks in `yii\validators\IpValidator` that sometimes caused wrong error message (silverfire)
- Bug #13200: Creating Urls for routes specified in `yii\rest\UrlRule::$extraPatterns` did not work if no HTTP verb was specified (cebe)
- Bug #13231: Fixed `destroy` method in `yii.gridView.js` which did not work as expected (arogachev)
- Bug #13232: Event handlers were not detached with changed selector in `yii.gridView.js` (arogachev)
- Bug #13108: Fix execute command with negative integer parameter (pana1990, uaoleg)
- Enh #475: Added Bash and Zsh completion support for the `./yii` command (cebe, silverfire)
- Enh #6242: Access to validator in inline validation (arogachev)

View File

@ -16,7 +16,7 @@
} else if (typeof method === 'object' || !method) {
return methods.init.apply(this, arguments);
} else {
$.error('Method ' + method + ' does not exist on jQuery.yiiGridView');
$.error('Method ' + method + ' does not exist in jQuery.yiiGridView');
return false;
}
};
@ -50,6 +50,32 @@
afterFilter: 'afterFilter'
};
/**
* Used for storing active event handlers and removing them later.
* The structure of single event handler is:
*
* {
* gridViewId: {
* type: {
* event: '...',
* selector: '...'
* }
* }
* }
*
* Used types:
*
* - filter, used for filtering grid with elements found by filterSelector
* - checkRow, used for checking single row
* - checkAllRows, used for checking all rows with according "Check all" checkbox
*
* event is the name of event, for example: 'change.yiiGridView'
* selector is a jQuery selector for finding elements
*
* @type {{}}
*/
var gridEventHandlers = {};
var methods = {
init: function (options) {
return this.each(function () {
@ -62,32 +88,32 @@
gridData[id] = $.extend(gridData[id], {settings: settings});
var filterEvents = 'change.yiiGridView keydown.yiiGridView';
var enterPressed = false;
$(document).off('change.yiiGridView keydown.yiiGridView', settings.filterSelector)
.on('change.yiiGridView keydown.yiiGridView', settings.filterSelector, function (event) {
if (event.type === 'keydown') {
if (event.keyCode !== 13) {
return; // only react to enter key
} else {
enterPressed = true;
}
initEventHandler($e, 'filter', filterEvents, settings.filterSelector, function (event) {
if (event.type === 'keydown') {
if (event.keyCode !== 13) {
return; // only react to enter key
} else {
// prevent processing for both keydown and change events
if (enterPressed) {
enterPressed = false;
return;
}
enterPressed = true;
}
} else {
// prevent processing for both keydown and change events
if (enterPressed) {
enterPressed = false;
return;
}
}
methods.applyFilter.apply($e);
methods.applyFilter.apply($e);
return false;
});
return false;
});
});
},
applyFilter: function () {
var $grid = $(this), event;
var $grid = $(this);
var settings = gridData[$grid.attr('id')].settings;
var data = {};
$.each($(settings.filterSelector).serializeArray(), function () {
@ -119,8 +145,8 @@
var pos = settings.filterUrl.indexOf('?');
var url = pos < 0 ? settings.filterUrl : settings.filterUrl.substring(0, pos);
var hashPos = settings.filterUrl.indexOf('#');
if (hashPos >= 0) {
url += settings.filterUrl.substring(pos);
if (pos >= 0 && hashPos >= 0) {
url += settings.filterUrl.substring(hashPos);
}
$grid.find('form.gridview-filter-form').remove();
@ -137,7 +163,7 @@
});
});
event = $.Event(gridEvents.beforeFilter);
var event = $.Event(gridEvents.beforeFilter);
$grid.trigger(event);
if (event.result === false) {
return;
@ -161,10 +187,10 @@
var checkAll = "#" + id + " input[name='" + options.checkAll + "']";
var inputs = options['class'] ? "input." + options['class'] : "input[name='" + options.name + "']";
var inputsEnabled = "#" + id + " " + inputs + ":enabled";
$(document).off('click.yiiGridView', checkAll).on('click.yiiGridView', checkAll, function () {
initEventHandler($grid, 'checkAllRows', 'click.yiiGridView', checkAll, function () {
$grid.find(inputs + ":enabled").prop('checked', this.checked);
});
$(document).off('click.yiiGridView', inputsEnabled).on('click.yiiGridView', inputsEnabled, function () {
initEventHandler($grid, 'checkRow', 'click.yiiGridView', inputsEnabled, function () {
var all = $grid.find(inputs).length == $grid.find(inputs + ":checked").length;
$grid.find("input[name='" + options.checkAll + "']").prop('checked', all);
});
@ -183,10 +209,17 @@
},
destroy: function () {
return this.each(function () {
$(window).unbind('.yiiGridView');
$(this).removeData('yiiGridView');
var events = ['.yiiGridView', gridEvents.beforeFilter, gridEvents.afterFilter].join(' ');
this.off(events);
var id = $(this).attr('id');
$.each(gridEventHandlers[id], function (type, data) {
$(document).off(data.event, data.selector);
});
delete gridData[id];
return this;
},
data: function () {
@ -194,4 +227,27 @@
return gridData[id];
}
};
/**
* Used for attaching event handler and prevent of duplicating them. With each call previously attached handler of
* the same type is removed even selector was changed.
* @param {jQuery} $gridView According jQuery grid view element
* @param {string} type Type of the event which acts like a key
* @param {string} event Event name, for example 'change.yiiGridView'
* @param {string} selector jQuery selector
* @param {function} callback The actual function to be executed with this event
*/
function initEventHandler($gridView, type, event, selector, callback) {
var id = $gridView.attr('id');
var prevHandler = gridEventHandlers[id];
if (prevHandler !== undefined && prevHandler[type] !== undefined) {
var data = prevHandler[type];
$(document).off(data.event, data.selector);
}
if (prevHandler === undefined) {
gridEventHandlers[id] = {};
}
$(document).on(event, selector, callback);
gridEventHandlers[id][type] = {event: event, selector: selector};
}
})(window.jQuery);

View File

@ -0,0 +1,170 @@
<!-- Filters for testing of multiple grid views -->
<div id="w-common-filters">
<input name="PostSearch[id]" type="text">
<input name="PostSearch[name]" type="text">
</div>
<!-- The main setup -->
<div id="w0" class="grid-view">
<table>
<thead>
<tr>
<th><input id="w0-check-all" name="selection_all" value="1" type="checkbox"></th>
<th>Name</th>
<th>Category</th>
<th>Tags</th>
</tr>
<tr id="w0-filters">
<td>&nbsp;</td>
<td><input id="w0-name" name="PostSearch[name]" type="text"></td>
<td>
<select id="w0-category" name="PostSearch[category_id]">
<option value="" selected>None</option>
<option value="1">Programming</option>
<option value="2">Traveling</option>
</select>
</td>
<td>
<select id="w0-tags" name="PostSearch[tags][]" multiple>
<option value="1">html</option>
<option value="2">css</option>
<option value="3">js</option>
<option value="4">php</option>
</select>
</td>
</tr>
</thead>
<tbody>
<tr data-key="1">
<td><input class="w0-check-row" name="selection[]" value="1" type="checkbox"></td>
<td>Name 1</td>
<td>Programming</td>
<td>html, css</td>
</tr>
<tr data-key="2">
<td><input class="w0-check-row" name="selection[]" value="2" type="checkbox"></td>
<td>Name 2</td>
<td>Programming</td>
<td>js</td>
</tr>
<tr data-key="3">
<td><input class="w0-check-row" name="selection[]" value="3" type="checkbox"></td>
<td>Name 3</td>
<td>Programming</td>
<td>php</td>
</tr>
</tbody>
</table>
</div>
<!-- The basic setup, used for testing of multiple grid views -->
<div id="w1" class="grid-view">
<table>
<thead>
<tr>
<th><input name="selection_all" value="1" type="checkbox"></th>
<th>ID</th>
<th>Name</th>
</tr>
<tr id="w1-filters">
<td>&nbsp;</td>
<td><input name="PostSearch[id]" type="text"></td>
<td><input name="PostSearch[name]" type="text"></td>
</tr>
</thead>
<tbody>
<tr data-key="1">
<td><input name="selection[]" value="1" type="checkbox"></td>
<td>1</td>
<td>Name 1</td>
</tr>
<tr data-key="2">
<td><input name="selection[]" value="2" type="checkbox"></td>
<td>2</td>
<td>Name 2</td>
</tr>
</tbody>
</table>
</div>
<!-- https://github.com/yiisoft/yii2/pull/10284 -->
<div id="w2">
<table>
<thead>
<tr>
<th>Name</th>
<th>Tags</th>
</tr>
<tr id="w2-filters">
<td><input name="PostSearch[name]" type="text"></td>
<td>
<input type="hidden" name="PostSearch[tags]" value="-1">
<select id="w2-tags" name="PostSearch[tags][]" multiple>
<option value="1">html</option>
<option value="2">css</option>
<option value="3">js</option>
<option value="4">php</option>
</select>
</td>
</tr>
</thead>
<tbody>
<tr data-key="1">
<td>Name 1</td>
<td>html, css</td>
</tr>
<tr data-key="2">
<td>Name 2</td>
<td>js</td>
</tr>
<tr data-key="3">
<td>Name 3</td>
<td>php</td>
</tr>
</tbody>
</table>
</div>
<!-- Setup for testing that event handlers are correctly removed with new selectors -->
<div id="w3">
<table>
<thead>
<tr>
<th>
<input name="selection_all" value="1" type="checkbox">
<input name="selection_all2" value="1" type="checkbox">
</th>
<th>ID</th>
<th>Name</th>
</tr>
<tr id="w3-filters">
<td>&nbsp;</td>
<td><input name="PostSearch[id]" type="text"></td>
<td><input name="PostSearch[name]" type="text"></td>
</tr>
</thead>
<tbody>
<tr data-key="1">
<td>
<input class="w3-check-row" name="selection[]" value="1" type="checkbox">
<input name="selection2[]" value="1" type="checkbox">
</td>
<td>1</td>
<td>Name 1</td>
</tr>
<tr data-key="2">
<td>
<input class="w3-check-row" name="selection[]" value="2" type="checkbox">
<input name="selection2[]" value="2" type="checkbox">
</td>
<td>2</td>
<td>Name 2</td>
</tr>
</tbody>
</table>
</div>

View File

@ -0,0 +1,754 @@
var assert = require('chai').assert;
var sinon;
var withData = require('leche').withData;
var jsdom = require('mocha-jsdom');
var fs = require('fs');
var vm = require('vm');
describe('yii.gridView', function () {
var yiiGridViewPath = 'framework/assets/yii.gridView.js';
var yiiPath = 'framework/assets/yii.js';
var jQueryPath = 'vendor/bower/jquery/dist/jquery.js';
var $;
var $gridView;
var settings = {
filterUrl: '/posts/index',
filterSelector: '#w0-filters input, #w0-filters select'
};
var commonSettings = {
filterUrl: '/posts/index',
filterSelector: '#w-common-filters input, #w-common-filters select'
};
var $textInput;
var $select;
var $multipleSelect;
var $listBox;
var $checkAllCheckbox;
var $checkRowCheckboxes;
function registerYii() {
var code = fs.readFileSync(yiiPath);
var script = new vm.Script(code);
var sandbox = {window: window, jQuery: $};
var context = new vm.createContext(sandbox);
script.runInContext(context);
return sandbox.window.yii;
}
function registerTestableCode() {
var yii = registerYii();
var code = fs.readFileSync(yiiGridViewPath);
var script = new vm.Script(code);
var context = new vm.createContext({window: window, document: window.document, yii: yii});
script.runInContext(context);
}
var gridViewHtml = fs.readFileSync('tests/js/data/yii.gridView.html', 'utf-8');
var html = '<!doctype html><html><head><meta charset="utf-8"></head><body>' + gridViewHtml + '</body></html>';
jsdom({
html: html,
src: fs.readFileSync(jQueryPath, 'utf-8')
});
before(function () {
$ = window.$;
registerTestableCode();
sinon = require('sinon');
});
beforeEach(function () {
$textInput = $('#w0-name');
$select = $('#w0-category');
$multipleSelect = $('#w0-tags');
$listBox = $('#w2-tags');
$checkAllCheckbox = $('#w0-check-all');
$checkRowCheckboxes = $('.w0-check-row');
});
afterEach(function () {
if ($gridView.length) {
$gridView.yiiGridView('destroy');
}
$textInput.val('');
$select.val('');
$multipleSelect.find('option:selected').prop('selected', false);
$listBox.find('option:selected').prop('selected', false);
$checkAllCheckbox.prop('checked', false);
$checkRowCheckboxes.prop('checked', false);
});
/**
* Simulate pressing "Enter" button while focused on some element
* @param $el
*/
function pressEnter($el) {
var e = $.Event('keydown', {keyCode: 13});
$el.trigger(e);
}
/**
* Simulate pressing keyboard button while focused on the text input. For simplicity, intended to use with letter
* buttons, such as "a", "b", etc. Case insensitive.
* @param $el
* @param buttonName
*/
function pressButton($el, buttonName) {
$el.val(buttonName);
var keyCode = buttonName.charCodeAt(0);
var e = $.Event('keydown', {keyCode: keyCode});
$el.trigger(e);
}
/**
* Simulate changing value in the select
* @param $el
* @param value
*/
function changeValue($el, value) {
$el.val(value);
var e = $.Event('change');
$el.trigger(e);
}
/**
* Simulate losing focus of the element after the value was changed
* @param $el
*/
function loseFocus($el) {
var e = $.Event('change');
$el.trigger(e);
}
/**
* Simulate click in the checkbox
* @param $el
*/
function click($el) {
var e = $.Event('click');
$el.trigger(e);
}
/**
* Simulate hovering on the new value and pressing "Enter" button in the select
* @param $el
*/
function hoverAndPressEnter($el) {
pressEnter($el);
// After pressing enter while hovering the value will be immediately changed as well like with losing focus
loseFocus($el);
}
describe('init', function () {
var customSettings = {
filterUrl: '/posts/filter',
filterSelector: '#w-common-filters input'
};
withData({
'no method specified': [function () {
$gridView = $('.grid-view').yiiGridView(commonSettings);
}, commonSettings],
'no method specified, custom settings': [function () {
$gridView = $('.grid-view').yiiGridView(customSettings);
}, customSettings],
'manual method call': [function () {
$gridView = $('.grid-view').yiiGridView('init', commonSettings);
}, commonSettings]
}, function (initFunction, expectedSettings) {
it('should save settings for all elements', function () {
initFunction();
assert.deepEqual($('#w0').yiiGridView('data'), {settings: expectedSettings});
assert.deepEqual($('#w1').yiiGridView('data'), {settings: expectedSettings});
});
});
describe('with repeated call', function () {
var jQuerySubmitStub;
before(function () {
jQuerySubmitStub = sinon.stub($.fn, 'submit');
});
after(function () {
jQuerySubmitStub.restore();
});
it('should remove "filter" event handler', function () {
$gridView = $('#w0').yiiGridView(settings);
$gridView.yiiGridView(settings);
// Change selector to make sure event handlers are removed regardless of the selector
$gridView.yiiGridView({
filterUrl: '/posts/index',
filterSelector: '#w0-filters select'
});
pressEnter($textInput);
assert.isFalse(jQuerySubmitStub.called);
changeValue($select, 1);
assert.isTrue(jQuerySubmitStub.calledOnce);
});
});
});
describe('applyFilter', function () {
var jQuerySubmit = function () {
};
var jQuerySubmitStub;
beforeEach(function () {
jQuerySubmitStub = sinon.stub($.fn, 'submit', jQuerySubmit);
});
afterEach(function () {
jQuerySubmitStub.restore();
});
describe('with beforeFilter returning not false', function () {
var calledMethods = []; // For testing the order of called methods
var beforeFilterSpy;
var afterFilterSpy;
before(function () {
jQuerySubmit = function () {
calledMethods.push('submit');
return this;
};
beforeFilterSpy = sinon.spy(function () {
calledMethods.push('beforeFilter');
});
afterFilterSpy = sinon.spy(function () {
calledMethods.push('afterFilter');
});
});
after(function () {
jQuerySubmit = function () {
};
beforeFilterSpy.reset();
afterFilterSpy.reset();
calledMethods = [];
});
var message = 'should send the request to correct url with correct parameters and apply events in ' +
'correct order';
it(message, function () {
$gridView = $('#w0').yiiGridView(settings)
.on('beforeFilter', beforeFilterSpy)
.on('afterFilter', afterFilterSpy);
$textInput.val('a');
$select.val(1);
$multipleSelect.find('option[value="1"]').prop('selected', true);
$multipleSelect.find('option[value="2"]').prop('selected', true);
$gridView.yiiGridView('applyFilter');
var expectedHtml = '<form action="/posts/index" method="get" class="gridview-filter-form" ' +
'style="display:none" data-pjax="">' +
'<input type="hidden" name="PostSearch[name]" value="a">' +
'<input type="hidden" name="PostSearch[category_id]" value="1">' +
'<input type="hidden" name="PostSearch[tags][]" value="1">' +
'<input type="hidden" name="PostSearch[tags][]" value="2">' +
'</form>';
var $form = $('.grid-view .gridview-filter-form');
assert.equal($form.get(0).outerHTML, expectedHtml);
assert.isTrue(beforeFilterSpy.calledOnce);
assert.instanceOf(beforeFilterSpy.getCall(0).args[0], $.Event);
assert.equal($(beforeFilterSpy.getCall(0).args[0].target).attr('id'), $gridView.attr('id'));
assert.isTrue(jQuerySubmitStub.calledOnce);
assert.equal(jQuerySubmitStub.returnValues[0].attr('class'), 'gridview-filter-form');
assert.isTrue(afterFilterSpy.calledOnce);
assert.instanceOf(afterFilterSpy.getCall(0).args[0], $.Event);
assert.equal($(afterFilterSpy.getCall(0).args[0].target).attr('id'), $gridView.attr('id'));
assert.deepEqual(calledMethods, ['beforeFilter', 'submit', 'afterFilter']);
});
});
describe('with beforeFilter returning false', function () {
var beforeFilterSpy;
var afterFilterSpy;
before(function () {
beforeFilterSpy = sinon.spy(function () {
return false;
});
afterFilterSpy = sinon.spy();
});
after(function () {
beforeFilterSpy.reset();
afterFilterSpy.reset();
});
it('should prevent from sending request and triggering "afterFilter" event', function () {
$gridView = $('#w0').yiiGridView(settings)
.on('beforeFilter', beforeFilterSpy)
.on('afterFilter', afterFilterSpy);
$gridView.yiiGridView('applyFilter');
assert.isTrue(beforeFilterSpy.calledOnce);
assert.isFalse(jQuerySubmitStub.called);
assert.isFalse(afterFilterSpy.called);
});
});
describe('with different urls', function () {
describe('with no filter data sent', function () {
withData({
'query parameters': [
'/posts/index?foo=1&bar=2',
'/posts/index',
'PostSearch[name]=&PostSearch[category_id]=&foo=1&bar=2'
],
// https://github.com/yiisoft/yii2/pull/10302
'query parameter with multiple values (not array)': [
'/posts/index?foo=1&foo=2',
'/posts/index',
'PostSearch[name]=&PostSearch[category_id]=&foo=1&foo=2'
],
'query parameter with multiple values (array)': [
'/posts/index?foo[]=1&foo[]=2',
'/posts/index',
'PostSearch[name]=&PostSearch[category_id]=&foo[]=1&foo[]=2'
],
// https://github.com/yiisoft/yii2/issues/12836
'anchor': [
'/posts/index#post',
'/posts/index#post',
'PostSearch[name]=&PostSearch[category_id]='
],
'query parameters, anchor': [
'/posts/index?foo=1&bar=2#post',
'/posts/index#post',
'PostSearch[name]=&PostSearch[category_id]=&foo=1&bar=2'
],
'relative url, query parameters': [
'?foo=1&bar=2',
'',
'PostSearch[name]=&PostSearch[category_id]=&foo=1&bar=2'
],
'relative url, anchor': [
'#post',
'#post',
'PostSearch[name]=&PostSearch[category_id]='
],
'relative url, query parameters, anchor': [
'?foo=1&bar=2#post',
'#post',
'PostSearch[name]=&PostSearch[category_id]=&foo=1&bar=2'
]
}, function (filterUrl, expectedUrl, expectedQueryString) {
it('should send the request to correct url with correct parameters', function () {
var customSettings = $.extend({}, settings, {filterUrl: filterUrl});
$gridView = $('#w0').yiiGridView(customSettings);
$gridView.yiiGridView('applyFilter');
var $form = $gridView.find('.gridview-filter-form');
assert.isTrue(jQuerySubmitStub.calledOnce);
assert.equal($form.attr('action'), expectedUrl);
assert.equal(decodeURIComponent($form.serialize()), expectedQueryString);
});
});
});
// https://github.com/yiisoft/yii2/pull/10302
describe('with filter data sent', function () {
it('should send the request to correct url with new parameter values', function () {
var filterUrl = '/posts/index?CategorySearch[id]=5&CategorySearch[name]=c' +
'&PostSearch[name]=a&PostSearch[category_id]=1&PostSearch[tags][]=1&PostSearch[tags][]=2' +
'&foo[]=1&foo[]=2&bar=1#post';
var customSettings = $.extend({}, settings, {filterUrl: filterUrl});
$gridView = $('#w0').yiiGridView(customSettings);
$textInput.val('b');
$select.val('1'); // Leave value as is (simulate setting "selected" in HTML)
$multipleSelect.find('option[value="2"]').prop('selected', true);
$multipleSelect.find('option[value="3"]').prop('selected', true);
$gridView.yiiGridView('applyFilter');
var $form = $gridView.find('.gridview-filter-form');
assert.isTrue(jQuerySubmitStub.calledOnce);
assert.equal($form.attr('action'), '/posts/index#post');
// Parameters not related with current filter are appended to the end
var expectedQueryString = 'PostSearch[name]=b&PostSearch[category_id]=1' +
'&PostSearch[tags][]=2&PostSearch[tags][]=3' +
'&CategorySearch[id]=5&CategorySearch[name]=c' +
'&foo[]=1&foo[]=2&bar=1';
assert.equal(decodeURIComponent($form.serialize()), expectedQueryString);
});
});
});
// https://github.com/yiisoft/yii2/pull/10284
describe('with list box', function () {
var queryString = 'PostSearch[name]=&PostSearch[tags]=-1&PostSearch[tags][]=1&PostSearch[tags][]=2';
beforeEach(function () {
$listBox.find('option[value="1"]').prop('selected', true);
$listBox.find('option[value="2"]').prop('selected', true);
});
describe('with values selected', function () {
it('should send the request to correct url with correct parameters', function () {
$gridView = $('#w2').yiiGridView({
filterUrl: '/posts/index',
filterSelector: '#w2-filters input, #w2-filters select'
});
$gridView.yiiGridView('applyFilter');
var $form = $gridView.find('.gridview-filter-form');
assert.equal($form.attr('action'), '/posts/index');
assert.equal(decodeURIComponent($form.serialize()), queryString);
});
});
describe('with unselected values after applied filter', function () {
it('should send the request to correct url with correct parameters', function () {
$gridView = $('#w2').yiiGridView({
filterUrl: '/posts/index/?' + queryString,
filterSelector: '#w2-filters input, #w2-filters select'
});
$listBox.find('option:selected').prop('selected', false);
$gridView.yiiGridView('applyFilter');
var $form = $gridView.find('.gridview-filter-form');
assert.equal($form.attr('action'), '/posts/index/');
assert.equal(decodeURIComponent($form.serialize()), 'PostSearch[name]=&PostSearch[tags]=-1');
});
});
});
describe('with repeated method call', function () {
it('should delete the hidden form', function () {
$gridView = $('#w0').yiiGridView(settings);
$gridView.yiiGridView('applyFilter');
$gridView.yiiGridView('applyFilter');
var $form = $gridView.find('.gridview-filter-form');
assert.lengthOf($form, 1);
});
});
describe('with filter event handlers', function () {
beforeEach(function () {
$gridView = $('#w0').yiiGridView(settings);
});
describe('with text entered in the text input', function () {
it('should not submit form', function () {
pressButton($textInput, 'a');
assert.isFalse(jQuerySubmitStub.called);
});
});
describe('with "Enter" pressed in the text input', function () {
it('should submit form once', function () {
pressEnter($textInput);
assert.isTrue(jQuerySubmitStub.calledOnce);
});
});
describe('with text entered in the text input and lost focus', function () {
it('should submit form once', function () {
pressButton($textInput, 'a');
loseFocus($textInput);
assert.isTrue(jQuerySubmitStub.calledOnce);
});
});
describe('with value changed in the select', function () {
it('should submit form once', function () {
changeValue($select, 1);
assert.isTrue(jQuerySubmitStub.calledOnce);
});
});
describe('with hover on different value and "Enter" pressed in select', function () {
it('should submit form once', function () {
// Simulate hovering on new value and pressing "Enter"
$select.val(1);
hoverAndPressEnter($select);
assert.isTrue(jQuerySubmitStub.calledOnce);
});
});
});
});
describe('setSelectionColumn method', function () {
describe('with name option and', function () {
withData({
'nothing else': [{}],
'checkAll option': [{checkAll: 'selection_all'}],
'multiple option set to true': [{multiple: true}],
'multiple and checkAll options, multiple set to false': [{multiple: false, checkAll: 'selection_all'}]
}, function (customOptions) {
it('should update data and do not activate "check all" functionality', function () {
$gridView = $('#w0').yiiGridView(settings);
var defaultOptions = {name: 'selection[]'};
var options = $.extend({}, defaultOptions, customOptions);
$gridView.yiiGridView('setSelectionColumn', options);
assert.equal($gridView.yiiGridView('data').selectionColumn, 'selection[]');
click($checkAllCheckbox);
assert.lengthOf($checkRowCheckboxes.filter(':checked'), 0);
click($checkAllCheckbox); // Back to initial condition
click($checkRowCheckboxes);
assert.isFalse($checkAllCheckbox.prop('checked'));
});
});
});
describe('with name, multiple and checkAll options, multiple set to true and', function () {
withData({
'nothing else': [{}],
// https://github.com/yiisoft/yii2/pull/11729
'class option': [{'class': 'w0-check-row'}]
}, function (customOptions) {
it('should update data and "check all" functionality should work', function () {
$gridView = $('#w0').yiiGridView(settings);
var defaultOptions = {name: 'selection[]', multiple: true, checkAll: 'selection_all'};
var options = $.extend({}, defaultOptions, customOptions);
$gridView.yiiGridView('setSelectionColumn', options);
assert.equal($gridView.yiiGridView('data').selectionColumn, 'selection[]');
var $checkFirstRowCheckbox = $checkRowCheckboxes.filter('[value="1"]');
// Check all
click($checkAllCheckbox);
assert.lengthOf($checkRowCheckboxes.filter(':checked'), 3);
assert.isTrue($checkAllCheckbox.prop('checked'));
// Uncheck all
click($checkAllCheckbox);
assert.lengthOf($checkRowCheckboxes.filter(':checked'), 0);
assert.isFalse($checkAllCheckbox.prop('checked'));
// Check all manually
click($checkRowCheckboxes);
assert.lengthOf($checkRowCheckboxes.filter(':checked'), 3);
assert.isTrue($checkAllCheckbox.prop('checked'));
// Uncheck all manually
click($checkRowCheckboxes);
assert.lengthOf($checkRowCheckboxes.filter(':checked'), 0);
assert.isFalse($checkAllCheckbox.prop('checked'));
// Check first row
click($checkFirstRowCheckbox);
assert.isTrue($checkFirstRowCheckbox.prop('checked'));
assert.lengthOf($checkRowCheckboxes.filter(':checked'), 1);
assert.isFalse($checkAllCheckbox.prop('checked'));
// Then check all
click($checkAllCheckbox);
assert.lengthOf($checkRowCheckboxes.filter(':checked'), 3);
assert.isTrue($checkAllCheckbox.prop('checked'));
// Uncheck first row
click($checkFirstRowCheckbox);
assert.isFalse($checkFirstRowCheckbox.prop('checked'));
assert.lengthOf($checkRowCheckboxes.filter(':checked'), 2);
assert.isFalse($checkAllCheckbox.prop('checked'));
});
});
});
describe('with repeated calls', function () {
var jQueryPropStub;
before(function () {
jQueryPropStub = sinon.stub($, 'prop');
});
after(function () {
jQueryPropStub.restore();
});
it('should not duplicate event handler calls', function () {
$gridView = $('#w3').yiiGridView({
filterUrl: '/posts/index',
filterSelector: '#w3-filters input, #w3-filters select'
});
$gridView.yiiGridView('setSelectionColumn', {
name: 'selection[]',
multiple: true,
checkAll: 'selection_all'
});
// Change selectors to make sure event handlers are removed regardless of the selector
$gridView.yiiGridView('setSelectionColumn', {
name: 'selection2[]',
multiple: true,
checkAll: 'selection_all2'
});
$gridView.yiiGridView('setSelectionColumn', {
name: 'selection[]',
multiple: true,
checkAll: 'selection_all'
});
$gridView.yiiGridView('setSelectionColumn', {
'class': 'w3-check-row',
multiple: true,
checkAll: 'selection_all'
});
// Check first row ("prop" should be called once)
click($gridView.find('input[name="selection[]"][value="1"]'));
// Check all rows ("prop" should be called 2 times, 1 time for each row)
click($gridView.find('input[name="selection_all"]'));
assert.equal(jQueryPropStub.callCount, 3);
});
});
});
describe('getSelectedRows method', function () {
withData({
'selectionColumn not set, no rows selected': [undefined, [], false, []],
'selectionColumn not set, 1st and 2nd rows selected': [undefined, [1, 2], false, []],
'selectionColumn set, no rows selected': ['selection[]', [], false, []],
'selectionColumn set, 1st row selected': ['selection[]', [1], false, [1]],
'selectionColumn set, 1st and 2nd rows selected': ['selection[]', [1, 2], false, [1, 2]],
'selectionColumn set, all rows selected, "Check all" checkbox checked': [
'selection[]', [1, 2, 3], true, [1, 2, 3]
]
}, function (selectionColumn, selectedRows, checkAll, expectedSelectedRows) {
it('should return array with ids of selected rows', function () {
$gridView = $('#w0').yiiGridView(settings);
$gridView.yiiGridView('setSelectionColumn', {name: selectionColumn});
for (var i = 0; i < selectedRows.length; i++) {
$checkRowCheckboxes.filter('[value="' + selectedRows[i] + '"]').prop('checked', true);
}
if (checkAll) {
$checkAllCheckbox.prop('checked', true);
}
assert.deepEqual($gridView.yiiGridView('getSelectedRows'), expectedSelectedRows);
});
});
});
describe('destroy method', function () {
var jQuerySubmitStub;
var jQueryPropStub;
var beforeFilterSpy;
var afterFilterSpy;
beforeEach(function () {
jQuerySubmitStub = sinon.stub($.fn, 'submit');
jQueryPropStub = sinon.stub($, 'prop');
beforeFilterSpy = sinon.spy();
afterFilterSpy = sinon.spy();
});
afterEach(function () {
jQuerySubmitStub.restore();
jQueryPropStub.restore();
beforeFilterSpy.reset();
afterFilterSpy.reset();
});
it('should remove saved settings for destroyed element only and return initial jQuery object', function () {
$gridView = $('.grid-view').yiiGridView(commonSettings);
var $gridView1 = $('#w0');
var $gridView2 = $('#w1');
var destroyResult = $gridView1.yiiGridView('destroy');
assert.strictEqual(destroyResult, $gridView1);
assert.isUndefined($gridView1.yiiGridView('data'));
assert.deepEqual($gridView2.yiiGridView('data'), {settings: commonSettings});
});
it('should remove "beforeFilter" and "afterFilter" event handlers for destroyed element only', function () {
$gridView = $('.grid-view').yiiGridView(commonSettings)
.on('beforeFilter', beforeFilterSpy)
.on('afterFilter', afterFilterSpy);
var $gridView1 = $('#w0');
var $gridView2 = $('#w1');
$gridView1.yiiGridView('destroy');
assert.throws(function () {
$gridView1.yiiGridView('applyFilter');
}, "Cannot read property 'settings' of undefined");
$gridView1.yiiGridView(settings); // Reinitialize without "beforeFilter" and "afterFilter" event handlers
$gridView1.yiiGridView('applyFilter');
assert.isTrue(jQuerySubmitStub.calledOnce);
assert.isFalse(beforeFilterSpy.called);
assert.isFalse(afterFilterSpy.called);
$gridView2.yiiGridView('applyFilter');
assert.isTrue(jQuerySubmitStub.calledTwice);
assert.isTrue(beforeFilterSpy.calledOnce);
assert.isTrue(afterFilterSpy.calledOnce);
});
it('should remove "filter" event handler for destroyed element only', function () {
var $gridView1 = $('#w0');
var $gridView2 = $('#w1');
$gridView1.yiiGridView(settings);
$gridView2.yiiGridView({
filterUrl: '/posts/index',
filterSelector: '#w1-filters input, #w1-filters select'
});
$gridView2.yiiGridView('destroy');
pressEnter($gridView2.find('input[name="PostSearch[id]"]'));
assert.isFalse(jQuerySubmitStub.called);
pressEnter($textInput);
assert.isTrue(jQuerySubmitStub.calledOnce);
});
it('should remove "checkRow" and "checkAllRows" filter event handlers for destroyed element only', function () {
$gridView = $('.grid-view').yiiGridView(commonSettings);
var options = {name: 'selection[]', multiple: true, checkAll: 'selection_all'};
var $gridView1 = $('#w0');
var $gridView2 = $('#w1');
$gridView1.yiiGridView('setSelectionColumn', options);
$gridView2.yiiGridView('setSelectionColumn', options);
$gridView2.yiiGridView('destroy');
click($gridView2.find('input[name="selection_all"]'));
click($gridView2.find('input[name="selection[]"][value="1"]'));
assert.equal(jQueryPropStub.callCount, 0);
click($checkRowCheckboxes.filter('[value="1"]')); // Check first row ("prop" should be called once)
click($checkAllCheckbox); // Check all rows ("prop" should be called 3 times, 1 time for each row)
assert.equal(jQueryPropStub.callCount, 4);
});
});
describe('data method', function () {
it('should return saved settings', function () {
$gridView = $('#w0').yiiGridView(settings);
assert.deepEqual($gridView.yiiGridView('data'), {settings: settings});
});
});
describe('call of not existing method', function () {
it('should throw according error', function () {
$gridView = $('#w0').yiiGridView(settings);
assert.throws(function () {
$gridView.yiiGridView('foobar');
}, 'Method foobar does not exist in jQuery.yiiGridView');
});
});
});