mirror of
https://github.com/yiisoft/yii2.git
synced 2025-08-26 14:26:54 +08:00
Additionally added tests for #10302 (#10284 (comment)), #10284 (#10217 (comment)), #11729
This commit is contained in:

committed by
Alexander Makarov

parent
24dae19fc8
commit
8d813f7bf1
@ -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)
|
||||
|
@ -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,9 +88,9 @@
|
||||
|
||||
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) {
|
||||
initEventHandler($e, 'filter', filterEvents, settings.filterSelector, function (event) {
|
||||
if (event.type === 'keydown') {
|
||||
if (event.keyCode !== 13) {
|
||||
return; // only react to enter key
|
||||
@ -87,7 +113,7 @@
|
||||
},
|
||||
|
||||
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);
|
||||
|
170
tests/js/data/yii.gridView.html
Normal file
170
tests/js/data/yii.gridView.html
Normal 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> </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> </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> </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>
|
754
tests/js/tests/yii.gridView.test.js
Normal file
754
tests/js/tests/yii.gridView.test.js
Normal 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');
|
||||
});
|
||||
});
|
||||
});
|
Reference in New Issue
Block a user