diff --git a/examples/index.html b/examples/index.html index 37c1d8a0..0992203c 100644 --- a/examples/index.html +++ b/examples/index.html @@ -66,6 +66,7 @@

jQuery QueryBuilder Example

+
@@ -137,7 +138,7 @@

Output

rows: 3 }, /* - * select + * checkbox */ { id: 'category', @@ -154,12 +155,32 @@

Output

6: 'Clothes' }, colors: { - 1: 'danger', + 1: 'foo', 2: 'warning', 5: 'success' }, operators: ['in', 'not_in', 'equal', 'not_equal', 'is_null', 'is_not_null'] }, + /* + * select + */ + { + id: 'contient', + label: 'Continent', + type: 'string', + input: 'select', + optgroup: 'core', + placeholder: 'Select something', + values: { + 'eur': 'Europe', + 'asia': 'Asia', + 'oce': 'Oceania', + 'afr': 'Africa', + 'na': 'North America', + 'sa': 'South America' + }, + operators: ['equal', 'not_equal', 'is_null', 'is_not_null'] + }, /* * Selectize */ @@ -357,6 +378,12 @@

Output

} }); +// change theme +$('.change-theme').on('click', function() { + $('#qb-theme').replaceWith(''); + $('#bt-theme').replaceWith(''); +}); + // set rules $('.set').on('click', function() { $('#builder').queryBuilder('setRules', { @@ -446,10 +473,27 @@

Output

)); }); -// change theme -$('.change-theme').on('click', function() { - $('#qb-theme').replaceWith(''); - $('#bt-theme').replaceWith(''); +// set filters +$('.set-filters').on('click', function() { + $(this).prop('disabled', true); + + // add a new filter after 'state' + $('#builder').queryBuilder('addFilter', + { + id: 'new_one', + label: 'New filter', + type: 'string' + }, + 'state' + ); + + // remove filter 'coord' + $('#builder').queryBuilder('removeFilter', + 'coord', + true + ); + + // also available : 'setFilters' }); diff --git a/src/core.js b/src/core.js index d72f1bf1..e91ed495 100644 --- a/src/core.js +++ b/src/core.js @@ -34,7 +34,7 @@ QueryBuilder.prototype.init = function($el, options) { // translations : english << 'lang_code' << custom if (QueryBuilder.regional['en'] === undefined) { - error('"i18n/en.js" not loaded.'); + error('"i18n/en.js" not loaded.'); } this.lang = $.extendext(true, 'replace', {}, QueryBuilder.regional['en'], QueryBuilder.regional[this.settings.lang_code], this.settings.lang); @@ -55,7 +55,7 @@ QueryBuilder.prototype.init = function($el, options) { // INIT this.$el.addClass('query-builder form-inline'); - this.checkFilters(); + this.filters = this.checkFilters(this.filters); this.bindEvents(); this.initPlugins(); @@ -73,15 +73,15 @@ QueryBuilder.prototype.init = function($el, options) { /** * Checks the configuration of each filter */ -QueryBuilder.prototype.checkFilters = function() { +QueryBuilder.prototype.checkFilters = function(filters) { var definedFilters = [], that = this; - if (!this.filters || this.filters.length === 0) { + if (!filters || filters.length === 0) { error('Missing filters list'); } - this.filters.forEach(function(filter, i) { + filters.forEach(function(filter, i) { if (!filter.id) { error('Missing filter {0} id', i); } @@ -143,9 +143,9 @@ QueryBuilder.prototype.checkFilters = function() { // group filters with same optgroup, preserving declaration order when possible if (this.status.has_optgroup) { var optgroups = [], - filters = []; + newFilters = []; - this.filters.forEach(function(filter, i) { + filters.forEach(function(filter, i) { var idx; if (filter.optgroup) { @@ -163,11 +163,13 @@ QueryBuilder.prototype.checkFilters = function() { } optgroups.splice(idx, 0, filter.optgroup); - filters.splice(idx, 0, filter); + newFilters.splice(idx, 0, filter); }); - this.filters = filters; + filters = newFilters; } + + return filters; }; /** @@ -434,7 +436,7 @@ QueryBuilder.prototype.createRuleFilters = function(rule) { var $filterSelect = $(this.getRuleFilterSelect(rule, filters)); - rule.$el.find('.rule-filter-container').append($filterSelect); + rule.$el.find('.rule-filter-container').html($filterSelect); this.trigger('afterCreateRuleFilters', rule); }; @@ -571,13 +573,13 @@ QueryBuilder.prototype.applyRuleFlags = function(rule, readonly) { var flags = rule.flags; if (flags.filter_readonly) { - rule.$el.find('[name$=_filter]').prop('disabled', true); + rule.$el.find('.rule-filter-container [name$=_filter]').prop('disabled', true); } if (flags.operator_readonly) { - rule.$el.find('[name$=_operator]').prop('disabled', true); + rule.$el.find('.rule-operator-container [name$=_operator]').prop('disabled', true); } if (flags.value_readonly) { - rule.$el.find('[name*=_value_]').prop('disabled', true); + rule.$el.find('.rule-value-container [name*=_value_]').prop('disabled', true); } if (flags.no_delete) { rule.$el.find('[data-delete=rule]').remove(); diff --git a/src/plugins/bt-selectpicker/plugin.js b/src/plugins/bt-selectpicker/plugin.js index e55754a6..59796555 100644 --- a/src/plugins/bt-selectpicker/plugin.js +++ b/src/plugins/bt-selectpicker/plugin.js @@ -18,6 +18,10 @@ QueryBuilder.define('bt-selectpicker', function(options) { rule.$el.find('.rule-operator-container select').removeClass('form-control').selectpicker(options); }); + this.on('afterCreateRuleInput', function(e, rule) { + rule.$el.find('.rule-value-container select').removeClass('form-control').selectpicker(options); + }); + // update selectpicker on change this.on('afterUpdateRuleFilter', function(e, rule) { rule.$el.find('.rule-filter-container select').selectpicker('render'); @@ -26,6 +30,10 @@ QueryBuilder.define('bt-selectpicker', function(options) { this.on('afterUpdateRuleOperator', function(e, rule) { rule.$el.find('.rule-operator-container select').selectpicker('render'); }); + + this.on('afterUpdateRuleValue', function(e, rule) { + rule.$el.find('.rule-value-container select').selectpicker('render'); + }); }, { container: 'body', style: 'btn-inverse btn-xs', diff --git a/src/plugins/change-filters/plugin.js b/src/plugins/change-filters/plugin.js new file mode 100644 index 00000000..4d7adb2f --- /dev/null +++ b/src/plugins/change-filters/plugin.js @@ -0,0 +1,126 @@ +/*! + * jQuery QueryBuilder Change Filters + * Allows to change available filters after plugin initialization. + * Copyright 2014-2015 Damien "Mistic" Sorel (http://www.strangeplanet.fr) + */ + +QueryBuilder.extend({ + /** + * Change the filters of the builder + * @param {boolean,optional} delete rules using old filters + * @param {object[]} new filters + */ + setFilters: function(delete_orphans, filters) { + var that = this; + + if (filters === undefined) { + filters = delete_orphans; + delete_orphans = false; + } + + filters = this.checkFilters(filters); + + var filtersIds = filters.map(function(filter) { + return filter.id; + }); + + // check for orphans + if (!delete_orphans) { + (function checkOrphans(node) { + node.each( + function(rule) { + if (rule.filter && filtersIds.indexOf(rule.filter.id) === -1) { + error('A rule is using filter "{0}"', rule.filter.id); + } + }, + checkOrphans + ); + }(this.model.root)); + } + + // replace filters + this.filters = filters; + + // apply on existing DOM + (function updateBuilder(node) { + node.each(true, + function(rule) { + if (rule.filter && filtersIds.indexOf(rule.filter.id) === -1) { + rule.drop(); + } + else { + that.createRuleFilters(rule); + + var $select = rule.$el.find('.rule-filter-container [name$=_filter]'); + + $select.val(rule.filter ? rule.filter.id : '-1'); + } + }, + updateBuilder + ); + }(this.model.root)); + + // update plugins + if (that.settings.plugins) { + if (that.settings.plugins['unique-filter']) { + this.updateDisabledFilters({ builder: this }); + } + else if (this.settings.plugins['bt-selectpicker']) { + this.$el.find('.rule-filter-container [name$=_filter]').selectpicker('render'); + } + } + }, + + /** + * Adds a new filter to the builder + * @param {object} the new filter + * @param {mixed,optional} numeric index or '#start' or '#end' + */ + addFilter: function(filter, position) { + if (position === undefined || position == '#end') { + position = this.filters.length; + } + else if (position == '#start') { + position = 0; + } + + var filters = $.extend(true, [], this.filters); + + // numeric position + if (parseInt(position) == position) { + filters.splice(position, 0, filter); + } + else { + // after filter by its id + if (this.filters.some(function(filter, index) { + if (filter.id == position) { + position = index+1; + return true; + } + })) { + filters.splice(position, 0, filter); + } + // defaults to end of list + else { + filters.push(filter); + } + } + + this.setFilters(filters); + }, + + /** + * Removes a filters the builder + * @param {string} the filter id + * @param {boolean,optional} delete rules using old filters + */ + removeFilter: function(filter_id, delete_orphans) { + var filters = $.extend(true, [], this.filters); + + filters = filters.filter(function(filter) { + return filter.id != filter_id; + }); + + this.setFilters(delete_orphans, filters); + } +}); \ No newline at end of file diff --git a/src/plugins/unique-filter/plugin.js b/src/plugins/unique-filter/plugin.js index dd62ff87..aebf76ed 100644 --- a/src/plugins/unique-filter/plugin.js +++ b/src/plugins/unique-filter/plugin.js @@ -62,7 +62,7 @@ QueryBuilder.extend({ // update Selectpicker if (self.settings.plugins && self.settings.plugins['bt-selectpicker']) { - self.$el.find('.rule-filter-container select').selectpicker('render'); + self.$el.find('.rule-filter-container [name$=_filter]').selectpicker('render'); } } }); \ No newline at end of file diff --git a/src/public.js b/src/public.js index f4d03842..e34cb123 100644 --- a/src/public.js +++ b/src/public.js @@ -174,6 +174,13 @@ QueryBuilder.prototype.getRules = function() { * @param data {object} */ QueryBuilder.prototype.setRules = function(data) { + if ($.isArray(data)) { + data = { + condition: this.settings.default_condition, + rules: data + }; + } + if (!data || !data.rules || (data.rules.length===0 && !this.settings.allow_empty)) { error('Incorrect data object passed'); } diff --git a/src/template.js b/src/template.js index 29635ec4..838d59f3 100644 --- a/src/template.js +++ b/src/template.js @@ -154,15 +154,9 @@ QueryBuilder.prototype.getRuleInput = function(rule, value_id) { } else { switch (filter.input) { - case 'radio': + case 'radio': case 'checkbox': iterateOptions(filter.values, function(key, val) { - h+= ' '+ val +' '; - }); - break; - - case 'checkbox': - iterateOptions(filter.values, function(key, val) { - h+= ' '+ val +' '; + h+= ' '+ val +' '; }); break; diff --git a/tests/common.js b/tests/common.js index 07aa4f01..2915c17f 100644 --- a/tests/common.js +++ b/tests/common.js @@ -3,36 +3,36 @@ * Otherwise the language file is loaded before instrumented files */ QUnit.begin(function() { - $.ajax({ - async: false, - url: '../dist/i18n/query-builder.en.js', - dataType: 'script' - }); + $.ajax({ + async: false, + url: '../dist/i18n/query-builder.en.js', + dataType: 'script' + }); }); /** * Add GitHub link in header */ QUnit.begin(function(){ - $('#qunit-header').append( - '
' + - '' + - '' + - '' + - '
' - ); + $('#qunit-header').append( + '
' + + '' + + '' + + '' + + '
' + ); }); /** * Modify Blanket results display */ QUnit.done(function(){ - $('#blanket-main') - .css('marginTop', '10px') - .addClass('col-lg-8 col-lg-push-2') - .find('.bl-file a').each(function(){ - this.innerHTML = this.innerHTML.replace(/(.*)\/src\/(.*)$/, '$2'); - }); + $('#blanket-main') + .css('marginTop', '10px') + .addClass('col-lg-8 col-lg-push-2') + .find('.bl-file a').each(function(){ + this.innerHTML = this.innerHTML.replace(/(.*)\/src\/(.*)$/, '$2'); + }); }); @@ -40,110 +40,110 @@ QUnit.done(function(){ * Custom assert to compare rules objects */ QUnit.assert.rulesMatch = function(actual, expected, message) { - var ok = (function match(a, b){ - var ok = true; - - if (b.hasOwnProperty('data')) { - if (!a.hasOwnProperty('data')) { - ok = false; - } - else { - ok = JSON.stringify(a.data) === JSON.stringify(b.data); - } - } - - if (b.hasOwnProperty('rules')) { - if (!a.hasOwnProperty('rules')) { - ok = false; - } - else { - for (var i=0, l=a.rules.length; i.rules-group-header>.group-conditions [value=OR]').trigger('click'); - $('[name=builder_rule_0_filter]').val('name').trigger('change'); - $('[name=builder_rule_0_operator]').val('not_equal').trigger('change'); - $('[name=builder_rule_0_value_0]').val('foo').trigger('change'); - $('#builder_group_0>.rules-group-header>.group-actions [data-add=rule]').trigger('click'); - $('#builder_group_0>.rules-group-header>.group-actions [data-add=group]').trigger('click'); - $('#builder_rule_1 [data-delete=rule]').trigger('click'); - $('#builder_group_1 [data-delete=group]').trigger('click'); - - assert.rulesMatch( - $b.queryBuilder('getRules'), - rules_after_ui_events, - 'Should return correct rules after UI events' - ); - }); - - /** - * Test filter.operators - */ - QUnit.test('Change operators', function(assert) { - $b.queryBuilder({ - filters: filters_for_custom_operators, - rules: rules_for_custom_operators, - operators: custom_operators + /** + * Test setRules and getRules + */ + QUnit.test('Set/get rules', function(assert) { + $b.queryBuilder({ + filters: basic_filters, + rules: basic_rules + }); + + assert.rulesMatch( + $b.queryBuilder('getRules'), + basic_rules, + 'Should return object with rules' + ); }); - assert.optionsMatch( - $('#builder_rule_0 [name$=_operator] option'), - ['equal', 'not_equal'], - '"name" filter should have "equal" & "not_equal" operators' - ); - - assert.optionsMatch( - $('#builder_rule_1 [name$=_operator] option'), - ['less', 'greater'], - '"price" filter should have "less" & "greater" operators' - ); - - assert.optionsMatch( - $('#builder_rule_2 [name$=_operator] option'), - ['before', 'equal', 'after'], - '"release" filter should have "before" & "equal" & "after" operators' - ); - }); - - /** - * Test custom conditions - */ - QUnit.test('Change conditions', function(assert) { - $b.queryBuilder({ - filters: basic_filters, - conditions: ['AND'] + /** + * Test UI events + */ + QUnit.test('UI events', function(assert) { + $b.queryBuilder({ + filters: basic_filters + }); + + $('#builder_group_0>.rules-group-header>.group-conditions [value=OR]').trigger('click'); + $('[name=builder_rule_0_filter]').val('name').trigger('change'); + $('[name=builder_rule_0_operator]').val('not_equal').trigger('change'); + $('[name=builder_rule_0_value_0]').val('foo').trigger('change'); + $('#builder_group_0>.rules-group-header>.group-actions [data-add=rule]').trigger('click'); + $('#builder_group_0>.rules-group-header>.group-actions [data-add=group]').trigger('click'); + $('#builder_rule_1 [data-delete=rule]').trigger('click'); + $('#builder_group_1 [data-delete=group]').trigger('click'); + + assert.rulesMatch( + $b.queryBuilder('getRules'), + rules_after_ui_events, + 'Should return correct rules after UI events' + ); }); - assert.optionsMatch( - $b.find('[name$=_cond]'), - ['AND'], - 'Available condition should be AND' - ); + /** + * Test filter.operators + */ + QUnit.test('Change operators', function(assert) { + $b.queryBuilder({ + filters: filters_for_custom_operators, + rules: rules_for_custom_operators, + operators: custom_operators + }); + + assert.optionsMatch( + $('#builder_rule_0 [name$=_operator] option'), + ['equal', 'not_equal'], + '"name" filter should have "equal" & "not_equal" operators' + ); - $b.queryBuilder('destroy'); + assert.optionsMatch( + $('#builder_rule_1 [name$=_operator] option'), + ['less', 'greater'], + '"price" filter should have "less" & "greater" operators' + ); - $b.queryBuilder({ - filters: basic_filters, - rules: rules_for_custom_conditions, - conditions: ['NAND', 'XOR'], - default_condition: 'NAND' + assert.optionsMatch( + $('#builder_rule_2 [name$=_operator] option'), + ['before', 'equal', 'after'], + '"release" filter should have "before" & "equal" & "after" operators' + ); }); - assert.rulesMatch( - $b.queryBuilder('getRules'), - rules_for_custom_conditions, - 'Should return correct rules' - ); - - assert.optionsMatch( - $('#builder_group_0 > .rules-group-header [name$=_cond]'), - ['NAND', 'XOR'], - 'Available onditions should be NAND & XOR' - ); - - assert.equal( - $('#builder_group_1 [name$=_cond]:checked').val(), - 'XOR', - 'The second group should have "XOR" condition selected' - ); - }); - - /** - * Test icons - */ - QUnit.test('Change icons', function(assert) { - $b.queryBuilder({ - filters: basic_filters, - icons: icons + /** + * Test custom conditions + */ + QUnit.test('Change conditions', function(assert) { + $b.queryBuilder({ + filters: basic_filters, + conditions: ['AND'] + }); + + assert.optionsMatch( + $b.find('[name$=_cond]'), + ['AND'], + 'Available condition should be AND' + ); + + $b.queryBuilder('destroy'); + + $b.queryBuilder({ + filters: basic_filters, + rules: rules_for_custom_conditions, + conditions: ['NAND', 'XOR'], + default_condition: 'NAND' + }); + + assert.rulesMatch( + $b.queryBuilder('getRules'), + rules_for_custom_conditions, + 'Should return correct rules' + ); + + assert.optionsMatch( + $('#builder_group_0 > .rules-group-header [name$=_cond]'), + ['NAND', 'XOR'], + 'Available onditions should be NAND & XOR' + ); + + assert.equal( + $('#builder_group_1 [name$=_cond]:checked').val(), + 'XOR', + 'The second group should have "XOR" condition selected' + ); }); - assert.equal( - $b.find('[data-add=rule] i').attr('class'), - 'fa fa-plus', - 'Rule add icon should have been replaced' - ); - - assert.equal( - $b.find('[data-delete=rule] i').attr('class'), - 'fa fa-times', - 'Rule delete icon should have been replaced' - ); - }); - - /** - * Test readonly - */ - QUnit.test('Readonly', function(assert) { - $b.queryBuilder({ - filters: basic_filters, - rules: readonly_rules + /** + * Test icons + */ + QUnit.test('Change icons', function(assert) { + $b.queryBuilder({ + filters: basic_filters, + icons: icons + }); + + assert.equal( + $b.find('[data-add=rule] i').attr('class'), + 'fa fa-plus', + 'Rule add icon should have been replaced' + ); + + assert.equal( + $b.find('[data-delete=rule] i').attr('class'), + 'fa fa-times', + 'Rule delete icon should have been replaced' + ); }); - assert.ok( - $('#builder_rule_0 [data-delete=rule]').length == 0, - 'Should hide delete button of "no_delete" rule' - ); - - assert.ok( - $('#builder_rule_0').find('input:disabled, select:disabled').length == 0, - 'Should not disable inputs of "no_delete" rule' - ); - - assert.ok( - $('#builder_rule_1 [data-delete=rule]').length == 0, - 'Should hide delete button of "readonly" rule' - ); - - assert.ok( - $('#builder_rule_1').find('input:disabled, select:disabled').length == 3, - 'Should disable inputs of "readonly" rule' - ); - - $('#builder_group_1 [data-delete=group]').click(); - - assert.rulesMatch( - $b.queryBuilder('getRules'), - readonly_rules_after, - 'Should not delete group with readonly rule' - ); - }); - - /** - * Test groups limit - */ - QUnit.test('No groups', function(assert) { - $b.queryBuilder({ - filters: basic_filters, - allow_groups: false + /** + * Test readonly + */ + QUnit.test('Readonly', function(assert) { + $b.queryBuilder({ + filters: basic_filters, + rules: readonly_rules + }); + + assert.ok( + $('#builder_rule_0 [data-delete=rule]').length == 0, + 'Should hide delete button of "no_delete" rule' + ); + + assert.ok( + $('#builder_rule_0').find('input:disabled, select:disabled').length == 0, + 'Should not disable inputs of "no_delete" rule' + ); + + assert.ok( + $('#builder_rule_1 [data-delete=rule]').length == 0, + 'Should hide delete button of "readonly" rule' + ); + + assert.ok( + $('#builder_rule_1').find('input:disabled, select:disabled').length == 3, + 'Should disable inputs of "readonly" rule' + ); + + $('#builder_group_1 [data-delete=group]').click(); + + assert.rulesMatch( + $b.queryBuilder('getRules'), + readonly_rules_after, + 'Should not delete group with readonly rule' + ); }); - assert.ok( - $('#builder_group_0 [data-add=group]').length == 0, - 'Should not contain group add button' - ); - - assert.throws( - function(){ $b.queryBuilder('setRules', basic_rules); }, - /No more than 0 groups are allowed/, - 'Should throw "No more than 0 groups are allowed" error' - ); - }); - - /** - * Test optgroups - */ - QUnit.test('Optgroups', function(assert) { - $b.queryBuilder({ - filters: optgroups_filters + /** + * Test groups limit + */ + QUnit.test('No groups', function(assert) { + $b.queryBuilder({ + filters: basic_filters, + allow_groups: false + }); + + assert.ok( + $('#builder_group_0 [data-add=group]').length == 0, + 'Should not contain group add button' + ); + + assert.throws( + function(){ $b.queryBuilder('setRules', basic_rules); }, + /No more than 0 groups are allowed/, + 'Should throw "No more than 0 groups are allowed" error' + ); }); - var select = []; - $('[name=builder_rule_0_filter]>*').each(function() { - if (this.nodeName == 'OPTION') { - select.push($(this).val()); - } - else { - var group = []; - $(this).find('option').each(function() { - group.push($(this).val()); + /** + * Test optgroups + */ + QUnit.test('Optgroups', function(assert) { + $b.queryBuilder({ + filters: optgroups_filters }); - select.push(group); - } + + var select = []; + $('[name=builder_rule_0_filter]>*').each(function() { + if (this.nodeName == 'OPTION') { + select.push($(this).val()); + } + else { + var group = []; + $(this).find('option').each(function() { + group.push($(this).val()); + }); + select.push(group); + } + }); + + assert.deepEqual( + select, + ['-1', ['1', '3', '6'], '2', ['4'], '5'], + 'Filters should have been put in optgroup, solving discontinuities and keeping order' + ); }); - assert.deepEqual( - select, - ['-1', ['1', '3', '6'], '2', ['4'], '5'], - 'Filters should have been put in optgroup, solving discontinuities and keeping order' - ); - }); - - /** - * Test access to defaults - */ - QUnit.test('Access to defaults', function(assert) { - if (QueryBuilder.defaults() == QueryBuilder.DEFAULTS) { - assert.push(false, '[copy]', '[original]', 'Should return full copy of defaults'); - } - else { + /** + * Test access to defaults + */ + QUnit.test('Access to defaults', function(assert) { + if (QueryBuilder.defaults() == QueryBuilder.DEFAULTS) { + assert.push(false, '[copy]', '[original]', 'Should return full copy of defaults'); + } + else { + assert.deepEqual( + QueryBuilder.defaults(), + QueryBuilder.DEFAULTS, + 'Should return full copy of defaults' + ); + } + + assert.equal( + QueryBuilder.defaults('allow_empty'), + QueryBuilder.DEFAULTS.allow_empty, + 'Should return a specific default primitive' + ); + + assert.deepEqual( + QueryBuilder.defaults('lang'), + QueryBuilder.DEFAULTS.lang, + 'Should return a specific default object' + ); + + QueryBuilder.defaults({ default_rule_flags: new_default_flags }); + assert.deepEqual( - QueryBuilder.defaults(), - QueryBuilder.DEFAULTS, - 'Should return full copy of defaults' + QueryBuilder.DEFAULTS.default_rule_flags, + new_default_flags, + 'Should have modified the default config object' ); - } - - assert.equal( - QueryBuilder.defaults('allow_empty'), - QueryBuilder.DEFAULTS.allow_empty, - 'Should return a specific default primitive' - ); - - assert.deepEqual( - QueryBuilder.defaults('lang'), - QueryBuilder.DEFAULTS.lang, - 'Should return a specific default object' - ); - - QueryBuilder.defaults({ default_rule_flags: new_default_flags }); - - assert.deepEqual( - QueryBuilder.DEFAULTS.default_rule_flags, - new_default_flags, - 'Should have modified the default config object' - ); - }); - - /** - * Test language load - */ - QUnit.test('Change language', function(assert) { - assert.expect(2); - var done = assert.async(); - - $.getScript('../dist/i18n/query-builder.fr.js', function() { - $b.queryBuilder({ - filters: basic_filters - }); - - assert.equal( - $b.find('[data-delete=rule]').text().trim(), - 'Supprimer', - 'Should be in french' - ); - - $b.queryBuilder('destroy'); - - $b.queryBuilder({ - filters: basic_filters, - lang_code: 'en' - }); - - assert.equal( - $b.find('[data-delete=rule]').text().trim(), - 'Delete', - 'Should be in english' - ); - - QueryBuilder.defaults({ lang_code: 'en' }); - - done(); }); - }); - - - var rules_after_ui_events = { - condition: 'OR', - rules: [{ - id: 'name', - operator: 'not_equal', - value: 'foo' - }] - }; - - var filters_for_custom_operators = [{ - id: 'name', - type: 'string' - }, { - id: 'price', - type: 'double' - }, { - id: 'release', - type: 'date', - operators: ['before', 'equal', 'after'] - }]; - - var rules_for_custom_operators = { - condition: 'AND', - rules: [{ - id: 'name', - operator: 'equal', - value: 'foo' + + /** + * Test language load + */ + QUnit.test('Change language', function(assert) { + assert.expect(2); + var done = assert.async(); + + $.getScript('../dist/i18n/query-builder.fr.js', function() { + $b.queryBuilder({ + filters: basic_filters + }); + + assert.equal( + $b.find('[data-delete=rule]').text().trim(), + 'Supprimer', + 'Should be in french' + ); + + $b.queryBuilder('destroy'); + + $b.queryBuilder({ + filters: basic_filters, + lang_code: 'en' + }); + + assert.equal( + $b.find('[data-delete=rule]').text().trim(), + 'Delete', + 'Should be in english' + ); + + QueryBuilder.defaults({ lang_code: 'en' }); + + done(); + }); + }); + + + var rules_after_ui_events = { + condition: 'OR', + rules: [{ + id: 'name', + operator: 'not_equal', + value: 'foo' + }] + }; + + var filters_for_custom_operators = [{ + id: 'name', + type: 'string' }, { - id: 'price', - operator: 'less', - value: 10 + id: 'price', + type: 'double' }, { - id: 'release', - operator: 'before', - value: '1995-5-1' - }] - }; - - var custom_operators = [ - {type: 'equal', nb_inputs: 1, apply_to: ['string']}, - {type: 'not_equal', nb_inputs: 1, apply_to: ['string']}, - {type: 'less', nb_inputs: 1, apply_to: ['number']}, - {type: 'greater', nb_inputs: 1, apply_to: ['number']}, - {type: 'before', nb_inputs: 1, apply_to: ['datetime']}, - {type: 'after', nb_inputs: 1, apply_to: ['datetime']} - ]; - - var rules_for_custom_conditions = { - condition: 'NAND', - rules: [{ - id: 'name', - operator: 'equal', - value: 'foo' + id: 'release', + type: 'date', + operators: ['before', 'equal', 'after'] + }]; + + var rules_for_custom_operators = { + condition: 'AND', + rules: [{ + id: 'name', + operator: 'equal', + value: 'foo' + }, { + id: 'price', + operator: 'less', + value: 10 + }, { + id: 'release', + operator: 'before', + value: '1995-5-1' + }] + }; + + var custom_operators = [ + {type: 'equal', nb_inputs: 1, apply_to: ['string']}, + {type: 'not_equal', nb_inputs: 1, apply_to: ['string']}, + {type: 'less', nb_inputs: 1, apply_to: ['number']}, + {type: 'greater', nb_inputs: 1, apply_to: ['number']}, + {type: 'before', nb_inputs: 1, apply_to: ['datetime']}, + {type: 'after', nb_inputs: 1, apply_to: ['datetime']} + ]; + + var rules_for_custom_conditions = { + condition: 'NAND', + rules: [{ + id: 'name', + operator: 'equal', + value: 'foo' + }, { + condition: 'XOR', + rules: [{ + id: 'name', + operator: 'equal', + value: 'bar' + }] + }] + }; + + var icons = { + add_group: 'fa fa-plus-circle', + add_rule: 'fa fa-plus', + remove_rule: 'fa fa-times', + remove_group: 'fa fa-times', + sort: 'fa fa-sort' + }; + + var readonly_rules = { + condition: 'AND', + rules: [{ + id: 'price', + operator: 'less', + value: 10.25, + flags: { + no_delete: true + } + }, { + condition: 'OR', + rules: [{ + id: 'id', + operator: 'not_equal', + value: '1234-azer-5678', + readonly: true + }] + }] + }; + + var readonly_rules_after = { + condition: 'AND', + rules: [{ + id: 'price', + operator: 'less', + value: 10.25 + }, { + condition: 'OR', + rules: [{ + id: 'id', + operator: 'not_equal', + value: '1234-azer-5678' + }] + }] + }; + + var optgroups_filters = [{ + id: '1', + optgroup: 'A' }, { - condition: 'XOR', - rules: [{ - id: 'name', - operator: 'equal', - value: 'bar' - }] - }] - }; - - var icons = { - add_group: 'fa fa-plus-circle', - add_rule: 'fa fa-plus', - remove_rule: 'fa fa-times', - remove_group: 'fa fa-times', - sort: 'fa fa-sort' - }; - - var readonly_rules = { - condition: 'AND', - rules: [{ - id: 'price', - operator: 'less', - value: 10.25, - flags: { - no_delete: true - } + id: '2' + }, { + id: '3', + optgroup: 'A' }, { - condition: 'OR', - rules: [{ - id: 'id', - operator: 'not_equal', - value: '1234-azer-5678', - readonly: true - }] - }] - }; - - var readonly_rules_after = { - condition: 'AND', - rules: [{ - id: 'price', - operator: 'less', - value: 10.25 + id: '4', + optgroup: 'B' }, { - condition: 'OR', - rules: [{ - id: 'id', - operator: 'not_equal', - value: '1234-azer-5678' - }] - }] - }; - - var optgroups_filters = [{ - id: '1', - optgroup: 'A' - }, { - id: '2' - }, { - id: '3', - optgroup: 'A' - }, { - id: '4', - optgroup: 'B' - }, { - id: '5' - }, { - id: '6', - optgroup: 'A' - }]; - - var new_default_flags = { - filter_readonly: true, - operator_readonly: false, - value_readonly: true, - no_delete: false - }; + id: '5' + }, { + id: '6', + optgroup: 'A' + }]; + + var new_default_flags = { + filter_readonly: true, + operator_readonly: false, + value_readonly: true, + no_delete: false + }; + }); \ No newline at end of file diff --git a/tests/data.module.js b/tests/data.module.js index 80f895db..36b64c8d 100644 --- a/tests/data.module.js +++ b/tests/data.module.js @@ -1,290 +1,290 @@ $(function(){ - var $b = $('#builder'); - - QUnit.module('data', { - afterEach: function() { - $b.queryBuilder('destroy'); - } - }); - - /** - * Test filters values - */ - QUnit.test('radio/checkbox/select values', function(assert) { - $b.queryBuilder({ - filters: values_filters, - rules: values_rules + var $b = $('#builder'); + + QUnit.module('data', { + afterEach: function() { + $b.queryBuilder('destroy'); + } }); - assert.optionsMatch( - $('#builder_rule_0 .rule-value-container input'), - ['one', 'two', 'three'], - 'Should take an array of values' - ); - - assert.optionsMatch( - $('#builder_rule_1 .rule-value-container input'), - ['one', 'two', 'three'], - 'Should take an object of values' - ); - - assert.optionsMatch( - $('#builder_rule_2 .rule-value-container option'), - ['one', 'two', 'three'], - 'Should take an array of objects of values' - ); - }); - - /** - * Test data validation - */ - QUnit.test('validation', function(assert) { - $b.queryBuilder({ - filters: validation_filters + /** + * Test filters values + */ + QUnit.test('radio/checkbox/select values', function(assert) { + $b.queryBuilder({ + filters: values_filters, + rules: values_rules + }); + + assert.optionsMatch( + $('#builder_rule_0 .rule-value-container input'), + ['one', 'two', 'three'], + 'Should take an array of values' + ); + + assert.optionsMatch( + $('#builder_rule_1 .rule-value-container input'), + ['one', 'two', 'three'], + 'Should take an object of values' + ); + + assert.optionsMatch( + $('#builder_rule_2 .rule-value-container option'), + ['one', 'two', 'three'], + 'Should take an array of objects of values' + ); }); - assert.validationError($b, - { id: 'radio' }, - /radio_empty/ - ); - - assert.validationError($b, - { id: 'checkbox' }, - /checkbox_empty/ - ); - - assert.validationError($b, - { id: 'checkbox', value: ['one', 'two'] }, - /operator_not_multiple/ - ); - - assert.validationError($b, - { id: 'select_mult' }, - /select_empty/ - ); - - assert.validationError($b, - { id: 'select_mult', value: ['one', 'two'] }, - /operator_not_multiple/ - ); - - assert.validationError($b, - { id: 'string' }, - /string_empty/ - ); - - assert.validationError($b, - { id: 'string_val', value: 'aa' }, - /string_exceed_min_length/ - ); - - assert.validationError($b, - { id: 'string_val', value: 'aaaaaa' }, - /string_exceed_max_length/ - ); - - assert.validationError($b, - { id: 'string_val', value: '12345' }, - /string_invalid_format/ - ); - - assert.validationError($b, - { id: 'integer', value: 5.2 }, - /number_not_integer/ - ); - - assert.validationError($b, - { id: 'double', value: 'abc' }, - /number_not_double/ - ); - - assert.validationError($b, - { id: 'integer', value: -15 }, - /number_exceed_min/ - ); - - assert.validationError($b, - { id: 'integer', value: 15 }, - /number_exceed_max/ - ); - - assert.validationError($b, - { id: 'double', value: 0.05 }, - /number_wrong_step/ - ); - - assert.validationError($b, - { id: 'date' }, - /datetime_empty/ - ); - - assert.validationError($b, - { id: 'date', value: '2014/13/15' }, - /datetime_invalid/ - ); - - assert.validationError($b, - { id: 'time', value: '07:00' }, - /datetime_exceed_min/ - ); - - assert.validationError($b, - { id: 'time', value: '18:00' }, - /datetime_exceed_max/ - ); - - assert.validationError($b, - { id: 'boolean', value: 'oui' }, - /boolean_not_valid/ - ); - - assert.validationError($b, - { id: 'custom', value: '' }, - /you_fool/ - ); - }); - - /** - * Test custom data - */ - QUnit.test('custom data', function(assert) { - assert.expect(2); - - $b.queryBuilder({ - filters: basic_filters + /** + * Test data validation + */ + QUnit.test('validation', function(assert) { + $b.queryBuilder({ + filters: validation_filters + }); + + assert.validationError($b, + { id: 'radio' }, + /radio_empty/ + ); + + assert.validationError($b, + { id: 'checkbox' }, + /checkbox_empty/ + ); + + assert.validationError($b, + { id: 'checkbox', value: ['one', 'two'] }, + /operator_not_multiple/ + ); + + assert.validationError($b, + { id: 'select_mult' }, + /select_empty/ + ); + + assert.validationError($b, + { id: 'select_mult', value: ['one', 'two'] }, + /operator_not_multiple/ + ); + + assert.validationError($b, + { id: 'string' }, + /string_empty/ + ); + + assert.validationError($b, + { id: 'string_val', value: 'aa' }, + /string_exceed_min_length/ + ); + + assert.validationError($b, + { id: 'string_val', value: 'aaaaaa' }, + /string_exceed_max_length/ + ); + + assert.validationError($b, + { id: 'string_val', value: '12345' }, + /string_invalid_format/ + ); + + assert.validationError($b, + { id: 'integer', value: 5.2 }, + /number_not_integer/ + ); + + assert.validationError($b, + { id: 'double', value: 'abc' }, + /number_not_double/ + ); + + assert.validationError($b, + { id: 'integer', value: -15 }, + /number_exceed_min/ + ); + + assert.validationError($b, + { id: 'integer', value: 15 }, + /number_exceed_max/ + ); + + assert.validationError($b, + { id: 'double', value: 0.05 }, + /number_wrong_step/ + ); + + assert.validationError($b, + { id: 'date' }, + /datetime_empty/ + ); + + assert.validationError($b, + { id: 'date', value: '2014/13/15' }, + /datetime_invalid/ + ); + + assert.validationError($b, + { id: 'time', value: '07:00' }, + /datetime_exceed_min/ + ); + + assert.validationError($b, + { id: 'time', value: '18:00' }, + /datetime_exceed_max/ + ); + + assert.validationError($b, + { id: 'boolean', value: 'oui' }, + /boolean_not_valid/ + ); + + assert.validationError($b, + { id: 'custom', value: '' }, + /you_fool/ + ); }); - - $b.on('afterAddRule.queryBuilder', function(e, rule) { - assert.ok( - JSON.stringify(rule.data) === JSON.stringify(rules_with_data.rules[0].data), - 'Custom data should be accessible in "afterAddRule" event' - ); + + /** + * Test custom data + */ + QUnit.test('custom data', function(assert) { + assert.expect(2); + + $b.queryBuilder({ + filters: basic_filters + }); + + $b.on('afterAddRule.queryBuilder', function(e, rule) { + assert.ok( + JSON.stringify(rule.data) === JSON.stringify(rules_with_data.rules[0].data), + 'Custom data should be accessible in "afterAddRule" event' + ); + }); + + $b.queryBuilder('setRules', rules_with_data); + + assert.rulesMatch( + $b.queryBuilder('getRules'), + rules_with_data, + 'Should keep custom data in "getRules"' + ); }); - - $b.queryBuilder('setRules', rules_with_data); - - assert.rulesMatch( - $b.queryBuilder('getRules'), - rules_with_data, - 'Should keep custom data in "getRules"' - ); - }); - - - var rules_with_data = { - condition: 'AND', - data: [1,2,3], - rules: [{ - id: 'name', - value: 'Mistic', - data: { - foo: 'bar' - } - }] - }; - - var values_filters = [{ - id: '1', - type: 'string', - input: 'radio', - values: ['one', 'two', 'three'] - }, { - id: '2', - type: 'string', - input: 'checkbox', - values: { - one: 'One', - two: 'Two', - three: 'Three' - } - }, { - id: '3', - type: 'string', - input: 'select', - values: [ - {one: 'One'}, - {two: 'Two'}, - {three: 'Three'} - ] - }]; - - var values_rules = { - rules: [{ - id: '1', - value: 'one' + + + var rules_with_data = { + condition: 'AND', + data: [1,2,3], + rules: [{ + id: 'name', + value: 'Mistic', + data: { + foo: 'bar' + } + }] + }; + + var values_filters = [{ + id: '1', + type: 'string', + input: 'radio', + values: ['one', 'two', 'three'] + }, { + id: '2', + type: 'string', + input: 'checkbox', + values: { + one: 'One', + two: 'Two', + three: 'Three' + } + }, { + id: '3', + type: 'string', + input: 'select', + values: [ + {one: 'One'}, + {two: 'Two'}, + {three: 'Three'} + ] + }]; + + var values_rules = { + rules: [{ + id: '1', + value: 'one' + }, { + id: '2', + value: 'two' + }, { + id: '3', + value: 'three' + }] + }; + + var validation_filters = [{ + id: 'radio', + input: 'radio', + values: ['one', 'two'] + }, { + id: 'checkbox', + input: 'checkbox', + values: ['one', 'two'] + }, { + id: 'select', + input: 'select', + values: ['one', 'two'] + }, { + id: 'select_mult', + input: 'select', + multiple: true, + values: ['one', 'two'] + }, { + id: 'string' + }, { + id: 'string_val', + validation: { + min: '4', max: '5', + format: '^[a-z]?$' + } + }, { + id: 'integer', + type: 'integer', + validation: { + min: -10, max: 10 + } + }, { + id: 'double', + type: 'double', + validation: { + step: 0.1 + } + }, { + id: 'date', + type: 'date', + validation: { + format: 'YYYY/MM/DD' + } + }, { + id: 'time', + type: 'time', + validation: { + format: 'HH:ss', + min: '08:00', + max: '17:00' + } }, { - id: '2', - value: 'two' + id: 'boolean', + type: 'boolean' }, { - id: '3', - value: 'three' - }] - }; - - var validation_filters = [{ - id: 'radio', - input: 'radio', - values: ['one', 'two'] - }, { - id: 'checkbox', - input: 'checkbox', - values: ['one', 'two'] - }, { - id: 'select', - input: 'select', - values: ['one', 'two'] - }, { - id: 'select_mult', - input: 'select', - multiple: true, - values: ['one', 'two'] - }, { - id: 'string' - }, { - id: 'string_val', - validation: { - min: '4', max: '5', - format: '^[a-z]?$' - } - }, { - id: 'integer', - type: 'integer', - validation: { - min: -10, max: 10 - } - }, { - id: 'double', - type: 'double', - validation: { - step: 0.1 - } - }, { - id: 'date', - type: 'date', - validation: { - format: 'YYYY/MM/DD' - } - }, { - id: 'time', - type: 'time', - validation: { - format: 'HH:ss', - min: '08:00', - max: '17:00' - } - }, { - id: 'boolean', - type: 'boolean' - }, { - id: 'custom', - type: 'string', - validation: { - callback: function(value, rule) { - if (value == null || !value.length) { - return 'you_fool'; + id: 'custom', + type: 'string', + validation: { + callback: function(value, rule) { + if (value == null || !value.length) { + return 'you_fool'; + } + } } - } - } - }]; + }]; }); \ No newline at end of file diff --git a/tests/index.html b/tests/index.html index 76446e30..df98555d 100644 --- a/tests/index.html +++ b/tests/index.html @@ -42,6 +42,7 @@ + @@ -53,6 +54,8 @@ + + diff --git a/tests/plugins-data.module.js b/tests/plugins-data.module.js new file mode 100644 index 00000000..c5cba4fe --- /dev/null +++ b/tests/plugins-data.module.js @@ -0,0 +1,478 @@ +$(function(){ + var $b = $('#builder'); + + QUnit.module('plugins-data', { + afterEach: function() { + $b.queryBuilder('destroy'); + } + }); + + /** + * SQL import/export + */ + QUnit.test('sql-support', function(assert) { + $b.queryBuilder({ + filters: basic_filters, + rules: basic_rules + }); + + assert.deepEqual( + $b.queryBuilder('getSQL', false), + basic_rules_sql_raw, + 'Should create SQL query' + ); + + assert.deepEqual( + $b.queryBuilder('getSQL', 'question_mark'), + basic_rules_sql_stmt, + 'Should create SQL query with statements (?)' + ); + + assert.deepEqual( + $b.queryBuilder('getSQL', 'numbered'), + basic_rules_sql_stmt_num, + 'Should create SQL query with statements (numbered)' + ); + + assert.deepEqual( + $b.queryBuilder('getSQL', 'named'), + basic_rules_sql_stmt_named, + 'Should create SQL query with statements (named)' + ); + + $b.queryBuilder('setRulesFromSQL', basic_rules_sql_raw); + assert.rulesMatch( + $b.queryBuilder('getRules'), + basic_rules, + 'Should parse SQL query' + ); + + $b.queryBuilder('setRulesFromSQL', basic_rules_sql_stmt, 'question_mark'); + assert.rulesMatch( + $b.queryBuilder('getRules'), + basic_rules, + 'Should parse SQL query with statements (?)' + ); + + $b.queryBuilder('setRulesFromSQL', basic_rules_sql_stmt_num, 'numbered'); + assert.rulesMatch( + $b.queryBuilder('getRules'), + basic_rules, + 'Should parse SQL query with statements (numbered)' + ); + + $b.queryBuilder('setRulesFromSQL', basic_rules_sql_stmt_named, 'named'); + assert.rulesMatch( + $b.queryBuilder('getRules'), + basic_rules, + 'Should parse SQL query with statements (named)' + ); + + $b.queryBuilder('destroy'); + $b.queryBuilder({ + filters: basic_filters, + rules: all_operators_rules + }); + + assert.deepEqual( + $b.queryBuilder('getSQL', 'question_mark'), + all_operators_rules_sql, + 'Should convert all kind of operators to SQL' + ); + + $b.queryBuilder('setRulesFromSQL', all_operators_rules_sql, 'question_mark'); + assert.rulesMatch( + $b.queryBuilder('getRules'), + all_operators_rules, + 'Should parse all kind of operators from SQL' + ); + + $b.queryBuilder('destroy'); + $b.queryBuilder({ + filters: simple_filters + }); + + $b.queryBuilder('setRulesFromSQL', nested_rules_sql); + assert.rulesMatch( + $b.queryBuilder('getRules'), + nested_rules, + 'Should parse SQL with deep nested rules' + ); + + $b.queryBuilder('setRulesFromSQL', one_rule_sql); + assert.rulesMatch( + $b.queryBuilder('getRules'), + one_rule, + 'Should parse SQL with one rule' + ); + }); + + /** + * MongoDB import/export + */ + QUnit.test('mongo-support', function(assert) { + $b.queryBuilder({ + filters: basic_filters, + rules: basic_rules + }); + + assert.deepEqual( + $b.queryBuilder('getMongo'), + basic_rules_mongodb, + 'Should create MongoDB query' + ); + + assert.deepEqual( + $b.queryBuilder('getRulesFromMongo', basic_rules_mongodb), + basic_rules, + 'Should return rules object from MongoDB query' + ); + + $b.queryBuilder('destroy'); + $b.queryBuilder({ + filters: basic_filters, + rules: all_operators_rules + }); + + assert.deepEqual( + $b.queryBuilder('getMongo'), + all_operators_rules_mongodb, + 'Should successfully convert all kind of operators to MongoDB' + ); + + $b.queryBuilder('setRulesFromMongo', all_operators_rules_mongodb); + assert.rulesMatch( + $b.queryBuilder('getRules'), + all_operators_rules, + 'Should successfully parse all kind of operators from MongoDB' + ); + }); + + + var basic_rules_sql_raw = { + sql: 'price < 10.25 AND name IS NULL AND ( category IN(\'mo\', \'mu\') OR id != \'1234-azer-5678\' ) ' + }; + + var basic_rules_sql_stmt = { + sql: 'price < ? AND name IS NULL AND ( category IN(?, ?) OR id != ? ) ', + params: [10.25, 'mo', 'mu', '1234-azer-5678'] + }; + + var basic_rules_sql_stmt_num = { + sql: 'price < $1 AND name IS NULL AND ( category IN($2, $3) OR id != $4 ) ', + params: [10.25, 'mo', 'mu', '1234-azer-5678'] + }; + + var basic_rules_sql_stmt_named = { + sql: 'price < :price_1 AND name IS NULL AND ( category IN(:category_1, :category_2) OR id != :id_1 ) ', + params: { + price_1: 10.25, + category_1: 'mo', + category_2: 'mu', + id_1: '1234-azer-5678' + } + }; + + var basic_rules_mongodb = {'$and': [ + {'price': { '$lt': 10.25 }}, + {'name': null}, + {'$or': [ + {'category': {'$in': ['mo', 'mu']}}, + {'id': {'$ne': '1234-azer-5678'}} + ]} + ]}; + + var all_operators_rules = { + condition: 'AND', + rules: [{ + id: 'name', + operator: 'equal', + value: 'foo' + }, { + id: 'name', + operator: 'not_equal', + value: 'foo' + }, { + id: 'category', + operator: 'in', + value: ['bk','mo'] + }, { + id: 'category', + operator: 'not_in', + value: ['bk','mo'] + }, { + id: 'price', + operator: 'less', + value: '5' + }, { + id: 'price', + operator: 'less_or_equal', + value: '5' + }, { + id: 'price', + operator: 'greater', + value: '4' + }, { + id: 'price', + operator: 'greater_or_equal', + value: '4' + }, { + id: 'price', + operator: 'between', + value: ['4','5'] + }, { + id: 'price', + operator: 'not_between', + value: ['4','5'] + }, { + id: 'name', + operator: 'begins_with', + value: 'foo' + }, { + id: 'name', + operator: 'not_begins_with', + value: 'foo' + }, { + id: 'name', + operator: 'contains', + value: 'foo' + }, { + id: 'name', + operator: 'not_contains', + value: 'foo' + }, { + id: 'name', + operator: 'ends_with', + value: 'foo' + }, { + id: 'name', + operator: 'not_ends_with', + value: 'foo' + }, { + id: 'name', + operator: 'is_empty', + value: null + }, { + id: 'name', + operator: 'is_not_empty', + value: null + }, { + id: 'name', + operator: 'is_null', + value: null + }, { + id: 'name', + operator: 'is_not_null', + value: null + }] + }; + + var all_operators_rules_sql = { + sql: + 'name = ? ' + + 'AND name != ? ' + + 'AND category IN(?, ?) ' + + 'AND category NOT IN(?, ?) ' + + 'AND price < ? ' + + 'AND price <= ? ' + + 'AND price > ? ' + + 'AND price >= ? ' + + 'AND price BETWEEN ? AND ? ' + + 'AND price NOT BETWEEN ? AND ? ' + + 'AND name LIKE(?) ' + + 'AND name NOT LIKE(?) ' + + 'AND name LIKE(?) ' + + 'AND name NOT LIKE(?) ' + + 'AND name LIKE(?) ' + + 'AND name NOT LIKE(?) ' + + 'AND name = \'\' ' + + 'AND name != \'\' ' + + 'AND name IS NULL ' + + 'AND name IS NOT NULL', + params: [ + 'foo', + 'foo', + 'bk', 'mo', + 'bk', 'mo', + 5, + 5, + 4, + 4, + 4, 5, + 4, 5, + 'foo%', + 'foo%', + '%foo%', + '%foo%', + '%foo', + '%foo' + ] + }; + + var all_operators_rules_mongodb = { + $and: [ + { name: 'foo' }, + { name: {$ne: 'foo'} }, + { category: { $in: ['bk','mo'] }}, + { category: { $nin: ['bk','mo'] }}, + { price: {$lt: 5} }, + { price: {$lte: 5} }, + { price: {$gt: 4} }, + { price: {$gte: 4} }, + { price: {$gte: 4, $lte: 5} }, + { price: {$lt: 4, $gt: 5} }, + { name: {$regex: '^foo'} }, + { name: {$regex: '^(?!foo)'} }, + { name: {$regex: 'foo'} }, + { name: {$regex: '^((?!foo).)*$', $options: 's'} }, + { name: {$regex: 'foo$'} }, + { name: {$regex: '(?Lorem Ipsum sit amet.' + }]; + + var description_rules = { + rules: [{ + id: 'name', + value: 'Mistic' + }] + }; + + var sorted_rules = $.extend(true, {}, basic_rules); + sorted_rules.rules.splice(2, 0, sorted_rules.rules[2].rules.pop()); + +}); \ No newline at end of file diff --git a/tests/plugins.module.js b/tests/plugins.module.js index 6d14fc5a..40276566 100644 --- a/tests/plugins.module.js +++ b/tests/plugins.module.js @@ -1,843 +1,220 @@ $(function(){ - var $b = $('#builder'); - - QUnit.module('plugins', { - afterEach: function() { - $b.queryBuilder('destroy'); - } - }); - - /** - * Test plugins loading - */ - QUnit.test('Plugins loading', function(assert) { - assert.ok(QueryBuilder.prototype.getSQL !== undefined, 'Should load SQL plugin automatically'); - - $b.queryBuilder({ - filters: basic_filters, - plugins: ['bt-tooltip-errors', 'filter-description'] - }); + var $b = $('#builder'); - assert.deepEqual( - $b[0].queryBuilder.plugins['bt-tooltip-errors'], - QueryBuilder.plugins['bt-tooltip-errors'].def, - 'Should load "bt-tooltip-errors" with default config' - ); - - assert.deepEqual( - $b[0].queryBuilder.plugins['filter-description'], - QueryBuilder.plugins['filter-description'].def, - 'Should load "filter-description" with default config' - ); - - $b.queryBuilder('destroy'); - - $b.queryBuilder({ - filters: basic_filters, - plugins: { - 'bt-tooltip-errors': null, - 'filter-description': { icon: 'fa fa-info' } - } + QUnit.module('plugins', { + afterEach: function() { + $b.queryBuilder('destroy'); + } }); - assert.deepEqual( - $b[0].queryBuilder.plugins['bt-tooltip-errors'], - QueryBuilder.plugins['bt-tooltip-errors'].def, - 'Should load "bt-tooltip-errors" with default config' - ); + /** + * Test plugins loading + */ + QUnit.test('Plugins loading', function(assert) { + assert.ok(QueryBuilder.prototype.getSQL !== undefined, 'Should load SQL plugin automatically'); + + $b.queryBuilder({ + filters: basic_filters, + plugins: ['bt-tooltip-errors', 'filter-description'] + }); + + assert.deepEqual( + $b[0].queryBuilder.plugins['bt-tooltip-errors'], + QueryBuilder.plugins['bt-tooltip-errors'].def, + 'Should load "bt-tooltip-errors" with default config' + ); - assert.deepEqual( - $b[0].queryBuilder.plugins['filter-description'], - { icon: 'fa fa-info', mode: 'popover' }, - 'Should load "filter-description" with custom config' - ); + assert.deepEqual( + $b[0].queryBuilder.plugins['filter-description'], + QueryBuilder.plugins['filter-description'].def, + 'Should load "filter-description" with default config' + ); - $b.queryBuilder('destroy'); + $b.queryBuilder('destroy'); - assert.throws( - function(){ $b.queryBuilder({ - filters: basic_filters, - plugins: ['__unknown__'] + filters: basic_filters, + plugins: { + 'bt-tooltip-errors': null, + 'filter-description': { icon: 'fa fa-info' } + } }); - }, - /Unable to find plugin "__unknown__"/, - 'Should throw error on unknown plugin' - ); - }); - - /** - * SQL import/export - */ - QUnit.test('sql-support', function(assert) { - $b.queryBuilder({ - filters: basic_filters, - rules: basic_rules - }); - assert.deepEqual( - $b.queryBuilder('getSQL', false), - basic_rules_sql_raw, - 'Should create SQL query' - ); - - assert.deepEqual( - $b.queryBuilder('getSQL', 'question_mark'), - basic_rules_sql_stmt, - 'Should create SQL query with statements (?)' - ); - - assert.deepEqual( - $b.queryBuilder('getSQL', 'numbered'), - basic_rules_sql_stmt_num, - 'Should create SQL query with statements (numbered)' - ); - - assert.deepEqual( - $b.queryBuilder('getSQL', 'named'), - basic_rules_sql_stmt_named, - 'Should create SQL query with statements (named)' - ); - - $b.queryBuilder('setRulesFromSQL', basic_rules_sql_raw); - assert.rulesMatch( - $b.queryBuilder('getRules'), - basic_rules, - 'Should parse SQL query' - ); - - $b.queryBuilder('setRulesFromSQL', basic_rules_sql_stmt, 'question_mark'); - assert.rulesMatch( - $b.queryBuilder('getRules'), - basic_rules, - 'Should parse SQL query with statements (?)' - ); - - $b.queryBuilder('setRulesFromSQL', basic_rules_sql_stmt_num, 'numbered'); - assert.rulesMatch( - $b.queryBuilder('getRules'), - basic_rules, - 'Should parse SQL query with statements (numbered)' - ); - - $b.queryBuilder('setRulesFromSQL', basic_rules_sql_stmt_named, 'named'); - assert.rulesMatch( - $b.queryBuilder('getRules'), - basic_rules, - 'Should parse SQL query with statements (named)' - ); - - $b.queryBuilder('destroy'); - $b.queryBuilder({ - filters: basic_filters, - rules: all_operators_rules + assert.deepEqual( + $b[0].queryBuilder.plugins['bt-tooltip-errors'], + QueryBuilder.plugins['bt-tooltip-errors'].def, + 'Should load "bt-tooltip-errors" with default config' + ); + + assert.deepEqual( + $b[0].queryBuilder.plugins['filter-description'], + { icon: 'fa fa-info', mode: 'popover' }, + 'Should load "filter-description" with custom config' + ); + + $b.queryBuilder('destroy'); + + assert.throws( + function(){ + $b.queryBuilder({ + filters: basic_filters, + plugins: ['__unknown__'] + }); + }, + /Unable to find plugin "__unknown__"/, + 'Should throw error on unknown plugin' + ); }); - assert.deepEqual( - $b.queryBuilder('getSQL', 'question_mark'), - all_operators_rules_sql, - 'Should convert all kind of operators to SQL' - ); - - $b.queryBuilder('setRulesFromSQL', all_operators_rules_sql, 'question_mark'); - assert.rulesMatch( - $b.queryBuilder('getRules'), - all_operators_rules, - 'Should parse all kind of operators from SQL' - ); - - $b.queryBuilder('destroy'); - $b.queryBuilder({ - filters: simple_filters - }); + /** + * Test unique-filter + */ + QUnit.test('unique-filter', function(assert) { + $b.queryBuilder({ + plugins: ['unique-filter'], + filters: unique_filters, + rules: basic_rules + }); - $b.queryBuilder('setRulesFromSQL', nested_rules_sql); - assert.rulesMatch( - $b.queryBuilder('getRules'), - nested_rules, - 'Should parse SQL with deep nested rules' - ); - - $b.queryBuilder('setRulesFromSQL', one_rule_sql); - assert.rulesMatch( - $b.queryBuilder('getRules'), - one_rule, - 'Should parse SQL with one rule' - ); - }); - - /** - * MongoDB import/export - */ - QUnit.test('mongo-support', function(assert) { - $b.queryBuilder({ - filters: basic_filters, - rules: basic_rules + assert.ok( + $('select[name=builder_rule_0_filter] option[value=id]').is(':disabled') && + $('select[name=builder_rule_1_filter] option[value=id]').is(':disabled') && + $('select[name=builder_rule_2_filter] option[value=id]').is(':disabled'), + '"Identifier" filter should be disabled everywhere' + ); + + /* + FIXME: the test always fails in Grunt + assert.ok( + $('select[name=builder_rule_1_filter] option[value=price]').is(':disabled') && + !$('select[name=builder_rule_2_filter] option[value=price]').is(':disabled') && + !$('select[name=builder_rule_3_filter] option[value=price]').is(':disabled'), + '"Price" filter should be disabled in his group only' + ); + */ }); - assert.deepEqual( - $b.queryBuilder('getMongo'), - basic_rules_mongodb, - 'Should create MongoDB query' - ); - - assert.deepEqual( - $b.queryBuilder('getRulesFromMongo', basic_rules_mongodb), - basic_rules, - 'Should return rules object from MongoDB query' - ); - - $b.queryBuilder('destroy'); - $b.queryBuilder({ - filters: basic_filters, - rules: all_operators_rules - }); + /** + * Test inversion + */ + QUnit.test('invert', function(assert) { + $b.queryBuilder({ + plugins: ['invert'], + filters: basic_filters, + rules: basic_rules + }); - assert.deepEqual( - $b.queryBuilder('getMongo'), - all_operators_rules_mongodb, - 'Should successfully convert all kind of operators to MongoDB' - ); - - $b.queryBuilder('setRulesFromMongo', all_operators_rules_mongodb); - assert.rulesMatch( - $b.queryBuilder('getRules'), - all_operators_rules, - 'Should successfully parse all kind of operators from MongoDB' - ); - }); - - /** - * Test bt-checkbox - */ - QUnit.test('bt-checkbox', function(assert) { - $b.queryBuilder({ - plugins: ['bt-checkbox'], - filters: bt_checkbox_filters, - rules: bt_checkbox_rules - }); + $b.queryBuilder('invert'); - assert.ok( - $('#builder_rule_0 .checkbox.checkbox-default').length == 2, - 'Should have 2 checkboxes with default color' - ); - - assert.ok( - $('#builder_rule_1 .checkbox.checkbox-primary').length == 3, - 'Should have 3 checkboxes with primary color' - ); - - assert.ok( - $('#builder_rule_2 .radio.radio-danger').length == 1 && - $('#builder_rule_2 .radio.radio-success').length == 1 && - $('#builder_rule_2 .radio.radio-default').length == 1, - 'Should have 3 radios with danger, success and default colors' - ); - }); - - /** - * Test bt-selectpicker - */ - QUnit.test('bt-selectpicker', function(assert) { - $b.queryBuilder({ - plugins: ['bt-selectpicker'], - filters: basic_filters, - rules: basic_rules + assert.rulesMatch( + $b.queryBuilder('getRules'), + basic_rules_invert, + 'Should have inverted all conditions and operators' + ); }); - assert.ok( - $b.find('.bootstrap-select').length == 8, - 'Should have initialized Bootstrap Select on all filters and operators selectors' - ); - }); - - /** - * Test bt-tooltip-errors - */ - QUnit.test('bt-tooltip-errors', function(assert) { - $b.queryBuilder({ - plugins: ['bt-tooltip-errors'], - filters: basic_filters, - rules: invalid_rules - }); + /** + * Test change filters + */ + QUnit.test('change-filters', function(assert) { + $b.queryBuilder({ + filters: [filter_a, filter_b], + rules: [rule_a, rule_b] + }); - $b.queryBuilder('validate'); - - assert.equal( - $('#builder_group_0 .error-container').eq(0).data('toggle'), - 'tooltip', - 'Should have added data-toggle="tooltip" in the template' - ); - - assert.equal( - $('#builder_rule_0 .error-container').data('originalTitle'), - 'Empty value', - 'Error title should be "Empty value"' - ); - }); - - /** - * Test filter-description - */ - QUnit.test('filter-description', function(assert) { - $b.queryBuilder({ - plugins: { - 'filter-description': { mode: 'inline' } - }, - filters: description_filters, - rules: description_rules + assert.throws( + function(){ + $b.queryBuilder('removeFilter', 'a'); + }, + /A rule is using filter "a"/, + 'Should throw error when deleting filter "a" w/o force' + ); + + $b.queryBuilder('removeFilter', 'a', true); + + assert.rulesMatch( + $b.queryBuilder('getRules'), + {condition:'AND', rules: [rule_b]}, + 'Should have deleted rule using filter "a"' + ); + + $b.queryBuilder('addFilter', filter_c, 0); + + assert.optionsMatch( + $('#builder_rule_1 [name$=_filter] option'), + ['-1', filter_c.id, filter_b.id], + 'Should have added filter "c" at begining' + ); + + $b.queryBuilder('addFilter', filter_a, 'c'); + + assert.optionsMatch( + $('#builder_rule_1 [name$=_filter] option'), + ['-1', filter_c.id, filter_a.id, filter_b.id], + 'Should have added filter "a" after "c"' + ); }); - assert.match( - $('#builder_rule_0 p.filter-description').html(), - new RegExp(description_filters[0].description), - 'Paragraph should contain filter description' - ); - - $b.queryBuilder('destroy'); - - $b.queryBuilder({ - plugins: { - 'filter-description': { mode: 'popover' } - }, - filters: description_filters, - rules: description_rules - }); - assert.ok( - $('#builder_rule_0 button.filter-description').data('toggle') == 'popover', - 'Rule should contain a new button enabled with Popover' - ); + var filter_a = { + id: 'a', + type: 'string' + }; - $b.queryBuilder('destroy'); + var filter_b = { + id: 'b', + type: 'string' + }; - $b.queryBuilder({ - plugins: { - 'filter-description': { mode: 'bootbox' } - }, - filters: description_filters, - rules: description_rules - }); + var filter_c = { + id: 'c', + type: 'string' + }; - assert.ok( - $('#builder_rule_0 button.filter-description').data('toggle') == 'bootbox', - 'Rule should contain a new button enabled with Bootbox' - ); - }); - - /** - * Test unique-filter - */ - QUnit.test('unique-filter', function(assert) { - $b.queryBuilder({ - plugins: ['unique-filter'], - filters: unique_filters, - rules: basic_rules - }); - - assert.ok( - $('select[name=builder_rule_0_filter] option[value=id]').is(':disabled') && - $('select[name=builder_rule_1_filter] option[value=id]').is(':disabled') && - $('select[name=builder_rule_2_filter] option[value=id]').is(':disabled'), - '"Identifier" filter should be disabled everywhere' - ); - - /* - FIXME: the test always fails in Grunt - assert.ok( - $('select[name=builder_rule_1_filter] option[value=price]').is(':disabled') && - !$('select[name=builder_rule_2_filter] option[value=price]').is(':disabled') && - !$('select[name=builder_rule_3_filter] option[value=price]').is(':disabled'), - '"Price" filter should be disabled in his group only' - ); - */ - }); - - /** - * Test sortable - */ - QUnit.test('sortable', function(assert) { - assert.expect(1); - var done = assert.async(); - - $b.queryBuilder({ - plugins: ['sortable'], - filters: basic_filters, - rules: basic_rules - }); - - $('#builder_rule_3').simulateDragDrop({ - dropTarget: $('#builder_rule_1'), - start: function() { - $(this).find('.drag-handle').trigger('mouseover'); - }, - done: function() { - assert.rulesMatch( - $b.queryBuilder('getRules'), - sorted_rules, - 'Should have moved "Identifier" rule' - ); - done(); - } - }); - }); - - /** - * Test inversion - */ - QUnit.test('invert', function(assert) { - $b.queryBuilder({ - plugins: ['invert'], - filters: basic_filters, - rules: basic_rules - }); - - $b.queryBuilder('invert'); - - assert.rulesMatch( - $b.queryBuilder('getRules'), - basic_rules_invert, - 'Should have inverted all conditions and operators' - ); - }); - - - var basic_rules_sql_raw = { - sql: 'price < 10.25 AND name IS NULL AND ( category IN(\'mo\', \'mu\') OR id != \'1234-azer-5678\' ) ' - }; - - var basic_rules_sql_stmt = { - sql: 'price < ? AND name IS NULL AND ( category IN(?, ?) OR id != ? ) ', - params: [10.25, 'mo', 'mu', '1234-azer-5678'] - }; - - var basic_rules_sql_stmt_num = { - sql: 'price < $1 AND name IS NULL AND ( category IN($2, $3) OR id != $4 ) ', - params: [10.25, 'mo', 'mu', '1234-azer-5678'] - }; - - var basic_rules_sql_stmt_named = { - sql: 'price < :price_1 AND name IS NULL AND ( category IN(:category_1, :category_2) OR id != :id_1 ) ', - params: { - price_1: 10.25, - category_1: 'mo', - category_2: 'mu', - id_1: '1234-azer-5678' - } - }; - - var basic_rules_mongodb = {'$and': [ - {'price': { '$lt': 10.25 }}, - {'name': null}, - {'$or': [ - {'category': {'$in': ['mo', 'mu']}}, - {'id': {'$ne': '1234-azer-5678'}} - ]} - ]}; - - var basic_rules_loopback = {'and': [ - {'price': { 'lt': 10.25 }}, - {'name': null}, - {'or': [ - {'category': {'inq': ['mo', 'mu']}}, - {'id': {'neq': '1234-azer-5678'}} - ]} - ]}; - - var all_operators_rules = { - condition: 'AND', - rules: [{ - id: 'name', - operator: 'equal', - value: 'foo' - }, { - id: 'name', - operator: 'not_equal', - value: 'foo' - }, { - id: 'category', - operator: 'in', - value: ['bk','mo'] - }, { - id: 'category', - operator: 'not_in', - value: ['bk','mo'] - }, { - id: 'price', - operator: 'less', - value: '5' - }, { - id: 'price', - operator: 'less_or_equal', - value: '5' - }, { - id: 'price', - operator: 'greater', - value: '4' - }, { - id: 'price', - operator: 'greater_or_equal', - value: '4' - }, { - id: 'price', - operator: 'between', - value: ['4','5'] - }, { - id: 'price', - operator: 'not_between', - value: ['4','5'] - }, { - id: 'name', - operator: 'begins_with', - value: 'foo' - }, { - id: 'name', - operator: 'not_begins_with', - value: 'foo' - }, { - id: 'name', - operator: 'contains', - value: 'foo' - }, { - id: 'name', - operator: 'not_contains', - value: 'foo' - }, { - id: 'name', - operator: 'ends_with', - value: 'foo' - }, { - id: 'name', - operator: 'not_ends_with', - value: 'foo' - }, { - id: 'name', - operator: 'is_empty', - value: null - }, { - id: 'name', - operator: 'is_not_empty', - value: null - }, { - id: 'name', - operator: 'is_null', - value: null - }, { - id: 'name', - operator: 'is_not_null', - value: null - }] - }; - - var all_operators_rules_sql = { - sql: - 'name = ? ' + - 'AND name != ? ' + - 'AND category IN(?, ?) ' + - 'AND category NOT IN(?, ?) ' + - 'AND price < ? ' + - 'AND price <= ? ' + - 'AND price > ? ' + - 'AND price >= ? ' + - 'AND price BETWEEN ? AND ? ' + - 'AND price NOT BETWEEN ? AND ? ' + - 'AND name LIKE(?) ' + - 'AND name NOT LIKE(?) ' + - 'AND name LIKE(?) ' + - 'AND name NOT LIKE(?) ' + - 'AND name LIKE(?) ' + - 'AND name NOT LIKE(?) ' + - 'AND name = \'\' ' + - 'AND name != \'\' ' + - 'AND name IS NULL ' + - 'AND name IS NOT NULL', - params: [ - 'foo', - 'foo', - 'bk', 'mo', - 'bk', 'mo', - 5, - 5, - 4, - 4, - 4, 5, - 4, 5, - 'foo%', - 'foo%', - '%foo%', - '%foo%', - '%foo', - '%foo' - ] - }; - - var all_operators_rules_mongodb = { - $and: [ - { name: 'foo' }, - { name: {$ne: 'foo'} }, - { category: { $in: ['bk','mo'] }}, - { category: { $nin: ['bk','mo'] }}, - { price: {$lt: 5} }, - { price: {$lte: 5} }, - { price: {$gt: 4} }, - { price: {$gte: 4} }, - { price: {$gte: 4, $lte: 5} }, - { price: {$lt: 4, $gt: 5} }, - { name: {$regex: '^foo'} }, - { name: {$regex: '^(?!foo)'} }, - { name: {$regex: 'foo'} }, - { name: {$regex: '^((?!foo).)*$', $options: 's'} }, - { name: {$regex: 'foo$'} }, - { name: {$regex: '(?Lorem Ipsum sit amet.' - }]; - - var description_rules = { - rules: [{ - id: 'name', - value: 'Mistic' - }] - }; - - var unique_filters = $.extend(true, [], basic_filters); - unique_filters[3].unique = 'group'; - unique_filters[4].unique = true; - - var sorted_rules = $.extend(true, {}, basic_rules); - sorted_rules.rules.splice(2, 0, sorted_rules.rules[2].rules.pop()); - - var basic_rules_invert = { - condition: 'OR', - rules: [{ - id: 'price', - field: 'price', - operator: 'greater_or_equal', - value: 10.25 - }, { - id: 'name', - field: 'name', - operator: 'is_not_null', - value: null - }, { - condition: 'AND', - rules: [{ - id: 'category', - field: 'category', - operator: 'not_in', - value: ['mo', 'mu'] - }, { - id: 'id', - field: 'id', - operator: 'equal', - value: '1234-azer-5678' - }] - }] - }; + value: '1234-azer-5678' + }] + }] + }; + }); \ No newline at end of file