From 24f14a8b4d16f540d127ae6a0b5ceebd1157f4fd Mon Sep 17 00:00:00 2001 From: Steve James <4stevejames@gmail.com> Date: Mon, 5 Feb 2018 18:53:49 -0800 Subject: [PATCH 1/6] Updating to QUnit 2.5, some spec formatting --- tests/unit/core.js | 377 +++++------- tests/unit/events.js | 223 ++++--- tests/unit/filter.js | 60 +- tests/unit/html.js | 32 +- tests/unit/index.htm | 11 +- tests/unit/methods.js | 155 ++--- tests/unit/options.js | 364 +++++------ tests/unit/qunit.css | 196 ------ tests/unit/qunit.js | 1341 ----------------------------------------- tests/unit/setup.js | 14 + 10 files changed, 555 insertions(+), 2218 deletions(-) delete mode 100644 tests/unit/qunit.css delete mode 100644 tests/unit/qunit.js create mode 100644 tests/unit/setup.js diff --git a/tests/unit/core.js b/tests/unit/core.js index f5fd824..dcfafa9 100644 --- a/tests/unit/core.js +++ b/tests/unit/core.js @@ -1,214 +1,169 @@ -var el; -var body = document.body; - -function button(){ - return el.next(); -} - -function menu(){ - return el.multiselect("widget"); -} - -function header(){ - return menu().find('.ui-multiselect-header'); -} - -QUnit.done = function(){ - $("select").hide(); -}; - -(function($){ - - module("core"); - - test("init", function(){ - expect(6); - - el = $("select").multiselect(), $header = header(); - ok( $header.find('a.ui-multiselect-all').css('display') !== 'none', 'select all is visible' ); - ok( $header.find('a.ui-multiselect-none').css('display') !== 'none', 'select none is visible' ); - ok( $header.find('a.ui-multiselect-close').css('display') !== 'none', 'close link is visible' ); - ok( menu().is(':hidden'), 'menu is hidden'); - ok( el.is(":hidden"), 'the original select is hidden'); - ok( el.attr('tabIndex') == 2, 'button inherited the correct tab index'); - el.multiselect("destroy"); - }); - - test("name space separation", function(){ - expect(1); - - var form = $('
').appendTo(body), - data; - - el1 = $('') - .appendTo(form) - .multiselect(); - - el2 = $('') - .appendTo(form) - .multiselect(); - - notEqual(el1.multiselect('widget').find('input').eq(0).attr('id'), el2.multiselect('widget').find('input').eq(0).attr('id'), 'name spaces for multiple widgets are different'); - - el1.multiselect('destroy'); - el2.multiselect('destroy'); - form.remove(); - }); - - test("form submission", function(){ - expect(3); - - var form = $('
').appendTo(body), - data; - - el = $('') - .appendTo(form) - .multiselect() - .multiselect("checkAll"); - - data = form.serialize(); - equals( data, 'test=foo&test=bar', 'after checking all and serializing the form, the correct keys were serialized'); - - el.multiselect("uncheckAll"); - data = form.serialize(); - equals( data.length, 0, 'after unchecking all and serializing the form, nothing was serialized'); - - // re-check all and destroy, exposing original select - el.multiselect("checkAll").multiselect("destroy"); - data = form.serialize(); - equals( data, 'test=foo&test=bar', 'after checking all, destroying the widget, and serializing the form, the correct keys were serialized'); - +(function ($) { + var form, data; + + QUnit.module("core", { + beforeEach: function () { + form = $('
').appendTo(body); + data = null; + }, + afterEach: function () { form.remove(); - }); - - test("form submission, optgroups", function(){ - expect(4); - - var form = $('
').appendTo(body), - data; - - el = $('') - .appendTo(form) - .multiselect() - .multiselect("checkAll"); - - data = form.serialize(); - equals( data, 'test=foo&test=bar&test=baz&test=bax', 'after checking all and serializing the form, the correct keys were serialized'); - - el.multiselect("uncheckAll"); - data = form.serialize(); - equals( data.length, 0, 'after unchecking all and serializing the form, nothing was serialized'); - - // re-check all and destroy, exposing original select - el.multiselect("checkAll").multiselect("destroy"); - data = form.serialize(); - equals( data, 'test=foo&test=bar&test=baz&test=bax', 'after checking all, destroying the widget, and serializing the form, the correct keys were serialized'); - - // reset option tags - el.find("option").each(function(){ - this.selected = false; - }); - - // test checking one option in both optgroups - el.multiselect(); - - // finds the first input in each optgroup (assumes 2 options per optgroup) - el.multiselect("widget").find('.ui-multiselect-checkboxes li:not(.ui-multiselect-optgroup-label) input:even').each(function( i ){ - this.click(); - }); - - data = form.serialize(); - equals( data, 'test=foo&test=baz', 'after manually checking one input in each group, the correct two are serialized'); - + } + }); + + QUnit.test("init", function (assert) { + el = $("select").multiselect(), $header = header(); + assert.ok($header.find('a.ui-multiselect-all').css('display') !== 'none', 'select all is visible'); + assert.ok($header.find('a.ui-multiselect-none').css('display') !== 'none', 'select none is visible'); + assert.ok($header.find('a.ui-multiselect-close').css('display') !== 'none', 'close link is visible'); + assert.ok(menu().is(':hidden'), 'menu is hidden'); + assert.ok(el.is(":hidden"), 'the original select is hidden'); + assert.ok(el.attr('tabIndex') == 2, 'button inherited the correct tab index'); + el.multiselect("destroy"); + }); + + QUnit.test("name space separation", function (assert) { + var el1 = $('') + .appendTo(form) + .multiselect(); + + var el2 = $('') + .appendTo(form) + .multiselect(); + + assert.notEqual(el1.multiselect('widget').find('input').eq(0).attr('id'), el2.multiselect('widget').find('input').eq(0).attr('id'), 'name spaces for multiple widgets are different'); + + el1.multiselect('destroy'); + el2.multiselect('destroy'); + }); + + QUnit.test("form submission", function (assert) { + el = $('') + .appendTo(form) + .multiselect() + .multiselect("checkAll"); + + data = form.serialize(); + assert.equal(data, 'test=foo&test=bar', 'after checking all and serializing the form, the correct keys were serialized'); + + el.multiselect("uncheckAll"); + data = form.serialize(); + assert.equal(data.length, 0, 'after unchecking all and serializing the form, nothing was serialized'); + + // re-check all and destroy, exposing original select + el.multiselect("checkAll").multiselect("destroy"); + data = form.serialize(); + assert.equal(data, 'test=foo&test=bar', 'after checking all, destroying the widget, and serializing the form, the correct keys were serialized'); + }); + + QUnit.test("form submission, optgroups", function (assert) { + el = $('') + .appendTo(form) + .multiselect() + .multiselect("checkAll"); + + data = form.serialize(); + assert.equal(data, 'test=foo&test=bar&test=baz&test=bax', 'after checking all and serializing the form, the correct keys were serialized'); + + el.multiselect("uncheckAll"); + data = form.serialize(); + assert.equal(data.length, 0, 'after unchecking all and serializing the form, nothing was serialized'); + + // re-check all and destroy, exposing original select + el.multiselect("checkAll").multiselect("destroy"); + data = form.serialize(); + assert.equal(data, 'test=foo&test=bar&test=baz&test=bax', 'after checking all, destroying the widget, and serializing the form, the correct keys were serialized'); + + // reset option tags + el.find("option").each(function () { + this.selected = false; + }); + + // test checking one option in both optgroups + el.multiselect(); + + // finds the first input in each optgroup (assumes 2 options per optgroup) + el.multiselect("widget").find('.ui-multiselect-checkboxes li:not(.ui-multiselect-optgroup-label) input:even').each(function (i) { + this.click(); + }); + + data = form.serialize(); + assert.equal(data, 'test=foo&test=baz', 'after manually checking one input in each group, the correct two are serialized'); + + el.multiselect('destroy'); + }); + + QUnit.test("form submission, single select", function (assert) { + // Use an underlying single-select here. + el = $('') + .appendTo(form) + .multiselect(); + + // select multiple radios to ensure that, in the underlying select, only one + // will remain selected + radios = menu().find(":radio"); + radios[0].click(); + radios[2].click(); + radios[1].click(); + + data = form.serialize(); + assert.equal(data, 'test=bar', 'the form serializes correctly after clicking on multiple radio buttons'); + assert.equal(radios.filter(":checked").length, 1, 'Only one radio button is selected'); + + // uncheckAll method + el.multiselect("uncheckAll"); + data = form.serialize(); + assert.equal(data.length, 0, 'After unchecking all, nothing was serialized'); + assert.equal(radios.filter(":checked").length, 0, 'No radio buttons are selected'); + + // checkAll method + el.multiselect("checkAll"); + data = form.serialize(); + assert.equal(el.multiselect("getChecked").length, 1, 'After checkAll, only one radio is selected'); + assert.equal(radios.filter(":checked").length, 1, 'One radio is selected'); + + // expose original + el.multiselect("destroy"); + data = form.serialize(); + assert.equal(data, 'test=baz', 'after destroying the widget and serializing the form, the correct key was serialized: ' + data); + }); + + QUnit.test("form reset, nothing pre-selected", function (assert) { + var noneSelected = 'Please check something'; + + el = $('') + .appendTo(form) + .multiselect({ noneSelectedText: noneSelected, selectedList: 0 }) + .multiselect("checkAll"); + + // trigger reset + var done = assert.async(); + form.trigger("reset"); + + setTimeout(function () { + assert.equal(menu().find(":checked").length, 0, "no checked checkboxes"); + assert.equal(button().text(), noneSelected, "none selected text"); el.multiselect('destroy'); - form.remove(); - }); - - test("form submission, single select", function(){ - expect(7); - - var form = $('
').appendTo("body"), - radios, data; - - // Use an underlying single-select here. - el = $('') - .appendTo(form) - .multiselect(); - - // select multiple radios to ensure that, in the underlying select, only one - // will remain selected - radios = menu().find(":radio"); - radios[0].click(); - radios[2].click(); - radios[1].click(); - - data = form.serialize(); - equals( data, 'test=bar', 'the form serializes correctly after clicking on multiple radio buttons'); - equals( radios.filter(":checked").length, 1, 'Only one radio button is selected'); - - // uncheckAll method - el.multiselect("uncheckAll"); - data = form.serialize(); - equals( data.length, 0, 'After unchecking all, nothing was serialized'); - equals( radios.filter(":checked").length, 0, 'No radio buttons are selected'); - - // checkAll method - el.multiselect("checkAll"); - data = form.serialize(); - equals( el.multiselect("getChecked").length, 1, 'After checkAll, only one radio is selected'); - equals( radios.filter(":checked").length, 1, 'One radio is selected'); - - // expose original - el.multiselect("destroy"); - data = form.serialize(); - equals( data, 'test=baz', 'after destroying the widget and serializing the form, the correct key was serialized: ' + data); - - form.remove(); - }); - - asyncTest("form reset, nothing pre-selected", function(){ - expect(2); - - var form = $('
').appendTo(body), - noneSelected = 'Please check something'; - - el = $('') - .appendTo(form) - .multiselect({ noneSelectedText: noneSelected, selectedList: 0 }) - .multiselect("checkAll"); - - // trigger reset - form.trigger("reset"); - - setTimeout(function(){ - equals( menu().find(":checked").length, 0, "no checked checkboxes" ); - equals( button().text(), noneSelected, "none selected text"); - el.multiselect('destroy'); - form.remove(); - start(); - }, 10); - }); - - asyncTest("form reset, pre-selected options", function(){ - expect(2); - - var form = $('
').appendTo(body); - - el = $('') - .appendTo(form) - .multiselect({ selectedText: '# of # selected', selectedList: 0 }) - .multiselect("uncheckAll"); - - // trigger reset - form.trigger("reset"); - - setTimeout(function(){ - equals( menu().find(":checked").length, 2, "two checked checkboxes" ); - equals( button().text(), "2 of 2 selected", "selected text" ); - el.multiselect('destroy'); - form.remove(); - start(); - }, 10); - }); - + done(); + }, 10); + }); + + QUnit.test("form reset, pre-selected options", function (assert) { + el = $('') + .appendTo(form) + .multiselect({ selectedText: '# of # selected', selectedList: 0 }) + .multiselect("uncheckAll"); + + // trigger reset + var done = assert.async(); + form.trigger("reset"); + + setTimeout(function () { + assert.equal(menu().find(":checked").length, 2, "two checked checkboxes"); + assert.equal(button().text(), "2 of 2 selected", "selected text"); + el.multiselect('destroy'); + done(); + }, 10); + }); + })(jQuery); diff --git a/tests/unit/events.js b/tests/unit/events.js index ab39c04..4093053 100644 --- a/tests/unit/events.js +++ b/tests/unit/events.js @@ -1,32 +1,30 @@ (function($){ - module("events"); - - test("multiselectopen", function(){ - expect(27); + QUnit.module("events"); + QUnit.test("multiselectopen", function(assert){ // inject widget el = $("").appendTo(body); el.multiselect({ open: function(e,ui){ - ok( true, 'option: multiselect("open") fires open callback' ); - equals(this, el[0], "option: context of callback"); - equals(e.type, 'multiselectopen', 'option: event type in callback'); - equals(menu().css("display"), 'block', 'menu display css property equals block'); - same(ui, {}, 'option: ui hash in callback'); + assert.ok( true, 'option: multiselect("open") fires open callback' ); + assert.equal(this, el[0], "option: context of callback"); + assert.equal(e.type, 'multiselectopen', 'option: event type in callback'); + assert.equal(menu().css("display"), 'block', 'menu display css property assert.equal block'); + assert.propEqual(ui, {}, 'option: ui hash in callback'); } }) .bind("multiselectopen", function(e,ui){ - ok(true, 'event: multiselect("open") fires multiselectopen event'); - equals(this, el[0], 'event: context of event'); - same(ui, {}, 'event: ui hash'); + assert.ok(true, 'event: multiselect("open") fires multiselectopen event'); + assert.equal(this, el[0], 'event: context of event'); + assert.propEqual(ui, {}, 'event: ui hash'); }); // now try to open it.. el.multiselect("open"); // make sure the width of the menu and button are equivalent - equals( button().outerWidth(), menu().outerWidth(), 'button and menu widths are equivalent'); + assert.equal( button().outerWidth(), menu().outerWidth(), 'button and menu widths are equivalent'); // close el.multiselect("close"); @@ -46,33 +44,31 @@ .appendTo(body) .multiselect() .bind("multiselectbeforeopen", function(){ - ok( true, "event: binding multiselectbeforeopen to return false (prevent from opening)" ); + assert.ok( true, "event: binding multiselectbeforeopen to return false (prevent from opening)" ); return false; }) .multiselect("open"); - ok( !el.multiselect("isOpen"), "multiselect is not open after multiselect('open')" ); + assert.ok( !el.multiselect("isOpen"), "multiselect is not open after multiselect('open')" ); el.multiselect("destroy").remove(); }); - test("multiselectclose", function(){ - expect(25); - + QUnit.test("multiselectclose", function(assert){ // inject widget el = $("").appendTo(body); el.multiselect({ close: function(e,ui){ - ok( true, 'option: multiselect("close") fires close callback' ); - equals(this, el[0], "option: context of callback"); - equals(e.type, 'multiselectclose', 'option: event type in callback'); - equals(menu().css("display"), 'none', 'menu display css property equals none'); - same(ui, {}, 'option: ui hash'); + assert.ok( true, 'option: multiselect("close") fires close callback' ); + assert.equal(this, el[0], "option: context of callback"); + assert.equal(e.type, 'multiselectclose', 'option: event type in callback'); + assert.equal(menu().css("display"), 'none', 'menu display css property assert.equal none'); + assert.propEqual(ui, {}, 'option: ui hash'); } }) .bind("multiselectclose", function(e,ui){ - ok(true, 'multiselect("close") fires multiselectclose event'); - equals(this, el[0], 'event: context of event'); - same(ui, {}, 'event: ui hash'); + assert.ok(true, 'multiselect("close") fires multiselectclose event'); + assert.equal(this, el[0], 'event: context of event'); + assert.propEqual(ui, {}, 'event: ui hash'); }) .multiselect("open") .multiselect("close") @@ -86,28 +82,26 @@ button().find("span:first").click(); // make sure that the menu is actually closed. see issue #68 - ok( el.multiselect('isOpen') === false, 'menu is indeed closed' ); + assert.ok( el.multiselect('isOpen') === false, 'menu is indeed closed' ); el.multiselect("destroy").remove(); }); - test("multiselectbeforeclose", function(){ - expect(8); - + QUnit.test("multiselectbeforeclose", function(assert){ // inject widget el = $("").appendTo(body); el.multiselect({ beforeclose: function(e,ui){ - ok( true, 'option: multiselect("beforeclose") fires close callback' ); - equals(this, el[0], "option: context of callback"); - equals(e.type, 'multiselectbeforeclose', 'option: event type in callback'); - same(ui, {}, 'option: ui hash'); + assert.ok( true, 'option: multiselect("beforeclose") fires close callback' ); + assert.equal(this, el[0], "option: context of callback"); + assert.equal(e.type, 'multiselectbeforeclose', 'option: event type in callback'); + assert.propEqual(ui, {}, 'option: ui hash'); } }) .bind("multiselectbeforeclose", function(e,ui){ - ok(true, 'multiselect("beforeclose") fires multiselectclose event'); - equals(this, el[0], 'event: context of event'); - same(ui, {}, 'event: ui hash'); + assert.ok(true, 'multiselect("beforeclose") fires multiselectclose event'); + assert.equal(this, el[0], 'event: context of event'); + assert.propEqual(ui, {}, 'event: ui hash'); }) .multiselect("open") .multiselect("close"); @@ -123,29 +117,26 @@ }); el.multiselect('open').multiselect('close'); - ok( menu().is(':visible') && el.multiselect("isOpen"), "returning false inside callback prevents menu from closing" ); + assert.ok( menu().is(':visible') && el.multiselect("isOpen"), "returning false inside callback prevents menu from closing" ); el.multiselect("destroy").remove(); }); - test("multiselectclick with multiple widgets", function() { - expect(3); + QUnit.test("multiselectclick with multiple widgets", function(assert) { var first = $("").appendTo(body).multiselect(); var second = $("").appendTo(body).multiselect(); - equals($('.ui-multiselect').length, 2, "two mutliselects are on the page"); + assert.equal($('.ui-multiselect').length, 2, "two mutliselects are on the page"); first.multiselect("refresh"); second.multiselect("refresh"); $label = $(second.multiselect("getLabels")[0]); $wrongInput = $(first.multiselect("getLabels")[0]).find("input"); $label.click(); - equals($label.find("input").prop("checked"), true, "the input for that label should be checked"); - equals($wrongInput.prop("checked"), false, "the input for the corresponding label on the first widget should not be checked"); + assert.equal($label.find("input").prop("checked"), true, "the input for that label should be checked"); + assert.equal($wrongInput.prop("checked"), false, "the input for the corresponding label on the first widget should not be checked"); first.multiselect("destroy").remove(); second.multiselect("destroy").remove(); }); - test("multiselectclick", function(){ - expect(28); - + QUnit.test("multiselectclick", function(assert){ var times = 0; // inject widget @@ -154,39 +145,39 @@ el.multiselect({ click: function(e,ui){ - ok(true, 'option: triggering the click event on the second checkbox fires the click callback' ); - equals(this, el[0], "option: context of callback"); - equals(e.type, 'multiselectclick', 'option: event type in callback'); - equals(ui.value, "2", "option: ui.value equals"); - equals(ui.text, "Option 2", "option: ui.text equals"); + assert.ok(true, 'option: triggering the click event on the second checkbox fires the click callback' ); + assert.equal(this, el[0], "option: context of callback"); + assert.equal(e.type, 'multiselectclick', 'option: event type in callback'); + assert.equal(ui.value, "2", "option: ui.value assert.equal"); + assert.equal(ui.text, "Option 2", "option: ui.text assert.equal"); if(times === 0) { - equals(ui.checked, true, "option: ui.checked equals"); + assert.equal(ui.checked, true, "option: ui.checked assert.equal"); } else if(times === 1) { - equals(ui.checked, false, "option: ui.checked equals"); + assert.equal(ui.checked, false, "option: ui.checked assert.equal"); } } }) .bind("multiselectclick", function(e,ui){ - ok(true, 'event: triggering the click event on the second checkbox triggers multiselectclick'); - equals(this, el[0], 'event: context of event'); - equals(ui.value, "2", "event: ui.value equals"); - equals(ui.text, "Option 2", "event: ui.text equals"); + assert.ok(true, 'event: triggering the click event on the second checkbox triggers multiselectclick'); + assert.equal(this, el[0], 'event: context of event'); + assert.equal(ui.value, "2", "event: ui.value assert.equal"); + assert.equal(ui.text, "Option 2", "event: ui.text assert.equal"); if(times === 0) { - equals(ui.checked, true, "option: ui.checked equals"); + assert.equal(ui.checked, true, "option: ui.checked assert.equal"); } else if(times === 1) { - equals(ui.checked, false, "option: ui.checked equals"); + assert.equal(ui.checked, false, "option: ui.checked assert.equal"); } }) .bind("change", function(e){ if(++times === 1){ - equals(el.val().join(), "2", "event: select element val() within the change event is correct" ); + assert.equal(el.val().join(), "2", "event: select element val() within the change event is correct" ); } else { - equals(el.val(), null, "event: select element val() within the change event is correct" ); + assert.equal(el.val(), null, "event: select element val() within the change event is correct" ); } - ok(true, "event: the select's change event fires"); + assert.ok(true, "event: the select's change event fires"); }) .multiselect("open"); @@ -198,100 +189,94 @@ lastInput[0].click(); // make sure it has focus - equals(true, lastInput.is(":focus"), "The input has focus"); + assert.equal(true, lastInput.is(":focus"), "The input has focus"); // make sure menu isn't closed automatically - equals( true, el.multiselect('isOpen'), 'menu stays open' ); + assert.equal( true, el.multiselect('isOpen'), 'menu stays open' ); el.multiselect("destroy").remove(); }); - test("multiselectcheckall", function(){ - expect(10); - + QUnit.test("multiselectcheckall", function(assert){ // inject widget el = $('').appendTo(body); el.multiselect({ checkAll: function(e,ui){ - ok( true, 'option: multiselect("checkAll") fires checkall callback' ); - equals(this, el[0], "option: context of callback"); - equals(e.type, 'multiselectcheckall', 'option: event type in callback'); - same(ui, {}, 'option: ui hash in callback'); + assert.ok( true, 'option: multiselect("checkAll") fires checkall callback' ); + assert.equal(this, el[0], "option: context of callback"); + assert.equal(e.type, 'multiselectcheckall', 'option: event type in callback'); + assert.propEqual(ui, {}, 'option: ui hash in callback'); } }) .bind("multiselectcheckall", function(e,ui){ - ok( true, 'event: multiselect("checkall") fires multiselectcheckall event' ); - equals(this, el[0], 'event: context of event'); - same(ui, {}, 'event: ui hash'); + assert.ok( true, 'event: multiselect("checkall") fires multiselectcheckall event' ); + assert.equal(this, el[0], 'event: context of event'); + assert.propEqual(ui, {}, 'event: ui hash'); }) .bind("change", function(){ - ok(true, "event: the select's change event fires"); - equals( el.val().join(), "1,2", "event: select element val() within the change event is correct" ); + assert.ok(true, "event: the select's change event fires"); + assert.equal( el.val().join(), "1,2", "event: select element val() within the change event is correct" ); }) .multiselect("open") .multiselect("checkAll"); - equals(menu().find("input").first().is(":focus"), true, "The first input has focus"); + assert.equal(menu().find("input").first().is(":focus"), true, "The first input has focus"); el.multiselect("destroy").remove(); }); - test("multiselectuncheckall", function(){ - expect(10); - + QUnit.test("multiselectuncheckall", function(assert){ // inject widget el = $('').appendTo(body); el.multiselect({ uncheckAll: function(e,ui){ - ok( true, 'option: multiselect("uncheckAll") fires uncheckall callback' ); - equals(this, el[0], "option: context of callback"); - equals(e.type, 'multiselectuncheckall', 'option: event type in callback'); - same(ui, {}, 'option: ui hash in callback'); + assert.ok( true, 'option: multiselect("uncheckAll") fires uncheckall callback' ); + assert.equal(this, el[0], "option: context of callback"); + assert.equal(e.type, 'multiselectuncheckall', 'option: event type in callback'); + assert.propEqual(ui, {}, 'option: ui hash in callback'); } }) .bind("multiselectuncheckall", function(e,ui){ - ok( true, 'event: multiselect("uncheckall") fires multiselectuncheckall event' ); - equals(this, el[0], 'event: context of event'); - same(ui, {}, 'event: ui hash'); + assert.ok( true, 'event: multiselect("uncheckall") fires multiselectuncheckall event' ); + assert.equal(this, el[0], 'event: context of event'); + assert.propEqual(ui, {}, 'event: ui hash'); }) .bind("change", function(){ - ok(true, "event: the select's change event fires"); - equals( el.val(), null, "event: select element val() within the change event is correct" ); + assert.ok(true, "event: the select's change event fires"); + assert.equal( el.val(), null, "event: select element val() within the change event is correct" ); }) .multiselect("open") .multiselect("uncheckAll"); - equals(menu().find("input").first().is(":focus"), true, "The first input has focus"); + assert.equal(menu().find("input").first().is(":focus"), true, "The first input has focus"); el.multiselect("destroy").remove(); }); - test("multiselectbeforeoptgrouptoggle", function(){ - expect(10); - + QUnit.test("multiselectbeforeoptgrouptoggle", function(assert){ // inject widget el = $('') .appendTo(body); el.bind("change", function(){ - ok(true, "the select's change event fires"); + assert.ok(true, "the select's change event fires"); }) .multiselect({ beforeoptgrouptoggle: function(e,ui){ - equals(this, el[0], "option: context of callback"); - equals(e.type, 'multiselectbeforeoptgrouptoggle', 'option: event type in callback'); - equals(ui.label, "Set One", 'option: ui.label equals'); - equals(ui.inputs.length, 2, 'option: number of inputs in the ui.inputs key'); + assert.equal(this, el[0], "option: context of callback"); + assert.equal(e.type, 'multiselectbeforeoptgrouptoggle', 'option: event type in callback'); + assert.equal(ui.label, "Set One", 'option: ui.label assert.equal'); + assert.equal(ui.inputs.length, 2, 'option: number of inputs in the ui.inputs key'); } }) .bind("multiselectbeforeoptgrouptoggle", function(e,ui){ - ok( true, 'option: multiselect("uncheckall") fires multiselectuncheckall event' ); - equals(this, el[0], 'event: context of event'); - equals(ui.label, "Set One", 'event: ui.label equals'); - equals(ui.inputs.length, 2, 'event: number of inputs in the ui.inputs key'); + assert.ok( true, 'option: multiselect("uncheckall") fires multiselectuncheckall event' ); + assert.equal(this, el[0], 'event: context of event'); + assert.equal(ui.label, "Set One", 'event: ui.label assert.equal'); + assert.equal(ui.inputs.length, 2, 'event: number of inputs in the ui.inputs key'); }) .multiselect("open"); @@ -302,51 +287,49 @@ // test return false preventing checkboxes from activating el.bind("change", function(){ - ok( true ); // should not fire + assert.ok( true ); // should not fire }).multiselect({ beforeoptgrouptoggle: function(){ return false; }, // if this fires the expected count will be off. just a redundant way of checking that return false worked optgrouptoggle: function(){ - ok( true ); + assert.ok( true ); } }).appendTo( body ); var label = menu().find("li.ui-multiselect-optgroup-label a").click(); - equals( menu().find(":input:checked").length, 0, "when returning false inside the optgrouptoggle handler, no checkboxes are checked" ); + assert.equal( menu().find(":input:checked").length, 0, "when returning false inside the optgrouptoggle handler, no checkboxes are checked" ); el.multiselect("destroy").remove(); }); - test("multiselectoptgrouptoggle", function(){ - expect(12); - + QUnit.test("multiselectoptgrouptoggle", function(assert){ // inject widget el = $('').appendTo(body); el.multiselect({ optgrouptoggle: function(e,ui){ - equals(this, el[0], "option: context of callback"); - equals(e.type, 'multiselectoptgrouptoggle', 'option: event type in callback'); - equals(ui.label, "Set One", 'option: ui.label equals'); - equals(ui.inputs.length, 2, 'option: number of inputs in the ui.inputs key'); - equals(ui.checked, true, 'option: ui.checked equals true'); + assert.equal(this, el[0], "option: context of callback"); + assert.equal(e.type, 'multiselectoptgrouptoggle', 'option: event type in callback'); + assert.equal(ui.label, "Set One", 'option: ui.label assert.equal'); + assert.equal(ui.inputs.length, 2, 'option: number of inputs in the ui.inputs key'); + assert.equal(ui.checked, true, 'option: ui.checked assert.equal true'); } }) .bind("multiselectoptgrouptoggle", function(e,ui){ - ok( true, 'option: multiselect("uncheckall") fires multiselectuncheckall event' ); - equals(this, el[0], 'event: context of event'); - equals(ui.label, "Set One", 'event: ui.label equals'); - equals(ui.inputs.length, 2, 'event: number of inputs in the ui.inputs key'); - equals(ui.checked, true, 'event: ui.checked equals true'); + assert.ok( true, 'option: multiselect("uncheckall") fires multiselectuncheckall event' ); + assert.equal(this, el[0], 'event: context of event'); + assert.equal(ui.label, "Set One", 'event: ui.label assert.equal'); + assert.equal(ui.inputs.length, 2, 'event: number of inputs in the ui.inputs key'); + assert.equal(ui.checked, true, 'event: ui.checked assert.equal true'); }) .multiselect("open"); // trigger native click event on optgroup menu().find(".ui-multiselect-optgroup a").click(); - equals(menu().find(":input:checked").length, 2, "both checkboxes are actually checked" ); + assert.equal(menu().find(":input:checked").length, 2, "both checkboxes are actually checked" ); - equals(menu().find("input").first().is(":focus"), true, "The first input has focus"); + assert.equal(menu().find("input").first().is(":focus"), true, "The first input has focus"); el.multiselect("destroy").remove(); }); diff --git a/tests/unit/filter.js b/tests/unit/filter.js index 95a9a59..1615bfe 100644 --- a/tests/unit/filter.js +++ b/tests/unit/filter.js @@ -25,11 +25,11 @@ message || (message = "searching for '#'"); message = message.replace("#", term); searchFor(term); - equals( getVisible().length, expected, message ); + QUnit.assert.equal( getVisible().length, expected, message ); } - module("filter widget - multiple select", { - setup: function() { + QUnit.module("filter widget - multiple select", { + beforeEach: function() { el = $(' @@ -36,7 +34,8 @@

- + + diff --git a/tests/unit/methods.js b/tests/unit/methods.js index 675cbaa..8d63d24 100644 --- a/tests/unit/methods.js +++ b/tests/unit/methods.js @@ -1,47 +1,37 @@ (function($){ - module("methods"); - - test("open", function(){ - expect(2); + QUnit.module("methods"); + QUnit.test("open", function(assert){ el = $("select").multiselect().multiselect("open"); - ok( el.multiselect("isOpen"), "isOpen parameter true" ); - equals( menu().css("display"), "block", "Test display CSS property" ); + assert.ok( el.multiselect("isOpen"), "isOpen parameter true" ); + assert.equal( menu().css("display"), "block", "Test display CSS property" ); el.multiselect("destroy"); }); - test("close", function(){ - expect(2); - + QUnit.test("close", function(assert){ el = $("select").multiselect().multiselect("open").multiselect("close"); - ok( !el.multiselect("isOpen"), "isOpen parameter false" ); - equals( menu().css("display"), "none", "Test display CSS property" ); + assert.ok( !el.multiselect("isOpen"), "isOpen parameter false" ); + assert.equal( menu().css("display"), "none", "Test display CSS property" ); el.multiselect("destroy"); }); - test("enable", function(){ - expect(2); - + QUnit.test("enable", function(assert){ el = $("select").multiselect().multiselect("disable").multiselect("enable"); - ok( button().is(":disabled") === false, "Button is enabled" ); - ok( el.is(":disabled") === false, "Original select is enabled" ); + assert.ok( button().is(":disabled") === false, "Button is enabled" ); + assert.ok( el.is(":disabled") === false, "Original select is enabled" ); el.multiselect("destroy"); }); - test("disable", function(){ - expect(2); - + QUnit.test("disable", function(assert){ // clone this one so the original is not affected el = $("select").clone(true).appendTo(body).multiselect().multiselect("disable"); - ok( button().is(":disabled"), 'Button is disabled'); - ok( el.is(":disabled"), 'Original select is disabled'); + assert.ok( button().is(":disabled"), 'Button is disabled'); + assert.ok( el.is(":disabled"), 'Original select is disabled'); el.multiselect("destroy").remove(); }); - test("enabling w/ pre-disabled tags (#216)", function(){ - expect(5); - + QUnit.test("enabling w/ pre-disabled tags (#216)", function(assert){ el = $(''; el = $(html).appendTo("body").multiselect({ - selectedList: 3, - selectedListSeparator: ', ' + selectedList: 3, + selectedListSeparator: ', ' }); el.multiselect("checkAll"); - equals( button().text(), 'foo "with quotes", bar, baz', 'after checkAll, button text is a list of all options in the select'); + assert.equal(button().text(), 'foo "with quotes", bar, baz', 'after checkAll, button text is a list of all options in the select'); el.multiselect("destroy").remove(); el = $(html).appendTo("body").multiselect({ - selectedList: 2, - selectedListSeparator: ', ' + selectedList: 2, + selectedListSeparator: ', ' }); el.multiselect("checkAll"); - equals( button().text(), '3 of 3 selected', 'after checkAll with a limited selectedList value, button value displays number of checked'); + assert.equal(button().text(), '3 of 3 selected', 'after checkAll with a limited selectedList value, button value displays number of checked'); el.multiselect("destroy").remove(); - }); - - test("selectedMax", function(){ - expect(1); + }); + QUnit.test("selectedMax", function (assert) { var html = ''; el = $(html).appendTo("body").multiselect({ - selectedMax: 2 + selectedMax: 2 }); - + checkboxes = el.multiselect("widget").find(":checkbox"); checkboxes.eq(0).trigger('click'); checkboxes.eq(1).trigger('click'); checkboxes.eq(2).trigger('click'); - - equals( menu().find("input").filter(":checked").length, 2 , 'after clicking each checkbox, count of checked restored to selectedMax of 2'); + + assert.equal(menu().find("input").filter(":checked").length, 2, 'after clicking each checkbox, count of checked restored to selectedMax of 2'); el.multiselect("destroy").remove(); - - }); - - function asyncSelectedList( useTrigger, message ){ - expect(1); - stop(); + }); + function asyncSelectedList(useTrigger, message, assert) { var html = '', - checkboxes; + checkboxes; el = $(html).appendTo(body).multiselect({ - selectedList: 2, - selectedListSeparator: ', ' + selectedList: 2, + selectedListSeparator: ', ' }); checkboxes = el.multiselect("widget").find(":checkbox"); + var done = assert.async(); - if( useTrigger ){ - checkboxes.eq(0).trigger('click'); - checkboxes.eq(1).trigger('click'); + if (useTrigger) { + checkboxes.eq(0).trigger('click'); + checkboxes.eq(1).trigger('click'); } else { - checkboxes.eq(0)[0].click(); - checkboxes.eq(1)[0].click(); + checkboxes.eq(0)[0].click(); + checkboxes.eq(1)[0].click(); } - setTimeout(function(){ - equals( button().text(), 'foo, bar', message); - el.multiselect("destroy").remove(); - start(); + setTimeout(function () { + assert.equal(button().text(), 'foo, bar', message); + el.multiselect("destroy").remove(); + done(); }, 10); - } - - test("selectedList - manual trigger - jQuery", function(){ - asyncSelectedList( true, 'manually checking items with trigger()' ); - }); + } - test("selectedList - manual trigger - native", function(){ - asyncSelectedList( false, 'manually checking items with element.click()' ); - }); + QUnit.test("selectedList - manual trigger - jQuery", function (assert) { + asyncSelectedList(true, 'manually checking items with trigger()', assert); + }); - test("selectedList - encoding", function() { - expect(1); + QUnit.test("selectedList - manual trigger - native", function (assert) { + asyncSelectedList(false, 'manually checking items with element.click()', assert); + }); + QUnit.test("selectedList - encoding", function (assert) { el = $('') - .appendTo("body") - .multiselect({ selectedList: 1 }); + .appendTo("body") + .multiselect({ selectedList: 1 }); - equals(button().text(), 'A&E'); + assert.equal(button().text(), 'A&E'); el.multiselect("destroy").remove(); - }); - - test("height", function(){ - expect(2); + }); + QUnit.test("height", function (assert) { var height = 100; el = $("select").multiselect({ height: height }).multiselect("open"); - equals( height, menu().find("ul.ui-multiselect-checkboxes").height(), 'height after opening property set to '+ height ); + assert.equal(height, menu().find("ul.ui-multiselect-checkboxes").height(), 'height after opening property set to ' + height); // change height and re-test height = 300; el.multiselect("option", "height", height); - equals( height, menu().find(".ui-multiselect-checkboxes").height(), 'changing value through api to '+ height ); + assert.equal(height, menu().find(".ui-multiselect-checkboxes").height(), 'changing value through api to ' + height); el.multiselect("destroy"); - }); - - test("minWidth", function(){ - expect(5); + }); + QUnit.test("minWidth", function (assert) { var minWidth = 321; - el = $("select").multiselect({ minWidth:minWidth }).multiselect("open"); - equals( minWidth, button().outerWidth(), 'outerWidth of button is ' + minWidth ); + el = $("select").multiselect({ minWidth: minWidth }).multiselect("open"); + assert.equal(minWidth, button().outerWidth(), 'outerWidth of button is ' + minWidth); // change height and re-test minWidth = 351; el.multiselect("option", "minWidth", minWidth); - equals( minWidth, button().outerWidth(), 'changing value through api to '+minWidth); + assert.equal(minWidth, button().outerWidth(), 'changing value through api to ' + minWidth); // change height to something that should fail. minWidth = 10; el.multiselect("option", "minWidth", minWidth); var outerWidth = button().outerWidth(); - ok( minWidth !== outerWidth, 'changing value through api to '+minWidth+' (too small), outerWidth is actually ' + outerWidth); + assert.ok(minWidth !== outerWidth, 'changing value through api to ' + minWidth + ' (too small), outerWidth is actually ' + outerWidth); // Reference: https://www.wired.com/2010/12/why-percentage-based-designs-dont-work-in-every-browser/ minWidth = "50%"; el.multiselect("option", "minWidth", minWidth); var outerWidthX2 = Math.floor(button().outerWidth() * 2); // Double to reduce chance of fractions var parentWidth = Math.floor(el.parent().outerWidth()); - ok(Math.abs(outerWidthX2 - parentWidth) <= 1, 'changing value to 50%'); // Off by 1 is ok due to floating point rounding discrepancies between browsers. + assert.ok(Math.abs(outerWidthX2 - parentWidth) <= 1, 'changing value to 50%'); // Off by 1 is assert.ok due to floating point rounding discrepancies between browsers. minWidth = "351px"; el.multiselect("option", "minWidth", minWidth); - equals( 351, button().outerWidth(), 'minWidth supports strings suffixed with px as well as integer px values'); + assert.equal(351, button().outerWidth(), 'minWidth supports strings suffixed with px as well as integer px values'); el.multiselect("destroy"); - }); + }); - test("menuWidth", function(){ - expect(2); - var width = 50; + QUnit.test("menuWidth", function (assert) { + var width = 50; - el = $("select").multiselect({ minWidth: 100, menuWidth:width }).multiselect("open"); + el = $("select").multiselect({ minWidth: 100, menuWidth: width }).multiselect("open"); - equals( menu().parent().find(".ui-multiselect-menu").outerWidth(), width, 'width after opening, property set to '+width ); + assert.equal(menu().parent().find(".ui-multiselect-menu").outerWidth(), width, 'width after opening, property set to ' + width); - // change height and re-test - width = 300; - el.multiselect("option", "menuWidth", width).multiselect('refresh'); - equals( menu().parent().find(".ui-multiselect-menu").outerWidth(), width, 'changing value through api to '+width ); + // change height and re-test + width = 300; + el.multiselect("option", "menuWidth", width).multiselect('refresh'); + assert.equal(menu().parent().find(".ui-multiselect-menu").outerWidth(), width, 'changing value through api to ' + width); - el.multiselect("destroy"); + el.multiselect("destroy"); }); - test("checkAllText", function(){ - expect(2); + QUnit.test("checkAllText", function (assert) { var text = "foo"; - el = $("select").multiselect({ checkAllText:text, showCheckAll:true }); - equals( text, menu().find(".ui-multiselect-all").text(), 'check all link reads '+text ); + el = $("select").multiselect({ checkAllText: text, showCheckAll: true }); + assert.equal(text, menu().find(".ui-multiselect-all").text(), 'check all link reads ' + text); // set through option text = "bar"; - el.multiselect("option","checkAllText","bar"); - equals( text, menu().find(".ui-multiselect-all").text(), 'check all link reads '+text ); + el.multiselect("option", "checkAllText", "bar"); + assert.equal(text, menu().find(".ui-multiselect-all").text(), 'check all link reads ' + text); el.multiselect("destroy"); - }); + }); - test("uncheckAllText", function(){ - expect(2); + QUnit.test("uncheckAllText", function (assert) { var text = "foo"; - el = $("select").multiselect({ uncheckAllText:text, showUncheckAll:true }); - equals( text, menu().find(".ui-multiselect-none").text(), 'check all link reads '+text ); + el = $("select").multiselect({ uncheckAllText: text, showUncheckAll: true }); + assert.equal(text, menu().find(".ui-multiselect-none").text(), 'check all link reads ' + text); // set through option text = "bar"; - el.multiselect("option","uncheckAllText","bar"); - equals( text, menu().find(".ui-multiselect-none").text(), 'changing value through api to '+text ); + el.multiselect("option", "uncheckAllText", "bar"); + assert.equal(text, menu().find(".ui-multiselect-none").text(), 'changing value through api to ' + text); el.multiselect("destroy"); - }); + }); - test("flipAllText", function(){ - expect(2); + QUnit.test("flipAllText", function (assert) { var text = "foo"; - el = $("select").multiselect({ flipAllText:text, showFlipAll:true }); - equals( text, menu().find(".ui-multiselect-flip").text(), 'flip all link reads '+text ); + el = $("select").multiselect({ flipAllText: text, showFlipAll: true }); + assert.equal(text, menu().find(".ui-multiselect-flip").text(), 'flip all link reads ' + text); // set through option text = "bar"; - el.multiselect("option","flipAllText","bar"); - equals( text, menu().find(".ui-multiselect-flip").text(), 'changing value through api to '+text ); + el.multiselect("option", "flipAllText", "bar"); + assert.equal(text, menu().find(".ui-multiselect-flip").text(), 'changing value through api to ' + text); el.multiselect("destroy"); - }); - - test("autoOpen", function(){ - expect(2); + }); - el = $("select").multiselect({ autoOpen:false }); + QUnit.test("autoOpen", function (assert) { + el = $("select").multiselect({ autoOpen: false }); - ok( menu().is(":hidden"), 'menu is hidden with autoOpen off'); + assert.ok(menu().is(":hidden"), 'menu is hidden with autoOpen off'); el.multiselect("destroy"); - el = $("select").multiselect({ autoOpen:true }); - ok( menu().is(":visible"), 'menu is visible with autoOpen on'); + el = $("select").multiselect({ autoOpen: true }); + assert.ok(menu().is(":visible"), 'menu is visible with autoOpen on'); el.multiselect("destroy"); + }); - // no built in support for change on the fly; not testing it. - }); - - test("multiple (false - single select)", function(){ - expect(10); - + QUnit.test("multiple (false - single select)", function (assert) { $("select").removeAttr("multiple"); - el = $("select").multiselect({ multiple:false }); + el = $("select").multiselect({ multiple: false }); // get some references var $menu = menu(), $header = header(); - ok( $header.find('a.ui-multiselect-all').is(':hidden'), 'select all link is hidden' ); - ok( $header.find('a.ui-multiselect-none').is(':hidden'), 'select none link is hidden' ); - ok( $header.find('a.ui-multiselect-close').css('display') !== 'hidden', 'close link is visible' ); - ok( !$menu.find(":checkbox").length, 'no checkboxes are present'); - ok( $menu.find(":radio").length > 0, 'but radio boxes are'); + assert.ok($header.find('a.ui-multiselect-all').is(':hidden'), 'select all link is hidden'); + assert.ok($header.find('a.ui-multiselect-none').is(':hidden'), 'select none link is hidden'); + assert.ok($header.find('a.ui-multiselect-close').css('display') !== 'hidden', 'close link is visible'); + assert.ok(!$menu.find(":checkbox").length, 'no checkboxes are present'); + assert.ok($menu.find(":radio").length > 0, 'but radio boxes are'); // simulate click on ALL radios var radios = $menu.find(":radio").trigger("click"); // at the end of that, only one radio should be checked and the menu closed - equals( radios.filter(":checked").length, 1, 'After checking all radios, only one is actually checked'); - equals( false, el.multiselect('isOpen'), 'Menu is closed' ); + assert.equal(radios.filter(":checked").length, 1, 'After checking all radios, only one is actually checked'); + assert.equal(false, el.multiselect('isOpen'), 'Menu is closed'); // uncheck boxes... should only be one radios.filter(":checked").trigger("click"); // method calls el.multiselect("checkAll"); - equals( $menu.find("input:radio:checked").length, 1, 'After checkAll method call only one is actually checked'); + assert.equal($menu.find("input:radio:checked").length, 1, 'After checkAll method call only one is actually checked'); el.multiselect("uncheckAll"); - equals( $menu.find("input:radio:checked").length, 0, 'After uncheckAll method nothing is checked'); + assert.equal($menu.find("input:radio:checked").length, 0, 'After uncheckAll method nothing is checked'); // check/uncheck all links - equals( $menu.find(".ui-multiselect-all, ui-multiselect-none").filter(":visible").length, 0, "Check/uncheck all links don't exist"); + assert.equal($menu.find(".ui-multiselect-all, ui-multiselect-none").filter(":visible").length, 0, "Check/uncheck all links don't exist"); el.multiselect("destroy"); - $("select").attr("multiple","multiple"); - }); - - test("multiple (changing dynamically)", function(){ - expect(6); + $("select").attr("multiple", "multiple"); + }); + QUnit.test("multiple (changing dynamically)", function (assert) { el = $('') - .appendTo("body") - .multiselect(); + .appendTo("body") + .multiselect(); el.multiselect("option", "multiple", false); - equals(el[0].multiple, false, "When changing a multiple select to a single select, the select element no longer has the multiple property"); - equals(menu().hasClass("ui-multiselect-single"), true, "...and the menu now has the single select class"); - equals(menu().find('input[type="radio"]').length, 1, "...and the checkbox is now a radio button"); + assert.equal(el[0].multiple, false, "When changing a multiple select to a single select, the select element no longer has the multiple property"); + assert.equal(menu().hasClass("ui-multiselect-single"), true, "...and the menu now has the single select class"); + assert.equal(menu().find('input[type="radio"]').length, 1, "...and the checkbox is now a radio button"); el.multiselect("option", "multiple", true); - equals(el[0].multiple, true, "When changing a single select to a multiple select, the select element has the multiple property"); - equals(menu().hasClass("ui-multiselect-single"), false, "...and the menu doesn't have the single select class"); - equals(menu().find('input[type="checkbox"]').length, 1, "...and the radio button is now a checkbox"); + assert.equal(el[0].multiple, true, "When changing a single select to a multiple select, the select element has the multiple property"); + assert.equal(menu().hasClass("ui-multiselect-single"), false, "...and the menu doesn't have the single select class"); + assert.equal(menu().find('input[type="checkbox"]').length, 1, "...and the radio button is now a checkbox"); el.multiselect("destroy").remove(); - }); - - test("classes", function(){ - expect(6); + }); + QUnit.test("classes", function (assert) { var classname = 'foo'; - el = $("select").multiselect({ classes:classname }); + el = $("select").multiselect({ classes: classname }); var $button = button(), $widget = menu(); - equals( $widget.hasClass(classname), true, 'menu has the class ' + classname); - equals( $button.hasClass(classname), true, 'button has the class ' + classname); + assert.equal($widget.hasClass(classname), true, 'menu has the class ' + classname); + assert.equal($button.hasClass(classname), true, 'button has the class ' + classname); // change it up var newclass = 'bar'; el.multiselect("option", "classes", newclass); - equals( $widget.hasClass(newclass), true, 'menu has the new class ' + newclass); - equals( $button.hasClass(newclass), true, 'button has the new class ' + newclass); - equals( $button.hasClass(classname), false, 'menu no longer has the class ' + classname); - equals( $button.hasClass(classname), false, 'button no longer has the class ' + classname); + assert.equal($widget.hasClass(newclass), true, 'menu has the new class ' + newclass); + assert.equal($button.hasClass(newclass), true, 'button has the new class ' + newclass); + assert.equal($button.hasClass(classname), false, 'menu no longer has the class ' + classname); + assert.equal($button.hasClass(classname), false, 'button no longer has the class ' + classname); el.multiselect("destroy"); - }); - - test("header", function(){ - expect(7); + }); - function countLinks(){ - return header().find("a").length; + QUnit.test("header", function (assert) { + function countLinks() { + return header().find("a").length; } // default - el = $("select").multiselect({ autoOpen:true }); - ok(header().is(':visible'), "default config: header is visible" ); + el = $("select").multiselect({ autoOpen: true }); + assert.ok(header().is(':visible'), "default config: header is visible"); el.multiselect("option", "header", false); - ok(header().is(':hidden'), "after changing header option on default config: header is no longer visible" ); + assert.ok(header().is(':hidden'), "after changing header option on default config: header is no longer visible"); // test for all links within the default header - equals(countLinks(), 3, "number of links in the default header config"); + assert.equal(countLinks(), 3, "number of links in the default header config"); el.multiselect("destroy"); // create again, this time header false - el = $("select").multiselect({ header:false, autoOpen:true }); - ok(header().is(':hidden'), "init with header false: header is not visible" ); + el = $("select").multiselect({ header: false, autoOpen: true }); + assert.ok(header().is(':hidden'), "init with header false: header is not visible"); el.multiselect("option", "header", true); - ok(header().is(':visible'), "after changing header option: header is visible" ); + assert.ok(header().is(':visible'), "after changing header option: header is visible"); el.multiselect("destroy"); // create again, this time custom header - el = $("select").multiselect({ header:"hai guyz", autoOpen:true }); - equals(header().text(), "hai guyz", "header equals custom text"); - equals(countLinks(), 1, "number of links in the custom header config (should be close button)"); + el = $("select").multiselect({ header: "hai guyz", autoOpen: true }); + assert.equal(header().text(), "hai guyz", "header assert.equal custom text"); + assert.equal(countLinks(), 1, "number of links in the custom header config (should be close button)"); el.multiselect("destroy"); - }); - test("selectedListSeparator", function(){ - expect(3); - el = $("select").multiselect({ selectedListSeparator: "
", selectedList: 15 }); - el.multiselect("checkAll"); - var text = $(button()).text(); - var matched = []; - equals(text.indexOf(","), -1, "There are no commas in the button text"); - matched = text.match(/\/g); - equals(matched.length, 8, "The 9 selected values are joined by
tags"); - el.multiselect("option", "selectedListSeparator", ", "); - text = $(button()).text(); - matched = text.match(/\,/g); - equals(matched.length, 8, "The 9 selected values are joined by commas again after calling the option method"); - el.multiselect("destroy"); + }); + QUnit.test("selectedListSeparator", function (assert) { + el = $("select").multiselect({ selectedListSeparator: "
", selectedList: 15 }); + el.multiselect("checkAll"); + var text = $(button()).text(); + var matched = []; + assert.equal(text.indexOf(","), -1, "There are no commas in the button text"); + matched = text.match(/\/g); + assert.equal(matched.length, 8, "The 9 selected values are joined by
tags"); + el.multiselect("option", "selectedListSeparator", ", "); + text = $(button()).text(); + matched = text.match(/\,/g); + assert.equal(matched.length, 8, "The 9 selected values are joined by commas again after calling the option method"); + el.multiselect("destroy"); }); })(jQuery); diff --git a/tests/unit/qunit.css b/tests/unit/qunit.css deleted file mode 100644 index 953adb9..0000000 --- a/tests/unit/qunit.css +++ /dev/null @@ -1,196 +0,0 @@ -/** Font Family and Sizes */ - -#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { - font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; -} - -#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } -#qunit-tests { font-size: smaller; } - - -/** Resets */ - -#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult { - margin: 0; - padding: 0; -} - - -/** Header */ - -#qunit-header { - padding: 0.5em 0 0.5em 1em; - - color: #8699a4; - background-color: #0d3349; - - font-size: 1.5em; - line-height: 1em; - font-weight: normal; - - border-radius: 15px 15px 0 0; - -moz-border-radius: 15px 15px 0 0; - -webkit-border-top-right-radius: 15px; - -webkit-border-top-left-radius: 15px; -} - -#qunit-header a { - text-decoration: none; - color: #c2ccd1; -} - -#qunit-header a:hover, -#qunit-header a:focus { - color: #fff; -} - -#qunit-banner { - height: 5px; -} - -#qunit-testrunner-toolbar { - padding: 0em 0 0.5em 2em; -} - -#qunit-userAgent { - padding: 0.5em 0 0.5em 2.5em; - background-color: #2b81af; - color: #fff; - text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; -} - - -/** Tests: Pass/Fail */ - -#qunit-tests { - list-style-position: inside; -} - -#qunit-tests li { - padding: 0.4em 0.5em 0.4em 2.5em; - border-bottom: 1px solid #fff; - list-style-position: inside; -} - -#qunit-tests li strong { - cursor: pointer; -} - -#qunit-tests ol { - margin-top: 0.5em; - padding: 0.5em; - - background-color: #fff; - - border-radius: 15px; - -moz-border-radius: 15px; - -webkit-border-radius: 15px; - - box-shadow: inset 0px 2px 13px #999; - -moz-box-shadow: inset 0px 2px 13px #999; - -webkit-box-shadow: inset 0px 2px 13px #999; -} - -#qunit-tests table { - border-collapse: collapse; - margin-top: .2em; -} - -#qunit-tests th { - text-align: right; - vertical-align: top; - padding: 0 .5em 0 0; -} - -#qunit-tests td { - vertical-align: top; -} - -#qunit-tests pre { - margin: 0; - white-space: pre-wrap; - word-wrap: break-word; -} - -#qunit-tests del { - background-color: #e0f2be; - color: #374e0c; - text-decoration: none; -} - -#qunit-tests ins { - background-color: #ffcaca; - color: #500; - text-decoration: none; -} - -/*** Test Counts */ - -#qunit-tests b.counts { color: black; } -#qunit-tests b.passed { color: #5E740B; } -#qunit-tests b.failed { color: #710909; } - -#qunit-tests li li { - margin: 0.5em; - padding: 0.4em 0.5em 0.4em 0.5em; - background-color: #fff; - border-bottom: none; - list-style-position: inside; -} - -/*** Passing Styles */ - -#qunit-tests li li.pass { - color: #5E740B; - background-color: #fff; - border-left: 26px solid #C6E746; -} - -#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } -#qunit-tests .pass .test-name { color: #366097; } - -#qunit-tests .pass .test-actual, -#qunit-tests .pass .test-expected { color: #999999; } - -#qunit-banner.qunit-pass { background-color: #C6E746; } - -/*** Failing Styles */ - -#qunit-tests li li.fail { - color: #710909; - background-color: #fff; - border-left: 26px solid #EE5757; -} - -#qunit-tests .fail { color: #000000; background-color: #EE5757; } -#qunit-tests .fail .test-name, -#qunit-tests .fail .module-name { color: #000000; } - -#qunit-tests .fail .test-actual { color: #EE5757; } -#qunit-tests .fail .test-expected { color: green; } - -#qunit-banner.qunit-fail, -#qunit-testrunner-toolbar { background-color: #EE5757; } - - -/** Footer */ - -#qunit-testresult { - padding: 0.5em 0.5em 0.5em 2.5em; - - color: #2b81af; - background-color: #D2E0E6; - - border-radius: 0 0 15px 15px; - -moz-border-radius: 0 0 15px 15px; - -webkit-border-bottom-right-radius: 15px; - -webkit-border-bottom-left-radius: 15px; -} - -/** Fixture */ - -#qunit-fixture { - position: absolute; - top: -10000px; - left: -10000px; -} \ No newline at end of file diff --git a/tests/unit/qunit.js b/tests/unit/qunit.js deleted file mode 100644 index f52d305..0000000 --- a/tests/unit/qunit.js +++ /dev/null @@ -1,1341 +0,0 @@ -/* - * QUnit - A JavaScript Unit Testing Framework - * - * http://docs.jquery.com/QUnit - * - * Copyright (c) 2009 John Resig, Jörn Zaefferer - * Dual licensed under the MIT (MIT-LICENSE.txt) - * and GPL (GPL-LICENSE.txt) licenses. - */ - -(function(window) { - -var defined = { - setTimeout: typeof window.setTimeout !== "undefined", - sessionStorage: (function() { - try { - return !!sessionStorage.getItem; - } catch(e){ - return false; - } - })() -} - -var testId = 0; - -var Test = function(name, testName, expected, testEnvironmentArg, async, callback) { - this.name = name; - this.testName = testName; - this.expected = expected; - this.testEnvironmentArg = testEnvironmentArg; - this.async = async; - this.callback = callback; - this.assertions = []; -}; -Test.prototype = { - init: function() { - var tests = id("qunit-tests"); - if (tests) { - var b = document.createElement("strong"); - b.innerHTML = "Running " + this.name; - var li = document.createElement("li"); - li.appendChild( b ); - li.id = this.id = "test-output" + testId++; - tests.appendChild( li ); - } - }, - setup: function() { - if (this.module != config.previousModule) { - if ( this.previousModule ) { - QUnit.moduleDone( this.module, config.moduleStats.bad, config.moduleStats.all ); - } - config.previousModule = this.module; - config.moduleStats = { all: 0, bad: 0 }; - QUnit.moduleStart( this.module, this.moduleTestEnvironment ); - } - - config.current = this; - this.testEnvironment = extend({ - setup: function() {}, - teardown: function() {} - }, this.moduleTestEnvironment); - if (this.testEnvironmentArg) { - extend(this.testEnvironment, this.testEnvironmentArg); - } - - QUnit.testStart( this.testName, this.testEnvironment ); - - // allow utility functions to access the current test environment - // TODO why?? - QUnit.current_testEnvironment = this.testEnvironment; - - try { - if ( !config.pollution ) { - saveGlobal(); - } - - this.testEnvironment.setup.call(this.testEnvironment); - } catch(e) { - // TODO use testName instead of name for no-markup message? - QUnit.ok( false, "Setup failed on " + this.name + ": " + e.message ); - } - }, - run: function() { - if ( this.async ) { - QUnit.stop(); - } - - try { - this.callback.call(this.testEnvironment); - } catch(e) { - // TODO use testName instead of name for no-markup message? - fail("Test " + this.name + " died, exception and test follows", e, this.callback); - QUnit.ok( false, "Died on test #" + (this.assertions.length + 1) + ": " + e.message + " - " + QUnit.jsDump.parse(e) ); - // else next test will carry the responsibility - saveGlobal(); - - // Restart the tests if they're blocking - if ( config.blocking ) { - start(); - } - } - }, - teardown: function() { - try { - checkPollution(); - this.testEnvironment.teardown.call(this.testEnvironment); - } catch(e) { - // TODO use testName instead of name for no-markup message? - QUnit.ok( false, "Teardown failed on " + this.name + ": " + e.message ); - } - }, - finish: function() { - if ( this.expected && this.expected != this.assertions.length ) { - QUnit.ok( false, "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run" ); - } - - var good = 0, bad = 0, - tests = id("qunit-tests"); - - config.stats.all += this.assertions.length; - config.moduleStats.all += this.assertions.length; - - if ( tests ) { - var ol = document.createElement("ol"); - - for ( var i = 0; i < this.assertions.length; i++ ) { - var assertion = this.assertions[i]; - - var li = document.createElement("li"); - li.className = assertion.result ? "pass" : "fail"; - li.innerHTML = assertion.message || (assertion.result ? "okay" : "failed"); - ol.appendChild( li ); - - if ( assertion.result ) { - good++; - } else { - bad++; - config.stats.bad++; - config.moduleStats.bad++; - } - } - - // store result when possible - defined.sessionStorage && sessionStorage.setItem("qunit-" + this.testName, bad); - - if (bad == 0) { - ol.style.display = "none"; - } - - var b = document.createElement("strong"); - b.innerHTML = this.name + " (" + bad + ", " + good + ", " + this.assertions.length + ")"; - - addEvent(b, "click", function() { - var next = b.nextSibling, display = next.style.display; - next.style.display = display === "none" ? "block" : "none"; - }); - - addEvent(b, "dblclick", function(e) { - var target = e && e.target ? e.target : window.event.srcElement; - if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) { - target = target.parentNode; - } - if ( window.location && target.nodeName.toLowerCase() === "strong" ) { - window.location.search = "?" + encodeURIComponent(getText([target]).replace(/\(.+\)$/, "").replace(/(^\s*|\s*$)/g, "")); - } - }); - - var li = id(this.id); - li.className = bad ? "fail" : "pass"; - li.style.display = resultDisplayStyle(!bad); - li.removeChild( li.firstChild ); - li.appendChild( b ); - li.appendChild( ol ); - - if ( bad ) { - var toolbar = id("qunit-testrunner-toolbar"); - if ( toolbar ) { - toolbar.style.display = "block"; - id("qunit-filter-pass").disabled = null; - } - } - - } else { - for ( var i = 0; i < this.assertions.length; i++ ) { - if ( !this.assertions[i].result ) { - bad++; - config.stats.bad++; - config.moduleStats.bad++; - } - } - } - - try { - QUnit.reset(); - } catch(e) { - // TODO use testName instead of name for no-markup message? - fail("reset() failed, following Test " + this.name + ", exception and reset fn follows", e, QUnit.reset); - } - - QUnit.testDone( this.testName, bad, this.assertions.length ); - }, - - queue: function() { - var test = this; - synchronize(function() { - test.init(); - }); - function run() { - // each of these can by async - synchronize(function() { - test.setup(); - }); - synchronize(function() { - test.run(); - }); - synchronize(function() { - test.teardown(); - }); - synchronize(function() { - test.finish(); - }); - } - // defer when previous test run passed, if storage is available - var bad = defined.sessionStorage && +sessionStorage.getItem("qunit-" + this.testName); - if (bad) { - run(); - } else { - synchronize(run); - }; - } - -} - -var QUnit = { - - // call on start of module test to prepend name to all tests - module: function(name, testEnvironment) { - config.previousModule = config.currentModule; - config.currentModule = name; - config.currentModuleTestEnviroment = testEnvironment; - }, - - asyncTest: function(testName, expected, callback) { - if ( arguments.length === 2 ) { - callback = expected; - expected = 0; - } - - QUnit.test(testName, expected, callback, true); - }, - - test: function(testName, expected, callback, async) { - var name = '' + testName + '', testEnvironmentArg; - - if ( arguments.length === 2 ) { - callback = expected; - expected = null; - } - // is 2nd argument a testEnvironment? - if ( expected && typeof expected === 'object') { - testEnvironmentArg = expected; - expected = null; - } - - if ( config.currentModule ) { - name = '' + config.currentModule + ": " + name; - } - - if ( !validTest(config.currentModule + ": " + testName) ) { - return; - } - - var test = new Test(name, testName, expected, testEnvironmentArg, async, callback); - test.previousModule = config.previousModule; - test.module = config.currentModule; - test.moduleTestEnvironment = config.currentModuleTestEnviroment; - test.queue(); - }, - - /** - * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. - */ - expect: function(asserts) { - config.current.expected = asserts; - }, - - /** - * Asserts true. - * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); - */ - ok: function(a, msg) { - a = !!a; - var details = { - result: a, - message: msg - }; - msg = escapeHtml(msg); - QUnit.log(a, msg, details); - config.current.assertions.push({ - result: a, - message: msg - }); - }, - - /** - * Checks that the first two arguments are equal, with an optional message. - * Prints out both actual and expected values. - * - * Prefered to ok( actual == expected, message ) - * - * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." ); - * - * @param Object actual - * @param Object expected - * @param String message (optional) - */ - equal: function(actual, expected, message) { - QUnit.push(expected == actual, actual, expected, message); - }, - - notEqual: function(actual, expected, message) { - QUnit.push(expected != actual, actual, expected, message); - }, - - deepEqual: function(actual, expected, message) { - QUnit.push(QUnit.equiv(actual, expected), actual, expected, message); - }, - - notDeepEqual: function(actual, expected, message) { - QUnit.push(!QUnit.equiv(actual, expected), actual, expected, message); - }, - - strictEqual: function(actual, expected, message) { - QUnit.push(expected === actual, actual, expected, message); - }, - - notStrictEqual: function(actual, expected, message) { - QUnit.push(expected !== actual, actual, expected, message); - }, - - raises: function(fn, message) { - try { - fn(); - QUnit.ok( false, message ); - } - catch (e) { - QUnit.ok( true, message ); - } - }, - - start: function() { - // A slight delay, to avoid any current callbacks - if ( defined.setTimeout ) { - window.setTimeout(function() { - if ( config.timeout ) { - clearTimeout(config.timeout); - } - - config.blocking = false; - process(); - }, 13); - } else { - config.blocking = false; - process(); - } - }, - - stop: function(timeout) { - config.blocking = true; - - if ( timeout && defined.setTimeout ) { - config.timeout = window.setTimeout(function() { - QUnit.ok( false, "Test timed out" ); - QUnit.start(); - }, timeout); - } - } - -}; - -// Backwards compatibility, deprecated -QUnit.equals = QUnit.equal; -QUnit.same = QUnit.deepEqual; - -// Maintain internal state -var config = { - // The queue of tests to run - queue: [], - - // block until document ready - blocking: true -}; - -// Load paramaters -(function() { - var location = window.location || { search: "", protocol: "file:" }, - GETParams = location.search.slice(1).split('&'); - - for ( var i = 0; i < GETParams.length; i++ ) { - GETParams[i] = decodeURIComponent( GETParams[i] ); - if ( GETParams[i] === "noglobals" ) { - GETParams.splice( i, 1 ); - i--; - config.noglobals = true; - } else if ( GETParams[i].search('=') > -1 ) { - GETParams.splice( i, 1 ); - i--; - } - } - - // restrict modules/tests by get parameters - config.filters = GETParams; - - // Figure out if we're running the tests from a server or not - QUnit.isLocal = !!(location.protocol === 'file:'); -})(); - -// Expose the API as global variables, unless an 'exports' -// object exists, in that case we assume we're in CommonJS -if ( typeof exports === "undefined" || typeof require === "undefined" ) { - extend(window, QUnit); - window.QUnit = QUnit; -} else { - extend(exports, QUnit); - exports.QUnit = QUnit; -} - -// define these after exposing globals to keep them in these QUnit namespace only -extend(QUnit, { - config: config, - - // Initialize the configuration options - init: function() { - extend(config, { - stats: { all: 0, bad: 0 }, - moduleStats: { all: 0, bad: 0 }, - started: +new Date, - updateRate: 1000, - blocking: false, - autostart: true, - autorun: false, - filters: [], - queue: [] - }); - - var tests = id("qunit-tests"), - banner = id("qunit-banner"), - result = id("qunit-testresult"); - - if ( tests ) { - tests.innerHTML = ""; - } - - if ( banner ) { - banner.className = ""; - } - - if ( result ) { - result.parentNode.removeChild( result ); - } - }, - - /** - * Resets the test setup. Useful for tests that modify the DOM. - * - * If jQuery is available, uses jQuery's html(), otherwise just innerHTML. - */ - reset: function() { - if ( window.jQuery ) { - jQuery( "#main, #qunit-fixture" ).html( config.fixture ); - } else { - var main = id( 'main' ) || id( 'qunit-fixture' ); - if ( main ) { - main.innerHTML = config.fixture; - } - } - }, - - /** - * Trigger an event on an element. - * - * @example triggerEvent( document.body, "click" ); - * - * @param DOMElement elem - * @param String type - */ - triggerEvent: function( elem, type, event ) { - if ( document.createEvent ) { - event = document.createEvent("MouseEvents"); - event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, - 0, 0, 0, 0, 0, false, false, false, false, 0, null); - elem.dispatchEvent( event ); - - } else if ( elem.fireEvent ) { - elem.fireEvent("on"+type); - } - }, - - // Safe object type checking - is: function( type, obj ) { - return QUnit.objectType( obj ) == type; - }, - - objectType: function( obj ) { - if (typeof obj === "undefined") { - return "undefined"; - - // consider: typeof null === object - } - if (obj === null) { - return "null"; - } - - var type = Object.prototype.toString.call( obj ) - .match(/^\[object\s(.*)\]$/)[1] || ''; - - switch (type) { - case 'Number': - if (isNaN(obj)) { - return "nan"; - } else { - return "number"; - } - case 'String': - case 'Boolean': - case 'Array': - case 'Date': - case 'RegExp': - case 'Function': - return type.toLowerCase(); - } - if (typeof obj === "object") { - return "object"; - } - return undefined; - }, - - push: function(result, actual, expected, message) { - var details = { - result: result, - message: message, - actual: actual, - expected: expected - }; - - message = escapeHtml(message) || (result ? "okay" : "failed"); - message = '' + message + ""; - expected = escapeHtml(QUnit.jsDump.parse(expected)); - actual = escapeHtml(QUnit.jsDump.parse(actual)); - var output = message + ''; - if (actual != expected) { - output += ''; - output += ''; - } - if (!result) { - var source = sourceFromStacktrace(); - if (source) { - details.source = source; - output += ''; - } - } - output += "
Expected:
' + expected + '
Result:
' + actual + '
Diff:
' + QUnit.diff(expected, actual) +'
Source:
' + source +'
"; - - QUnit.log(result, message, details); - - config.current.assertions.push({ - result: !!result, - message: output - }); - }, - - // Logging callbacks - begin: function() {}, - done: function(failures, total) {}, - log: function(result, message) {}, - testStart: function(name, testEnvironment) {}, - testDone: function(name, failures, total) {}, - moduleStart: function(name, testEnvironment) {}, - moduleDone: function(name, failures, total) {} -}); - -if ( typeof document === "undefined" || document.readyState === "complete" ) { - config.autorun = true; -} - -addEvent(window, "load", function() { - QUnit.begin(); - - // Initialize the config, saving the execution queue - var oldconfig = extend({}, config); - QUnit.init(); - extend(config, oldconfig); - - config.blocking = false; - - var userAgent = id("qunit-userAgent"); - if ( userAgent ) { - userAgent.innerHTML = navigator.userAgent; - } - var banner = id("qunit-header"); - if ( banner ) { - var paramsIndex = location.href.lastIndexOf(location.search); - if ( paramsIndex > -1 ) { - var mainPageLocation = location.href.slice(0, paramsIndex); - if ( mainPageLocation == location.href ) { - banner.innerHTML = ' ' + banner.innerHTML + ' '; - } else { - var testName = decodeURIComponent(location.search.slice(1)); - banner.innerHTML = '' + banner.innerHTML + '' + testName + ''; - } - } - } - - var toolbar = id("qunit-testrunner-toolbar"); - if ( toolbar ) { - toolbar.style.display = "none"; - - var filter = document.createElement("input"); - filter.type = "checkbox"; - filter.id = "qunit-filter-pass"; - filter.disabled = true; - addEvent( filter, "click", function() { - var li = document.getElementsByTagName("li"); - for ( var i = 0; i < li.length; i++ ) { - if ( li[i].className.indexOf("pass") > -1 ) { - li[i].style.display = filter.checked ? "none" : ""; - } - } - }); - toolbar.appendChild( filter ); - - var label = document.createElement("label"); - label.setAttribute("for", "qunit-filter-pass"); - label.innerHTML = "Hide passed tests"; - toolbar.appendChild( label ); - } - - var main = id('main') || id('qunit-fixture'); - if ( main ) { - config.fixture = main.innerHTML; - } - - if (config.autostart) { - QUnit.start(); - } -}); - -function done() { - config.autorun = true; - - // Log the last module results - if ( config.currentModule ) { - QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all ); - } - - var banner = id("qunit-banner"), - tests = id("qunit-tests"), - html = ['Tests completed in ', - +new Date - config.started, ' milliseconds.
', - '', config.stats.all - config.stats.bad, ' tests of ', config.stats.all, ' passed, ', config.stats.bad,' failed.'].join(''); - - if ( banner ) { - banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass"); - } - - if ( tests ) { - var result = id("qunit-testresult"); - - if ( !result ) { - result = document.createElement("p"); - result.id = "qunit-testresult"; - result.className = "result"; - tests.parentNode.insertBefore( result, tests.nextSibling ); - } - - result.innerHTML = html; - } - - QUnit.done( config.stats.bad, config.stats.all ); -} - -function validTest( name ) { - var i = config.filters.length, - run = false; - - if ( !i ) { - return true; - } - - while ( i-- ) { - var filter = config.filters[i], - not = filter.charAt(0) == '!'; - - if ( not ) { - filter = filter.slice(1); - } - - if ( name.indexOf(filter) !== -1 ) { - return !not; - } - - if ( not ) { - run = true; - } - } - - return run; -} - -// so far supports only Firefox, Chrome and Opera (buggy) -// could be extended in the future to use something like https://github.com/csnover/TraceKit -function sourceFromStacktrace() { - try { - throw new Error(); - } catch ( e ) { - if (e.stacktrace) { - // Opera - return e.stacktrace.split("\n")[6]; - } else if (e.stack) { - // Firefox, Chrome - return e.stack.split("\n")[4]; - } - } -} - -function resultDisplayStyle(passed) { - return passed && id("qunit-filter-pass") && id("qunit-filter-pass").checked ? 'none' : ''; -} - -function escapeHtml(s) { - if (!s) { - return ""; - } - s = s + ""; - return s.replace(/[\&"<>\\]/g, function(s) { - switch(s) { - case "&": return "&"; - case "\\": return "\\\\"; - case '"': return '\"'; - case "<": return "<"; - case ">": return ">"; - default: return s; - } - }); -} - -function synchronize( callback ) { - config.queue.push( callback ); - - if ( config.autorun && !config.blocking ) { - process(); - } -} - -function process() { - var start = (new Date()).getTime(); - - while ( config.queue.length && !config.blocking ) { - if ( config.updateRate <= 0 || (((new Date()).getTime() - start) < config.updateRate) ) { - config.queue.shift()(); - } else { - window.setTimeout( process, 13 ); - break; - } - } - if (!config.blocking && !config.queue.length) { - done(); - } -} - -function saveGlobal() { - config.pollution = []; - - if ( config.noglobals ) { - for ( var key in window ) { - config.pollution.push( key ); - } - } -} - -function checkPollution( name ) { - var old = config.pollution; - saveGlobal(); - - var newGlobals = diff( old, config.pollution ); - if ( newGlobals.length > 0 ) { - ok( false, "Introduced global variable(s): " + newGlobals.join(", ") ); - config.current.expected++; - } - - var deletedGlobals = diff( config.pollution, old ); - if ( deletedGlobals.length > 0 ) { - ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") ); - config.current.expected++; - } -} - -// returns a new Array with the elements that are in a but not in b -function diff( a, b ) { - var result = a.slice(); - for ( var i = 0; i < result.length; i++ ) { - for ( var j = 0; j < b.length; j++ ) { - if ( result[i] === b[j] ) { - result.splice(i, 1); - i--; - break; - } - } - } - return result; -} - -function fail(message, exception, callback) { - if ( typeof console !== "undefined" && console.error && console.warn ) { - console.error(message); - console.error(exception); - console.warn(callback.toString()); - - } else if ( window.opera && opera.postError ) { - opera.postError(message, exception, callback.toString); - } -} - -function extend(a, b) { - for ( var prop in b ) { - a[prop] = b[prop]; - } - - return a; -} - -function addEvent(elem, type, fn) { - if ( elem.addEventListener ) { - elem.addEventListener( type, fn, false ); - } else if ( elem.attachEvent ) { - elem.attachEvent( "on" + type, fn ); - } else { - fn(); - } -} - -function id(name) { - return !!(typeof document !== "undefined" && document && document.getElementById) && - document.getElementById( name ); -} - -// Test for equality any JavaScript type. -// Discussions and reference: http://philrathe.com/articles/equiv -// Test suites: http://philrathe.com/tests/equiv -// Author: Philippe Rathé -QUnit.equiv = function () { - - var innerEquiv; // the real equiv function - var callers = []; // stack to decide between skip/abort functions - var parents = []; // stack to avoiding loops from circular referencing - - // Call the o related callback with the given arguments. - function bindCallbacks(o, callbacks, args) { - var prop = QUnit.objectType(o); - if (prop) { - if (QUnit.objectType(callbacks[prop]) === "function") { - return callbacks[prop].apply(callbacks, args); - } else { - return callbacks[prop]; // or undefined - } - } - } - - var callbacks = function () { - - // for string, boolean, number and null - function useStrictEquality(b, a) { - if (b instanceof a.constructor || a instanceof b.constructor) { - // to catch short annotaion VS 'new' annotation of a declaration - // e.g. var i = 1; - // var j = new Number(1); - return a == b; - } else { - return a === b; - } - } - - return { - "string": useStrictEquality, - "boolean": useStrictEquality, - "number": useStrictEquality, - "null": useStrictEquality, - "undefined": useStrictEquality, - - "nan": function (b) { - return isNaN(b); - }, - - "date": function (b, a) { - return QUnit.objectType(b) === "date" && a.valueOf() === b.valueOf(); - }, - - "regexp": function (b, a) { - return QUnit.objectType(b) === "regexp" && - a.source === b.source && // the regex itself - a.global === b.global && // and its modifers (gmi) ... - a.ignoreCase === b.ignoreCase && - a.multiline === b.multiline; - }, - - // - skip when the property is a method of an instance (OOP) - // - abort otherwise, - // initial === would have catch identical references anyway - "function": function () { - var caller = callers[callers.length - 1]; - return caller !== Object && - typeof caller !== "undefined"; - }, - - "array": function (b, a) { - var i, j, loop; - var len; - - // b could be an object literal here - if ( ! (QUnit.objectType(b) === "array")) { - return false; - } - - len = a.length; - if (len !== b.length) { // safe and faster - return false; - } - - //track reference to avoid circular references - parents.push(a); - for (i = 0; i < len; i++) { - loop = false; - for(j=0;j= 0) { - type = "array"; - } else { - type = typeof obj; - } - return type; - }, - separator:function() { - return this.multiline ? this.HTML ? '
' : '\n' : this.HTML ? ' ' : ' '; - }, - indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing - if ( !this.multiline ) - return ''; - var chr = this.indentChar; - if ( this.HTML ) - chr = chr.replace(/\t/g,' ').replace(/ /g,' '); - return Array( this._depth_ + (extra||0) ).join(chr); - }, - up:function( a ) { - this._depth_ += a || 1; - }, - down:function( a ) { - this._depth_ -= a || 1; - }, - setParser:function( name, parser ) { - this.parsers[name] = parser; - }, - // The next 3 are exposed so you can use them - quote:quote, - literal:literal, - join:join, - // - _depth_: 1, - // This is the list of parsers, to modify them, use jsDump.setParser - parsers:{ - window: '[Window]', - document: '[Document]', - error:'[ERROR]', //when no parser is found, shouldn't happen - unknown: '[Unknown]', - 'null':'null', - undefined:'undefined', - 'function':function( fn ) { - var ret = 'function', - name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE - if ( name ) - ret += ' ' + name; - ret += '('; - - ret = [ ret, QUnit.jsDump.parse( fn, 'functionArgs' ), '){'].join(''); - return join( ret, QUnit.jsDump.parse(fn,'functionCode'), '}' ); - }, - array: array, - nodelist: array, - arguments: array, - object:function( map ) { - var ret = [ ]; - QUnit.jsDump.up(); - for ( var key in map ) - ret.push( QUnit.jsDump.parse(key,'key') + ': ' + QUnit.jsDump.parse(map[key]) ); - QUnit.jsDump.down(); - return join( '{', ret, '}' ); - }, - node:function( node ) { - var open = QUnit.jsDump.HTML ? '<' : '<', - close = QUnit.jsDump.HTML ? '>' : '>'; - - var tag = node.nodeName.toLowerCase(), - ret = open + tag; - - for ( var a in QUnit.jsDump.DOMAttrs ) { - var val = node[QUnit.jsDump.DOMAttrs[a]]; - if ( val ) - ret += ' ' + a + '=' + QUnit.jsDump.parse( val, 'attribute' ); - } - return ret + close + open + '/' + tag + close; - }, - functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function - var l = fn.length; - if ( !l ) return ''; - - var args = Array(l); - while ( l-- ) - args[l] = String.fromCharCode(97+l);//97 is 'a' - return ' ' + args.join(', ') + ' '; - }, - key:quote, //object calls it internally, the key part of an item in a map - functionCode:'[code]', //function calls it internally, it's the content of the function - attribute:quote, //node calls it internally, it's an html attribute value - string:quote, - date:quote, - regexp:literal, //regex - number:literal, - 'boolean':literal - }, - DOMAttrs:{//attributes to dump from nodes, name=>realName - id:'id', - name:'name', - 'class':'className' - }, - HTML:false,//if true, entities are escaped ( <, >, \t, space and \n ) - indentChar:' ',//indentation unit - multiline:true //if true, items in a collection, are separated by a \n, else just a space. - }; - - return jsDump; -})(); - -// from Sizzle.js -function getText( elems ) { - var ret = "", elem; - - for ( var i = 0; elems[i]; i++ ) { - elem = elems[i]; - - // Get the text from text nodes and CDATA nodes - if ( elem.nodeType === 3 || elem.nodeType === 4 ) { - ret += elem.nodeValue; - - // Traverse everything else, except comment nodes - } else if ( elem.nodeType !== 8 ) { - ret += getText( elem.childNodes ); - } - } - - return ret; -}; - -/* - * Javascript Diff Algorithm - * By John Resig (http://ejohn.org/) - * Modified by Chu Alan "sprite" - * - * Released under the MIT license. - * - * More Info: - * http://ejohn.org/projects/javascript-diff-algorithm/ - * - * Usage: QUnit.diff(expected, actual) - * - * QUnit.diff("the quick brown fox jumped over", "the quick fox jumps over") == "the quick brown fox jumped jumps over" - */ -QUnit.diff = (function() { - function diff(o, n){ - var ns = new Object(); - var os = new Object(); - - for (var i = 0; i < n.length; i++) { - if (ns[n[i]] == null) - ns[n[i]] = { - rows: new Array(), - o: null - }; - ns[n[i]].rows.push(i); - } - - for (var i = 0; i < o.length; i++) { - if (os[o[i]] == null) - os[o[i]] = { - rows: new Array(), - n: null - }; - os[o[i]].rows.push(i); - } - - for (var i in ns) { - if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) { - n[ns[i].rows[0]] = { - text: n[ns[i].rows[0]], - row: os[i].rows[0] - }; - o[os[i].rows[0]] = { - text: o[os[i].rows[0]], - row: ns[i].rows[0] - }; - } - } - - for (var i = 0; i < n.length - 1; i++) { - if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null && - n[i + 1] == o[n[i].row + 1]) { - n[i + 1] = { - text: n[i + 1], - row: n[i].row + 1 - }; - o[n[i].row + 1] = { - text: o[n[i].row + 1], - row: i + 1 - }; - } - } - - for (var i = n.length - 1; i > 0; i--) { - if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null && - n[i - 1] == o[n[i].row - 1]) { - n[i - 1] = { - text: n[i - 1], - row: n[i].row - 1 - }; - o[n[i].row - 1] = { - text: o[n[i].row - 1], - row: i - 1 - }; - } - } - - return { - o: o, - n: n - }; - } - - return function(o, n){ - o = o.replace(/\s+$/, ''); - n = n.replace(/\s+$/, ''); - var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/)); - - var str = ""; - - var oSpace = o.match(/\s+/g); - if (oSpace == null) { - oSpace = [" "]; - } - else { - oSpace.push(" "); - } - var nSpace = n.match(/\s+/g); - if (nSpace == null) { - nSpace = [" "]; - } - else { - nSpace.push(" "); - } - - if (out.n.length == 0) { - for (var i = 0; i < out.o.length; i++) { - str += '' + out.o[i] + oSpace[i] + ""; - } - } - else { - if (out.n[0].text == null) { - for (n = 0; n < out.o.length && out.o[n].text == null; n++) { - str += '' + out.o[n] + oSpace[n] + ""; - } - } - - for (var i = 0; i < out.n.length; i++) { - if (out.n[i].text == null) { - str += '' + out.n[i] + nSpace[i] + ""; - } - else { - var pre = ""; - - for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) { - pre += '' + out.o[n] + oSpace[n] + ""; - } - str += " " + out.n[i].text + nSpace[i] + pre; - } - } - } - - return str; - }; -})(); - -})(this); \ No newline at end of file diff --git a/tests/unit/setup.js b/tests/unit/setup.js new file mode 100644 index 0000000..f294301 --- /dev/null +++ b/tests/unit/setup.js @@ -0,0 +1,14 @@ +var el; +var body = document.body; + +function button(){ + return el.next(); +} + +function menu(){ + return el.multiselect("widget"); +} + +function header(){ + return menu().find('.ui-multiselect-header'); +} \ No newline at end of file From 84144011a6884f9ef52f64064d2259b2e2fc1870 Mon Sep 17 00:00:00 2001 From: Steve James <4stevejames@gmail.com> Date: Mon, 5 Feb 2018 19:00:41 -0800 Subject: [PATCH 2/6] Dimensions 2 1. Updated sizing/dimensions code. 2. Added text wrapping option. 3. Using getBoundingClientRect() 4. Ditch globals 5. Update tests. 6. Update CSS --- css/jquery.multiselect.css | 36 ++-- src/jquery.multiselect.js | 371 +++++++++++++++++++++++++++---------- tests/unit/core.js | 5 +- tests/unit/events.js | 4 +- tests/unit/options.js | 26 +-- 5 files changed, 314 insertions(+), 128 deletions(-) diff --git a/css/jquery.multiselect.css b/css/jquery.multiselect.css index bf9daa9..0344579 100644 --- a/css/jquery.multiselect.css +++ b/css/jquery.multiselect.css @@ -1,25 +1,35 @@ -.ui-multiselect { padding:2px 0 2px 4px; text-align:left } -.ui-multiselect .ui-icon { float:right } -.ui-multiselect-single .ui-multiselect-checkboxes input { left:-9999px; position:absolute !important; top: auto !important; } -.ui-multiselect-single .ui-multiselect-checkboxes label { padding:5px !important } +.ui-multiselect {box-sizing: border-box; padding:2px 0 2px 4px; text-align:left;} +.ui-multiselect .ui-multiselect-open { float:right } + +.ui-multiselect-menu { display:none; box-sizing:border-box; position:absolute; text-align:left; z-index:1010; width:auto; padding:3px;} -.ui-multiselect-header { margin-bottom:3px; padding:3px 0 3px 4px; } +.ui-multiselect-header { display:block; box-sizing:border-box; position:relative; width:auto; padding:3px 0 3px 4px; margin-bottom:3px;} .ui-multiselect-header > ul { font-size:0.9em } -.ui-multiselect-header > ul > li { float:left; padding:0 10px 0 0; } +.ui-multiselect-header > ul > li { float:left; padding:0 10px 0 0;} .ui-multiselect-header a { text-decoration:none; } .ui-multiselect-header a:hover { text-decoration:underline; cursor: pointer;} .ui-multiselect-header .ui-icon { float:left; } .ui-multiselect-header .ui-multiselect-close { float:right; padding-right:0; text-align:right; } -.ui-multiselect-menu { display:none; padding:3px; position:absolute; text-align: left; } -.ui-multiselect-checkboxes { overflow-y:auto; position:relative; } -.ui-multiselect-checkboxes label { border:1px solid transparent; cursor:default; display:block; padding:3px 1px; } -.ui-multiselect-checkboxes label > input { position:relative; top:1px } +.ui-multiselect-checkboxes { display:block; box-sizing:border-box; position:relative; overflow:auto; width: auto; border: 0;} +.ui-multiselect-checkboxes label { border:1px solid transparent; cursor:default; display:block; padding:3px 1px;} +.ui-multiselect-checkboxes label > input { position:relative; top:1px; } .ui-multiselect-checkboxes label img { height: 30px; vertical-align: middle; padding-right: 3px;} -.ui-multiselect-checkboxes li { clear:both; font-size:0.9em; list-style: none; padding-right:3px; } -.ui-multiselect-checkboxes > .ui-multiselect-optgroup { padding: 3px; } -.ui-multiselect-columns { display: inline-block; vertical-align: top; } .ui-multiselect-checkboxes > .ui-multiselect-optgroup > a { border-bottom:1px solid; cursor: pointer; display:block; font-weight:bold; margin:1px 0; padding:3px; text-align:center; text-decoration:none; } +.ui-multiselect-checkboxes > .ui-multiselect-optgroup > ul { padding: 3px; } +.ui-multiselect-checkboxes li:not(.ui-multiselect-optgroup) { clear:both; font-size:0.9em; list-style: none; padding-right:3px;} +.ui-multiselect-columns { display: inline-block; vertical-align: top; } + +.ui-multiselect-single .ui-multiselect-checkboxes input { left:-9999px; position:absolute !important; top: auto !important; } +.ui-multiselect-single .ui-multiselect-checkboxes label { padding:5px !important } + +.ui-multiselect.ui-multiselect-nowrap { white-space: nowrap} +.ui-multiselect.ui-multiselect-nowrap > span {display: inline-block} +.ui-multiselect-checkboxes.ui-multiselect-nowrap li, +.ui-multiselect-checkboxes.ui-multiselect-nowrap a{ white-space: nowrap} + +.ui-multiselect-measure > .ui-multiselect-header, +.ui-multiselect-measure > .ui-multiselect-checkboxes { float:left; } @media print{ .ui-multiselect-menu {display: none;} diff --git a/src/jquery.multiselect.js b/src/jquery.multiselect.js index e1b2a7a..284e508 100644 --- a/src/jquery.multiselect.js +++ b/src/jquery.multiselect.js @@ -19,6 +19,8 @@ (function($, undefined) { // Counter used to prevent collisions var multiselectID = 0; + // Scroll bar width saved for auto menu width determinations. + var _scrollbarWidth = 0; var defaultIcons = { 'open': '' to make the button grow vertically showing 1 selection per line. htmlButtonText: false, // (true | false) If true, then the text used for the button's label is treated as html rather than plain text. htmlOptionText: false, // (true | false) If true, then the text for option label is treated as html rather than plain text. addInputNames: true, // (true | false) If true, names are created for each option input in the multi-select. + wrapText: 'button', // (list of button, header, &/or menu) Comma separated list defining what parts of the widget to wrap text for. disableInputsOnToggle: true, // (true | false) - groupColumns: false // (true | false) + groupColumns: false // (true | false) Displays groups in a horizonal column layout. }, /** @@ -94,16 +97,18 @@ * - Calls refresh to populate the menu */ _create: function() { + this._selectWidth = this.element[0].getBoundingClientRect().width; var $element = this.element.hide(); var elSelect = $element.get(0); var options = this.options; var classes = options.classes; var headerOn = options.header; - // Do an extend here to address icons missing from options.iconSet--missing icons default to those in msIcons. var checkAllText = options.checkAllText; + // Do an extend here to address icons missing from options.iconSet--missing icons default to those in defaultIcons. var iconSet = $.extend({}, defaultIcons, options.iconSet || {}); var uncheckAllText = options.uncheckAllText; var flipAllText = options.flipAllText; + var wrapText = options.wrapText || ''; // default speed for effects this.speed = $.fx.speeds._default; @@ -118,12 +123,15 @@ // The button that opens the widget menu. Note that this is inserted later below. var $button = (this.$button = $( document.createElement('button') ) ) - .addClass('ui-multiselect ui-widget ui-state-default ui-corner-all' + (classes ? ' ' + classes : '')) + .addClass('ui-multiselect ui-widget ui-state-default ui-corner-all' + + (/\bbutton\b/i.test(wrapText) ? '' : ' ui-multiselect-nowrap') + + (classes ? ' ' + classes : '') + ) .attr({ - 'type': 'button', - 'title': elSelect.title, - 'tabIndex': elSelect.tabIndex, - 'id': elSelect.id ? elSelect.id + '_ms' : null + 'type': 'button', + 'title': elSelect.title, + 'tabIndex': elSelect.tabIndex, + 'id': elSelect.id ? elSelect.id + '_ms' : null }) .prop('aria-haspopup', true) .html('' + iconSet.open + ''); // Necessary to simplify dynamically changing the open icon. @@ -142,9 +150,9 @@ this.$headerLinkContainer = $( document.createElement('ul') ) .addClass('ui-helper-reset') - .html(headerLinksHTML - + '
  • ' - + iconSet.close + .html(headerLinksHTML + + '
  • ' + + iconSet.close + '
  • '); // Menu header to hold controls for the menu @@ -153,15 +161,15 @@ .append( this.$headerLinkContainer ); // Holds the actual check boxes for inputs - var $checkboxContainer = (this.$checkboxContainer = $( document.createElement('ul') ) ) - .addClass('ui-multiselect-checkboxes ui-helper-reset'); + var $checkboxes = (this.$checkboxes = $( document.createElement('ul') ) ) + .addClass('ui-multiselect-checkboxes ui-helper-reset' + (/\bmenu\b/i.test(wrapText) ? '' : ' ui-multiselect-nowrap')); // This is the menu that will hold all the options. var $menu = (this.$menu = $( document.createElement('div') ) ) - .addClass('ui-multiselect-menu ui-widget ui-widget-content ui-corner-all ' - + (elSelect.multiple ? '' : 'ui-multiselect-single ') - + classes) - .append($header, $checkboxContainer); + .addClass('ui-multiselect-menu ui-widget ui-widget-content ui-corner-all' + + (elSelect.multiple ? '' : ' ui-multiselect-single ') + + (classes ? ' ' + classes : '')) + .append($header, $checkboxes); $button.insertAfter($element); // This is an empty menu at this point. @@ -212,7 +220,7 @@ var self = this; var title = option.title || null; var elSelect = self.element.get(0); - // unique ID for the label & option tags + // Determine unique ID for the label & option tags var id = elSelect.id || self.multiselectID; var inputID = 'ui-multiselect-' + self.multiselectID + '-' + (option.id || id + '-option-' + self.inputIdCounter++); // Pick up the select type from the underlying element @@ -235,13 +243,13 @@ for (var name in inputAttribs) { if (inputAttribs[name] !== null) { input.setAttribute(name,inputAttribs[name]); - } + } } + // Clone data attributes var optionAttribs = option.attributes; var len = optionAttribs.length; - // Clone data attributes - for (x = 0; x < len; x++) { - attribute = optionAttribs[x]; + for (var x = 0; x < len; x++) { + var attribute = optionAttribs[x]; if ( /^data\-.+/.test(attribute.name) ) input.setAttribute(attribute.name, attribute.value) } @@ -252,8 +260,8 @@ span.innerHTML = option.innerHTML; } else { span.textContent = option.textContent; - } - + } + // Icon images for each item. var optionImageSrc = option.getAttribute('data-image-src'); if (optionImageSrc) { @@ -266,16 +274,16 @@ label.setAttribute('for', inputID); if (title !== null) { label.setAttribute('title', title); - } - label.className += (isDisabled ? ' ui-state-disabled' : '') - + (isSelected && !isMultiple ? ' ui-state-active' : '') + } + label.className += (isDisabled ? ' ui-state-disabled' : '') + + (isSelected && !isMultiple ? ' ui-state-active' : '') + ' ui-corner-all'; label.appendChild(input); label.appendChild(span); var item = document.createElement('li'); - item.className += (isDisabled ? ' ui-multiselect-disabled' : '') - + (' ' + option.className || '') + item.className += (isDisabled ? ' ui-multiselect-disabled' : '') + + (' ' + option.className || '') + ' ui-multiselect-nowrap'; item.appendChild(label); @@ -284,13 +292,13 @@ /** * Processes option and optgroup tags from underlying select to construct the menu's option list - * This clears the items currently in $checkboxContainer + * This clears the items currently in $checkboxes * Defers to _makeOption to actually build the options * Resets the input ID counter * @param {object} $element element whose option/group tags need to be converted - * @param {object} $checkboxContainer widget's container to append the built options to + * @param {object} $checkboxes widget's container to append the built options to */ - _buildOptionList: function($element, $checkboxContainer) { + _buildOptionList: function($element, $checkboxes) { var self = this; var list = []; @@ -307,14 +315,13 @@ }); // Build the list section for this optgroup, complete w/ option inputs... - var $optGroupLabel = $( document.createElement('a') ).text( elem.getAttribute('label') ); var $optGroupItem = $( document.createElement('li') ) - .addClass('ui-multiselect-optgroup' - + (self.options.groupColumns ? ' ui-multiselect-columns' : '') - + (elem.className && ' ') + elem.className) - var $optionGroup = $( document.createElement('ul') ).append(options) - $optGroupItem.append($optGroupLabel, $optionGroup) - + .addClass('ui-multiselect-optgroup' + + (self.options.groupColumns ? ' ui-multiselect-columns' : '') + + (elem.className ? ' ' + elem.className : '')); + var $optGroupLabel = $( document.createElement('a') ).text( elem.getAttribute('label') ); + var $optionGroup = $( document.createElement('ul') ).append(options); + $optGroupItem.append($optGroupLabel, $optionGroup); list.push($optGroupItem); } else { @@ -322,7 +329,7 @@ } }); - $checkboxContainer.empty().append(list); + $checkboxes.empty().append(list); }, /** @@ -341,7 +348,7 @@ .find('.ui-multiselect-all, .ui-multiselect-none, .ui-multiselect-flip') .toggle( !!$element[0].multiple ); - this._buildOptionList($element, this.$checkboxContainer); // Clear and rebuild the menu. + this._buildOptionList($element, this.$checkboxes); // Clear and rebuild the menu. this._updateCache(); // cache some more useful elements this._setButtonWidth(); @@ -356,12 +363,16 @@ * Updates cached values used elsewhere in the widget */ _updateCache: function() { - // Invalidate cached dimensions and positioning state. + // Invalidate cached dimensions and positioning state to force recalcs. this._savedButtonWidth = 0; this._savedMenuWidth = 0; this._ulHeight = 0; this._positioned = false; + // Recreate important cached jQuery objects + this.$header = this.$menu.children('.ui-multiselect-header'); + this.$checkboxes = this.$menu.children('.ui-multiselect-checkboxes'); + // Update saved labels and inputs this.$labels = this.$menu.find('label'); this.$inputs = this.$labels.children('input'); @@ -398,6 +409,9 @@ self._setButtonValue(value, isDefault); + if ( !/\bbutton\b/.test( options.wrapText ) ) + this._setButtonWidth(true); + // Check if the menu needs to be repositioned due to button height changing from adding/removing selections. if (self._isOpen && self._savedButtonHeight != self.$button.outerHeight(false)) self._position(true); @@ -568,7 +582,7 @@ return; } - if ( selectedMax && checked + if ( selectedMax && checked && ( typeof selectedMax === 'function' ? !!selectedMax.call(input, $allInputs) : numChecked > selectedMax ) ) { var saveText = options.selectedText; @@ -627,9 +641,9 @@ // Reference to this anchor element var $this = $(this); var headerLinks = { - 'ui-multiselect-close' : 'close', - 'ui-multiselect-all' : 'checkAll', - 'ui-multiselect-none' : 'uncheckAll', + 'ui-multiselect-close' : 'close', + 'ui-multiselect-all' : 'checkAll', + 'ui-multiselect-none' : 'uncheckAll', 'ui-multiselect-flip' : 'flipAll' }; for (hdgClass in headerLinks) { @@ -648,9 +662,9 @@ break; case 9: var $target = $(e.target); - if((e.shiftKey - && !$target.parent().prev().length - && !self.$header.find(".ui-multiselect-filter").length) + if((e.shiftKey + && !$target.parent().prev().length + && !self.$header.find(".ui-multiselect-filter").length) || (!$target.parent().next().length && !self.$labels.length && !e.shiftKey)) { self.close(); e.preventDefault(); @@ -690,34 +704,57 @@ }, /** - * Determines minimum width for the button and menu - * minWidth can be a number, string, or a percentage + * Converts dimensions specified in options to pixel values. + * Determines if specified value is a minimum, maximum or exact value. + * The value can be a number or a string with px, pts, ems, or % units. * Number/Numeric string treated as pixel measurements * - 30 * - '30' + * - '>30px' + * - '1.3em' + * - '20 pt' * - '30%' + * @returns {pixels, minimax} object containing pixels and -1/1/0 indicating min/max/exact. */ - _getMinWidth: function() { - var minWidth = this.options.minWidth; - var width = 0; - - switch (typeof minWidth) { - case 'number': - width = minWidth; - break; - case 'string': - width = parseInt(minWidth, 10); - - if ( minWidth.slice(-1) === '%' ) - width = this.element.parent().innerWidth() * (width/100); + _parse2px: function(dimText, $elem, isHeight) { + if (typeof dimText !== 'string') + return {px: dimText, minimax: 0}; + + var parts = dimText.match(/([<>])?=?\s*([.\d]+)\s*([eimnptx%]*)s?/i); + var minimax = parts[1]; + var value = parseFloat(parts[2]); + var unit = parts[3].toLowerCase(); + var pixels = -1; + switch (unit) { + case 'pt': + case 'in': + case 'cm': + case 'mm': + pixels = {'pt': 4.0 / 3.0, 'in': 96.0, 'cm': 96.0 / 2.54, 'mm': 96.0 / 25.4}[unit] * value; + break; + case 'em': + var bodyFontSize = ( window.getComputedStyle + ? getComputedStyle(document.body).fontSize + : document.body.currentStyle.fontSize ) || '16px'; + pixels = parseFloat(bodyFontSize) * value; + break; + case '%': + if ( !!$elem ) { + if (typeof $elem === 'string' || !$elem.jquery) + $elem = $($elem); + pixels = ( !!isHeight ? $elem.parent().height() : $elem.parent().width() ) * (value / 100.0); + } // else returns -1 default value from above. + break; + default: + pixels = value; } - - return width; + // minimax: -1 => minimum value, 1 => maximum value, 0 => exact value + return {px: pixels, minimax: minimax == '>' ? -1 : ( minimax == '<' ? 1 : 0 ) }; }, /** * Sets and caches the width of the button - * Will use the minWidth option's value if less than calculated width + * Can set a minimum value if less than calculated width of native select. * If the cache is cleared, the menu will be re-positioned on the next open * @param {boolean} recalc true if cached value needs to be re-calculated */ @@ -726,11 +763,24 @@ return; this._positioned = false; - var width = this.element.outerWidth(); - var minWidth = this._getMinWidth(); - this._savedButtonWidth = width < minWidth ? minWidth : width; - this.$button.outerWidth(this._savedButtonWidth); + // this._selectWidth set in _create() for native select element before hiding it. + var width = this._selectWidth || this._getBCRWidth( this.element ); + var buttonWidth = this.options.buttonWidth || ''; + if (/\d/.test(buttonWidth)) { + var parsed = this._parse2px(buttonWidth, this.element); + var pixels = parsed.px; + var minimax = parsed.minimax; + width = minimax < 0 ? Math.max(width, pixels) : ( minimax > 0 ? Math.min(width, pixels) : pixels ); + } + else // keywords + buttonWidth = buttonWidth.toLowerCase(); + + this._savedButtonWidth = width; + if (buttonWidth === 'auto') + this.$button.css('width', 'auto'); + else + this.$button.outerWidth(width); }, /** @@ -744,18 +794,43 @@ return; this._positioned = false; - var width = this.options.menuWidth; - if (!width) { - // Make width match button's width. - width = this._savedButtonWidth || this.$button.outerWidth(); - if (width <= 0) { - width = this._getMinWidth(); - } + // Note that it is assumed that the button width was set prior. + var width = this._savedButtonWidth || this._getBCRWidth( this.$button ); + + var menuWidth = this.options.menuWidth || ''; + if ( /\d/.test(menuWidth) ) { + var parsed = this._parse2px(menuWidth, this.element); + var pixels = parsed.px; + var minimax = parsed.minimax; + width = minimax < 0 ? Math.max(width, pixels) : ( minimax > 0 ? Math.min(width, pixels) : pixels ); + } + else // keywords + menuWidth = menuWidth.toLowerCase(); + + // Note that the menu width defaults to the button width if menuWidth option is null or blank. + if (menuWidth !== 'auto') { + this._savedMenuWidth = width; + this.$menu.outerWidth(width); + return; } - this._savedMenuWidth = width; - this.$menu.outerWidth(width); + // Auto width determination: get intrinsic / "shrink-wrapped" outer widths w/ margins by applying floats. + // Note that a correction is made for jQuery floating point round-off errors below. + this.$menu.addClass('ui-multiselect-measure'); + var headerWidth = this.$header.outerWidth(true) + this._jqWidthFix(this.$header); + var cbWidth = this.$checkboxes.outerWidth(true) + this._jqWidthFix(this.$checkboxes); + this.$menu.removeClass('ui-multiselect-measure'); + + // Need extra width to account for increased width of highlighted item (.ui-hover-state). + var uiHoverStateIncrease = 4; + var contentWidth = Math.max(/\bheader\b/.test(this.options.wrapText) ? 0 : headerWidth, + cbWidth + this._getScrollBarWidth() + uiHoverStateIncrease); + + // Use $().width() to set menu width not including padding or border. + this.$menu.width(contentWidth); + // Save width including padding and border for consistency w/ normal width setting. + this._savedMenuWidth = this._getBCRWidth( this.$menu ); }, /** @@ -773,16 +848,30 @@ self._positioned = false; var $menu = self.$menu; - var headerHeight = $menu.children('.ui-multiselect-header').filter(':visible').outerHeight(true); - var $checkboxes = $menu.children(".ui-multiselect-checkboxes"); - // Retrieves native select's size attribute or defaults to 4 (like native select). - var elSelectSize = self.element[0].size || 4; - var optionHeight = self.options.height; - // Determine if overall height should be based on native select 'size' attribute? - var useSelectSize = (optionHeight === 'size'); - // The maximum available height for the $checkboxes. - var availableHeight = window.innerHeight - headerHeight; - var maxHeight = (useSelectSize || optionHeight > availableHeight ? availableHeight : optionHeight); + var $header = self.$header.filter(':visible'); + var headerHeight = $header.outerHeight(true) + self._jqHeightFix($header); + var $checkboxes = self.$checkboxes; + + // The maximum available height for the $checkboxes: + var maxHeight = $(window).height() + - headerHeight + - this._parse2px( $menu.css('padding-top'), this.element, true ).px + - this._parse2px( $menu.css('padding-bottom'), this.element, true ).px; + + var optionHeight = self.options.height || ''; + var useSelectSize = false; + var elSelectSize = 4; + if ( /\d/.test(optionHeight) ) { + optionHeight = this._parse2px(optionHeight, this.element, true).px; + maxHeight = Math.min(optionHeight, maxHeight); + } + else if (optionHeight.toLowerCase() === 'size') { + // Overall height based on native select 'size' attribute + useSelectSize = true; + // Retrieves native select's size attribute or defaults to 4 (like native select). + elSelectSize = self.element[0].size || elSelectSize; + } + var overflowSetting = 'hidden'; var itemCount = 0; var ulHeight = 0; @@ -791,7 +880,7 @@ // of item heights summed equal or exceed the native select size attribute, the loop is aborted. // If the loop is aborted, this means that the menu must be scrolled to see all the items. $checkboxes.find('li,a').each( function() { - ulHeight += $(this).outerHeight(true); + ulHeight += $(this).outerHeight(true) + self._jqHeightFix(this); if (useSelectSize && ++itemCount >= elSelectSize || ulHeight > maxHeight) { overflowSetting = 'auto'; if (!useSelectSize) { @@ -801,11 +890,70 @@ } }); - $checkboxes.css("overflow", overflowSetting).height(ulHeight); + $checkboxes.css('overflow', overflowSetting).height(ulHeight); $menu.height(headerHeight + ulHeight); self._ulHeight = ulHeight; }, + // Calculate accurate outerWidth(false) using getBoundingClientRect() + _getBCRWidth: function(elem) { + if (!elem || !!elem.jquery && !elem[0]) return null; + var domRect = !!elem.jquery ? elem[0].getBoundingClientRect() : elem.getBoundingClientRect(); + return domRect.right - domRect.left; + }, + + // Calculate accurate outerHeight(false) using getBoundingClientRect() + _getBCRHeight: function(elem) { + if (!elem || !!elem.jquery && !elem[0]) return null; + var domRect = !!elem.jquery ? elem[0].getBoundingClientRect() : elem.getBoundingClientRect(); + return domRect.bottom - domRect.top; + }, + + // jQuery width correction factor to fix floating point round-off errors. + _jqWidthFix: function(elem) { + if (!elem || !!elem.jquery && !elem[0]) return null; + return !!elem.jquery + ? this._getBCRWidth(elem[0]) - elem.outerWidth(false) + : this._getBCRWidth(elem) - $(elem).outerWidth(false); + }, + + // jQuery height correction factor to fix floating point round-off errors. + _jqHeightFix: function(elem) { + if (!elem || !!elem.jquery && !elem[0]) return null; + return !!elem.jquery + ? this._getBCRHeight(elem[0]) - elem.outerHeight(false) + : this._getBCRHeight(elem) - $(elem).outerHeight(false); + }, + + // Determines scroll bar width for automatic width determinations. + // Only needs to be ran once--width saved for all instances. + _getScrollBarWidth: function() { + if (_scrollbarWidth) + return _scrollbarWidth; + + if ($.ui && $.ui.position) + _scrollbarWidth = $.position.scrollbarWidth(); + if (_scrollbarWidth) + return _scrollbarWidth; + + // https://davidwalsh.name/detect-scrollbar-width + // Create the measurement node + var scrollDiv = document.createElement("div"); + scrollDiv.style.width = 100; + scrollDiv.style.height = 100; + scrollDiv.style.overflow = 'scroll'; + scrollDiv.style.position = 'absolute'; + scrollDiv.style.top = -9999; + document.body.appendChild(scrollDiv); + + // Get the scrollbar width + _scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth; + + // Delete the DIV + document.body.removeChild(scrollDiv); + return _scrollbarWidth; + }, + // Resizes the menu, called every time the menu is opened _resizeMenu: function() { this._setMenuWidth(); @@ -865,6 +1013,8 @@ /** * Toggles the checked state on options within the menu * Potentially scoped down to visible elements from filteredInputs + * Groups don't show up in the filter search right now, so both + * group and filteredOptions won't apply at the same time * @param {string} flag checked property to set * @param {object} group option group that was clicked, if any * @param {array} filteredInputs visible elements with the filter applied @@ -901,7 +1051,7 @@ // trigger the change event on the select if ($inputs.length) { $element.trigger("change"); - } + } }, /** @@ -956,7 +1106,7 @@ // bail if the multiselect open event returns false, this widget is disabled, or is already open if(this._trigger('beforeopen') === false || $button.hasClass('ui-state-disabled') || this._isOpen) { return; - } + } var $menu = this.$menu; var $header = this.$header; @@ -976,7 +1126,10 @@ // show the menu, maybe with a speed/effect combo // if there's an effect, assume jQuery UI is in use - $.fn.show.apply($menu, effect ? [ effect, speed ] : []); + if (effect) + $.fn.show.apply($menu, effect ? [ effect, speed ] : []); + else + $menu.css('display','block'); this._resizeMenu(); this._position(); @@ -1015,16 +1168,24 @@ speed = options.hide[1] || this.speed; } - $.fn.hide.apply(this.$menu, effect ? [ effect, speed ] : []); + // hide the menu, maybe with a speed/effect combo + // if there's an effect, assume jQuery UI is in use + if (effect) + $.fn.hide.apply(this.$menu, effect ? [ effect, speed ] : []); + else + this.$menu.css('display','none'); + $button.removeClass('ui-state-active').trigger('blur').trigger('mouseleave'); this._isOpen = false; this._trigger('close'); $button.trigger('focus'); }, + // Enable widget enable: function() { this._toggleDisabled(false); }, + // Disable widget disable: function() { this._toggleDisabled(true); @@ -1047,6 +1208,7 @@ this._toggleChecked('!'); this._trigger('flipAll'); }, + /** * Provides a list of all checked options * @returns {array} list of inputs @@ -1054,6 +1216,7 @@ getChecked: function() { return this.$menu.find('input:checked'); }, + /** * Provides a list of all options that are not checked * @returns {array} list of inputs @@ -1061,6 +1224,7 @@ getUnchecked: function() { return this.$menu.find('input:not(:checked)'); }, + /** * Destroys the widget instance * @returns {object} reference to widget @@ -1079,24 +1243,28 @@ return this; }, + /** * @returns {boolean} indicates whether the menu is open */ isOpen: function() { return this._isOpen; }, + /** * @returns {object} jQuery object for menu */ widget: function() { return this.$menu; }, + /** * @returns {object} jQuery object for button */ getButton: function() { return this.$button; }, + /** * Essentially an alias for widget * @returns {object} jQuery object for menu @@ -1104,6 +1272,7 @@ getMenu: function() { return this.$menu; }, + /** * @returns {array} List of the option labels */ @@ -1139,6 +1308,7 @@ self._updateCache(); }, + /** * Removes an option from the widget and underlying select * @param {string} value attribute corresponding to option being removed @@ -1151,6 +1321,7 @@ this._updateCache(); }, + /** * Public version of _position, always ignores the cache */ @@ -1170,7 +1341,7 @@ // Save this so that we can determine when the button height has changed due adding/removing selections. this._savedButtonHeight = this.$button.outerHeight(false); - var pos = $.extend({'my': 'top', 'at': 'bottom', 'of': $button}, this.options.position || {}); + var pos = $.extend({'my': 'left top', 'at': 'left bottom', 'of': $button}, this.options.position || {}); if($.ui && $.ui.position) this.$menu.position(pos); @@ -1220,7 +1391,7 @@ this.options[key] = value; this._setMenuHeight(true); // true forces recalc of cached value. break; - case 'minWidth': + case 'buttonWidth': case 'menuWidth': this.options[key] = value; this._setButtonWidth(true); // true forces recalc of cached value. diff --git a/tests/unit/core.js b/tests/unit/core.js index dcfafa9..183b034 100644 --- a/tests/unit/core.js +++ b/tests/unit/core.js @@ -12,7 +12,8 @@ }); QUnit.test("init", function (assert) { - el = $("select").multiselect(), $header = header(); + el = $("select").multiselect(); + var $header = header(); assert.ok($header.find('a.ui-multiselect-all').css('display') !== 'none', 'select all is visible'); assert.ok($header.find('a.ui-multiselect-none').css('display') !== 'none', 'select none is visible'); assert.ok($header.find('a.ui-multiselect-close').css('display') !== 'none', 'close link is visible'); @@ -101,7 +102,7 @@ // select multiple radios to ensure that, in the underlying select, only one // will remain selected - radios = menu().find(":radio"); + var radios = menu().find(":radio"); radios[0].click(); radios[2].click(); radios[1].click(); diff --git a/tests/unit/events.js b/tests/unit/events.js index 4093053..20f4576 100644 --- a/tests/unit/events.js +++ b/tests/unit/events.js @@ -127,8 +127,8 @@ assert.equal($('.ui-multiselect').length, 2, "two mutliselects are on the page"); first.multiselect("refresh"); second.multiselect("refresh"); - $label = $(second.multiselect("getLabels")[0]); - $wrongInput = $(first.multiselect("getLabels")[0]).find("input"); + var $label = $(second.multiselect("getLabels")[0]); + var $wrongInput = $(first.multiselect("getLabels")[0]).find("input"); $label.click(); assert.equal($label.find("input").prop("checked"), true, "the input for that label should be checked"); assert.equal($wrongInput.prop("checked"), false, "the input for the corresponding label on the first widget should not be checked"); diff --git a/tests/unit/options.js b/tests/unit/options.js index 3a4c242..2e793b8 100644 --- a/tests/unit/options.js +++ b/tests/unit/options.js @@ -89,7 +89,7 @@ selectedMax: 2 }); - checkboxes = el.multiselect("widget").find(":checkbox"); + var checkboxes = el.multiselect("widget").find(":checkbox"); checkboxes.eq(0).trigger('click'); checkboxes.eq(1).trigger('click'); checkboxes.eq(2).trigger('click'); @@ -146,7 +146,7 @@ var height = 100; el = $("select").multiselect({ height: height }).multiselect("open"); - assert.equal(height, menu().find("ul.ui-multiselect-checkboxes").height(), 'height after opening property set to ' + height); + assert.equal(height, menu().find(".ui-multiselect-checkboxes").height(), 'height after opening property set to ' + height); // change height and re-test height = 300; @@ -159,30 +159,30 @@ QUnit.test("minWidth", function (assert) { var minWidth = 321; - el = $("select").multiselect({ minWidth: minWidth }).multiselect("open"); + el = $("select").multiselect({ buttonWidth: '>=' + minWidth }).multiselect("open"); assert.equal(minWidth, button().outerWidth(), 'outerWidth of button is ' + minWidth); - // change height and re-test + // change width and re-test minWidth = 351; - el.multiselect("option", "minWidth", minWidth); + el.multiselect("option", "buttonWidth", '>=' + minWidth); assert.equal(minWidth, button().outerWidth(), 'changing value through api to ' + minWidth); - // change height to something that should fail. + // change width to something that should fail. minWidth = 10; - el.multiselect("option", "minWidth", minWidth); + el.multiselect("option", "buttonWidth", '>=' + minWidth); var outerWidth = button().outerWidth(); assert.ok(minWidth !== outerWidth, 'changing value through api to ' + minWidth + ' (too small), outerWidth is actually ' + outerWidth); // Reference: https://www.wired.com/2010/12/why-percentage-based-designs-dont-work-in-every-browser/ minWidth = "50%"; - el.multiselect("option", "minWidth", minWidth); + el.multiselect("option", "buttonWidth", '>=' + minWidth); var outerWidthX2 = Math.floor(button().outerWidth() * 2); // Double to reduce chance of fractions var parentWidth = Math.floor(el.parent().outerWidth()); assert.ok(Math.abs(outerWidthX2 - parentWidth) <= 1, 'changing value to 50%'); // Off by 1 is assert.ok due to floating point rounding discrepancies between browsers. minWidth = "351px"; - el.multiselect("option", "minWidth", minWidth); - assert.equal(351, button().outerWidth(), 'minWidth supports strings suffixed with px as well as integer px values'); + el.multiselect("option", "buttonWidth", '>=' + minWidth); + assert.equal(351, button().outerWidth(), 'buttonWidth supports strings suffixed with px as well as integer px values'); el.multiselect("destroy"); }); @@ -194,11 +194,15 @@ assert.equal(menu().parent().find(".ui-multiselect-menu").outerWidth(), width, 'width after opening, property set to ' + width); - // change height and re-test + // change width and re-test width = 300; el.multiselect("option", "menuWidth", width).multiselect('refresh'); assert.equal(menu().parent().find(".ui-multiselect-menu").outerWidth(), width, 'changing value through api to ' + width); + width = "3in"; + el.multiselect("option", "menuWidth", width).multiselect('refresh'); + assert.equal(menu().parent().find(".ui-multiselect-menu").outerWidth(), 3 * 96.0, 'menuWidth supports strings suffixed with various units as well as integer px values'); + el.multiselect("destroy"); }); From 25e35914a27c62cfa7c92339f51a90a2aaaae5f0 Mon Sep 17 00:00:00 2001 From: Steve James <4stevejames@gmail.com> Date: Mon, 5 Feb 2018 21:58:48 -0800 Subject: [PATCH 3/6] Add support for native select placeholder text --- src/jquery.multiselect.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/jquery.multiselect.js b/src/jquery.multiselect.js index 284e508..9003b0c 100644 --- a/src/jquery.multiselect.js +++ b/src/jquery.multiselect.js @@ -43,7 +43,7 @@ checkAllText: 'Check all', // (str | blank | null) If blank, only icon shown. If null, no icon, text or link is shown. uncheckAllText: 'Uncheck all', // (str | blank | null) If blank, only icon shown. If null, no icon, text or link is shown. flipAllText: null, // (str | blank | null) If blank, only icon shown. If null, no icon, text or link is shown. - noneSelectedText: 'Select options', // (str) The text to show in the button where nothing is selected. + noneSelectedText: 'Select options', // (str | null) The text to show in the button where nothing is selected. Set to null to use the native select's placeholder text. selectedText: '# of # selected', // (str) A "template" that indicates how to show the count of selections in the button. The "#'s" are replaced by the selection count & option count. selectedList: 0, // (int) The actual list selections will be shown in the button when the count of selections is <= than this number. selectedMax: null, // (int | function) If selected count > selectedMax or if function returns 1, then message is displayed, and new selection is undone. @@ -137,7 +137,7 @@ .html('' + iconSet.open + ''); // Necessary to simplify dynamically changing the open icon. this.$buttonlabel = $( document.createElement('span') ) - .html(options.noneSelectedText) + .html(options.noneSelectedText || $element[0].placeholder || 'Select options') .appendTo( $button ); // Header controls, will contain the check all/uncheck all buttons From 529a6dff9c03f295772fa8b7644d403624ef0f06 Mon Sep 17 00:00:00 2001 From: Steve James <4stevejames@gmail.com> Date: Tue, 6 Feb 2018 16:39:03 -0800 Subject: [PATCH 4/6] Requested changes --- src/jquery.multiselect.js | 272 +++++++++++++++++++++++++------------- 1 file changed, 179 insertions(+), 93 deletions(-) diff --git a/src/jquery.multiselect.js b/src/jquery.multiselect.js index 9003b0c..f6f6fc9 100644 --- a/src/jquery.multiselect.js +++ b/src/jquery.multiselect.js @@ -35,7 +35,7 @@ // default options options: { header: true, // (true | false) If true, the header is shown. - height: 175, // (int | 'size' | 'auto') Sets the height of the menu in pixels or determines it using native select's size setting. + height: 175, // (int | 'str' | 'auto' | 'size') Sets the height of the menu in pixels or determines it using native select's size setting. buttonWidth: 225, // (int | str | 'auto' | null) Sets the min/max/exact width of the button. menuWidth: null, // (int | str | 'auto' | null) If a number is provided, sets the exact menu width. classes: '', // Classes that you can provide to be applied to the elements making up the widget. @@ -56,7 +56,7 @@ htmlButtonText: false, // (true | false) If true, then the text used for the button's label is treated as html rather than plain text. htmlOptionText: false, // (true | false) If true, then the text for option label is treated as html rather than plain text. addInputNames: true, // (true | false) If true, names are created for each option input in the multi-select. - wrapText: 'button', // (list of button, header, &/or menu) Comma separated list defining what parts of the widget to wrap text for. + wrapText: 'button,header,menu', // (list of button, header, &/or menu) Comma separated list defining what parts of the widget to wrap text for. disableInputsOnToggle: true, // (true | false) groupColumns: false // (true | false) Displays groups in a horizonal column layout. }, @@ -68,6 +68,7 @@ * 3. If still do not have a valid DOM element to append to, then append to the document body. * * NOTE: this.element and this.document are jQuery objects per the jQuery UI widget API. + * @returns {object} jQuery object to append to or document body. */ _getAppendEl: function() { var elem = this.options.appendTo; // jQuery object or selector, DOM element or null. @@ -75,11 +76,11 @@ if (elem) { // NOTE: The find below handles the jQuery selector case elem = !!elem.jquery ? elem : ( !!elem.nodeType ? $(elem) : this.document.find(elem).eq(0) ); } - if(!elem || !elem[0]) { + if (!elem || !elem[0]) { elem = this.element.closest(".ui-front, dialog"); } - if(!elem.length) { - elem = this.document[0].body; // Position at end of body. Note that this returns a DOM element. + if (!elem.length) { + elem = document.body; // Position at end of body. Note that this returns a DOM element. } return elem; }, @@ -97,9 +98,9 @@ * - Calls refresh to populate the menu */ _create: function() { - this._selectWidth = this.element[0].getBoundingClientRect().width; - var $element = this.element.hide(); - var elSelect = $element.get(0); + + var $element = this.element; + var elSelect = $element[0]; var options = this.options; var classes = options.classes; var headerOn = options.header; @@ -110,6 +111,10 @@ var flipAllText = options.flipAllText; var wrapText = options.wrapText || ''; + // grab select width before hiding it + this._selectWidth = elSelect.getBoundingClientRect().width; + $element.hide(); + // default speed for effects this.speed = $.fx.speeds._default; this._isOpen = false; @@ -137,7 +142,7 @@ .html('' + iconSet.open + ''); // Necessary to simplify dynamically changing the open icon. this.$buttonlabel = $( document.createElement('span') ) - .html(options.noneSelectedText || $element[0].placeholder || 'Select options') + .html(options.noneSelectedText || $element[0].placeholder) .appendTo( $button ); // Header controls, will contain the check all/uncheck all buttons @@ -191,18 +196,23 @@ _init: function() { var elSelect = this.element.get(0); - if (this.options.header) + if (this.options.header) { this.$headerLinkContainer .find('.ui-multiselect-all, .ui-multiselect-none, .ui-multiselect-flip') .toggle( !!elSelect.multiple ); - else + } + else { this.$header.hide(); + } - if(this.options.autoOpen) + if (this.options.autoOpen) { this.open(); + } - if(elSelect.disabled) + if (elSelect.disabled) { this.disable(); + } + }, /** @@ -250,15 +260,17 @@ var len = optionAttribs.length; for (var x = 0; x < len; x++) { var attribute = optionAttribs[x]; - if ( /^data\-.+/.test(attribute.name) ) - input.setAttribute(attribute.name, attribute.value) + if ( /^data\-.+/.test(attribute.name) ) { + input.setAttribute(attribute.name, attribute.value); + } } // Option text or html var span = document.createElement('span'); if (self.options.htmlOptionText) { span.innerHTML = option.innerHTML; - } else { + } + else { span.textContent = option.textContent; } @@ -292,19 +304,19 @@ /** * Processes option and optgroup tags from underlying select to construct the menu's option list - * This clears the items currently in $checkboxes + * This clears the items currently in this.$checkboxes * Defers to _makeOption to actually build the options * Resets the input ID counter - * @param {object} $element element whose option/group tags need to be converted - * @param {object} $checkboxes widget's container to append the built options to + + */ - _buildOptionList: function($element, $checkboxes) { + _buildOptionList: function() { var self = this; var list = []; this.inputIdCounter = 0; - $element.children().each( function() { + this.element.children().each( function() { var elem = this; if (elem.tagName === 'OPTGROUP') { @@ -329,7 +341,7 @@ } }); - $checkboxes.empty().append(list); + this.$checkboxes.empty().append(list); }, /** @@ -343,20 +355,21 @@ var $element = this.element; // update header link container visibility if needed - if (this.options.header) + if (this.options.header) { this.$headerLinkContainer .find('.ui-multiselect-all, .ui-multiselect-none, .ui-multiselect-flip') .toggle( !!$element[0].multiple ); - - this._buildOptionList($element, this.$checkboxes); // Clear and rebuild the menu. + } + this._buildOptionList(); // Clear and rebuild the menu. this._updateCache(); // cache some more useful elements this._setButtonWidth(); this.update(true); // broadcast refresh event; useful for widgets - if (!init) + if (!init) { this._trigger('refresh'); + } }, /** @@ -397,24 +410,30 @@ var value; if (numChecked) { - if (typeof selectedText === 'function') + if (typeof selectedText === 'function') { value = selectedText.call(self, numChecked, inputCount, $checked.get()); - else if(/\d/.test(selectedList) && selectedList > 0 && numChecked <= selectedList) + } + else if (/\d/.test(selectedList) && selectedList > 0 && numChecked <= selectedList) { value = $checked.map(function() { return $(this).next().text() }).get().join(options.selectedListSeparator); - else + } + else { value = selectedText.replace('#', numChecked).replace('#', inputCount); + } } - else + else { value = options.noneSelectedText; + } self._setButtonValue(value, isDefault); - if ( !/\bbutton\b/.test( options.wrapText ) ) + if ( !/\bbutton\b/.test( options.wrapText ) ) { this._setButtonWidth(true); + } // Check if the menu needs to be repositioned due to button height changing from adding/removing selections. - if (self._isOpen && self._savedButtonHeight != self.$button.outerHeight(false)) + if (self._isOpen && self._savedButtonHeight != self.$button.outerHeight(false)) { self._position(true); + } }, /** @@ -425,8 +444,9 @@ _setButtonValue: function(value, isDefault) { this.$buttonlabel[this.options.htmlButtonText ? 'html' : 'text'](value); - if (!!isDefault) + if (!!isDefault) { this.$button[0].defaultValue = value; + } }, /** @@ -458,7 +478,7 @@ } }, mouseenter: function() { - if(!$button.hasClass('ui-state-disabled')) { + if (!$button.hasClass('ui-state-disabled')) { $button.addClass('ui-state-hover'); } }, @@ -466,7 +486,7 @@ $button.removeClass('ui-state-hover'); }, focus: function() { - if(!$button.hasClass('ui-state-disabled')) { + if (!$button.hasClass('ui-state-disabled')) { $button.addClass('ui-state-focus'); } }, @@ -495,7 +515,7 @@ var label = this.textContent; // trigger before callback and bail if the return is false - if(self._trigger('beforeoptgrouptoggle', e, { inputs:nodes, label:label }) === false) { + if (self._trigger('beforeoptgrouptoggle', e, { inputs:nodes, label:label }) === false) { return; } @@ -512,7 +532,7 @@ }); }) .on('mouseenter.multiselect', 'label', function() { - if(!$(this).hasClass('ui-state-disabled')) { + if (!$(this).hasClass('ui-state-disabled')) { self.$labels.removeClass('ui-state-hover'); $(this).addClass('ui-state-hover').find('input').focus(); } @@ -520,21 +540,24 @@ // Keyboard navigation of the menu .on('keydown.multiselect', 'label', function(e) { // Don't capture function keys or 'r' - if(e.which === 82) + if (e.which === 82) { return; // r + } - if(e.which > 111 && e.which < 124) + if (e.which > 111 && e.which < 124) { return; // Function keys. + } e.preventDefault(); switch(e.which) { case 9: // tab - if(e.shiftKey) { + if (e.shiftKey) { self.$menu.find(".ui-state-hover").removeClass("ui-state-hover"); self.$header.find("li").last().find("a").focus(); } - else + else { self.close(); + } break; case 27: // esc self.close(); @@ -550,12 +573,14 @@ $(this).find('input')[0].click(); break; case 65: // Ctrl-A - if (e.altKey) + if (e.altKey) { self.checkAll(); + } break; case 85: // Ctrl-U - if (e.altKey) + if (e.altKey) { self.uncheckAll(); + } break; } }) @@ -577,7 +602,7 @@ var selectedMax = options.selectedMax; // bail if this input is disabled or the event is cancelled - if(input.disabled || self._trigger('click', e, { value: val, text: optionText, checked: checked }) === false) { + if (input.disabled || self._trigger('click', e, { value: val, text: optionText, checked: checked }) === false) { e.preventDefault(); return; } @@ -612,7 +637,7 @@ }); // some additional single select-specific logic - if(!isMultiple) { + if (!isMultiple) { self.$labels.removeClass('ui-state-active'); $input.closest('label').toggleClass('ui-state-active', checked); @@ -662,7 +687,7 @@ break; case 9: var $target = $(e.target); - if((e.shiftKey + if ((e.shiftKey && !$target.parent().prev().length && !self.$header.find(".ui-multiselect-filter").length) || (!$target.parent().next().length && !self.$labels.length && !e.shiftKey)) { @@ -690,8 +715,9 @@ var button = self.$button.get(0); var menu = self.$menu.get(0); - if ( self._isOpen && button !== target && !$.contains(button, target) && menu !== target && !$.contains(menu, target) ) + if ( self._isOpen && button !== target && !$.contains(button, target) && menu !== target && !$.contains(menu, target) ) { self.close(); + } }); // deal with form resets. the problem here is that buttons aren't @@ -714,11 +740,15 @@ * - '1.3em' * - '20 pt' * - '30%' + * @param {string} dimText Option text (or number) containing possibly < or >, number, and a unit. + * @param {object} $elem jQuery object (or node) to reference for % calculations. + * @param {boolean} isHeight T/F to change from using width in % calculations. * @returns {pixels, minimax} object containing pixels and -1/1/0 indicating min/max/exact. */ _parse2px: function(dimText, $elem, isHeight) { - if (typeof dimText !== 'string') + if (typeof dimText !== 'string') { return {px: dimText, minimax: 0}; + } var parts = dimText.match(/([<>])?=?\s*([.\d]+)\s*([eimnptx%]*)s?/i); var minimax = parts[1]; @@ -740,8 +770,9 @@ break; case '%': if ( !!$elem ) { - if (typeof $elem === 'string' || !$elem.jquery) + if (typeof $elem === 'string' || !$elem.jquery) { $elem = $($elem); + } pixels = ( !!isHeight ? $elem.parent().height() : $elem.parent().width() ) * (value / 100.0); } // else returns -1 default value from above. break; @@ -759,8 +790,9 @@ * @param {boolean} recalc true if cached value needs to be re-calculated */ _setButtonWidth: function(recalc) { - if (this._savedButtonWidth && !recalc) + if (this._savedButtonWidth && !recalc) { return; + } this._positioned = false; @@ -773,14 +805,17 @@ var minimax = parsed.minimax; width = minimax < 0 ? Math.max(width, pixels) : ( minimax > 0 ? Math.min(width, pixels) : pixels ); } - else // keywords + else { // keywords buttonWidth = buttonWidth.toLowerCase(); + } this._savedButtonWidth = width; - if (buttonWidth === 'auto') + if (buttonWidth === 'auto') { this.$button.css('width', 'auto'); - else + } + else { this.$button.outerWidth(width); + } }, /** @@ -790,8 +825,9 @@ * @param {boolean} recalc true if cached value needs to be re-calculated */ _setMenuWidth: function(recalc) { - if (this._savedMenuWidth && !recalc) + if (this._savedMenuWidth && !recalc) { return; + } this._positioned = false; @@ -805,8 +841,9 @@ var minimax = parsed.minimax; width = minimax < 0 ? Math.max(width, pixels) : ( minimax > 0 ? Math.min(width, pixels) : pixels ); } - else // keywords + else { // keywords menuWidth = menuWidth.toLowerCase(); + } // Note that the menu width defaults to the button width if menuWidth option is null or blank. if (menuWidth !== 'auto') { @@ -843,8 +880,9 @@ */ _setMenuHeight: function(recalc) { var self = this; - if (self._ulHeight && !recalc) + if (self._ulHeight && !recalc) { return; + } self._positioned = false; var $menu = self.$menu; @@ -895,46 +933,80 @@ self._ulHeight = ulHeight; }, - // Calculate accurate outerWidth(false) using getBoundingClientRect() + + /** + * Calculate accurate outerWidth(false) using getBoundingClientRect() + * Note that this presumes that the element is visible in the layout. + * @param {node} DOM node or jQuery equivalent get width for. + * @returns {float} Decimal floating point value for the width. + */ _getBCRWidth: function(elem) { - if (!elem || !!elem.jquery && !elem[0]) return null; + if (!elem || !!elem.jquery && !elem[0]) { + return null; + } var domRect = !!elem.jquery ? elem[0].getBoundingClientRect() : elem.getBoundingClientRect(); return domRect.right - domRect.left; }, - // Calculate accurate outerHeight(false) using getBoundingClientRect() + /** + * Calculate accurate outerHeight(false) using getBoundingClientRect() + * Note that this presumes that the element is visible in the layout. + * @param {node} DOM node or jQuery equivalent get height for. + * @returns {float} Decimal floating point value for the height. + */ _getBCRHeight: function(elem) { - if (!elem || !!elem.jquery && !elem[0]) return null; + if (!elem || !!elem.jquery && !elem[0]) { + return null; + } var domRect = !!elem.jquery ? elem[0].getBoundingClientRect() : elem.getBoundingClientRect(); return domRect.bottom - domRect.top; }, - // jQuery width correction factor to fix floating point round-off errors. + /** + * Calculate jQuery width correction factor to fix floating point round-off errors. + * Note that this presumes that the element is visible in the layout. + * @param {node} DOM node or jQuery equivalent get width for. + * @returns {float} Correction value for the width--typically a decimal < 1.0 + */ _jqWidthFix: function(elem) { - if (!elem || !!elem.jquery && !elem[0]) return null; + if (!elem || !!elem.jquery && !elem[0]) { + return null; + } return !!elem.jquery ? this._getBCRWidth(elem[0]) - elem.outerWidth(false) : this._getBCRWidth(elem) - $(elem).outerWidth(false); }, - // jQuery height correction factor to fix floating point round-off errors. + /** + * Calculate jQuery height correction factor to fix floating point round-off errors. + * Note that this presumes that the element is visible in the layout. + * @param {node} DOM node or jQuery equivalent get height for. + * @returns {float} Correction value for the height--typically a decimal < 1.0 + */ _jqHeightFix: function(elem) { - if (!elem || !!elem.jquery && !elem[0]) return null; + if (!elem || !!elem.jquery && !elem[0]) { + return null; + } return !!elem.jquery ? this._getBCRHeight(elem[0]) - elem.outerHeight(false) : this._getBCRHeight(elem) - $(elem).outerHeight(false); }, - // Determines scroll bar width for automatic width determinations. - // Only needs to be ran once--width saved for all instances. + /** + * Determines scroll bar width for automatic width determinations. + * Only needs to be ran once--width saved for all instances. + * @returns {integer} width of the scroll bar. + */ _getScrollBarWidth: function() { - if (_scrollbarWidth) + if (_scrollbarWidth) { return _scrollbarWidth; - - if ($.ui && $.ui.position) + } + if ($.ui && $.ui.position) { _scrollbarWidth = $.position.scrollbarWidth(); - if (_scrollbarWidth) + } + if (_scrollbarWidth) { return _scrollbarWidth; + } // https://davidwalsh.name/detect-scrollbar-width // Create the measurement node @@ -985,10 +1057,12 @@ // set scroll position $container.scrollTop(moveToLast ? $container.height() : 0); - } else { + } + else { $next.find('label').filter(':visible')[ moveToLast ? "last" : "first" ]().trigger('mouseover'); } }, + /** * Internal function to toggle checked property and related attributes on a checkbox * The context of this function should be a checkbox; do not proxy it. @@ -999,22 +1073,22 @@ return function() { var state = (flag === '!') ? !this[prop] : flag; - if( !this.disabled ) + if ( !this.disabled ) { this[ prop ] = state; + } - if (state) + if (state) { this.setAttribute('aria-' + prop, true); - else + } + else { this.removeAttribute('aria-' + prop); - + } }; }, /** * Toggles the checked state on options within the menu * Potentially scoped down to visible elements from filteredInputs - * Groups don't show up in the filter search right now, so both - * group and filteredOptions won't apply at the same time * @param {string} flag checked property to set * @param {object} group option group that was clicked, if any * @param {array} filteredInputs visible elements with the filter applied @@ -1043,7 +1117,7 @@ $element[0].selectedIndex = -1; $element.find('option') .each( function() { - if(!this.disabled && values[this.value]) { + if (!this.disabled && values[this.value]) { self._toggleState('selected', flag).call(this); } }); @@ -1077,7 +1151,8 @@ matchedInputs[i].setAttribute("aria-disabled", "disabled"); matchedInputs[i].parentNode.className = matchedInputs[i].parentNode.className + " ui-state-disabled"; } - } else { + } + else { matchedInputs = checkboxes.querySelectorAll("input:disabled"); for (i = 0; i < matchedInputs.length; i++) { if (matchedInputs[i].hasAttribute(key)) { @@ -1104,7 +1179,7 @@ var $button = this.$button; // bail if the multiselect open event returns false, this widget is disabled, or is already open - if(this._trigger('beforeopen') === false || $button.hasClass('ui-state-disabled') || this._isOpen) { + if (this._trigger('beforeopen') === false || $button.hasClass('ui-state-disabled') || this._isOpen) { return; } @@ -1126,10 +1201,12 @@ // show the menu, maybe with a speed/effect combo // if there's an effect, assume jQuery UI is in use - if (effect) + if (effect) { $.fn.show.apply($menu, effect ? [ effect, speed ] : []); - else + } + else { $menu.css('display','block'); + } this._resizeMenu(); this._position(); @@ -1138,9 +1215,11 @@ var filter = $header.find(".ui-multiselect-filter"); if (filter.length) { filter.first().find('input').trigger('focus'); - } else if ($labels.length) { + } + else if ($labels.length) { $labels.filter(':not(.ui-state-disabled)').eq(0).trigger('mouseover').trigger('mouseenter').find('input').trigger('focus'); - } else { + } + else { $header.find('a').first().trigger('focus'); } @@ -1154,8 +1233,9 @@ var self = this; // bail if the multiselect close event returns false - if (this._trigger('beforeclose') === false) + if (this._trigger('beforeclose') === false) { return; + } var options = this.options; var effect = options.hide; @@ -1170,10 +1250,12 @@ // hide the menu, maybe with a speed/effect combo // if there's an effect, assume jQuery UI is in use - if (effect) + if (effect) { $.fn.hide.apply(this.$menu, effect ? [ effect, speed ] : []); - else + } + else { this.$menu.css('display','none'); + } $button.removeClass('ui-state-active').trigger('blur').trigger('mouseleave'); this._isOpen = false; @@ -1198,9 +1280,10 @@ uncheckAll: function() { this._toggleChecked(false); - if ( !this.element[0].multiple ) + if ( !this.element[0].multiple ) { // Forces the underlying single-select to have no options selected. this.element[0].selectedIndex = -1; + } this._trigger('uncheckAll'); }, @@ -1314,8 +1397,9 @@ * @param {string} value attribute corresponding to option being removed */ removeOption: function(value) { - if (!value) + if (!value) { return; + } this.element.find("option[value=" + value + "]").remove(); this.$labels.find("input[value=" + value + "]").parents("li").remove(); @@ -1334,17 +1418,18 @@ * @param {boolean} reposition forces the menu to reposition if true */ _position: function(reposition) { - if (!!this._positioned && !reposition) + if (!!this._positioned && !reposition) { return; - + } var $button = this.$button; // Save this so that we can determine when the button height has changed due adding/removing selections. this._savedButtonHeight = this.$button.outerHeight(false); var pos = $.extend({'my': 'left top', 'at': 'left bottom', 'of': $button}, this.options.position || {}); - if($.ui && $.ui.position) + if ($.ui && $.ui.position) { this.$menu.position(pos); + } else { pos = $button.position(); pos.top += this._savedButtonHeight; @@ -1364,8 +1449,9 @@ switch(key) { case 'header': - if (typeof value === 'boolean') + if (typeof value === 'boolean') { this.$header.toggle( value ); + } else if (typeof value === 'string') { this.$headerLinkContainer.children('li:not(:last-child)').remove(); this.$headerLinkContainer.prepend('
  • ' + value + '
  • '); From 5b514cdb34697413237db24acb1444882f8d0f1c Mon Sep 17 00:00:00 2001 From: Steve James <4stevejames@gmail.com> Date: Tue, 6 Feb 2018 16:40:42 -0800 Subject: [PATCH 5/6] Changes requested + buttonWidth vs minWidth --- tests/unit/options.js | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/unit/options.js b/tests/unit/options.js index 2e793b8..cb2889f 100644 --- a/tests/unit/options.js +++ b/tests/unit/options.js @@ -156,32 +156,32 @@ el.multiselect("destroy"); }); - QUnit.test("minWidth", function (assert) { - var minWidth = 321; + QUnit.test("buttonWidth", function (assert) { + var buttonWidth = 321; - el = $("select").multiselect({ buttonWidth: '>=' + minWidth }).multiselect("open"); - assert.equal(minWidth, button().outerWidth(), 'outerWidth of button is ' + minWidth); + el = $("select").multiselect({ buttonWidth: '>=' + buttonWidth }).multiselect("open"); + assert.equal(buttonWidth, button().outerWidth(), 'outerWidth of button is ' + buttonWidth); // change width and re-test - minWidth = 351; - el.multiselect("option", "buttonWidth", '>=' + minWidth); - assert.equal(minWidth, button().outerWidth(), 'changing value through api to ' + minWidth); + buttonWidth = 351; + el.multiselect("option", "buttonWidth", '>=' + buttonWidth); + assert.equal(buttonWidth, button().outerWidth(), 'changing value through api to ' + buttonWidth); // change width to something that should fail. - minWidth = 10; - el.multiselect("option", "buttonWidth", '>=' + minWidth); + buttonWidth = 10; + el.multiselect("option", "buttonWidth", '>=' + buttonWidth); var outerWidth = button().outerWidth(); - assert.ok(minWidth !== outerWidth, 'changing value through api to ' + minWidth + ' (too small), outerWidth is actually ' + outerWidth); + assert.ok(buttonWidth !== outerWidth, 'changing value through api to ' + buttonWidth + ' (too small), outerWidth is actually ' + outerWidth); // Reference: https://www.wired.com/2010/12/why-percentage-based-designs-dont-work-in-every-browser/ - minWidth = "50%"; - el.multiselect("option", "buttonWidth", '>=' + minWidth); + buttonWidth = "50%"; + el.multiselect("option", "buttonWidth", '>=' + buttonWidth); var outerWidthX2 = Math.floor(button().outerWidth() * 2); // Double to reduce chance of fractions var parentWidth = Math.floor(el.parent().outerWidth()); assert.ok(Math.abs(outerWidthX2 - parentWidth) <= 1, 'changing value to 50%'); // Off by 1 is assert.ok due to floating point rounding discrepancies between browsers. - minWidth = "351px"; - el.multiselect("option", "buttonWidth", '>=' + minWidth); + buttonWidth = "351px"; + el.multiselect("option", "buttonWidth", '>=' + buttonWidth); assert.equal(351, button().outerWidth(), 'buttonWidth supports strings suffixed with px as well as integer px values'); el.multiselect("destroy"); @@ -190,7 +190,7 @@ QUnit.test("menuWidth", function (assert) { var width = 50; - el = $("select").multiselect({ minWidth: 100, menuWidth: width }).multiselect("open"); + el = $("select").multiselect({ buttonWidth: 100, menuWidth: width }).multiselect("open"); assert.equal(menu().parent().find(".ui-multiselect-menu").outerWidth(), width, 'width after opening, property set to ' + width); @@ -201,7 +201,7 @@ width = "3in"; el.multiselect("option", "menuWidth", width).multiselect('refresh'); - assert.equal(menu().parent().find(".ui-multiselect-menu").outerWidth(), 3 * 96.0, 'menuWidth supports strings suffixed with various units as well as integer px values'); + assert.equal(menu().parent().find(".ui-multiselect-menu").outerWidth(), 3 * 96.0, 'menuWidth supports strings suffixed with "in" unit as well as integer "px" values'); el.multiselect("destroy"); }); From 812694ec7d560f31be184e91328e7f3829a0ef8d Mon Sep 17 00:00:00 2001 From: Steve James <4stevejames@gmail.com> Date: Wed, 7 Feb 2018 00:51:12 -0800 Subject: [PATCH 6/6] Fix an oversight --- src/jquery.multiselect.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/jquery.multiselect.js b/src/jquery.multiselect.js index f6f6fc9..29dfb60 100644 --- a/src/jquery.multiselect.js +++ b/src/jquery.multiselect.js @@ -294,9 +294,8 @@ label.appendChild(span); var item = document.createElement('li'); - item.className += (isDisabled ? ' ui-multiselect-disabled' : '') - + (' ' + option.className || '') - + ' ui-multiselect-nowrap'; + item.className = (isDisabled ? 'ui-multiselect-disabled ' : '') + + (option.className || ''); item.appendChild(label); return item;