Repository: twbs/bootlint
Pull request #422 (2018-09-18 12:09:31)

[v1] New container-related checks
Add E003 (misplaced .row), and remove E004 (nestable containers) since that works now.

#### Status

- **Code**: Done
- **Tests**: Done
- **Wiki**: Done

Herst: Oops, some of the tests use the old column classes, now I am in a quandary, fixing this would make this PR depended on #420.
Herst: > Oops, some of the tests use the old column classes, now I am in a quandary, fixing this would make this PR depended on #420.

Rebased it now with the new column classes.
Herst: /cc @XhmikosR @Johann-S 

Patch:
diff --git a/src/bootlint.js b/src/bootlint.js
index 8d4decf..2d984f1 100644
--- a/src/bootlint.js
+++ b/src/bootlint.js
@@ -346,14 +346,6 @@ var LocationIndex = _location.LocationIndex;
     });
     */
     /*
-    addLinter('W004', function lintRemoteModals($, reporter) {
-        var remoteModalTriggers = $('[data-toggle="modal"][data-remote]');
-        if (remoteModalTriggers.length) {
-            reporter('Found one or more modals using the deprecated `remote` option', remoteModalTriggers);
-        }
-    });
-    */
-    /*
     addLinter('W005', function lintJquery($, reporter) {
         var OLD_JQUERY = 'Found what might be an outdated version of jQuery; Bootstrap requires jQuery v' + MIN_JQUERY_VERSION + ' or higher';
         var NO_JQUERY_BUT_BS_JS = 'Unable to locate jQuery, which is required for Bootstrap\'s JavaScript plugins to work';
@@ -594,7 +586,6 @@ var LocationIndex = _location.LocationIndex;
             }
         };
     })());
-    /*
     addLinter('E003', function lintContainers($, reporter) {
         var notAnyColClass = COL_CLASSES.map(function (colClass) {
             return ':not(' + colClass + ')';
@@ -615,15 +606,6 @@ var LocationIndex = _location.LocationIndex;
             reporter('Found one or more `.row`s that were not children of a grid column or descendants of a `.container` or `.container-fluid` or `.modal-body`', rowsOutsideColumnsAndContainers);
         }
     });
-    */
-    /*
-    addLinter('E004', function lintNestedContainers($, reporter) {
-        var nestedContainers = $('.container, .container-fluid').children('.container, .container-fluid');
-        if (nestedContainers.length) {
-            reporter('Containers (`.container` and `.container-fluid`) are not nestable', nestedContainers);
-        }
-    });
-    */
     addLinter('E005', function lintRowAndColOnSameElem($, reporter) {
         var selector = COL_CLASSES.map(function (col) {
             return '.row' + col;
@@ -804,7 +786,6 @@ var LocationIndex = _location.LocationIndex;
         }
     });
     */
-    /*
     addLinter('E022', function lintModalsWithinOtherComponents($, reporter) {
         var selector = [
             '.table .modal',
@@ -815,7 +796,6 @@ var LocationIndex = _location.LocationIndex;
             reporter('Modal markup should not be placed within other components, so as to avoid the component\'s styles interfering with the modal\'s appearance or functionality', badNestings);
         }
     });
-    */
     addLinter('E023', function lintCardBodyWithoutCard($, reporter) {
         var badCardBody = $('.card-body').filter(function () {
             return $(this).closest('.card').length !== 1;
@@ -912,7 +892,6 @@ var LocationIndex = _location.LocationIndex;
             );
         });
     });
-    /*
     addLinter('E032', function lintModalStructure($, reporter) {
         var elements;
 
@@ -946,7 +925,6 @@ var LocationIndex = _location.LocationIndex;
             reporter('`.modal-title` must be a child of `.modal-header`', elements);
         }
     });
-    */
     /*
     addLinter('E033', function lintAlertMissingDismissible($, reporter) {
         var alertsMissingDismissible = $('.alert:not(.alert-dismissible):has([data-dismiss="alert"])');
@@ -997,14 +975,6 @@ var LocationIndex = _location.LocationIndex;
     });
     */
     /*
-    addLinter('E040', function lintModalHide($, reporter) {
-        var modalsWithHide = $('.modal.hide');
-        if (modalsWithHide.length) {
-            reporter('`.hide` should not be used on `.modal` in Bootstrap v3.', modalsWithHide);
-        }
-    });
-    */
-    /*
     addLinter('E041', function lintCarouselStructure($, reporter) {
         var carouselsWithWrongInners = $('.carousel').filter(function () {
             return $(this).children('.carousel-inner').length !== 1;
@@ -1076,14 +1046,12 @@ var LocationIndex = _location.LocationIndex;
         }
     });
     */
-    /*
     addLinter('E046', function lintModalTabIndex($, reporter) {
         var modalsWithoutTabindex = $('.modal:not([tabindex])');
         if (modalsWithoutTabindex.length) {
             reporter('`.modal` elements must have a `tabindex` attribute.', modalsWithoutTabindex);
         }
     });
-    */
     /*
     addLinter('E047', function lintBtnElements($, reporter) {
         var btns = $('.btn:not(a,button,input,label)');
@@ -1092,22 +1060,18 @@ var LocationIndex = _location.LocationIndex;
         }
     });
     */
-    /*
     addLinter('E048', function lintModalRole($, reporter) {
         var modals = $('.modal:not([role="dialog"])');
         if (modals.length) {
             reporter('`.modal` must have a `role="dialog"` attribute.', modals);
         }
     });
-    */
-    /*
     addLinter('E049', function lintModalDialogRole($, reporter) {
         var modalDialogs = $('.modal-dialog:not([role="document"])');
         if (modalDialogs.length) {
             reporter('`.modal-dialog` must have a `role="document"` attribute.', modalDialogs);
         }
     });
-    */
     /*
     addLinter('E050', function lintNestedFormGroups($, reporter) {
         var nestedFormGroups = $('.form-group > .form-group');


Test patch:
diff --git a/test/_old_fixtures/modal/remote.html b/test/_old_fixtures/modal/remote.html
deleted file mode 100644
index 4e39e01..0000000
--- a/test/_old_fixtures/modal/remote.html
+++ /dev/null
@@ -1,27 +0,0 @@
-<!doctype html>
-<html lang="en">
-    <head>
-        <meta charset="utf-8">
-        <meta http-equiv="X-UA-Compatible" content="IE=edge">
-        <meta name="viewport" content="width=device-width, initial-scale=1">
-        <title>Test</title>
-        <link rel="stylesheet" href="../../../node_modules/qunit/qunit/qunit.css">
-
-        <!--[if lt IE 9]>
-            <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
-            <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
-        <![endif]-->
-        <script src="../../../node_modules/jquery/dist/jquery.min.js"></script>
-        <script src="../../../node_modules/qunit/qunit/qunit.js"></script>
-        <script src="../../../dist/browser/bootlint.js"></script>
-        <script src="../generic-qunit.js"></script>
-    </head>
-    <body>
-        <button type="button" data-toggle="modal" data-remote="http://example.invalid/modal.html" data-target="#myModal">Launch modal</button>
-
-        <div id="qunit"></div>
-        <ol id="bootlint">
-            <li data-lint="Found one or more modals using the deprecated `remote` option"></li>
-        </ol>
-    </body>
-</html>
diff --git a/test/bootlint_test.js b/test/bootlint_test.js
index 77dc735..bbca813 100644
--- a/test/bootlint_test.js
+++ b/test/bootlint_test.js
@@ -97,25 +97,6 @@ exports.bootlint = {
             'should not complain when rows are children of `.modal-body`.');
         test.done();
     },
-    */
-    /*
-    'nested containers': function (test) {
-        test.expect(4);
-        test.deepEqual(lintHtml(utf8Fixture('containers/nested-fixed-fixed.html')),
-            ['Containers (`.container` and `.container-fluid`) are not nestable'],
-            'should complain when a container is within a container.');
-        test.deepEqual(lintHtml(utf8Fixture('containers/nested-fixed-fluid.html')),
-            ['Containers (`.container` and `.container-fluid`) are not nestable'],
-            'should complain when a container is within a container.');
-        test.deepEqual(lintHtml(utf8Fixture('containers/nested-fluid-fluid.html')),
-            ['Containers (`.container` and `.container-fluid`) are not nestable'],
-            'should complain when a container is within a container.');
-        test.deepEqual(lintHtml(utf8Fixture('containers/nested-fluid-fixed.html')),
-            ['Containers (`.container` and `.container-fluid`) are not nestable'],
-            'should complain when a container is within a container.');
-        test.done();
-    },
-    */
     /*
     'viewport meta tag': function (test) {
         test.expect(2);
@@ -138,7 +119,6 @@ exports.bootlint = {
             'should complain when .row and .col* are used on the same element.');
         test.done();
     },
-    /*
     'row and container classes on same element': function (test) {
         test.expect(2);
         test.deepEqual(lintHtml(utf8Fixture('containers/fixed-row-same-elem.html')),
@@ -150,15 +130,6 @@ exports.bootlint = {
         test.done();
     },
     /*
-    'remote modals': function (test) {
-        test.expect(1);
-        test.deepEqual(lintHtml(utf8Fixture('modal/remote.html')),
-            ['Found one or more modals using the deprecated `remote` option'],
-            'should complain when remote modals are present.');
-        test.done();
-    },
-    */
-    /*
     'jQuery': function (test) {
         test.expect(5);
         test.deepEqual(lintHtml(utf8Fixture('jquery/present.html')),
@@ -729,20 +700,6 @@ exports.bootlint = {
     },
     */
     /*
-    'modal with .hide class': function (test) {
-        test.expect(2);
-        test.deepEqual(lintHtml(utf8Fixture('modal/with-hide.html')),
-            ['`.hide` should not be used on `.modal` in Bootstrap v3.'],
-            'should complain about a modal with the .hide class.'
-        );
-        test.deepEqual(lintHtml(utf8Fixture('modal/valid.html')),
-            [],
-            'should not complain about a modal without the .hide class.'
-        );
-        test.done();
-    },
-    */
-    /*
     'carousel structure': function (test) {
         test.expect(5);
         test.deepEqual(lintHtml(utf8Fixture('carousel/valid.html')),
diff --git a/test/_old_fixtures/containers/ancestor.html b/test/fixtures/containers/ancestor.html
similarity index 100%
rename from test/_old_fixtures/containers/ancestor.html
rename to test/fixtures/containers/ancestor.html
diff --git a/test/_old_fixtures/containers/columns.html b/test/fixtures/containers/columns.html
similarity index 89%
rename from test/_old_fixtures/containers/columns.html
rename to test/fixtures/containers/columns.html
index a041d90..3a13dc3 100644
--- a/test/_old_fixtures/containers/columns.html
+++ b/test/fixtures/containers/columns.html
@@ -19,14 +19,14 @@
     <body>
         <div class="container-fluid">
             <div class="row">
-                <div class="col-xs-6">
+                <div class="col-sm-6">
                     <div class="row">
-                        <div class="col-xs-12">
+                        <div class="col-xl-12">
                             We can nest rows within columns.
                         </div>
                     </div>
                 </div>
-                <div class="col-xs-6">Rest</div>
+                <div class="col-md-6">Rest</div>
             </div>
         </div>
 
diff --git a/test/_old_fixtures/containers/fixed-row-same-elem.html b/test/fixtures/containers/fixed-row-same-elem.html
similarity index 100%
rename from test/_old_fixtures/containers/fixed-row-same-elem.html
rename to test/fixtures/containers/fixed-row-same-elem.html
diff --git a/test/_old_fixtures/containers/fixed.html b/test/fixtures/containers/fixed.html
similarity index 100%
rename from test/_old_fixtures/containers/fixed.html
rename to test/fixtures/containers/fixed.html
diff --git a/test/_old_fixtures/containers/fluid-row-same-elem.html b/test/fixtures/containers/fluid-row-same-elem.html
similarity index 100%
rename from test/_old_fixtures/containers/fluid-row-same-elem.html
rename to test/fixtures/containers/fluid-row-same-elem.html
diff --git a/test/_old_fixtures/containers/fluid.html b/test/fixtures/containers/fluid.html
similarity index 100%
rename from test/_old_fixtures/containers/fluid.html
rename to test/fixtures/containers/fluid.html
diff --git a/test/_old_fixtures/containers/missing.html b/test/fixtures/containers/missing.html
similarity index 100%
rename from test/_old_fixtures/containers/missing.html
rename to test/fixtures/containers/missing.html
diff --git a/test/_old_fixtures/containers/nested-fixed-fixed.html b/test/fixtures/modal/body-outside-content.html
similarity index 70%
rename from test/_old_fixtures/containers/nested-fixed-fixed.html
rename to test/fixtures/modal/body-outside-content.html
index 59730c2..b268518 100644
--- a/test/_old_fixtures/containers/nested-fixed-fixed.html
+++ b/test/fixtures/modal/body-outside-content.html
@@ -17,13 +17,18 @@
         <script src="../generic-qunit.js"></script>
     </head>
     <body>
-        <div class="container">
-            <div class="container"></div>
+        <div class="modal fade" tabindex="-1" role="dialog">
+            <div class="modal-dialog" role="document">
+                <!--Oops, forgot about the div.modal-content-->
+                <div class="modal-body">
+                    <p>One fine body&hellip;</p>
+                </div>
+            </div>
         </div>
 
         <div id="qunit"></div>
         <ol id="bootlint">
-            <li data-lint="Containers (`.container` and `.container-fluid`) are not nestable"></li>
+            <li data-lint="`.modal-body` must be a child of `.modal-content`"></li>
         </ol>
     </body>
 </html>
diff --git a/test/_old_fixtures/containers/nested-fluid-fluid.html b/test/fixtures/modal/content-outside-dialog.html
similarity index 51%
rename from test/_old_fixtures/containers/nested-fluid-fluid.html
rename to test/fixtures/modal/content-outside-dialog.html
index 85e54cb..7e96002 100644
--- a/test/_old_fixtures/containers/nested-fluid-fluid.html
+++ b/test/fixtures/modal/content-outside-dialog.html
@@ -17,13 +17,26 @@
         <script src="../generic-qunit.js"></script>
     </head>
     <body>
-        <div class="container-fluid">
-            <div class="container-fluid"></div>
+        <div class="modal fade" tabindex="-1" role="dialog">
+            <!--Oops, forgot about the .modal-dialog-->
+            <div class="modal-content">
+                <div class="modal-header">
+                    <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span></button>
+                    <h4 class="modal-title">Modal title</h4>
+                </div>
+                <div class="modal-body">
+                    <p>One fine body&hellip;</p>
+                </div>
+                <div class="modal-footer">
+                    <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
+                    <button type="button" class="btn btn-primary">Save changes</button>
+                </div>
+            </div>
         </div>
 
         <div id="qunit"></div>
         <ol id="bootlint">
-            <li data-lint="Containers (`.container` and `.container-fluid`) are not nestable"></li>
+            <li data-lint="`.modal-content` must be a child of `.modal-dialog`"></li>
         </ol>
     </body>
 </html>
diff --git a/test/_old_fixtures/containers/nested-fluid-fixed.html b/test/fixtures/modal/dialog-outside-modal.html
similarity index 51%
rename from test/_old_fixtures/containers/nested-fluid-fixed.html
rename to test/fixtures/modal/dialog-outside-modal.html
index 226fdca..53d667e 100644
--- a/test/_old_fixtures/containers/nested-fluid-fixed.html
+++ b/test/fixtures/modal/dialog-outside-modal.html
@@ -17,13 +17,26 @@
         <script src="../generic-qunit.js"></script>
     </head>
     <body>
-        <div class="container">
-            <div class="container-fluid"></div>
+        <!--Oops, forgot the div.modal wrapper...-->
+        <div class="modal-dialog" role="document">
+            <div class="modal-content">
+                <div class="modal-header">
+                    <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span></button>
+                    <h4 class="modal-title">Modal title</h4>
+                </div>
+                <div class="modal-body">
+                    <p>One fine body&hellip;</p>
+                </div>
+                <div class="modal-footer">
+                    <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
+                    <button type="button" class="btn btn-primary">Save changes</button>
+                </div>
+            </div>
         </div>
 
         <div id="qunit"></div>
         <ol id="bootlint">
-            <li data-lint="Containers (`.container` and `.container-fluid`) are not nestable"></li>
+            <li data-lint="`.modal-dialog` must be a child of `.modal`"></li>
         </ol>
     </body>
 </html>
diff --git a/test/fixtures/modal/footer-outside-content.html b/test/fixtures/modal/footer-outside-content.html
new file mode 100644
index 0000000..1f15beb
--- /dev/null
+++ b/test/fixtures/modal/footer-outside-content.html
@@ -0,0 +1,44 @@
+<!doctype html>
+<html lang="en">
+    <head>
+        <meta charset="utf-8">
+        <meta http-equiv="X-UA-Compatible" content="IE=edge">
+        <meta name="viewport" content="width=device-width, initial-scale=1">
+        <title>Test</title>
+        <link rel="stylesheet" href="../../../node_modules/qunit/qunit/qunit.css">
+
+        <!--[if lt IE 9]>
+            <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
+            <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
+        <![endif]-->
+        <script src="../../../node_modules/jquery/dist/jquery.min.js"></script>
+        <script src="../../../node_modules/qunit/qunit/qunit.js"></script>
+        <script src="../../../dist/browser/bootlint.js"></script>
+        <script src="../generic-qunit.js"></script>
+    </head>
+    <body>
+        <div class="modal fade" tabindex="-1" role="dialog">
+            <div class="modal-dialog" role="document">
+                <div class="modal-content">
+                    <div class="modal-header">
+                        <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span></button>
+                        <h4 class="modal-title">Modal title</h4>
+                    </div>
+                    <div class="modal-body">
+                        <p>One fine body&hellip;</p>
+                    </div>
+                </div>
+                <!--Oops, forgot that the .modal-footer should go within the .modal-content-->
+                <div class="modal-footer">
+                    <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
+                    <button type="button" class="btn btn-primary">Save changes</button>
+                </div>
+            </div>
+        </div>
+
+        <div id="qunit"></div>
+        <ol id="bootlint">
+            <li data-lint="`.modal-footer` must be a child of `.modal-content`"></li>
+        </ol>
+    </body>
+</html>
diff --git a/test/fixtures/modal/header-outside-content.html b/test/fixtures/modal/header-outside-content.html
new file mode 100644
index 0000000..d6bdf8f
--- /dev/null
+++ b/test/fixtures/modal/header-outside-content.html
@@ -0,0 +1,44 @@
+<!doctype html>
+<html lang="en">
+    <head>
+        <meta charset="utf-8">
+        <meta http-equiv="X-UA-Compatible" content="IE=edge">
+        <meta name="viewport" content="width=device-width, initial-scale=1">
+        <title>Test</title>
+        <link rel="stylesheet" href="../../../node_modules/qunit/qunit/qunit.css">
+
+        <!--[if lt IE 9]>
+            <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
+            <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
+        <![endif]-->
+        <script src="../../../node_modules/jquery/dist/jquery.min.js"></script>
+        <script src="../../../node_modules/qunit/qunit/qunit.js"></script>
+        <script src="../../../dist/browser/bootlint.js"></script>
+        <script src="../generic-qunit.js"></script>
+    </head>
+    <body>
+        <div class="modal fade" tabindex="-1" role="dialog">
+            <div class="modal-dialog" role="document">
+                <!--Oops, the .modal-header ought to be within the .modal-content-->
+                <div class="modal-header">
+                    <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span></button>
+                    <h4 class="modal-title">Modal title</h4>
+                </div>
+                <div class="modal-content">
+                    <div class="modal-body">
+                        <p>One fine body&hellip;</p>
+                    </div>
+                    <div class="modal-footer">
+                        <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
+                        <button type="button" class="btn btn-primary">Save changes</button>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+        <div id="qunit"></div>
+        <ol id="bootlint">
+            <li data-lint="`.modal-header` must be a child of `.modal-content`"></li>
+        </ol>
+    </body>
+</html>
diff --git a/test/_old_fixtures/modal/with-hide.html b/test/fixtures/modal/missing-role-dialog.html
similarity index 92%
rename from test/_old_fixtures/modal/with-hide.html
rename to test/fixtures/modal/missing-role-dialog.html
index 9a7ce73..6c5a9c6 100644
--- a/test/_old_fixtures/modal/with-hide.html
+++ b/test/fixtures/modal/missing-role-dialog.html
@@ -17,7 +17,7 @@
         <script src="../generic-qunit.js"></script>
     </head>
     <body>
-        <div class="modal hide" tabindex="-1" role="dialog">
+        <div class="modal fade" tabindex="-1">
             <div class="modal-dialog" role="document">
                 <div class="modal-content">
                     <div class="modal-header">
@@ -37,7 +37,7 @@
 
         <div id="qunit"></div>
         <ol id="bootlint">
-            <li data-lint="`.hide` should not be used on `.modal` in Bootstrap v3."></li>
+            <li data-lint="`.modal` must have a `role=&quot;dialog&quot;` attribute."></li>
         </ol>
     </body>
 </html>
diff --git a/test/fixtures/modal/missing-role-document.html b/test/fixtures/modal/missing-role-document.html
new file mode 100644
index 0000000..18fd70f
--- /dev/null
+++ b/test/fixtures/modal/missing-role-document.html
@@ -0,0 +1,43 @@
+<!doctype html>
+<html lang="en">
+    <head>
+        <meta charset="utf-8">
+        <meta http-equiv="X-UA-Compatible" content="IE=edge">
+        <meta name="viewport" content="width=device-width, initial-scale=1">
+        <title>Test</title>
+        <link rel="stylesheet" href="../../../node_modules/qunit/qunit/qunit.css">
+
+        <!--[if lt IE 9]>
+            <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
+            <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
+        <![endif]-->
+        <script src="../../../node_modules/jquery/dist/jquery.min.js"></script>
+        <script src="../../../node_modules/qunit/qunit/qunit.js"></script>
+        <script src="../../../dist/browser/bootlint.js"></script>
+        <script src="../generic-qunit.js"></script>
+    </head>
+    <body>
+        <div class="modal fade" tabindex="-1" role="dialog">
+            <div class="modal-dialog">
+                <div class="modal-content">
+                    <div class="modal-header">
+                        <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span></button>
+                        <h4 class="modal-title">Modal title</h4>
+                    </div>
+                    <div class="modal-body">
+                        <p>One fine body&hellip;</p>
+                    </div>
+                    <div class="modal-footer">
+                        <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
+                        <button type="button" class="btn btn-primary">Save changes</button>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+        <div id="qunit"></div>
+        <ol id="bootlint">
+            <li data-lint="`.modal-dialog` must have a `role=&quot;document&quot;` attribute."></li>
+        </ol>
+    </body>
+</html>
diff --git a/test/fixtures/modal/tabindex-missing.html b/test/fixtures/modal/tabindex-missing.html
new file mode 100644
index 0000000..3af5c4b
--- /dev/null
+++ b/test/fixtures/modal/tabindex-missing.html
@@ -0,0 +1,43 @@
+<!doctype html>
+<html lang="en">
+    <head>
+        <meta charset="utf-8">
+        <meta http-equiv="X-UA-Compatible" content="IE=edge">
+        <meta name="viewport" content="width=device-width, initial-scale=1">
+        <title>Test</title>
+        <link rel="stylesheet" href="../../../node_modules/qunit/qunit/qunit.css">
+
+        <!--[if lt IE 9]>
+            <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
+            <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
+        <![endif]-->
+        <script src="../../../node_modules/jquery/dist/jquery.min.js"></script>
+        <script src="../../../node_modules/qunit/qunit/qunit.js"></script>
+        <script src="../../../dist/browser/bootlint.js"></script>
+        <script src="../generic-qunit.js"></script>
+    </head>
+    <body>
+        <div class="modal fade" role="dialog">
+            <div class="modal-dialog" role="document">
+                <div class="modal-content">
+                    <div class="modal-header">
+                        <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span></button>
+                        <h4 class="modal-title">Modal title</h4>
+                    </div>
+                    <div class="modal-body">
+                        <p>One fine body&hellip;</p>
+                    </div>
+                    <div class="modal-footer">
+                        <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
+                        <button type="button" class="btn btn-primary">Save changes</button>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+        <div id="qunit"></div>
+        <ol id="bootlint">
+            <li data-lint="`.modal` elements must have a `tabindex` attribute."></li>
+        </ol>
+    </body>
+</html>
diff --git a/test/_old_fixtures/containers/nested-fixed-fluid.html b/test/fixtures/modal/title-outside-header.html
similarity index 55%
rename from test/_old_fixtures/containers/nested-fixed-fluid.html
rename to test/fixtures/modal/title-outside-header.html
index 226fdca..1bf18dc 100644
--- a/test/_old_fixtures/containers/nested-fixed-fluid.html
+++ b/test/fixtures/modal/title-outside-header.html
@@ -17,13 +17,24 @@
         <script src="../generic-qunit.js"></script>
     </head>
     <body>
-        <div class="container">
-            <div class="container-fluid"></div>
+        <div class="modal fade" tabindex="-1" role="dialog">
+            <div class="modal-dialog" role="document">
+                <div class="modal-content">
+                    <h4 class="modal-title">Modal title but I should be inside a modal header</h4>
+                    <div class="modal-body">
+                        <p>One fine body&hellip;</p>
+                    </div>
+                    <div class="modal-footer">
+                        <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
+                        <button type="button" class="btn btn-primary">Save changes</button>
+                    </div>
+                </div>
+            </div>
         </div>
 
         <div id="qunit"></div>
         <ol id="bootlint">
-            <li data-lint="Containers (`.container` and `.container-fluid`) are not nestable"></li>
+            <li data-lint="`.modal-title` must be a child of `.modal-header`"></li>
         </ol>
     </body>
 </html>
diff --git a/test/fixtures/modal/valid.html b/test/fixtures/modal/valid.html
new file mode 100644
index 0000000..523f161
--- /dev/null
+++ b/test/fixtures/modal/valid.html
@@ -0,0 +1,41 @@
+<!doctype html>
+<html lang="en">
+    <head>
+        <meta charset="utf-8">
+        <meta http-equiv="X-UA-Compatible" content="IE=edge">
+        <meta name="viewport" content="width=device-width, initial-scale=1">
+        <title>Test</title>
+        <link rel="stylesheet" href="../../../node_modules/qunit/qunit/qunit.css">
+
+        <!--[if lt IE 9]>
+            <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
+            <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
+        <![endif]-->
+        <script src="../../../node_modules/jquery/dist/jquery.min.js"></script>
+        <script src="../../../node_modules/qunit/qunit/qunit.js"></script>
+        <script src="../../../dist/browser/bootlint.js"></script>
+        <script src="../generic-qunit.js"></script>
+    </head>
+    <body>
+        <div class="modal fade" tabindex="-1" role="dialog">
+            <div class="modal-dialog" role="document">
+                <div class="modal-content">
+                    <div class="modal-header">
+                        <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span></button>
+                        <h4 class="modal-title">Modal title</h4>
+                    </div>
+                    <div class="modal-body">
+                        <p>One fine body&hellip;</p>
+                    </div>
+                    <div class="modal-footer">
+                        <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
+                        <button type="button" class="btn btn-primary">Save changes</button>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+        <div id="qunit"></div>
+        <ol id="bootlint"></ol>
+    </body>
+</html>
diff --git a/test/fixtures/modal/within-navbar.html b/test/fixtures/modal/within-navbar.html
new file mode 100644
index 0000000..bfa1032
--- /dev/null
+++ b/test/fixtures/modal/within-navbar.html
@@ -0,0 +1,48 @@
+<!doctype html>
+<html lang="en">
+    <head>
+        <meta charset="utf-8">
+        <meta http-equiv="X-UA-Compatible" content="IE=edge">
+        <meta name="viewport" content="width=device-width, initial-scale=1">
+        <title>Test</title>
+        <link rel="stylesheet" href="../../../node_modules/qunit/qunit/qunit.css">
+
+        <!--[if lt IE 9]>
+            <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
+            <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
+        <![endif]-->
+        <script src="../../../node_modules/jquery/dist/jquery.min.js"></script>
+        <script src="../../../node_modules/qunit/qunit/qunit.js"></script>
+        <script src="../../../dist/browser/bootlint.js"></script>
+        <script src="../generic-qunit.js"></script>
+    </head>
+    <body>
+      <nav class="navbar navbar-default" role="navigation">
+        <div class="container-fluid">
+          <button type="button" class="btn btn-primary btn-lg" data-toggle="modal" data-target="#myModal">Open ze modal!</button>
+          <div class="modal fade" id="myModal" tabindex="-1" role="dialog">
+              <div class="modal-dialog" role="document">
+                  <div class="modal-content">
+                      <div class="modal-header">
+                          <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span></button>
+                          <h4 class="modal-title">Modal title</h4>
+                      </div>
+                      <div class="modal-body">
+                          <p>One fine body&hellip;</p>
+                      </div>
+                      <div class="modal-footer">
+                          <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
+                          <button type="button" class="btn btn-primary">Save changes</button>
+                      </div>
+                  </div>
+              </div>
+          </div>
+        </div>
+      </nav>
+
+        <div id="qunit"></div>
+        <ol id="bootlint">
+            <li data-lint="Modal markup should not be placed within other components, so as to avoid the component's styles interfering with the modal's appearance or functionality"></li>
+        </ol>
+    </body>
+</html>
diff --git a/test/fixtures/modal/within-table.html b/test/fixtures/modal/within-table.html
new file mode 100644
index 0000000..2e7aa2b
--- /dev/null
+++ b/test/fixtures/modal/within-table.html
@@ -0,0 +1,53 @@
+<!doctype html>
+<html lang="en">
+    <head>
+        <meta charset="utf-8">
+        <meta http-equiv="X-UA-Compatible" content="IE=edge">
+        <meta name="viewport" content="width=device-width, initial-scale=1">
+        <title>Test</title>
+        <link rel="stylesheet" href="../../../node_modules/qunit/qunit/qunit.css">
+
+        <!--[if lt IE 9]>
+            <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
+            <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
+        <![endif]-->
+        <script src="../../../node_modules/jquery/dist/jquery.min.js"></script>
+        <script src="../../../node_modules/qunit/qunit/qunit.js"></script>
+        <script src="../../../dist/browser/bootlint.js"></script>
+        <script src="../generic-qunit.js"></script>
+    </head>
+    <body>
+        <table class="table">
+            <tbody>
+                <tr>
+                    <td>One cell</td>
+                    <td>
+                        <button type="button" class="btn btn-primary btn-lg" data-toggle="modal" data-target="#myModal">Open ze modal!</button>
+                        <div class="modal fade" id="myModal" tabindex="-1" role="dialog">
+                            <div class="modal-dialog" role="document">
+                                <div class="modal-content">
+                                    <div class="modal-header">
+                                        <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span></button>
+                                        <h4 class="modal-title">Modal title</h4>
+                                    </div>
+                                    <div class="modal-body">
+                                        <p>One fine body&hellip;</p>
+                                    </div>
+                                    <div class="modal-footer">
+                                        <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
+                                        <button type="button" class="btn btn-primary">Save changes</button>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+                    </td>
+                </tr>
+            </tbody>
+        </table>
+
+        <div id="qunit"></div>
+        <ol id="bootlint">
+            <li data-lint="Modal markup should not be placed within other components, so as to avoid the component's styles interfering with the modal's appearance or functionality"></li>
+        </ol>
+    </body>
+</html>