diff --git a/.gitignore b/.gitignore index 84d48d86e28c87459c1983a44d436fb11249ce53..559118e173e0e9bd525e9f5286d2cdbf066d3845 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,4 @@ validation-status.json # Folders to ignore bower_components node_modules +.build* diff --git a/.travis.yml b/.travis.yml index 99e274d3a2e760fb1a8c48fbe3bdd92698edea75..b54316e551865e0b723fa24a0aa862985c427ec3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,8 @@ install: - npm install -g grunt-cli - ./test-infra/s3_cache.py download npm-modules - if [ "$TWBS_TEST" = validate-html ] && [ $TWBS_DO_VALIDATOR -ne 0 ]; then ./test-infra/s3_cache.py download rubygems; fi + # Install Meteor preemptively - https://github.com/MeteorPackaging/grunt-meteor#travisyml + - curl https://install.meteor.com | /bin/sh after_script: - if [ "$TRAVIS_REPO_SLUG" != twbs-savage/bootstrap ] && [ "$TWBS_TEST" = core ]; then ./test-infra/s3_cache.py upload npm-modules; fi - if [ "$TRAVIS_REPO_SLUG" != twbs-savage/bootstrap ] && [ "$TWBS_TEST" = validate-html ] && [ $TWBS_DO_VALIDATOR -ne 0 ]; then ./test-infra/s3_cache.py upload rubygems; fi diff --git a/Gruntfile.js b/Gruntfile.js index daa63a5923f585d54807434b5e8820184c0fac6a..913c26a78a353f636290862cc78e99eec253ac36 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -52,7 +52,8 @@ module.exports = function (grunt) { // Task configuration. clean: { dist: 'dist', - docs: 'docs/dist' + docs: 'docs/dist', + meteor: ['.build.*', 'versions.json', 'package.js'] }, jshint: { @@ -384,6 +385,15 @@ module.exports = function (grunt) { exec: { npmUpdate: { command: 'npm update' + }, + // These tasks require Meteor to be installed: curl https://install.meteor.com/ | sh; + meteorTest: { + // the -noglyph(icons) package only runs a subset of the tests from package.js, so skip it + command: 'cp grunt/meteor/package.js .; node_modules/.bin/spacejam --mongo-url mongodb:// test-packages ./' + }, + meteorPublish: { + // publish both packages + command: 'cp grunt/meteor/package.js .; meteor publish; cp grunt/meteor/package-noglyph.js package.js; meteor publish' } }, @@ -456,6 +466,11 @@ module.exports = function (grunt) { grunt.registerTask('less-compile', ['less:compileCore', 'less:compileTheme']); grunt.registerTask('dist-css', ['less-compile', 'autoprefixer:core', 'autoprefixer:theme', 'usebanner', 'csscomb:dist', 'cssmin:minifyCore', 'cssmin:minifyTheme']); + // Meteor tasks + grunt.registerTask('meteor-test', ['exec:meteorTest', 'clean:meteor']); + grunt.registerTask('meteor-publish', ['exec:meteorPublish', 'clean:meteor']); + grunt.registerTask('meteor', ['exec:meteorTest', 'exec:meteorPublish', 'clean:meteor']); + // Full distribution task. grunt.registerTask('dist', ['clean:dist', 'dist-css', 'copy:fonts', 'dist-js']); @@ -507,4 +522,5 @@ module.exports = function (grunt) { done(); }); }); + }; diff --git a/grunt/meteor/README.md b/grunt/meteor/README.md new file mode 100644 index 0000000000000000000000000000000000000000..5bcc473afcc9bf0a130e5e5d1c0dab82bca3123e --- /dev/null +++ b/grunt/meteor/README.md @@ -0,0 +1,52 @@ +[Bootstrap](http://getbootstrap.com) packaged for [Meteor.js](http://meteor.com). + + +# Usage + +```sh +meteor add twbs:bootstrap +``` + +Features requiring JavaScript (such as drop-downs) or custom jQuery plugins like tooltip or popover should work automatically. +If they don't work in templates other than `body`, make sure to run the initialization code in `Template.<yourtemplate>.rendered`: + +```js +Template.foo.rendered = function () { + this.$('[data-toggle="dropdown"]').dropdown(); + this.$('[data-toggle="tooltip"]').tooltip(); + this.$('[data-toggle="popover"]').popover(); +} +``` + +For performance reasons, [the Tooltip and Popover data-apis are opt-in](http://getbootstrap.com/javascript/#popovers). +Above, we initialize them in the limited scope of the template DOM. + + + +# Package features + +* Opt-in jQuery plugins are enabled, as long as you use the `data-toggle` attribute in your HTML. + [Tooltips and popovers](http://getbootstrap.com/javascript/#popovers) "just work". +* No need for CSS override files - Meteor will automatically "convert relative URLs to absolute URLs + when merging CSS files" [since v0.8.1](https://github.com/meteor/meteor/blob/b96c5d7962a9e59b9efaeb93eb81020e0548e378/History.md#v081) + so CSS `@font-face src url('../fonts/...')` will be resolved to the correct `/packages/.../fonts/...` path. +* Tests that fonts are downloadable. +* Tests that [all jQuery custom plugins](http://getbootstrap.com/javascript/) instantiate correctly. +* Visual checks for plugins, including dropdown, popover, tooltip, are included. + + +# Versions + +There are two versions of this package: + +* [twbs:bootstrap](https://atmospherejs.com/twbs/bootstrap) - the CSS, JS including [all jQuery plugins](http://getbootstrap.com/javascript/), + and the Glyphicons font are included. +* [twbs:bootstrap-noglyph](https://atmospherejs.com/twbs/bootstrap-noglyph) - Only the Bootstrap .CSS and .JS files (including the plugins) are + packaged. Useful if you plan to use a different icon set instead of Glyphicons. + +If you need more detailed control on the files, or to use `.less`, see [Nemo64's package](https://github.com/Nemo64/meteor-bootstrap). + + +# Issues + +If you encounter a Meteor-related issue while using this package, please CC @dandv when you [file it](https://github.com/twbs/bootstrap/issues). diff --git a/grunt/meteor/init.js b/grunt/meteor/init.js new file mode 100644 index 0000000000000000000000000000000000000000..7a915c9e250005cade1ed8b876d603de46315ab6 --- /dev/null +++ b/grunt/meteor/init.js @@ -0,0 +1,6 @@ +Meteor.startup(function () { + Template.body.rendered = function () { + $('[data-toggle="tooltip"]').tooltip(); + $('[data-toggle="popover"]').popover(); + }; +}); diff --git a/grunt/meteor/package-noglyph.js b/grunt/meteor/package-noglyph.js new file mode 100644 index 0000000000000000000000000000000000000000..a63b6437afa1dfbd86be288079816625341eb0b4 --- /dev/null +++ b/grunt/meteor/package-noglyph.js @@ -0,0 +1,32 @@ +// package metadata file for Meteor.js +'use strict' + +var packageName = 'twbs:bootstrap-noglyph' // http://atmospherejs.com/twbs/bootstrap-noglyph +var where = 'client' // where to install: 'client' or 'server'. For both, pass nothing. + +var packageJson = JSON.parse(Npm.require("fs").readFileSync('package.json')) + +Package.describe({ + name: packageName, + summary: 'Bootstrap without the Glyphicons font (official): the most popular HTML/CSS/JS responsive framework', // limited to 100 characters + version: packageJson.version, + git: 'https://github.com/twbs/bootstrap.git', + documentation: 'grunt/meteor/README.md' +}) + +Package.onUse(function (api) { + api.versionsFrom(['METEOR@0.9.0', 'METEOR@1.0']) + api.use('jquery') // required by Bootstrap's JavaScript + api.addFiles([ + 'dist/css/bootstrap.css', + 'dist/js/bootstrap.js', + 'grunt/meteor/init.js' + ], where) +}) + +Package.onTest(function (api) { + api.use(packageName, where) + api.use(['tinytest', 'http'], where) + + api.addFiles('grunt/meteor/test-noglyph.js', where) +}) diff --git a/grunt/meteor/package.js b/grunt/meteor/package.js new file mode 100644 index 0000000000000000000000000000000000000000..2095a0e411935ebfbe65c0cdd3ac44fa1ebf4536 --- /dev/null +++ b/grunt/meteor/package.js @@ -0,0 +1,38 @@ +// package metadata file for Meteor.js +'use strict' + +var packageName = 'twbs:bootstrap' // http://atmospherejs.com/twbs/bootstrap +var where = 'client' // where to install: 'client' or 'server'. For both, pass nothing. + +var packageJson = JSON.parse(Npm.require("fs").readFileSync('package.json')) + +Package.describe({ + name: packageName, + summary: 'Bootstrap (official): the most popular HTML/CSS/JS framework for responsive, mobile first projects', // limited to 100 characters + version: packageJson.version, + git: 'https://github.com/twbs/bootstrap.git', + documentation: 'grunt/meteor/README.md' +}) + +Package.onUse(function (api) { + api.versionsFrom(['METEOR@0.9.0', 'METEOR@1.0']) + api.use('jquery') // required by Bootstrap's JavaScript + api.addFiles([ + // we bundle all font files, but the client will request only one of them via the CSS @font-face rule + 'dist/fonts/glyphicons-halflings-regular.eot', // IE8 or older + 'dist/fonts/glyphicons-halflings-regular.svg', // SVG fallback for iOS < 5 - http://caniuse.com/#feat=svg-fonts, http://stackoverflow.com/a/11002874/1269037 + 'dist/fonts/glyphicons-halflings-regular.ttf', // Android Browers 4.1, 4.3 - http://caniuse.com/#feat=ttf + 'dist/fonts/glyphicons-halflings-regular.woff', // Supported by all modern browsers + 'dist/fonts/glyphicons-halflings-regular.woff2', // Most modern font format + 'dist/css/bootstrap.css', + 'dist/js/bootstrap.js', + 'grunt/meteor/init.js' + ], where) +}) + +Package.onTest(function (api) { + api.use(packageName, where) + api.use(['tinytest', 'http'], where) + + api.addFiles('grunt/meteor/test.js', where) +}) diff --git a/grunt/meteor/test-noglyph.js b/grunt/meteor/test-noglyph.js new file mode 100644 index 0000000000000000000000000000000000000000..e972788143181fbf56bcb4567fb7b983e1e9706e --- /dev/null +++ b/grunt/meteor/test-noglyph.js @@ -0,0 +1,41 @@ +'use strict'; // TWBS code style mandates no semicolons - https://github.com/twbs/bootstrap/blob/master/CONTRIBUTING.md#js + +var plugins = ['affix', 'alert', 'button', 'carousel', 'collapse', 'dropdown', 'modal', 'popover', 'scrollspy', 'tab', 'tooltip'] + +// test plugins +plugins.forEach(function (plugin) { + Tinytest.add('Plugin - ' + plugin, function (test) { + test.instanceOf($(document.body)[plugin], Function, 'instantiated correctly') + }) +}) + +// visual check +plugins.forEach(function (plugin) { + + Tinytest.addAsync('Visual check - ' + plugin, function (test, done) { + var bootstrapDropZone = document.createElement('div') + document.body.appendChild(bootstrapDropZone) + + + // TODO ideally we'd get the htmls straight from this repo, but no idea how to do this from TinyTest - http://stackoverflow.com/questions/27180892/pull-an-html-file-into-a-tinytest + HTTP.get('http://rawgit.com/twbs/bootstrap/master/js/tests/visual/' + plugin + '.html', function callback(error, result) { + if (error) { + test.fail('Error getting the test file. Do we have an Internet connection to rawgit.com?') + } else { + // [^] matches across newlines. Stay within the container div, or else the fragment will attempt to load resources on its own. + bootstrapDropZone.innerHTML = result.content.match(/<div[^]+<\/div>/) + test.ok({message: 'Test passed if the display looks OK *and* clicking dropdowns/popovers/tooltips works.'}) + // Only does anything after loading the 'dropdown' plugin test. No idea why it's necessary though. + $('[data-toggle="dropdown"]').dropdown() + // toltips and popovers are initialized by the package, but on Template.body.rendered, which is not available in this Tinytest + $('[data-toggle="tooltip"]').tooltip() + $('[data-toggle="popover"]').popover() + // don't initialize the modals because that messes up the Tinytest runner HTML + } + + done() + }) + + }) + +}) diff --git a/grunt/meteor/test.js b/grunt/meteor/test.js new file mode 100644 index 0000000000000000000000000000000000000000..171834c4128ad4406d6eb8dd274f7d06ba331770 --- /dev/null +++ b/grunt/meteor/test.js @@ -0,0 +1,82 @@ +'use strict'; // TWBS code style mandates no semicolons - https://github.com/twbs/bootstrap/blob/master/CONTRIBUTING.md#js + +var packageName // there seems to be no official way of finding out the name of the very package we're testing - http://stackoverflow.com/questions/27180709/in-a-tinytest-test-file-how-do-i-get-the-name-of-the-package + +// Check that the font files are downloadable. Meteor places assets at /packages/<packageName>/. +['eot', 'svg', 'ttf', 'woff', 'woff2'].forEach(function (font) { + Tinytest.addAsync(font + ' fonts are shipped', function (test, done) { + + // curiously enough, the 'local-test:...' package isn't loaded into Package before calling Tinytest, so we can't do this determination outside this loop + if (!packageName) + Object.keys(Package).forEach(function(p) { + if (p.search(/local-test/) > -1) + packageName = p.replace('local-test:', '') // we should stop the loop, but forEach can't do that + }) + + if (!packageName) { + test.exception({message: 'Package not quite loaded... go figure'}) + return + } + + var packagePath = packageName.replace(':', '_') // e.g. twbs_bootstrap + + HTTP.get( + '/packages/' + packagePath + '/dist/fonts/glyphicons-halflings-regular.' + font, + { + headers: { + 'Cache-Control': 'no-cache' // because Meteor has cached fonts even after they were removed from package.js (!) - https://github.com/meteor/meteor/issues/3196 + } + }, + function callback(error, result) { + if (error) { + test.fail({message: 'Font failed to load'}) + } else { + // if the file is 404, Meteor will redirect to / and return the Meteor.js boilerplate + test.isTrue(result.content.length > 15000, font + ' font could not be downloaded') + } + + done() + } + ) + }) +}) + +var plugins = ['affix', 'alert', 'button', 'carousel', 'collapse', 'dropdown', 'modal', 'popover', 'scrollspy', 'tab', 'tooltip'] + +// test plugins +plugins.forEach(function (plugin) { + Tinytest.add('Plugin - ' + plugin, function (test) { + test.instanceOf($(document.body)[plugin], Function, 'instantiated correctly') + }) +}) + +// visual check +plugins.forEach(function (plugin) { + + Tinytest.addAsync('Visual check - ' + plugin, function (test, done) { + var bootstrapDropZone = document.createElement('div') + document.body.appendChild(bootstrapDropZone) + + + // TODO ideally we'd get the htmls straight from this repo, but no idea how to do this from TinyTest - http://stackoverflow.com/questions/27180892/pull-an-html-file-into-a-tinytest + HTTP.get('http://rawgit.com/twbs/bootstrap/master/js/tests/visual/' + plugin + '.html', function callback(error, result) { + if (error) { + test.fail('Error getting the test file. Do we have an Internet connection to rawgit.com?') + } else { + // [^] matches across newlines. Stay within the container div, or else the fragment will attempt to load resources on its own. + bootstrapDropZone.innerHTML = result.content.match(/<div[^]+<\/div>/) + test.ok({message: 'Test passed if the display looks OK *and* clicking dropdowns/popovers/tooltips works.'}) + // Only does anything after loading the 'dropdown' plugin test. No idea why it's necessary though. + $('[data-toggle="dropdown"]').dropdown() + // toltips and popovers are initialized by the package, but on Template.body.rendered, which is not available in this Tinytest + $('[data-toggle="tooltip"]').tooltip() + $('[data-toggle="popover"]').popover() + // don't initialize the modals because that messes up the Tinytest runner HTML + } + + done() + }) + + }) + +}) diff --git a/package.json b/package.json index c2af0b5242ce3c1774aa99c29db322e7f015e364..95815c5c8d5cc73e4866c0e48c19b5406ea7f4c2 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,8 @@ "load-grunt-tasks": "~3.0.0", "markdown-it": "^3.0.4", "npm-shrinkwrap": "^200.0.0", - "time-grunt": "~1.0.0" + "time-grunt": "~1.0.0", + "spacejam": "^1.1.1" }, "engines": { "node": "~0.10.1"