Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Meteor integration #15376

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,4 @@ validation-status.json
# Folders to ignore
bower_components
node_modules
.build*
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 17 additions & 1 deletion Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down Expand Up @@ -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'
}
},

Expand Down Expand Up @@ -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']);

Expand Down Expand Up @@ -507,4 +522,5 @@ module.exports = function (grunt) {
done();
});
});

};
52 changes: 52 additions & 0 deletions grunt/meteor/README.md
Original file line number Diff line number Diff line change
@@ -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).
6 changes: 6 additions & 0 deletions grunt/meteor/init.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Meteor.startup(function () {
Template.body.rendered = function () {
$('[data-toggle="tooltip"]').tooltip();
$('[data-toggle="popover"]').popover();
};
});
32 changes: 32 additions & 0 deletions grunt/meteor/package-noglyph.js
Original file line number Diff line number Diff line change
@@ -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(['[email protected]', '[email protected]'])
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)
})
38 changes: 38 additions & 0 deletions grunt/meteor/package.js
Original file line number Diff line number Diff line change
@@ -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(['[email protected]', '[email protected]'])
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)
})
41 changes: 41 additions & 0 deletions grunt/meteor/test-noglyph.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
'use strict'; // TWBS code style mandates no semicolons - https://github.com/twbs/bootstrap/blob/master/CONTRIBUTING.md#js
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove superfluous comment

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"No semicolons" is a rather unique coding style, which conflicts with the Meteor style guide, so I added a mention for future Meteor package maintainers to no start adding semicolons.


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()
})

})

})
82 changes: 82 additions & 0 deletions grunt/meteor/test.js
Original file line number Diff line number Diff line change
@@ -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()
})

})

})
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down