From 1ec68d748ba3c313df8c17556c16945c3221bbde Mon Sep 17 00:00:00 2001 From: Johann-S Date: Wed, 14 Jun 2017 10:29:09 +0200 Subject: [PATCH 1/7] Upgrade QUnit to 2.3.3 --- js/tests/vendor/qunit.css | 4 +- js/tests/vendor/qunit.js | 563 ++++++++++++++++++++++---------------- 2 files changed, 326 insertions(+), 241 deletions(-) diff --git a/js/tests/vendor/qunit.css b/js/tests/vendor/qunit.css index 75d8b6279f..7a46935334 100644 --- a/js/tests/vendor/qunit.css +++ b/js/tests/vendor/qunit.css @@ -1,12 +1,12 @@ /*! - * QUnit 2.3.2 + * QUnit 2.3.3 * https://qunitjs.com/ * * Copyright jQuery Foundation and other contributors * Released under the MIT license * https://jquery.org/license * - * Date: 2017-04-18T02:19Z + * Date: 2017-06-02T14:07Z */ /** Font Family and Sizes */ diff --git a/js/tests/vendor/qunit.js b/js/tests/vendor/qunit.js index fa728db5b6..3cda99631a 100644 --- a/js/tests/vendor/qunit.js +++ b/js/tests/vendor/qunit.js @@ -1,12 +1,12 @@ /*! - * QUnit 2.3.2 + * QUnit 2.3.3 * https://qunitjs.com/ * * Copyright jQuery Foundation and other contributors * Released under the MIT license * https://jquery.org/license * - * Date: 2017-04-18T02:19Z + * Date: 2017-06-02T14:07Z */ (function (global$1) { 'use strict'; @@ -14,6 +14,7 @@ global$1 = 'default' in global$1 ? global$1['default'] : global$1; var window = global$1.window; + var self$1 = global$1.self; var console = global$1.console; var setTimeout = global$1.setTimeout; var clearTimeout = global$1.clearTimeout; @@ -238,6 +239,27 @@ return objectType(obj) === type; } + // Based on Java's String.hashCode, a simple but not + // rigorously collision resistant hashing function + function generateHash(module, testName) { + var str = module + "\x1C" + testName; + var hash = 0; + + for (var i = 0; i < str.length; i++) { + hash = (hash << 5) - hash + str.charCodeAt(i); + hash |= 0; + } + + // Convert the possibly negative integer hash code into an 8 character hex string, which isn't + // strictly necessary but increases user understanding that the id is a SHA-like hash + var hex = (0x100000000 + hash).toString(16); + if (hex.length < 8) { + hex = "0000000" + hex; + } + + return hex.slice(-8); + } + // Test for equality any JavaScript type. // Authors: Philippe Rathé , David Chan var equiv = (function () { @@ -608,21 +630,19 @@ // Set of all modules. modules: [], - // Stack of nested modules - moduleStack: [], - // The first unnamed module currentModule: { name: "", tests: [], childModules: [], - testsRun: 0 + testsRun: 0, + unskippedTestsRun: 0 }, callbacks: {}, // The storage module to use for reordering tests - storage: sessionStorage + storage: localSessionStorage }; // take a predefined QUnit.config and extend the defaults @@ -1064,6 +1084,135 @@ return extractStacktrace(error, offset); } + var priorityCount = 0; + var unitSampler = void 0; + + /** + * Advances the ProcessingQueue to the next item if it is ready. + * @param {Boolean} last + */ + function advance() { + var start = now(); + config.depth = (config.depth || 0) + 1; + + while (config.queue.length && !config.blocking) { + var elapsedTime = now() - start; + + if (!defined.setTimeout || config.updateRate <= 0 || elapsedTime < config.updateRate) { + if (priorityCount > 0) { + priorityCount--; + } + + config.queue.shift()(); + } else { + setTimeout(advance, 13); + break; + } + } + + config.depth--; + + if (!config.blocking && !config.queue.length && config.depth === 0) { + done(); + } + } + + function addToQueueImmediate(callback) { + if (objectType(callback) === "array") { + while (callback.length) { + addToQueueImmediate(callback.pop()); + } + + return; + } + + config.queue.unshift(callback); + priorityCount++; + } + + /** + * Adds a function to the ProcessingQueue for execution. + * @param {Function|Array} callback + * @param {Boolean} priority + * @param {String} seed + */ + function addToQueue(callback, prioritize, seed) { + if (prioritize) { + config.queue.splice(priorityCount++, 0, callback); + } else if (seed) { + if (!unitSampler) { + unitSampler = unitSamplerGenerator(seed); + } + + // Insert into a random position after all prioritized items + var index = Math.floor(unitSampler() * (config.queue.length - priorityCount + 1)); + config.queue.splice(priorityCount + index, 0, callback); + } else { + config.queue.push(callback); + } + } + + /** + * Creates a seeded "sample" generator which is used for randomizing tests. + */ + function unitSamplerGenerator(seed) { + + // 32-bit xorshift, requires only a nonzero seed + // http://excamera.com/sphinx/article-xorshift.html + var sample = parseInt(generateHash(seed), 16) || -1; + return function () { + sample ^= sample << 13; + sample ^= sample >>> 17; + sample ^= sample << 5; + + // ECMAScript has no unsigned number type + if (sample < 0) { + sample += 0x100000000; + } + + return sample / 0x100000000; + }; + } + + /** + * This function is called when the ProcessingQueue is done processing all + * items. It handles emitting the final run events. + */ + function done() { + var storage = config.storage; + + ProcessingQueue.finished = true; + + var runtime = now() - config.started; + var passed = config.stats.all - config.stats.bad; + + emit("runEnd", globalSuite.end(true)); + runLoggingCallbacks("done", { + passed: passed, + failed: config.stats.bad, + total: config.stats.all, + runtime: runtime + }); + + // Clear own storage items if all tests passed + if (storage && config.stats.bad === 0) { + for (var i = storage.length - 1; i >= 0; i--) { + var key = storage.key(i); + + if (key.indexOf("qunit-test-") === 0) { + storage.removeItem(key); + } + } + } + } + + var ProcessingQueue = { + finished: false, + add: addToQueue, + addImmediate: addToQueueImmediate, + advance: advance + }; + var TestReport = function () { function TestReport(name, suite, options) { classCallCheck(this, TestReport); @@ -1077,6 +1226,8 @@ this.skipped = !!options.skip; this.todo = !!options.todo; + this.valid = options.valid; + this._startTime = 0; this._endTime = 0; @@ -1149,13 +1300,24 @@ value: function getAssertions() { return this.assertions.slice(); } + + // Remove actual and expected values from assertions. This is to prevent + // leaking memory throughout a test suite. + + }, { + key: "slimAssertions", + value: function slimAssertions() { + this.assertions = this.assertions.map(function (assertion) { + delete assertion.actual; + delete assertion.expected; + return assertion; + }); + } }]); return TestReport; }(); - var unitSampler; var focused = false; - var priorityCount = 0; function Test(settings) { var i, l; @@ -1166,14 +1328,14 @@ extend(this, settings); this.assertions = []; this.semaphore = 0; - this.usedAsync = false; this.module = config.currentModule; this.stack = sourceFromStacktrace(3); this.steps = []; this.testReport = new TestReport(settings.testName, this.module.suiteReport, { todo: settings.todo, - skip: settings.skip + skip: settings.skip, + valid: this.valid() }); // Register unique strings @@ -1187,7 +1349,8 @@ this.module.tests.push({ name: this.testName, - testId: this.testId + testId: this.testId, + skip: !!settings.skip }); if (settings.skip) { @@ -1234,12 +1397,6 @@ config.current = this; - if (module.testEnvironment) { - delete module.testEnvironment.before; - delete module.testEnvironment.beforeEach; - delete module.testEnvironment.afterEach; - delete module.testEnvironment.after; - } this.testEnvironment = extend({}, module.testEnvironment); this.started = now(); @@ -1297,14 +1454,14 @@ test = this; return function runHook() { if (hookName === "before") { - if (hookOwner.testsRun !== 0) { + if (hookOwner.unskippedTestsRun !== 0) { return; } test.preserveEnvironment = true; } - if (hookName === "after" && hookOwner.testsRun !== numberOfTests(hookOwner) - 1) { + if (hookName === "after" && hookOwner.unskippedTestsRun !== numberOfUnskippedTests(hookOwner) - 1 && config.queue.length > 2) { return; } @@ -1334,8 +1491,8 @@ if (module.parentModule) { processHooks(test, module.parentModule); } - if (module.testEnvironment && objectType(module.testEnvironment[handler]) === "function") { - hooks.push(test.queueHook(module.testEnvironment[handler], handler, module)); + if (module.hooks && objectType(module.hooks[handler]) === "function") { + hooks.push(test.queueHook(module.hooks[handler], handler, module)); } } @@ -1378,7 +1535,7 @@ } } - notifyTestsRan(module); + notifyTestsRan(module, skipped); // Store result when possible if (storage) { @@ -1389,7 +1546,11 @@ } } + // After emitting the js-reporters event we cleanup the assertion data to + // avoid leaking it. It is not used by the legacy testDone callbacks. emit("testEnd", this.testReport.end(true)); + this.testReport.slimAssertions(); + runLoggingCallbacks("testDone", { name: testName, module: moduleName, @@ -1409,6 +1570,20 @@ }); if (module.testsRun === numberOfTests(module)) { + logSuiteEnd(module); + + // Check if the parent modules, iteratively, are done. If that the case, + // we emit the `suiteEnd` event and trigger `moduleDone` callback. + var parent = module.parentModule; + while (parent && parent.testsRun === numberOfTests(parent)) { + logSuiteEnd(parent); + parent = parent.parentModule; + } + } + + config.current = undefined; + + function logSuiteEnd(module) { emit("suiteEnd", module.suiteReport.end(true)); runLoggingCallbacks("moduleDone", { name: module.name, @@ -1419,8 +1594,6 @@ runtime: now() - module.stats.started }); } - - config.current = undefined; }, preserveTestEnvironment: function preserveTestEnvironment() { @@ -1431,18 +1604,16 @@ }, queue: function queue() { - var priority, - previousFailCount, - test = this; + var test = this; if (!this.valid()) { return; } - function run() { + function runTest() { // Each of these can by async - synchronize([function () { + ProcessingQueue.addImmediate([function () { test.before(); }, test.hooks("before"), function () { test.preserveTestEnvironment(); @@ -1455,17 +1626,26 @@ }]); } - previousFailCount = config.storage && +config.storage.getItem("qunit-test-" + this.module.name + "-" + this.testName); + var previousFailCount = config.storage && +config.storage.getItem("qunit-test-" + this.module.name + "-" + this.testName); // Prioritize previously failed tests, detected from storage - priority = config.reorder && previousFailCount; + var prioritize = config.reorder && !!previousFailCount; this.previousFailure = !!previousFailCount; - return synchronize(run, priority, config.seed); + ProcessingQueue.add(runTest, prioritize, config.seed); + + // If the queue has already finished, we manually process the new test + if (ProcessingQueue.finished) { + ProcessingQueue.advance(); + } }, + pushResult: function pushResult(resultInfo) { + if (this !== config.current) { + throw new Error("Assertion occured after test had finished."); + } // Destructure of resultInfo = { result, actual, expected, message, negative } var source, @@ -1503,7 +1683,7 @@ throw new Error("pushFailure() assertion outside test context, was " + sourceFromStacktrace(2)); } - this.assert.pushResult({ + this.pushResult({ result: false, message: message || "error", actual: actual || null, @@ -1643,79 +1823,6 @@ return currentTest.pushFailure.apply(currentTest, arguments); } - // Based on Java's String.hashCode, a simple but not - // rigorously collision resistant hashing function - function generateHash(module, testName) { - var hex, - i = 0, - hash = 0, - str = module + "\x1C" + testName, - len = str.length; - - for (; i < len; i++) { - hash = (hash << 5) - hash + str.charCodeAt(i); - hash |= 0; - } - - // Convert the possibly negative integer hash code into an 8 character hex string, which isn't - // strictly necessary but increases user understanding that the id is a SHA-like hash - hex = (0x100000000 + hash).toString(16); - if (hex.length < 8) { - hex = "0000000" + hex; - } - - return hex.slice(-8); - } - - function synchronize(callback, priority, seed) { - var last = !priority, - index; - - if (objectType(callback) === "array") { - while (callback.length) { - synchronize(callback.shift()); - } - return; - } - - if (priority) { - config.queue.splice(priorityCount++, 0, callback); - } else if (seed) { - if (!unitSampler) { - unitSampler = unitSamplerGenerator(seed); - } - - // Insert into a random position after all priority items - index = Math.floor(unitSampler() * (config.queue.length - priorityCount + 1)); - config.queue.splice(priorityCount + index, 0, callback); - } else { - config.queue.push(callback); - } - - if (internalState.autorun && !config.blocking) { - process(last); - } - } - - function unitSamplerGenerator(seed) { - - // 32-bit xorshift, requires only a nonzero seed - // http://excamera.com/sphinx/article-xorshift.html - var sample = parseInt(generateHash(seed), 16) || -1; - return function () { - sample ^= sample << 13; - sample ^= sample >>> 17; - sample ^= sample << 5; - - // ECMAScript has no unsigned number type - if (sample < 0) { - sample += 0x100000000; - } - - return sample / 0x100000000; - }; - } - function saveGlobal() { config.pollution = []; @@ -1888,24 +1995,40 @@ } } - function numberOfTests(module) { - var count = module.tests.length, - modules = [].concat(toConsumableArray(module.childModules)); + function collectTests(module) { + var tests = [].concat(module.tests); + var modules = [].concat(toConsumableArray(module.childModules)); // Do a breadth-first traversal of the child modules while (modules.length) { var nextModule = modules.shift(); - count += nextModule.tests.length; + tests.push.apply(tests, nextModule.tests); modules.push.apply(modules, toConsumableArray(nextModule.childModules)); } - return count; + return tests; } - function notifyTestsRan(module) { + function numberOfTests(module) { + return collectTests(module).length; + } + + function numberOfUnskippedTests(module) { + return collectTests(module).filter(function (test) { + return !test.skip; + }).length; + } + + function notifyTestsRan(module, skipped) { module.testsRun++; + if (!skipped) { + module.unskippedTestsRun++; + } while (module = module.parentModule) { module.testsRun++; + if (!skipped) { + module.unskippedTestsRun++; + } } } @@ -1978,18 +2101,22 @@ }, { key: "async", value: function async(count) { - var test$$1 = this.test, - popped = false, + var test$$1 = this.test; + + var popped = false, acceptCallCount = count; if (typeof acceptCallCount === "undefined") { acceptCallCount = 1; } - test$$1.usedAsync = true; var resume = internalStop(test$$1); return function done() { + if (config.current !== test$$1) { + throw Error("assert.async callback called after test finished."); + } + if (popped) { test$$1.pushFailure("Too many calls to the `assert.async` callback", sourceFromStacktrace(2)); return; @@ -2027,8 +2154,8 @@ value: function pushResult(resultInfo) { // Destructure of resultInfo = { result, actual, expected, message, negative } - var assert = this, - currentTest = assert instanceof Assert && assert.test || config.current; + var assert = this; + var currentTest = assert instanceof Assert && assert.test || config.current; // Backwards compatibility fix. // Allows the direct use of global exported assertions and QUnit.assert.* @@ -2039,12 +2166,6 @@ throw new Error("assertion outside test context, in " + sourceFromStacktrace(2)); } - if (currentTest.usedAsync === true && currentTest.semaphore === 0) { - currentTest.pushFailure("Assertion after the final `assert.async` was resolved", sourceFromStacktrace(2)); - - // Allow this assertion to continue running anyway... - } - if (!(assert instanceof Assert)) { assert = currentTest.assert; } @@ -2181,8 +2302,9 @@ key: "throws", value: function throws(block, expected, message) { var actual = void 0, - result = false, - currentTest = this instanceof Assert && this.test || config.current; + result = false; + + var currentTest = this instanceof Assert && this.test || config.current; // 'expected' is optional unless doing string comparison if (objectType(expected) === "string") { @@ -2306,6 +2428,11 @@ }); QUnit.config.autostart = false; } + + // For Web/Service Workers + if (self$1 && self$1.WorkerGlobalScope && self$1 instanceof self$1.WorkerGlobalScope) { + self$1.QUnit = QUnit; + } } var SuiteReport = function () { @@ -2386,8 +2513,11 @@ var counts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : { passed: 0, failed: 0, skipped: 0, todo: 0, total: 0 }; counts = this.tests.reduce(function (counts, test) { - counts[test.getStatus()]++; - counts.total++; + if (test.valid) { + counts[test.getStatus()]++; + counts.total++; + } + return counts; }, counts); @@ -2451,27 +2581,49 @@ // it since each module has a suiteReport associated with it. config.currentModule.suiteReport = globalSuite; + var moduleStack = []; var globalStartCalled = false; var runStarted = false; - var internalState = { - autorun: false - }; - // Figure out if we're running the tests from a server or not QUnit.isLocal = !(defined.document && window.location.protocol !== "file:"); // Expose the current QUnit version - QUnit.version = "2.2.0"; + QUnit.version = "2.3.3"; + + function createModule(name, testEnvironment) { + var parentModule = moduleStack.length ? moduleStack.slice(-1)[0] : null; + var moduleName = parentModule !== null ? [parentModule.name, name].join(" > ") : name; + var parentSuite = parentModule ? parentModule.suiteReport : globalSuite; + + var module = { + name: moduleName, + parentModule: parentModule, + tests: [], + moduleId: generateHash(moduleName), + testsRun: 0, + unskippedTestsRun: 0, + childModules: [], + suiteReport: new SuiteReport(name, parentSuite) + }; + + var env = {}; + if (parentModule) { + parentModule.childModules.push(module); + extend(env, parentModule.testEnvironment); + } + extend(env, testEnvironment); + module.testEnvironment = env; + + config.modules.push(module); + return module; + } extend(QUnit, { on: on, // Call on start of module test to prepend name to all tests module: function module(name, testEnvironment, executeNow) { - var module, moduleFns; - var currentModule = config.currentModule; - if (arguments.length === 2) { if (objectType(testEnvironment) === "function") { executeNow = testEnvironment; @@ -2479,57 +2631,40 @@ } } - module = createModule(); + var module = createModule(name, testEnvironment); - moduleFns = { + // Move any hooks to a 'hooks' object + if (module.testEnvironment) { + module.hooks = { + before: module.testEnvironment.before, + beforeEach: module.testEnvironment.beforeEach, + afterEach: module.testEnvironment.afterEach, + after: module.testEnvironment.after + }; + + delete module.testEnvironment.before; + delete module.testEnvironment.beforeEach; + delete module.testEnvironment.afterEach; + delete module.testEnvironment.after; + } + + var moduleFns = { before: setHook(module, "before"), beforeEach: setHook(module, "beforeEach"), afterEach: setHook(module, "afterEach"), after: setHook(module, "after") }; + var currentModule = config.currentModule; if (objectType(executeNow) === "function") { - config.moduleStack.push(module); - setCurrentModule(module); + moduleStack.push(module); + config.currentModule = module; executeNow.call(module.testEnvironment, moduleFns); - config.moduleStack.pop(); + moduleStack.pop(); module = module.parentModule || currentModule; } - setCurrentModule(module); - - function createModule() { - var parentModule = config.moduleStack.length ? config.moduleStack.slice(-1)[0] : null; - var moduleName = parentModule !== null ? [parentModule.name, name].join(" > ") : name; - var parentSuite = parentModule ? parentModule.suiteReport : globalSuite; - - var module = { - name: moduleName, - parentModule: parentModule, - tests: [], - moduleId: generateHash(moduleName), - testsRun: 0, - childModules: [], - suiteReport: new SuiteReport(name, parentSuite) - }; - - var env = {}; - if (parentModule) { - parentModule.childModules.push(module); - extend(env, parentModule.testEnvironment); - delete env.beforeEach; - delete env.afterEach; - } - extend(env, testEnvironment); - module.testEnvironment = env; - - config.modules.push(module); - return module; - } - - function setCurrentModule(module) { - config.currentModule = module; - } + config.currentModule = module; }, test: test, @@ -2664,73 +2799,16 @@ } config.blocking = false; - process(true); - } - - function process(last) { - function next() { - process(last); - } - var start = now(); - config.depth = (config.depth || 0) + 1; - - while (config.queue.length && !config.blocking) { - if (!defined.setTimeout || config.updateRate <= 0 || now() - start < config.updateRate) { - if (config.current) { - - // Reset async tracking for each phase of the Test lifecycle - config.current.usedAsync = false; - } - config.queue.shift()(); - } else { - setTimeout(next, 13); - break; - } - } - config.depth--; - if (last && !config.blocking && !config.queue.length && config.depth === 0) { - done(); - } - } - - function done() { - var runtime, - passed, - i, - key, - storage = config.storage; - - internalState.autorun = true; - - runtime = now() - config.started; - passed = config.stats.all - config.stats.bad; - - emit("runEnd", globalSuite.end(true)); - runLoggingCallbacks("done", { - failed: config.stats.bad, - passed: passed, - total: config.stats.all, - runtime: runtime - }); - - // Clear own storage items if all tests passed - if (storage && config.stats.bad === 0) { - for (i = storage.length - 1; i >= 0; i--) { - key = storage.key(i); - if (key.indexOf("qunit-test-") === 0) { - storage.removeItem(key); - } - } - } + ProcessingQueue.advance(); } function setHook(module, hookName) { - if (module.testEnvironment === undefined) { - module.testEnvironment = {}; + if (!module.hooks) { + module.hooks = {}; } return function (callback) { - module.testEnvironment[hookName] = callback; + module.hooks[hookName] = callback; }; } @@ -3588,13 +3666,19 @@ message += "Result:
" + escapeText(actual) + "
"; - // Don't show diff if actual or expected are booleans - if (!/^(true|false)$/.test(actual) && !/^(true|false)$/.test(expected)) { + if (typeof details.actual === "number" && typeof details.expected === "number") { + if (!isNaN(details.actual) && !isNaN(details.expected)) { + showDiff = true; + diff = details.actual - details.expected; + diff = (diff > 0 ? "+" : "") + diff; + } + } else if (typeof details.actual !== "boolean" && typeof details.expected !== "boolean") { diff = QUnit.diff(expected, actual); + + // don't show diff if there is zero overlap showDiff = stripHtml(diff).length !== stripHtml(expected).length + stripHtml(actual).length; } - // Don't show diff if expected and actual are totally different if (showDiff) { message += "Diff:
" + diff + "
"; } @@ -3691,6 +3775,7 @@ var todoLabel = document$$1.createElement("em"); todoLabel.className = "qunit-todo-label"; todoLabel.innerHTML = "todo"; + testItem.className += " todo"; testItem.insertBefore(todoLabel, testTitle); } From 4a2b183e48a834bf345a80d27ca37327b25e9018 Mon Sep 17 00:00:00 2001 From: Johann-S Date: Wed, 14 Jun 2017 10:48:58 +0200 Subject: [PATCH 2/7] Fix unit tests with no assertion --- js/tests/unit/dropdown.js | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/js/tests/unit/dropdown.js b/js/tests/unit/dropdown.js index 1dd675b0b6..7e96745ab6 100644 --- a/js/tests/unit/dropdown.js +++ b/js/tests/unit/dropdown.js @@ -45,7 +45,8 @@ $(function () { }) QUnit.test('should not open dropdown if target is disabled via attribute', function (assert) { - assert.expect(0) + assert.expect(1) + var done = assert.async() var dropdownHTML = '
' + '' + '
' + '' - var $dropdown = $(dropdownHTML).find('[data-toggle="dropdown"]').bootstrapDropdown() - setTimeout(function () { - assert.ok(!$dropdown.parent('.dropdown').hasClass('show'), '"show" class added on click') - }, 300) + $(dropdownHTML).appendTo('#qunit-fixture') + var $dropdown = $('#qunit-fixture').find('[data-toggle="dropdown"]').bootstrapDropdown() + $dropdown.on('click', function () { + assert.ok(!$dropdown.parent('.dropdown').hasClass('show')) + done() + }) + $dropdown.trigger($.Event('click')) }) QUnit.test('should set aria-expanded="true" on target when dropdown menu is shown', function (assert) { @@ -77,7 +81,10 @@ $(function () { + '' + '' + '' - var $dropdown = $(dropdownHTML).find('[data-toggle="dropdown"]').bootstrapDropdown() + var $dropdown = $(dropdownHTML) + .appendTo('#qunit-fixture') + .find('[data-toggle="dropdown"]') + .bootstrapDropdown() $dropdown .parent('.dropdown') .on('shown.bs.dropdown', function () { @@ -118,7 +125,8 @@ $(function () { }) QUnit.test('should not open dropdown if target is disabled via class', function (assert) { - assert.expect(0) + assert.expect(1) + var done = assert.async() var dropdownHTML = '
' + '' + '
' + '' - var $dropdown = $(dropdownHTML).find('[data-toggle="dropdown"]').bootstrapDropdown().trigger('click') - setTimeout(function () { - assert.ok(!$dropdown.parent('.dropdown').hasClass('show'), '"show" class added on click') - }, 300) + + $(dropdownHTML).appendTo('#qunit-fixture') + var $dropdown = $('#qunit-fixture').find('[data-toggle="dropdown"]').bootstrapDropdown() + $dropdown.on('click', function () { + assert.ok(!$dropdown.parent('.dropdown').hasClass('show')) + done() + }) + $dropdown.trigger($.Event('click')) }) QUnit.test('should add class show to menu if clicked', function (assert) { From ddf0dbbd29e556518f5f4baa3c6ba25a9891fe7b Mon Sep 17 00:00:00 2001 From: Johann-S Date: Wed, 14 Jun 2017 11:04:28 +0200 Subject: [PATCH 3/7] Use localhost because Win Edge block loopback access --- Gruntfile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gruntfile.js b/Gruntfile.js index e3044bf04c..351802b94f 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -18,7 +18,7 @@ module.exports = function (grunt) { concurrency: 10, maxRetries: 3, maxPollRetries: 4, - urls: ['http://127.0.0.1:3000/js/tests/index.html?hidepassed'], + urls: ['http://localhost:3000/js/tests/index.html?hidepassed'], browsers: grunt.file.readYAML('build/sauce_browsers.yml') } } From f0124769c9850bea321cbd82d6250b756a52bb52 Mon Sep 17 00:00:00 2001 From: Johann-S Date: Wed, 14 Jun 2017 13:21:49 +0200 Subject: [PATCH 4/7] Collapse supports multi-target thanks to @vanduynslagerp (#22713) --- docs/4.0/components/collapse.md | 31 ++++++++++- js/src/collapse.js | 35 ++++++++++--- js/tests/unit/collapse.js | 93 +++++++++++++++++++++++++++++++-- 3 files changed, 146 insertions(+), 13 deletions(-) diff --git a/docs/4.0/components/collapse.md b/docs/4.0/components/collapse.md index cbdc50bb72..e1d3e3b644 100644 --- a/docs/4.0/components/collapse.md +++ b/docs/4.0/components/collapse.md @@ -32,6 +32,35 @@ You can use a link with the `href` attribute, or a button with the `data-target` {% endexample %} +## Multiple triggers / targets + +A ` + +

+
+
+
+
+ Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. +
+
+
+
+
+
+ Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. +
+
+
+
+{% endexample %} + ## Accordion example Using the [card]({{ site.baseurl }}/docs/{{ site.docs_version }}/components/card/) component, you can extend the default collapse behavior to create an accordion. @@ -129,7 +158,7 @@ These classes can be found in `_transitions.scss`. ### Via data attributes -Just add `data-toggle="collapse"` and a `data-target` to the element to automatically assign control of a collapsible element. The `data-target` attribute accepts a CSS selector to apply the collapse to. Be sure to add the class `collapse` to the collapsible element. If you'd like it to default open, add the additional class `show`. +Just add `data-toggle="collapse"` and a `data-target` to the element to automatically assign control of one or more collapsible elements. The `data-target` attribute accepts a CSS selector to apply the collapse to. Be sure to add the class `collapse` to the collapsible element. If you'd like it to default open, add the additional class `show`. To add accordion-like group management to a collapsible area, add the data attribute `data-parent="#selector"`. Refer to the demo to see this in action. diff --git a/js/src/collapse.js b/js/src/collapse.js index bf1c738f56..78ed32906b 100644 --- a/js/src/collapse.js +++ b/js/src/collapse.js @@ -77,6 +77,14 @@ const Collapse = (($) => { `[data-toggle="collapse"][href="#${element.id}"],` + `[data-toggle="collapse"][data-target="#${element.id}"]` )) + const tabToggles = $(Selector.DATA_TOGGLE) + for (let i = 0; i < tabToggles.length; i++) { + const elem = tabToggles[i] + const selector = Util.getSelectorFromElement(elem) + if (selector !== null && $(selector).filter(element).length > 0) { + this._triggerArray.push(elem) + } + } this._parent = this._config.parent ? this._getParent() : null @@ -215,9 +223,17 @@ const Collapse = (($) => { .removeClass(ClassName.SHOW) if (this._triggerArray.length) { - $(this._triggerArray) - .addClass(ClassName.COLLAPSED) - .attr('aria-expanded', false) + for (let i = 0; i < this._triggerArray.length; i++) { + const trigger = this._triggerArray[i] + const selector = Util.getSelectorFromElement(trigger) + if (selector !== null) { + const $elem = $(selector) + if (!$elem.hasClass(ClassName.SHOW)) { + $(trigger).addClass(ClassName.COLLAPSED) + .attr('aria-expanded', false) + } + } + } } this.setTransitioning(true) @@ -349,11 +365,14 @@ const Collapse = (($) => { event.preventDefault() } - const target = Collapse._getTargetFromElement(this) - const data = $(target).data(DATA_KEY) - const config = data ? 'toggle' : $(this).data() - - Collapse._jQueryInterface.call($(target), config) + const $trigger = $(this) + const selector = Util.getSelectorFromElement(this) + $(selector).each(function () { + const $target = $(this) + const data = $target.data(DATA_KEY) + const config = data ? 'toggle' : $trigger.data() + Collapse._jQueryInterface.call($target, config) + }) }) diff --git a/js/tests/unit/collapse.js b/js/tests/unit/collapse.js index 2b9d0e58fc..0e9e8b6a73 100644 --- a/js/tests/unit/collapse.js +++ b/js/tests/unit/collapse.js @@ -52,8 +52,28 @@ $(function () { assert.ok(!/height/i.test($el.attr('style')), 'has height reset') }) + + QUnit.test('should show multiple collapsed elements', function (assert) { + assert.expect(4) + var done = assert.async() + var $target = $('
').appendTo('#qunit-fixture') + var $trigger2 = $('').appendTo('#qunit-fixture') + var $trigger3 = $('').appendTo('#qunit-fixture') + + var $target1 = $('
').appendTo('#qunit-fixture') + var $target2 = $('
').appendTo('#qunit-fixture') + + $target2.one('shown.bs.collapse', function () { + assert.ok(!$trigger1.hasClass('collapsed'), 'trigger1 does not have collapsed class') + assert.ok(!$trigger2.hasClass('collapsed'), 'trigger2 does not have collapsed class') + assert.ok(!$trigger3.hasClass('collapsed'), 'trigger3 does not have collapsed class') + $target2.one('hidden.bs.collapse', function () { + assert.ok(!$trigger1.hasClass('collapsed'), 'trigger1 does not have collapsed class') + assert.ok($trigger2.hasClass('collapsed'), 'trigger2 has collapsed class') + assert.ok(!$trigger3.hasClass('collapsed'), 'trigger3 does not have collapsed class') + $target1.one('hidden.bs.collapse', function () { + assert.ok($trigger1.hasClass('collapsed'), 'trigger1 has collapsed class') + assert.ok($trigger2.hasClass('collapsed'), 'trigger2 has collapsed class') + assert.ok($trigger3.hasClass('collapsed'), 'trigger3 has collapsed class') + done() + }) + $trigger1.trigger('click') + }) + $trigger2.trigger('click') + }) + $trigger3.trigger('click') + }) + + QUnit.test('should set aria-expanded="true" to triggers targetting shown collaspe and aria-expanded="false" only when all the targeted collapses are shown', function (assert) { + assert.expect(9) + var done = assert.async() + + var $trigger1 = $('').appendTo('#qunit-fixture') + var $trigger2 = $('').appendTo('#qunit-fixture') + var $trigger3 = $('').appendTo('#qunit-fixture') + + var $target1 = $('
').appendTo('#qunit-fixture') + var $target2 = $('
').appendTo('#qunit-fixture') + + $target2.one('shown.bs.collapse', function () { + assert.strictEqual($trigger1.attr('aria-expanded'), 'true', 'aria-expanded on trigger1 is "true"') + assert.strictEqual($trigger2.attr('aria-expanded'), 'true', 'aria-expanded on trigger2 is "true"') + assert.strictEqual($trigger3.attr('aria-expanded'), 'true', 'aria-expanded on trigger3 is "true"') + $target2.one('hidden.bs.collapse', function () { + assert.strictEqual($trigger1.attr('aria-expanded'), 'true', 'aria-expanded on trigger1 is "true"') + assert.strictEqual($trigger2.attr('aria-expanded'), 'false', 'aria-expanded on trigger2 is "false"') + assert.strictEqual($trigger3.attr('aria-expanded'), 'true', 'aria-expanded on trigger3 is "true"') + $target1.one('hidden.bs.collapse', function () { + assert.strictEqual($trigger1.attr('aria-expanded'), 'false', 'aria-expanded on trigger1 is "fasle"') + assert.strictEqual($trigger2.attr('aria-expanded'), 'false', 'aria-expanded on trigger2 is "false"') + assert.strictEqual($trigger3.attr('aria-expanded'), 'false', 'aria-expanded on trigger3 is "false"') + done() + }) + $trigger1.trigger('click') + }) + $trigger2.trigger('click') + }) + $trigger3.trigger('click') + }) }) From 8f431fc5b81a3fddeb6d3907edf6f07c25df0f86 Mon Sep 17 00:00:00 2001 From: Johann-S Date: Wed, 14 Jun 2017 21:00:01 +0200 Subject: [PATCH 5/7] Update Popper.js 1.10.1 --- _config.yml | 4 ++-- assets/js/vendor/popper.min.js | 2 +- bower.json | 2 +- build/npm-shrinkwrap.json | 6 +++--- docs/4.0/components/dropdowns.md | 2 +- docs/4.0/components/popovers.md | 2 +- docs/4.0/components/tooltips.md | 2 +- docs/4.0/examples/album/index.html | 2 +- docs/4.0/examples/blog/index.html | 2 +- docs/4.0/examples/carousel/index.html | 2 +- docs/4.0/examples/cover/index.html | 2 +- docs/4.0/examples/dashboard/index.html | 2 +- docs/4.0/examples/jumbotron/index.html | 2 +- docs/4.0/examples/justified-nav/index.html | 2 +- docs/4.0/examples/navbar-top-fixed/index.html | 2 +- docs/4.0/examples/navbar-top/index.html | 2 +- docs/4.0/examples/navbars/index.html | 2 +- docs/4.0/examples/offcanvas/index.html | 2 +- docs/4.0/examples/starter-template/index.html | 2 +- docs/4.0/examples/sticky-footer-navbar/index.html | 2 +- docs/4.0/examples/tooltip-viewport/index.html | 2 +- package.json | 2 +- 22 files changed, 25 insertions(+), 25 deletions(-) diff --git a/_config.yml b/_config.yml index ebf6e74e78..b169e9d15f 100644 --- a/_config.yml +++ b/_config.yml @@ -53,5 +53,5 @@ cdn: js_hash: "sha384-vBWWzlZJ8ea9aCX4pEW3rVHjgjt7zpkNpZk+02D9phzyeVkE+jo0ieGizqPLForn" jquery: https://code.jquery.com/jquery-3.2.1.slim.min.js jquery_hash: "sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" - popper: https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.9.9/umd/popper.min.js - popper_hash: "sha256-c477vRLKQv1jt9o7w6TTBzFyFznTaZjoMLTDFi7Hlxc=" + popper: https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.10.1/umd/popper.min.js + popper_hash: "sha256-nxD3NU7Wocq19nG7DTQAx9troUwVoxjUhYrAhFSO3HM=" diff --git a/assets/js/vendor/popper.min.js b/assets/js/vendor/popper.min.js index ac85cba988..2e231df3ff 100644 --- a/assets/js/vendor/popper.min.js +++ b/assets/js/vendor/popper.min.js @@ -1,3 +1,3 @@ -var _Mathfloor=Math.floor,_Mathmin=Math.min,_Mathround=Math.round,_Mathmax=Math.max;(function(e,t){'object'==typeof exports&&'undefined'!=typeof module?module.exports=t():'function'==typeof define&&define.amd?define(t):e.Popper=t()})(this,function(){'use strict';function o(Ne){return''!==Ne&&!isNaN(parseFloat(Ne))&&isFinite(Ne)}function r(Ne,We){Object.keys(We).forEach(function(Pe){var De='';-1!==['width','height','top','right','bottom','left'].indexOf(Pe)&&o(We[Pe])&&(De='px'),Ne.style[Pe]=We[Pe]+De})}function p(Ne){return Ne&&'[object Function]'==={}.toString.call(Ne)}function s(Ne,We){if(1!==Ne.nodeType)return[];var Pe=window.getComputedStyle(Ne,null);return We?Pe[We]:Pe}function d(Ne){return'HTML'===Ne.nodeName?Ne:Ne.parentNode||Ne.host}function f(Ne){if(!Ne||-1!==['HTML','BODY','#document'].indexOf(Ne.nodeName))return window.document.body;var We=s(Ne),Pe=We.overflow,De=We.overflowX,He=We.overflowY;return /(auto|scroll)/.test(Pe+He+De)?Ne:f(d(Ne))}function l(Ne){var We=Ne.nodeName;return'BODY'!==We&&('HTML'===We||Ne.firstElementChild.offsetParent===Ne)}function m(Ne){return null===Ne.parentNode?Ne:m(Ne.parentNode)}function h(Ne){var We=Ne&&Ne.offsetParent,Pe=We&&We.nodeName;return Pe&&'BODY'!==Pe&&'HTML'!==Pe?We:window.document.documentElement}function c(Ne,We){if(!Ne||!Ne.nodeType||!We||!We.nodeType)return window.document.documentElement;var Pe=Ne.compareDocumentPosition(We)&Node.DOCUMENT_POSITION_FOLLOWING,De=Pe?Ne:We,He=Pe?We:Ne,Be=document.createRange();Be.setStart(De,0),Be.setEnd(He,0);var Me=Be.commonAncestorContainer;if(Ne!==Me&&We!==Me||De.contains(He))return l(Me)?Me:h(Me);var Ie=m(Ne);return Ie.host?c(Ie.host,We):c(Ne,m(We).host)}function u(Ne){var We=1=Pe.clientWidth&&qe>=Pe.clientHeight}),Ue=0De[Ae]&&!We.escapeWithReference&&(Ue=_Mathmin(Be[Re],De[Ae]-('right'===Ae?Be.width:Be.height))),ve({},Re,Ue)}};return He.forEach(function(Ie){var Ae=-1===['left','top'].indexOf(Ie)?'secondary':'primary';Be=xe({},Be,Me[Ae](Ie))}),Ne.offsets.popper=Be,Ne},priority:['left','right','top','bottom'],padding:5,boundariesElement:'scrollParent'},keepTogether:{order:400,enabled:!0,fn:function(Ne){var We=O(Ne.offsets.popper),Pe=Ne.offsets.reference,De=Ne.placement.split('-')[0],He=_Mathfloor,Be=-1!==['top','bottom'].indexOf(De),Me=Be?'right':'bottom',Ie=Be?'left':'top',Ae=Be?'width':'height';return We[Me]He(Pe[Me])&&(Ne.offsets.popper[Ie]=He(Pe[Me])),Ne}},arrow:{order:500,enabled:!0,fn:function(Ne,We){if(!Q(Ne.instance.modifiers,'arrow','keepTogether'))return Ne;var Pe=We.element;if('string'==typeof Pe){if(Pe=Ne.instance.popper.querySelector(Pe),!Pe)return Ne;}else if(!Ne.instance.popper.contains(Pe))return console.warn('WARNING: `arrow.element` must be child of its popper element!'),Ne;var De=Ne.placement.split('-')[0],He=O(Ne.offsets.popper),Be=Ne.offsets.reference,Me=-1!==['left','right'].indexOf(De),Ie=Me?'height':'width',Ae=Me?'top':'left',Re=Me?'left':'top',Ue=Me?'bottom':'right',Ye=D(Pe)[Ie];Be[Ue]-YeHe[Ue]&&(Ne.offsets.popper[Ae]+=Be[Ae]+Ye-He[Ue]);var Fe=Be[Ae]+Be[Ie]/2-Ye/2,je=Fe-O(Ne.offsets.popper)[Ae];return je=_Mathmax(_Mathmin(He[Ie]-Ye,je),0),Ne.arrowElement=Pe,Ne.offsets.arrow={},Ne.offsets.arrow[Ae]=_Mathfloor(je),Ne.offsets.arrow[Re]='',Ne},element:'[x-arrow]'},flip:{order:600,enabled:!0,fn:function(Ne,We){if(U(Ne.instance.modifiers,'inner'))return Ne;if(Ne.flipped&&Ne.placement===Ne.originalPlacement)return Ne;var Pe=k(Ne.instance.popper,Ne.instance.reference,We.padding,We.boundariesElement),De=Ne.placement.split('-')[0],He=H(De),Be=Ne.placement.split('-')[1]||'',Me=[];switch(We.behavior){case Se.FLIP:Me=[De,He];break;case Se.CLOCKWISE:Me=J(De);break;case Se.COUNTERCLOCKWISE:Me=J(De,!0);break;default:Me=We.behavior;}return Me.forEach(function(Ie,Ae){if(De!==Ie||Me.length===Ae+1)return Ne;De=Ne.placement.split('-')[0],He=H(De);var Re=O(Ne.offsets.popper),Ue=Ne.offsets.reference,Ye=_Mathfloor,Fe='left'===De&&Ye(Re.right)>Ye(Ue.left)||'right'===De&&Ye(Re.left)Ye(Ue.top)||'bottom'===De&&Ye(Re.top)Ye(Pe.right),Ke=Ye(Re.top)Ye(Pe.bottom),_e='left'===De&&je||'right'===De&&qe||'top'===De&&Ke||'bottom'===De&&ze,Ve=-1!==['top','bottom'].indexOf(De),Ge=!!We.flipVariations&&(Ve&&'start'===Be&&je||Ve&&'end'===Be&&qe||!Ve&&'start'===Be&&Ke||!Ve&&'end'===Be&&ze);(Fe||_e||Ge)&&(Ne.flipped=!0,(Fe||_e)&&(De=Me[Ae+1]),Ge&&(Be=$(Be)),Ne.placement=De+(Be?'-'+Be:''),Ne.offsets.popper=xe({},Ne.offsets.popper,B(Ne.instance.popper,Ne.offsets.reference,Ne.placement)),Ne=A(Ne.instance.modifiers,Ne,'flip'))}),Ne},behavior:'flip',padding:5,boundariesElement:'viewport'},inner:{order:700,enabled:!1,fn:function(Ne){var We=Ne.placement,Pe=We.split('-')[0],De=O(Ne.offsets.popper),He=O(Ne.offsets.reference),Be=-1!==['left','right'].indexOf(Pe),Me=-1===['top','left'].indexOf(Pe);return De[Be?'left':'top']=He[We]-(Me?De[Be?'width':'height']:0),Ne.placement=H(We),Ne.offsets.popper=O(De),Ne}},hide:{order:800,enabled:!0,fn:function(Ne){if(!Q(Ne.instance.modifiers,'hide','preventOverflow'))return Ne;var We=Ne.offsets.reference,Pe=M(Ne.instance.modifiers,function(De){return'preventOverflow'===De.name}).boundaries;if(We.bottomPe.right||We.top>Pe.bottom||We.right=o.clientWidth&&i>=o.clientHeight}),f=0i[e]&&!t.escapeWithReference&&(n=V(r[o],i[e]-('right'===e?r.width:r.height))),pe({},o,n)}};return n.forEach(function(e){var t=-1===['left','top'].indexOf(e)?'secondary':'primary';r=se({},r,p[t](e))}),e.offsets.popper=r,e},priority:['left','right','top','bottom'],padding:5,boundariesElement:'scrollParent'},keepTogether:{order:400,enabled:!0,fn:function(e){var t=e.offsets,o=t.popper,i=t.reference,n=e.placement.split('-')[0],r=z,p=-1!==['top','bottom'].indexOf(n),s=p?'right':'bottom',d=p?'left':'top',a=p?'width':'height';return o[s]r(i[s])&&(e.offsets.popper[d]=r(i[s])),e}},arrow:{order:500,enabled:!0,fn:function(e,t){if(!F(e.instance.modifiers,'arrow','keepTogether'))return e;var o=t.element;if('string'==typeof o){if(o=e.instance.popper.querySelector(o),!o)return e;}else if(!e.instance.popper.contains(o))return console.warn('WARNING: `arrow.element` must be child of its popper element!'),e;var i=e.placement.split('-')[0],n=e.offsets,r=n.popper,p=n.reference,s=-1!==['left','right'].indexOf(i),d=s?'height':'width',a=s?'top':'left',f=s?'left':'top',l=s?'bottom':'right',m=S(o)[d];p[l]-mr[l]&&(e.offsets.popper[a]+=p[a]+m-r[l]);var h=p[a]+p[d]/2-m/2,c=h-u(e.offsets.popper)[a];return c=_(V(r[d]-m,c),0),e.arrowElement=o,e.offsets.arrow={},e.offsets.arrow[a]=z(c),e.offsets.arrow[f]='',e},element:'[x-arrow]'},flip:{order:600,enabled:!0,fn:function(e,t){if(P(e.instance.modifiers,'inner'))return e;if(e.flipped&&e.placement===e.originalPlacement)return e;var o=E(e.instance.popper,e.instance.reference,t.padding,t.boundariesElement),i=e.placement.split('-')[0],n=C(i),r=e.placement.split('-')[1]||'',p=[];switch(t.behavior){case fe.FLIP:p=[i,n];break;case fe.CLOCKWISE:p=K(i);break;case fe.COUNTERCLOCKWISE:p=K(i,!0);break;default:p=t.behavior;}return p.forEach(function(s,d){if(i!==s||p.length===d+1)return e;i=e.placement.split('-')[0],n=C(i);var a=e.offsets.popper,f=e.offsets.reference,l=z,m='left'===i&&l(a.right)>l(f.left)||'right'===i&&l(a.left)l(f.top)||'bottom'===i&&l(a.top)l(o.right),g=l(a.top)l(o.bottom),b='left'===i&&h||'right'===i&&c||'top'===i&&g||'bottom'===i&&u,y=-1!==['top','bottom'].indexOf(i),w=!!t.flipVariations&&(y&&'start'===r&&h||y&&'end'===r&&c||!y&&'start'===r&&g||!y&&'end'===r&&u);(m||b||w)&&(e.flipped=!0,(m||b)&&(i=p[d+1]),w&&(r=j(r)),e.placement=i+(r?'-'+r:''),e.offsets.popper=se({},e.offsets.popper,T(e.instance.popper,e.offsets.reference,e.placement)),e=B(e.instance.modifiers,e,'flip'))}),e},behavior:'flip',padding:5,boundariesElement:'viewport'},inner:{order:700,enabled:!1,fn:function(e){var t=e.placement,o=t.split('-')[0],i=e.offsets,n=i.popper,r=i.reference,p=-1!==['left','right'].indexOf(o),s=-1===['top','left'].indexOf(o);return n[p?'left':'top']=r[t]-(s?n[p?'width':'height']:0),e.placement=C(t),e.offsets.popper=u(n),e}},hide:{order:800,enabled:!0,fn:function(e){if(!F(e.instance.modifiers,'hide','preventOverflow'))return e;var t=e.offsets.reference,o=N(e.instance.modifiers,function(e){return'preventOverflow'===e.name}).boundaries;if(t.bottomo.right||t.top>o.bottom||t.right=1.9.1", - "popper.js": "^1.9.9" + "popper.js": "^1.10.1" } } diff --git a/build/npm-shrinkwrap.json b/build/npm-shrinkwrap.json index eab64153b2..1ae4e341e0 100644 --- a/build/npm-shrinkwrap.json +++ b/build/npm-shrinkwrap.json @@ -3390,9 +3390,9 @@ "dev": true }, "popper.js": { - "version": "1.9.9", - "from": "popper.js@>=1.9.9 <3.0.0", - "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.9.9.tgz" + "version": "1.10.1", + "from": "popper.js@>=1.10.1 <2.0.0", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.10.1.tgz" }, "postcss": { "version": "5.2.17", diff --git a/docs/4.0/components/dropdowns.md b/docs/4.0/components/dropdowns.md index 01c35c7a22..4840e84803 100644 --- a/docs/4.0/components/dropdowns.md +++ b/docs/4.0/components/dropdowns.md @@ -10,7 +10,7 @@ toc: true Dropdowns are toggleable, contextual overlays for displaying lists of links and more. They're made interactive with the included Bootstrap dropdown JavaScript plugin. They're toggled by clicking, not by hovering; this is [an intentional design decision.](http://markdotto.com/2012/02/27/bootstrap-explained-dropdowns/) -Dropdowns are built on a third party library, [Popper.js](https://popper.js.org), which provides dynamic positioning and viewport detection. Be sure to include [popper.min.js](https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.9.9/umd/popper.min.js) before Bootstrap's JavaScript. +Dropdowns are built on a third party library, [Popper.js](https://popper.js.org), which provides dynamic positioning and viewport detection. Be sure to include [popper.min.js](https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.10.1/umd/popper.min.js) before Bootstrap's JavaScript. ## Accessibility diff --git a/docs/4.0/components/popovers.md b/docs/4.0/components/popovers.md index 20ab3fe75b..cd65991581 100644 --- a/docs/4.0/components/popovers.md +++ b/docs/4.0/components/popovers.md @@ -11,7 +11,7 @@ toc: true Things to know when using the popover plugin: -- Popovers rely on the 3rd party library [Popper.js](https://popper.js.org) for positioning. You must include [popper.min.js](https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.9.9/umd/popper.min.js) before bootstrap.js in order for popovers to work! +- Popovers rely on the 3rd party library [Popper.js](https://popper.js.org) for positioning. You must include [popper.min.js](https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.10.1/umd/popper.min.js) before bootstrap.js in order for popovers to work! - Popovers require the [tooltip plugin]({{ site.baseurl }}/docs/{{ site.docs_version }}/components/tooltips/) as a dependency. - Popovers are opt-in for performance reasons, so **you must initialize them yourself**. - Zero-length `title` and `content` values will never show a popover. diff --git a/docs/4.0/components/tooltips.md b/docs/4.0/components/tooltips.md index c6cf14dc93..7c0bd9f6a6 100644 --- a/docs/4.0/components/tooltips.md +++ b/docs/4.0/components/tooltips.md @@ -10,7 +10,7 @@ toc: true Things to know when using the tooltip plugin: -- Tooltips rely on the 3rd party library [Popper.js](https://popper.js.org) for positioning. You must include [popper.min.js](https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.9.9/umd/popper.min.js) before bootstrap.js in order for tooltips to work! +- Tooltips rely on the 3rd party library [Popper.js](https://popper.js.org) for positioning. You must include [popper.min.js](https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.10.1/umd/popper.min.js) before bootstrap.js in order for tooltips to work! - Tooltips are opt-in for performance reasons, so **you must initialize them yourself**. - Tooltips with zero-length titles are never displayed. - Specify `container: 'body'` to avoid rendering problems in more complex components (like our input groups, button groups, etc). diff --git a/docs/4.0/examples/album/index.html b/docs/4.0/examples/album/index.html index f309058137..f442be943d 100644 --- a/docs/4.0/examples/album/index.html +++ b/docs/4.0/examples/album/index.html @@ -118,7 +118,7 @@ - + - + diff --git a/docs/4.0/examples/carousel/index.html b/docs/4.0/examples/carousel/index.html index 95a8bed580..451b4c38f0 100644 --- a/docs/4.0/examples/carousel/index.html +++ b/docs/4.0/examples/carousel/index.html @@ -176,7 +176,7 @@ - + diff --git a/docs/4.0/examples/cover/index.html b/docs/4.0/examples/cover/index.html index 87efec4d7f..7edfe51edc 100644 --- a/docs/4.0/examples/cover/index.html +++ b/docs/4.0/examples/cover/index.html @@ -60,7 +60,7 @@ - + diff --git a/docs/4.0/examples/dashboard/index.html b/docs/4.0/examples/dashboard/index.html index 01d94fba4a..e535f19d13 100644 --- a/docs/4.0/examples/dashboard/index.html +++ b/docs/4.0/examples/dashboard/index.html @@ -254,7 +254,7 @@ - + diff --git a/docs/4.0/examples/jumbotron/index.html b/docs/4.0/examples/jumbotron/index.html index a2f4a7b716..15b449f78c 100644 --- a/docs/4.0/examples/jumbotron/index.html +++ b/docs/4.0/examples/jumbotron/index.html @@ -93,7 +93,7 @@ - + diff --git a/docs/4.0/examples/justified-nav/index.html b/docs/4.0/examples/justified-nav/index.html index 4ebaeed062..16ac2a10b9 100644 --- a/docs/4.0/examples/justified-nav/index.html +++ b/docs/4.0/examples/justified-nav/index.html @@ -96,7 +96,7 @@ - + diff --git a/docs/4.0/examples/navbar-top-fixed/index.html b/docs/4.0/examples/navbar-top-fixed/index.html index 5f81d49058..f27b42dd8e 100644 --- a/docs/4.0/examples/navbar-top-fixed/index.html +++ b/docs/4.0/examples/navbar-top-fixed/index.html @@ -56,7 +56,7 @@ - + diff --git a/docs/4.0/examples/navbar-top/index.html b/docs/4.0/examples/navbar-top/index.html index 66f86f6bf5..bdad691717 100644 --- a/docs/4.0/examples/navbar-top/index.html +++ b/docs/4.0/examples/navbar-top/index.html @@ -56,7 +56,7 @@ - + diff --git a/docs/4.0/examples/navbars/index.html b/docs/4.0/examples/navbars/index.html index de0a500fb8..7b829a8b23 100644 --- a/docs/4.0/examples/navbars/index.html +++ b/docs/4.0/examples/navbars/index.html @@ -351,7 +351,7 @@ - + diff --git a/docs/4.0/examples/offcanvas/index.html b/docs/4.0/examples/offcanvas/index.html index d0581f997c..89485ea620 100644 --- a/docs/4.0/examples/offcanvas/index.html +++ b/docs/4.0/examples/offcanvas/index.html @@ -127,7 +127,7 @@ - + diff --git a/docs/4.0/examples/starter-template/index.html b/docs/4.0/examples/starter-template/index.html index ce430a60d9..d69f5a3973 100644 --- a/docs/4.0/examples/starter-template/index.html +++ b/docs/4.0/examples/starter-template/index.html @@ -66,7 +66,7 @@ - + diff --git a/docs/4.0/examples/sticky-footer-navbar/index.html b/docs/4.0/examples/sticky-footer-navbar/index.html index aa1f046ad2..4f5fe32ddf 100644 --- a/docs/4.0/examples/sticky-footer-navbar/index.html +++ b/docs/4.0/examples/sticky-footer-navbar/index.html @@ -64,7 +64,7 @@ - + diff --git a/docs/4.0/examples/tooltip-viewport/index.html b/docs/4.0/examples/tooltip-viewport/index.html index f60ca47074..f44e98b0e7 100644 --- a/docs/4.0/examples/tooltip-viewport/index.html +++ b/docs/4.0/examples/tooltip-viewport/index.html @@ -39,7 +39,7 @@ - + diff --git a/package.json b/package.json index 08fe1b40c6..bedd027e1f 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "license": "MIT", "dependencies": { "jquery": ">=1.9.1", - "popper.js": "^1.9.9" + "popper.js": "^1.10.1" }, "devDependencies": { "autoprefixer": "^7.1.1", From ff5c75510d4c7445770048a117b4ac7764ca2031 Mon Sep 17 00:00:00 2001 From: "Patrick H. Lauke" Date: Fri, 28 Apr 2017 00:35:55 +0100 Subject: [PATCH 6/7] Rename .form-control-static to .form-control-readonly-plain --- docs/4.0/components/forms.md | 8 ++++---- scss/_forms.scss | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/4.0/components/forms.md b/docs/4.0/components/forms.md index 2bc4a91cb6..0a5756c4d6 100644 --- a/docs/4.0/components/forms.md +++ b/docs/4.0/components/forms.md @@ -115,16 +115,16 @@ Add the `readonly` boolean attribute on an input to prevent modification of the {% endexample %} -### Static +### Readonly plain text -If you want to have read-only fields in your form styled as plain text, use the `.form-control-static` class to remove the default form field styling and preserve the correct margin and padding. +If you want to have `` elements in your form styled as plain text, use the `.form-control-plaintext` class to remove the default form field styling and preserve the correct margin and padding. {% example html %}
- +
@@ -140,7 +140,7 @@ If you want to have read-only fields in your form styled as plain text, use the
- +
diff --git a/scss/_forms.scss b/scss/_forms.scss index 89766563cd..ecf0a3df17 100644 --- a/scss/_forms.scss +++ b/scss/_forms.scss @@ -122,12 +122,12 @@ select.form-control { } -// Static form control text +// Readonly controls as plain text // -// Apply class to an element to make any string of text align with labels in a -// horizontal form layout. +// Apply class to a readonly input to make it appear like regular plain +// text (without any border, background color, focus indicator) -.form-control-static { +.form-control-plaintext { padding-top: $input-btn-padding-y; padding-bottom: $input-btn-padding-y; margin-bottom: 0; // match inputs if this class comes on inputs with default margins From 658fbd5c96244a28c225bd6e5e64f2c8c0409327 Mon Sep 17 00:00:00 2001 From: "Patrick H. Lauke" Date: Fri, 28 Apr 2017 00:49:24 +0100 Subject: [PATCH 7/7] Add note to migration guide --- docs/4.0/migration.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/4.0/migration.md b/docs/4.0/migration.md index be861bac93..5d5343ca91 100644 --- a/docs/4.0/migration.md +++ b/docs/4.0/migration.md @@ -105,6 +105,7 @@ New to Bootstrap 4 is the [Reboot]({{ site.baseurl }}/docs/{{ site.docs_version - Added new `.form-control-label` class to vertically center labels with `.form-control`s. - Added custom forms support (for checkboxes, radios, selects, and file inputs). - Renamed `.has-error` to `.has-danger`. +- Renamed `.form-control-static` to `.form-control-plaintext`. ### Buttons