diff --git a/.gitignore b/.gitignore index 6b5c221c..2fe5b599 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,21 @@ -node_modules +# OS and IDE .emacs* *.flymake *~ .#* .idea +*.sublime-* + +# npm +node_modules +npm-debug.log + +# project-specific +tmp test/browser/less.js test/browser/test-runner-*.htm test/sourcemaps/*.map test/sourcemaps/*.css + +# grunt +.grunt diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 52f86662..0d89c29a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,7 +3,7 @@ > We welcome feature requests and bug reports. Please read these guidelines before submitting one. -**Words that begin with the at sign (`@`) must be wrapped in backticks!** . as a courtesy to avoid sending notifications to any user that might have the `@username` being referenced. Remember, usernames start with the at sign. +**Words that begin with the at sign (`@`) must be wrapped in backticks!** . As a courtesy to avoid sending notifications to any user that might have the `@username` being referenced, please remember that GitHub usernames also start with the at sign. If you don't wrap them in backticks, users will get unintended notifications from you. GitHub has other great markdown features as well, [go here to learn more about them](https://help.github.com/articles/github-flavored-markdown). diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 00000000..ea89099d --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,262 @@ +'use strict'; + +module.exports = function(grunt) { + + // Report the elapsed execution time of tasks. + require('time-grunt')(grunt); + + // Project configuration. + grunt.initConfig({ + + // Metadata required for build. + build: grunt.file.readYAML('build/build.yml'), + pkg: grunt.file.readJSON('package.json'), + meta: { + license: '<%= _.pluck(pkg.licenses, "type").join(", ") %>', + copyright: 'Copyright (c) 2009-<%= grunt.template.today("yyyy") %>', + banner: + '/* \n' + + ' * LESS - <%= pkg.description %> v<%= pkg.version %> \n' + + ' * http://lesscss.org \n' + + ' * \n' + + ' * <%= meta.copyright %>, <%= pkg.author.name %> <<%= pkg.author.email %>> \n' + + ' * Licensed under the <%= meta.license %> License. \n' + + ' * \n' + + ' * @licence \n' + + ' */ \n\n' + }, + + shell: { + options: {stdout: 'log'}, + test: { + command: 'node test/less-test.js' + }, + benchmark: { + command: 'node benchmark/less-benchmark.js' + } + }, + + concat: { + options: { + stripBanners: 'all', + banner: '<%= meta.banner %>\n\n(function (window, undefined) {', + footer: '\n})(window);' + }, + // Browser versions + browser: { + src: ['<%= build.browser %>'], + dest: 'test/browser/less.js' + }, + alpha: { + src: ['<%= build.browser %>'], + dest: 'dist/less-<%= pkg.version %>-alpha.js' + }, + beta: { + src: ['<%= build.browser %>'], + dest: 'dist/less-<%= pkg.version %>-beta.js' + }, + // Rhino + rhino: { + options: { + banner: '/* LESS.js v<%= pkg.version %> RHINO | <%= meta.copyright %>, <%= pkg.author.name %> <<%= pkg.author.email %>> */\n\n', + footer: '' // override task-level footer + }, + src: ['<%= build.rhino %>'], + dest: 'dist/less-rhino-<%= pkg.version %>.js' + }, + // Generate readme + readme: { + // override task-level banner and footer + options: {process: true, banner: '', footer: ''}, + src: ['build/README.md'], + dest: 'README.md' + } + }, + + uglify: { + options: { + banner: '<%= meta.banner %>', + mangle: true + }, + browser: { + src: ['<%= concat.browser.src %>'], + dest: 'dist/less-<%= pkg.version %>.min.js' + }, + alpha: { + src: ['<%= concat.alpha.src %>'], + dest: 'dist/less-<%= pkg.version %>-alpha.min.js' + }, + beta: { + src: ['<%= concat.beta.src %>'], + dest: 'dist/less-<%= pkg.version %>-beta.min.js' + } + }, + + jshint: { + options: {jshintrc: '.jshintrc'}, + files: { + src: [ + 'Gruntfile.js', + 'lib/**/*.js', + 'test/**/*.js', + 'benchmark/*.js' + ] + } + }, + + connect: { + server: { + options: { + port: 8081 + } + } + }, + + jasmine: { + options: { + // version: '2.0.0-rc2', + keepRunner: true, + host: 'http://localhost:8081/', + vendor: 'test/browser/common.js', + template: 'test/browser/test-runner-template.tmpl' + }, + main: { + // src is used to build list of less files to compile + src: ['test/less/*.less', '!test/less/javascript.less', '!test/less/urls.less'], + options: { + helpers: 'test/browser/runner-main-options.js', + specs: 'test/browser/runner-main-spec.js', + outfile: 'tmp/browser/test-runner-main.html' + } + }, + legacy: { + src: ['test/less/legacy/*.less'], + options: { + helpers: 'test/browser/runner-legacy-options.js', + specs: 'test/browser/runner-legacy-spec.js', + outfile: 'tmp/browser/test-runner-legacy.html' + } + }, + errors: { + src: ['test/less/errors/*.less', '!test/less/errors/javascript-error.less'], + options: { + timeout: 20000, + helpers: 'test/browser/runner-errors-options.js', + specs: 'test/browser/runner-errors-spec.js', + outfile: 'tmp/browser/test-runner-errors.html' + } + }, + noJsErrors: { + src: ['test/less/no-js-errors/*.less'], + options: { + helpers: 'test/browser/runner-no-js-errors-options.js', + specs: 'test/browser/runner-no-js-errors-spec.js', + outfile: 'tmp/browser/test-runner-no-js-errors.html' + } + }, + browser: { + src: ['test/browser/less/*.less'], + options: { + helpers: 'test/browser/runner-browser-options.js', + specs: 'test/browser/runner-browser-spec.js', + outfile: 'tmp/browser/test-runner-browser.html' + } + }, + relativeUrls: { + src: ['test/browser/less/relative-urls/*.less'], + options: { + helpers: 'test/browser/runner-relative-urls-options.js', + specs: 'test/browser/runner-relative-urls-spec.js', + outfile: 'tmp/browser/test-runner-relative-urls.html' + } + }, + rootpath: { + src: ['test/browser/less/rootpath/*.less'], + options: { + helpers: 'test/browser/runner-rootpath-options.js', + specs: 'test/browser/runner-rootpath-spec.js', + outfile: 'tmp/browser/test-runner-rootpath.html' + } + }, + rootpathRelative: { + src: ['test/browser/less/rootpath-relative/*.less'], + options: { + helpers: 'test/browser/runner-rootpath-relative-options.js', + specs: 'test/browser/runner-rootpath-relative-spec.js', + outfile: 'tmp/browser/test-runner-rootpath-relative.html' + } + }, + production: { + src: ['test/browser/less/production/*.less'], + options: { + helpers: 'test/browser/runner-production-options.js', + specs: 'test/browser/runner-production-spec.js', + outfile: 'tmp/browser/test-runner-production.html' + } + }, + modifyVars: { + src: ['test/browser/less/modify-vars/*.less'], + options: { + helpers: 'test/browser/runner-modify-vars-options.js', + specs: 'test/browser/runner-modify-vars-spec.js', + outfile: 'tmp/browser/test-runner-modify-vars.html' + } + } + }, + + // Before running tests, clean out the results + // of any previous tests. (this will need to be + // setup based on configuration of browser tests). + clean: { + test: ['test/browser/test-runner-*.htm'] + } + }); + + // Load these plugins to provide the necessary tasks + require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks); + + // Actually load this plugin's task(s). + grunt.loadTasks('build/tasks'); + + // Default task to build Less.js + grunt.registerTask('default', [ + 'concat:browser', + 'uglify:browser' + ]); + + // Alpha + grunt.registerTask('alpha', [ + 'concat:alpha', + 'uglify:alpha' + ]); + + // Beta + grunt.registerTask('beta', [ + 'concat:beta', + 'uglify:beta' + ]); + + // Run all tests + grunt.registerTask('browser', [ + 'connect', + 'jasmine' + ]); + + // Run all tests + grunt.registerTask('test', [ + 'clean', + 'jshint', + 'shell:test', + 'browser' + ]); + + // Run benchmark + grunt.registerTask('benchmark', [ + 'shell:benchmark' + ]); + + // Readme. + grunt.registerTask('readme', [ + 'concat:readme' + ]); +}; diff --git a/Makefile b/Makefile deleted file mode 100644 index ed898a9e..00000000 --- a/Makefile +++ /dev/null @@ -1,114 +0,0 @@ -# -# Run all tests -# -test: - node test/less-test.js - -# -# Run benchmark -# -benchmark: - node benchmark/less-benchmark.js - -# -# Build less.js -# -SRC = lib/less -HEADER = build/header.js -VERSION = `cat package.json | grep version \ - | grep -o '[0-9]\.[0-9]\.[0-9]\+'` -DIST = dist/less-${VERSION}.js -RHINO = dist/less-rhino-${VERSION}.js -DIST_MIN = dist/less-${VERSION}.min.js - -browser-prepare: DIST := test/browser/less.js - -alpha: DIST := dist/less-${VERSION}-alpha.js -alpha: DIST_MIN := dist/less-${VERSION}-alpha.min.js - -beta: DIST := dist/less-${VERSION}-beta.js -beta: DIST_MIN := dist/less-${VERSION}-beta.min.js - -less: - @@mkdir -p dist - @@touch ${DIST} - @@cat ${HEADER} | sed s/@VERSION/${VERSION}/ > ${DIST} - @@echo "(function (window, undefined) {" >> ${DIST} - @@cat build/require.js\ - build/browser-header.js\ - ${SRC}/parser.js\ - ${SRC}/functions.js\ - ${SRC}/colors.js\ - ${SRC}/tree.js\ - ${SRC}/tree/*.js\ - ${SRC}/env.js\ - ${SRC}/visitor.js\ - ${SRC}/import-visitor.js\ - ${SRC}/join-selector-visitor.js\ - ${SRC}/to-css-visitor.js\ - ${SRC}/extend-visitor.js\ - ${SRC}/browser.js\ - build/amd.js >> ${DIST} - @@echo "})(window);" >> ${DIST} - @@echo ${DIST} built. - -browser-prepare: less - node test/browser-test-prepare.js - -browser-test: browser-prepare - phantomjs test/browser/phantom-runner.js - -browser-test-server: browser-prepare - phantomjs test/browser/phantom-runner.js --no-tests - -jshint: - node_modules/.bin/jshint --config ./.jshintrc . - -test-sourcemaps: - node bin/lessc --source-map --source-map-inline test/less/import.less test/sourcemaps/import.css - node bin/lessc --source-map --source-map-inline test/less/sourcemaps/basic.less test/sourcemaps/basic.css - node node_modules/http-server/bin/http-server test/sourcemaps -p 8083 - -rhino: - @@mkdir -p dist - @@touch ${RHINO} - @@cat build/require-rhino.js\ - build/rhino-header.js\ - ${SRC}/parser.js\ - ${SRC}/env.js\ - ${SRC}/visitor.js\ - ${SRC}/import-visitor.js\ - ${SRC}/join-selector-visitor.js\ - ${SRC}/to-css-visitor.js\ - ${SRC}/extend-visitor.js\ - ${SRC}/functions.js\ - ${SRC}/colors.js\ - ${SRC}/tree/*.js\ - ${SRC}/tree.js\ - ${SRC}/rhino.js > ${RHINO} - @@echo ${RHINO} built. - -min: less - @@echo minifying... - @@uglifyjs ${DIST} > ${DIST_MIN} - @@echo ${DIST_MIN} built. - -alpha: min - -beta: min - -alpha-release: alpha - git add dist/*.js - git commit -m "Update alpha ${VERSION}" - -dist: min rhino - git add dist/* - git commit -a -m "(dist) build ${VERSION}" - git archive master --prefix=less/ -o less-${VERSION}.tar.gz - npm publish less-${VERSION}.tar.gz - -stable: - npm tag less@${VERSION} stable - - -.PHONY: test benchmark diff --git a/README.md b/README.md index af7a5689..6783ffc6 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,330 @@ -less.js -======= +# [Less.js v1.4.2](http://lesscss.org) -The **dynamic** stylesheet language. - - - -about ------ +> The **dynamic** stylesheet language. [http://lesscss.org](http://lesscss.org). This is the JavaScript, and now official, stable version of LESS. -For more information on the language and usage visit [lesscss.org](http://lesscss.org). More information also available [in our wiki](https://github.com/less/less.js/wiki) -license -------- +## Getting Started -See `LICENSE` file. +Options for adding Less.js to your project: -> Copyright (c) 2009-2013 Alexis Sellier & The Core Less Team +* Install with [NPM](https://npmjs.org/): `npm install less` +* [Download the latest release][download] +* Clone the repo: `git clone git://github.com/less/less.js.git` + + + +## Feature Highlights +LESS extends CSS with dynamic features such as: + +* [nesting](#nesting) +* [variables](#variables) +* [operations](#operations) +* [mixins](#mixins) +* [extend](#extend) (selector inheritance) + +To learn about the many other features Less.js has to offer please visit [http://lesscss.org](http://lesscss.org) and [the Less.js wiki][wiki] + + +### Examples +#### nesting +Take advantage of nesting to make code more readable and maintainable. This: + +```less +.nav > li > a { + border: 1px solid #f5f5f5; + &:hover { + border-color: #ddd; + } +} +``` + +renders to: + +```css +.nav > li > a { + border: 1px solid #f5f5f5; +} +.nav > li > a:hover { + border-color: #ddd; +} +``` + + +#### variables +Updated commonly used values from a single location. + +```less +// Variables ("inline" comments like this can be used) +@link-color: #428bca; // appears as "sea blue" + +/* Or "block comments" that span + multiple lines, like this */ +a { + color: @link-color; // use the variable in styles +} +``` + +Variables can also be used in `@import` statements, URLs, selector names, and more. + + + +#### operations +Continuing with the same example above, we can use our variables even easier to maintain with _operations_, which enables the use of addition, subraction, multiplication and division in your styles: + +```less +// Variables +@link-color: #428bca; +@link-color-hover: darken(@link-color, 10%); + +// Styles +a { + color: @link-color; +} +a:hover { + color: @link-color-hover; +} +``` +renders to: + +```css +a { + color: #428bca; +} +a:hover { + color: #3071a9; +} +``` + +#### mixins +##### "implicit" mixins +Mixins enable you to apply the styles of one selector inside another selector like this: + +```less +// Any "regular" class... +.link { + color: @link-color; +} +a { + font-weight: bold; + .link; // ...can be used as an "implicit" mixin +} +``` + +renders to: + +```css +.link { + color: #428bca; +} +a { + font-weight: bold; + color: #428bca; +} +``` + +So any selector can be an "implicit mixin". We'll show you a DRYer way to do this below. + + + +##### parametric mixins +Mixins can also accept parameters: + +```less +// Transition mixin +.transition(@transition) { + -webkit-transition: @transition; + -moz-transition: @transition; + -o-transition: @transition; + transition: @transition; +} +``` + +used like this: + +```less +a { + font-weight: bold; + color: @link-color; + .transition(color .2s ease-in-out); + // Hover state + &:hover { + color: @link-color-hover; + } +} +``` + +renders to: + +```css +a { + font-weight: bold; + color: #428bca; + -webkit-transition: color 0.2s ease-in-out; + -moz-transition: color 0.2s ease-in-out; + -o-transition: color 0.2s ease-in-out; + transition: color 0.2s ease-in-out; +} +a:hover { + color: #3071a9; +} +``` + + +#### extend +The `extend` feature can be thought of as the _inverse_ of mixins. It accomplishes the goal of "borrowing styles", but rather than copying all the rules of _Selector A_ over to _Selector B_, `extend` copies the name of the _inheriting selector_ (_Selector B_) over to the _extending selector_ (_Selector A_). So continuing with the example used for [mixins](#mixins) above, extend works like this: + +```less +.link { + color: @link-color; +} +a:extend(.link) { + font-weight: bold; +} +// Can also be written as +a { + &:extend(.link); + font-weight: bold; +} +``` + +renders to: + +```css +.link, a { + color: #428bca; +} +``` + +## Usage + +### Compiling and Parsing +Invoke the compiler from node: + +```javascript +var less = require('less'); + +less.render('.class { width: (1 + 1) }', function (e, css) { + console.log(css); +}); +``` + +Outputs: + +```css +.class { + width: 2; +} +``` + +You may also manually invoke the parser and compiler: + +```javascript +var parser = new(less.Parser); + +parser.parse('.class { width: (1 + 1) }', function (err, tree) { + if (err) { return console.error(err) } + console.log(tree.toCSS()); +}); +``` + + +### Configuration +You may also pass options to the compiler: + +```javascript +var parser = new(less.Parser)({ + paths: ['.', './src/less'], // Specify search paths for @import directives + filename: 'style.less' // Specify a filename, for better error messages +}); + +parser.parse('.class { width: (1 + 1) }', function (e, tree) { + tree.toCSS({ compress: true }); // Minify CSS output +}); +``` + +## More information + +For general information on the language, configuration options or usage visit [lesscss.org](http://lesscss.org) or [the less wiki][wiki]. + +Here are other resources for using Less.js: + +* [stackoverflow.com][so] is a great place to get answers about Less. +* [node.js tools](https://github.com/less/less.js/wiki/Converting-LESS-to-CSS) for converting Less to CSS +* [GUI compilers for Less](https://github.com/less/less.js/wiki/GUI-compilers-that-use-LESS.js) +* [Less.js Issues][issues] for reporting bugs + + + +## Contributing +Please read [CONTRIBUTING.md](./CONTRIBUTING.md). Add unit tests for any new or changed functionality. Lint and test your code using [Grunt](http://gruntjs.com/). + +### Reporting Issues + +Before opening any issue, please search for existing issues and read the [Issue Guidelines](https://github.com/necolas/issue-guidelines), written by [Nicolas Gallagher](https://github.com/necolas/). After that if you find a bug or would like to make feature request, [please open a new issue][issues]. + + + +### Development + +#### Install Less.js + +Start by either [downloading this project][download] manually, or in the command line: + +```shell +git clone https://github.com/less/less.js.git "less" +``` +and then `cd less`. + + +#### Install dependencies + +Tests and benchmarking require Grunt `~0.4.1`. If you haven't used [Grunt](http://gruntjs.com/) before, be sure to check out the [Getting Started](http://gruntjs.com/getting-started) guide, as it explains how to install and use Grunt plugins, which are necessary for development with Less.js. + +To install Grunt and other necessary dependencies run: + +```shell +npm install +``` + +You should now be able to build Less.js, run tests, benchmarking, and other tasks listed in the Gruntfile. + + +## Building Less.js + +The Less.js [Gruntfile](Gruntfile.js) is configured with the following "convenience tasks" (you must first install [the necessary local dependencies](package.json)): + +```shell +$ npm install +``` + +When all of the necessary dependencies are installed you may run the various Grunt.js commands provided: + +#### build - `grunt` +Build Less.js from from the `/lib/less` source files. + +#### test - `grunt test` +Runs jshint, nodeunit and headless jasmine tests using [phantomjs](http://code.google.com/p/phantomjs/). You must have phantomjs installed for the jasmine tests to run. + +#### readme - `grunt readme` +Build the README file from [a template](build/README.md) to ensure that metadata is up-to-date and (more likely to be) correct. + +Please review the [Gruntfile](Gruntfile.js) to become acquainted with the other available tasks. + +**Please note** that if you have any issues installing dependencies or running any of the Gruntfile commands, please make sure to uninstall any previous versions, both in the local node_modules directory, and clear your global npm cache, and then try running `npm install` again. After that if you still have issues, please let us know about it so we can help. + + +## Release History +See the [changelog](CHANGELOG) + +## [License](LICENSE) + +Copyright (c) 2009-2013 [Alexis Sellier](http://cloudhead.io/) & The Core Less Team +Licensed under the [Apache License](LICENSE). + + +[so]: http://stackoverflow.com/questions/tagged/twitter-bootstrap+less "StackOverflow.com" +[issues]: https://github.com/less/less.js/issues "GitHub Issues for Less.js" +[wiki]: https://github.com/less/less.js/wiki "The official wiki for Less.js" +[download]: https://github.com/less/less.js/zipball/master "Download Less.js" \ No newline at end of file diff --git a/build/README.md b/build/README.md new file mode 100644 index 00000000..e3d4e49c --- /dev/null +++ b/build/README.md @@ -0,0 +1,330 @@ +# [Less.js v<%= pkg.version %>](http://lesscss.org) + +> The **dynamic** stylesheet language. [http://lesscss.org](http://lesscss.org). + +This is the JavaScript, and now official, stable version of LESS. + + +## Getting Started + +Options for adding Less.js to your project: + +* Install with [NPM](https://npmjs.org/): `npm install less` +* [Download the latest release][download] +* Clone the repo: `git clone git://github.com/less/less.js.git` + + + +## Feature Highlights +LESS extends CSS with dynamic features such as: + +* [nesting](#nesting) +* [variables](#variables) +* [operations](#operations) +* [mixins](#mixins) +* [extend](#extend) (selector inheritance) + +To learn about the many other features Less.js has to offer please visit [http://lesscss.org](http://lesscss.org) and [the Less.js wiki][wiki] + + +### Examples +#### nesting +Take advantage of nesting to make code more readable and maintainable. This: + +```less +.nav > li > a { + border: 1px solid #f5f5f5; + &:hover { + border-color: #ddd; + } +} +``` + +renders to: + +```css +.nav > li > a { + border: 1px solid #f5f5f5; +} +.nav > li > a:hover { + border-color: #ddd; +} +``` + + +#### variables +Updated commonly used values from a single location. + +```less +// Variables ("inline" comments like this can be used) +@link-color: #428bca; // appears as "sea blue" + +/* Or "block comments" that span + multiple lines, like this */ +a { + color: @link-color; // use the variable in styles +} +``` + +Variables can also be used in `@import` statements, URLs, selector names, and more. + + + +#### operations +Continuing with the same example above, we can use our variables even easier to maintain with _operations_, which enables the use of addition, subraction, multiplication and division in your styles: + +```less +// Variables +@link-color: #428bca; +@link-color-hover: darken(@link-color, 10%); + +// Styles +a { + color: @link-color; +} +a:hover { + color: @link-color-hover; +} +``` +renders to: + +```css +a { + color: #428bca; +} +a:hover { + color: #3071a9; +} +``` + +#### mixins +##### "implicit" mixins +Mixins enable you to apply the styles of one selector inside another selector like this: + +```less +// Any "regular" class... +.link { + color: @link-color; +} +a { + font-weight: bold; + .link; // ...can be used as an "implicit" mixin +} +``` + +renders to: + +```css +.link { + color: #428bca; +} +a { + font-weight: bold; + color: #428bca; +} +``` + +So any selector can be an "implicit mixin". We'll show you a DRYer way to do this below. + + + +##### parametric mixins +Mixins can also accept parameters: + +```less +// Transition mixin +.transition(@transition) { + -webkit-transition: @transition; + -moz-transition: @transition; + -o-transition: @transition; + transition: @transition; +} +``` + +used like this: + +```less +a { + font-weight: bold; + color: @link-color; + .transition(color .2s ease-in-out); + // Hover state + &:hover { + color: @link-color-hover; + } +} +``` + +renders to: + +```css +a { + font-weight: bold; + color: #428bca; + -webkit-transition: color 0.2s ease-in-out; + -moz-transition: color 0.2s ease-in-out; + -o-transition: color 0.2s ease-in-out; + transition: color 0.2s ease-in-out; +} +a:hover { + color: #3071a9; +} +``` + + +#### extend +The `extend` feature can be thought of as the _inverse_ of mixins. It accomplishes the goal of "borrowing styles", but rather than copying all the rules of _Selector A_ over to _Selector B_, `extend` copies the name of the _inheriting selector_ (_Selector B_) over to the _extending selector_ (_Selector A_). So continuing with the example used for [mixins](#mixins) above, extend works like this: + +```less +.link { + color: @link-color; +} +a:extend(.link) { + font-weight: bold; +} +// Can also be written as +a { + &:extend(.link); + font-weight: bold; +} +``` + +renders to: + +```css +.link, a { + color: #428bca; +} +``` + +## Usage + +### Compiling and Parsing +Invoke the compiler from node: + +```javascript +var less = require('less'); + +less.render('.class { width: (1 + 1) }', function (e, css) { + console.log(css); +}); +``` + +Outputs: + +```css +.class { + width: 2; +} +``` + +You may also manually invoke the parser and compiler: + +```javascript +var parser = new(less.Parser); + +parser.parse('.class { width: (1 + 1) }', function (err, tree) { + if (err) { return console.error(err) } + console.log(tree.toCSS()); +}); +``` + + +### Configuration +You may also pass options to the compiler: + +```javascript +var parser = new(less.Parser)({ + paths: ['.', './src/less'], // Specify search paths for @import directives + filename: 'style.less' // Specify a filename, for better error messages +}); + +parser.parse('.class { width: (1 + 1) }', function (e, tree) { + tree.toCSS({ compress: true }); // Minify CSS output +}); +``` + +## More information + +For general information on the language, configuration options or usage visit [lesscss.org](http://lesscss.org) or [the less wiki][wiki]. + +Here are other resources for using Less.js: + +* [stackoverflow.com][so] is a great place to get answers about Less. +* [node.js tools](https://github.com/less/less.js/wiki/Converting-LESS-to-CSS) for converting Less to CSS +* [GUI compilers for Less](https://github.com/less/less.js/wiki/GUI-compilers-that-use-LESS.js) +* [Less.js Issues][issues] for reporting bugs + + + +## Contributing +Please read [CONTRIBUTING.md](./CONTRIBUTING.md). Add unit tests for any new or changed functionality. Lint and test your code using [Grunt](http://gruntjs.com/). + +### Reporting Issues + +Before opening any issue, please search for existing issues and read the [Issue Guidelines](https://github.com/necolas/issue-guidelines), written by [Nicolas Gallagher](https://github.com/necolas/). After that if you find a bug or would like to make feature request, [please open a new issue][issues]. + + + +### Development + +#### Install Less.js + +Start by either [downloading this project][download] manually, or in the command line: + +```shell +git clone https://github.com/less/less.js.git "less" +``` +and then `cd less`. + + +#### Install dependencies + +Tests and benchmarking require Grunt `<%= pkg.devDependencies.grunt %>`. If you haven't used [Grunt](http://gruntjs.com/) before, be sure to check out the [Getting Started](http://gruntjs.com/getting-started) guide, as it explains how to install and use Grunt plugins, which are necessary for development with Less.js. + +To install Grunt and other necessary dependencies run: + +```shell +npm install +``` + +You should now be able to build Less.js, run tests, benchmarking, and other tasks listed in the Gruntfile. + + +## Building Less.js + +The Less.js [Gruntfile](Gruntfile.js) is configured with the following "convenience tasks" (you must first install [the necessary local dependencies](package.json)): + +```shell +$ npm install +``` + +When all of the necessary dependencies are installed you may run the various Grunt.js commands provided: + +#### build - `grunt` +Build Less.js from from the `/lib/less` source files. + +#### test - `grunt test` +Runs jshint, nodeunit and headless jasmine tests using [phantomjs](http://code.google.com/p/phantomjs/). You must have phantomjs installed for the jasmine tests to run. + +#### readme - `grunt readme` +Build the README file from [a template](build/README.md) to ensure that metadata is up-to-date and (more likely to be) correct. + +Please review the [Gruntfile](Gruntfile.js) to become acquainted with the other available tasks. + +**Please note** that if you have any issues installing dependencies or running any of the Gruntfile commands, please make sure to uninstall any previous versions, both in the local node_modules directory, and clear your global npm cache, and then try running `npm install` again. After that if you still have issues, please let us know about it so we can help. + + +## Release History +See the [changelog](CHANGELOG) + +## [License](LICENSE) + +Copyright (c) 2009-<%= grunt.template.today("yyyy") %> [Alexis Sellier](http://cloudhead.io/) & The Core Less Team +Licensed under the [Apache License](LICENSE). + + +[so]: http://stackoverflow.com/questions/tagged/twitter-bootstrap+less "StackOverflow.com" +[issues]: https://github.com/less/less.js/issues "GitHub Issues for Less.js" +[wiki]: https://github.com/less/less.js/wiki "The official wiki for Less.js" +[download]: https://github.com/less/less.js/zipball/master "Download Less.js" \ No newline at end of file diff --git a/build/build.yml b/build/build.yml new file mode 100644 index 00000000..b4f28bd7 --- /dev/null +++ b/build/build.yml @@ -0,0 +1,147 @@ +### +# NOTICE: +# this file is specifically for controlling +# paths for Less.js source files, as well as +# the order in which source files are +# concatenated. +# +# Please do not add paths for anything else +# to this file. All other paths for testing, +# benchmarking and so on should be controlled +# in the Gruntfile. +### + +# Less.js Lib +lib: lib/less + + +# ================================= +# General +# ================================= +prepend: + browser: ['build/require.js', 'build/browser-header.js'] + rhino: build/require-rhino.js + +append: + amd: build/amd.js + browser: <%= build.lib %>/browser.js + rhino: <%= build.lib %>/rhino.js + + +# ================================= +# Core less files +# ================================= + +# <%= build.less.* %> +less: + parser : <%= build.lib %>/parser.js + functions : <%= build.lib %>/functions.js + colors : <%= build.lib %>/colors.js + tree : <%= build.lib %>/tree.js + treedir : <%= build.lib %>/tree/*.js # glob all files in ./lib/less/tree directory + env : <%= build.lib %>/env.js + visitor : <%= build.lib %>/visitor.js + import_visitor : <%= build.lib %>/import-visitor.js + join : <%= build.lib %>/join-selector-visitor.js + to_css_visitor : <%= build.lib %>/to-css-visitor.js + extend_visitor : <%= build.lib %>/extend-visitor.js + browser : <%= build.lib %>/browser.js + source_map_output: <%= build.lib %>/source-map-output.js + + +# ================================= +# Browser build +# ================================= + +# <%= build.browser %> +browser: + + # prepend utils + - <%= build.prepend.browser %> + + # core + - <%= build.less.parser %> + - <%= build.less.functions %> + - <%= build.less.colors %> + - <%= build.less.treedir %> # glob all files + - <%= build.less.tree %> + - <%= build.less.env %> + - <%= build.less.visitor %> + - <%= build.less.import_visitor %> + - <%= build.less.join %> + - <%= build.less.to_css_visitor %> + - <%= build.less.extend_visitor %> + - <%= build.less.source_map_output %> + + # append browser-specific code + - <%= build.append.browser %> + - <%= build.append.amd %> + + +# ================================= +# Rhino build +# ================================= + +# <%= build.rhino %> +rhino: + # prepend utils + - <%= build.prepend.rhino %> + + # core + - <%= build.less.parser %> + - <%= build.less.env %> + - <%= build.less.visitor %> + - <%= build.less.import_visitor %> + - <%= build.less.join %> + - <%= build.less.to_css_visitor %> + - <%= build.less.extend_visitor %> + - <%= build.less.functions %> + - <%= build.less.colors %> + - <%= build.less.treedir %> # glob all files + - <%= build.less.tree %> + - <%= build.less.source_map_output %> + + # append rhino-specific code + - <%= build.append.rhino %> + + +# ================================= +# Tree files +# ================================= + +# <%= build.tree %> +# Technically listing the array out this way isn't +# necessary since we can glob the files in alphabetical +# order anyway. But this gives you control over the order +# the files are used, and allows targeting of individual +# files directly in the Gruntfile. But be we can just +# remove this if files can be concatenated in any order. +tree: + - <%= build.lib %>/tree/alpha.js + - <%= build.lib %>/tree/anonymous.js + - <%= build.lib %>/tree/assignment.js + - <%= build.lib %>/tree/call.js + - <%= build.lib %>/tree/color.js + - <%= build.lib %>/tree/comment.js + - <%= build.lib %>/tree/condition.js + - <%= build.lib %>/tree/dimension.js + - <%= build.lib %>/tree/directive.js + - <%= build.lib %>/tree/element.js + - <%= build.lib %>/tree/expression.js + - <%= build.lib %>/tree/extend.js + - <%= build.lib %>/tree/import.js + - <%= build.lib %>/tree/javascript.js + - <%= build.lib %>/tree/keyword.js + - <%= build.lib %>/tree/media.js + - <%= build.lib %>/tree/mixin.js + - <%= build.lib %>/tree/negative.js + - <%= build.lib %>/tree/operation.js + - <%= build.lib %>/tree/paren.js + - <%= build.lib %>/tree/quoted.js + - <%= build.lib %>/tree/rule.js + - <%= build.lib %>/tree/ruleset.js + - <%= build.lib %>/tree/selector.js + - <%= build.lib %>/tree/unicode-descriptor.js + - <%= build.lib %>/tree/url.js + - <%= build.lib %>/tree/value.js + - <%= build.lib %>/tree/variable.js diff --git a/build/header.js b/build/header.js deleted file mode 100644 index 791b5514..00000000 --- a/build/header.js +++ /dev/null @@ -1,9 +0,0 @@ -/* - * LESS - Leaner CSS v@VERSION - * http://lesscss.org - * - * Copyright (c) 2009-2013, Alexis Sellier - * Licensed under the Apache 2.0 License. - * - * @licence - */ diff --git a/build/tasks/.gitkeep b/build/tasks/.gitkeep new file mode 100644 index 00000000..9a69c4cb --- /dev/null +++ b/build/tasks/.gitkeep @@ -0,0 +1 @@ +# Reserved for specialized Less.js tasks. \ No newline at end of file diff --git a/package.json b/package.json index db7423c3..e4f19180 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,10 @@ "version": "1.5.0-b1", "description": "Leaner CSS", "homepage": "http://lesscss.org", - "author": "Alexis Sellier ", + "author": { + "name": "Alexis Sellier", + "email": "self@cloudhead.net" + }, "contributors": [ "The Core Less Team" ], @@ -14,6 +17,12 @@ "type": "git", "url": "https://github.com/less/less.js.git" }, + "licenses": [ + { + "type": "Apache v2", + "url": "https://github.com/less/less.js/blob/master/LICENSE" + } + ], "bin": { "lessc": "./bin/lessc" }, @@ -28,8 +37,7 @@ "node": ">=0.4.2" }, "scripts": { - "pretest": "make jshint", - "test": "make test" + "test": "grunt test" }, "optionalDependencies": { "mime": "1.2.x", @@ -40,9 +48,18 @@ }, "devDependencies": { "diff": "~1.0", - "jshint": "~2.1.4", - "http-server": "~0.5.5" - }, + "grunt": "~0.4.1", + "grunt-contrib-clean": "~0.5.0", + "grunt-contrib-concat": "~0.3.0", + "grunt-contrib-connect": "~0.3.0", + "grunt-contrib-jasmine": "~0.5.1", + "grunt-contrib-jshint": "~0.6.0", + "grunt-contrib-uglify": "~0.2.2", + "grunt-shell": "~0.3.1", + "http-server": "~0.5.5", + "matchdep": "~0.1.2", + "time-grunt": "~0.1.1" + }, "keywords": [ "compile less", "css nesting", @@ -67,11 +84,5 @@ "stylesheet", "variables in css", "css less" - ], - "licenses": [ - { - "type": "Apache v2", - "url": "https://github.com/less/less.js/blob/master/LICENSE" - } ] -} +} \ No newline at end of file diff --git a/test/browser-test-prepare.js b/test/browser-test-prepare.js deleted file mode 100644 index b8724641..00000000 --- a/test/browser-test-prepare.js +++ /dev/null @@ -1,48 +0,0 @@ -var path = require('path'), - fs = require('fs'); - -var readDirFilesSync = function(dir, regex, callback) { - fs.readdirSync(dir).forEach(function (file) { - if (! regex.test(file)) { return; } - callback(file); - }); -}; - -var createTestRunnerPage = function(dir, exclude, testSuiteName, dir2) { - var output = '\n'; - - readDirFilesSync(path.join("test", dir, 'less', dir2 || ""), /\.less$/, function (file) { - var name = path.basename(file, '.less'), - id = (dir ? dir + '-' : "") + 'less-' + (dir2 ? dir2 + "-" : "") + name; - - if (exclude && name.match(exclude)) { return; } - - output += '\n'; - output += '\n'; - }); - - output += String(fs.readFileSync(path.join('test/browser', 'template.htm'))).replace("{runner-name}", testSuiteName); - - fs.writeFileSync(path.join('test/browser', 'test-runner-'+testSuiteName+'.htm'), output); -}; - -var removeFiles = function(dir, regex) { - readDirFilesSync(dir, regex, function(file) { - fs.unlinkSync(path.join(dir, file), function() { - console.log("Failed to delete " + file); - }); - }); -}; - -removeFiles("test/browser", /test-runner-[a-zA-Z-]*\.htm$/); -createTestRunnerPage("", /javascript|urls/, "main"); -createTestRunnerPage("", null, "legacy", "legacy"); -createTestRunnerPage("", /javascript/, "errors", "errors"); -createTestRunnerPage("", null, "no-js-errors", "no-js-errors"); -createTestRunnerPage("browser", null, "console-errors", "console-errors"); -createTestRunnerPage("browser", null, "browser"); -createTestRunnerPage("browser", null, "relative-urls", "relative-urls"); -createTestRunnerPage("browser", null, "rootpath", "rootpath"); -createTestRunnerPage("browser", null, "rootpath-relative", "rootpath-relative"); -createTestRunnerPage("browser", null, "production"); -createTestRunnerPage("browser", null, "modify-vars", "modify-vars"); diff --git a/test/browser/common.js b/test/browser/common.js index 5460fd44..70152b1e 100644 --- a/test/browser/common.js +++ b/test/browser/common.js @@ -1,16 +1,24 @@ -/*if not async then phantomjs fails to run the webserver and the test concurrently*/ -var less = { async: true, strictMath: true }; - /* record log messages for testing */ +// var logAllIds = function() { +// var allTags = document.head.getElementsByTagName('style'); +// var ids = []; +// for (var tg = 0; tg < allTags.length; tg++) { +// var tag = allTags[tg]; +// if (tag.id) { +// console.log(tag.id); +// } +// } +// }; + var logMessages = [], - realConsoleLog = console.log; + realConsoleLog = console.log; console.log = function(msg) { - logMessages.push(msg); - realConsoleLog.call(console, msg); + logMessages.push(msg); + realConsoleLog.call(console, msg); }; var testLessEqualsInDocument = function() { - testLessInDocument(testSheet); + testLessInDocument(testSheet); }; var testLessErrorsInDocument = function(isConsole) { @@ -18,68 +26,97 @@ var testLessErrorsInDocument = function(isConsole) { }; var testLessInDocument = function(testFunc) { - var links = document.getElementsByTagName('link'), - typePattern = /^text\/(x-)?less$/; + var links = document.getElementsByTagName('link'), + typePattern = /^text\/(x-)?less$/; - for (var i = 0; i < links.length; i++) { - if (links[i].rel === 'stylesheet/less' || (links[i].rel.match(/stylesheet/) && - (links[i].type.match(typePattern)))) { - testFunc(links[i]); - } + for (var i = 0; i < links.length; i++) { + if (links[i].rel === 'stylesheet/less' || (links[i].rel.match(/stylesheet/) && + (links[i].type.match(typePattern)))) { + testFunc(links[i]); } + } }; var testSheet = function(sheet) { - it(sheet.id + " should match the expected output", function() { - var lessOutputId = sheet.id.replace("original-", ""), - expectedOutputId = "expected-" + lessOutputId, - lessOutput = document.getElementById(lessOutputId).innerText, - expectedOutputHref = document.getElementById(expectedOutputId).href, - expectedOutput = loadFile(expectedOutputHref); + it(sheet.id + " should match the expected output", function() { + var lessOutputId = sheet.id.replace("original-", ""), + expectedOutputId = "expected-" + lessOutputId, + lessOutputObj, + lessOutput, + expectedOutputHref = document.getElementById(expectedOutputId).href, + expectedOutput = loadFile(expectedOutputHref); - waitsFor(function() { - return expectedOutput.loaded; - }, "failed to load expected outout", 10000); - - runs(function() { - // use sheet to do testing - expect(expectedOutput.text).toEqual(lessOutput); - }); + // Browser spec generates less on the fly, so we need to loose control + waitsFor(function() { + lessOutputObj = document.getElementById(lessOutputId); + // the type condition is necessary because of inline browser tests + return lessOutputObj !== null && lessOutputObj.type === "text/css"; + }, "generation of " + lessOutputId + "", 700); + + runs(function() { + lessOutput = lessOutputObj.innerText; }); + + waitsFor(function() { + return expectedOutput.loaded; + }, "failed to load expected outout", 10000); + + runs(function() { + // use sheet to do testing + expect(expectedOutput.text).toEqual(lessOutput); + }); + }); }; +//TODO: do it cleaner - the same way as in css + +function extractId(href) { + return href.replace(/^[a-z-]+:\/+?[^\/]+/, '') // Remove protocol & domain + .replace(/^\//, '') // Remove root / + .replace(/\.[a-zA-Z]+$/, '') // Remove simple extension + .replace(/[^\.\w-]+/g, '-') // Replace illegal characters + .replace(/\./g, ':'); // Replace dots with colons(for valid id) +} + var testErrorSheet = function(sheet) { - it(sheet.id + " should match an error", function() { - var lessHref = sheet.href, - id = sheet.id.replace(/^original-less:/, "less-error-message:"), - errorHref = lessHref.replace(/.less$/, ".txt"), - errorFile = loadFile(errorHref), - actualErrorElement = document.getElementById(id), - actualErrorMsg; + it(sheet.id + " should match an error", function() { + var lessHref = sheet.href, + id = "less-error-message:" + extractId(lessHref), + // id = sheet.id.replace(/^original-less:/, "less-error-message:"), + errorHref = lessHref.replace(/.less$/, ".txt"), + errorFile = loadFile(errorHref), + actualErrorElement, + actualErrorMsg; - describe("the error", function() { - expect(actualErrorElement).not.toBe(null); - }); - - actualErrorMsg = actualErrorElement.innerText - .replace(/\n\d+/g, function(lineNo) { return lineNo + " "; }) - .replace(/\n\s*in /g, " in ") - .replace("\n\n", "\n"); + // Less.js sets 10ms timer in order to add error message on top of page. + waitsFor(function() { + actualErrorElement = document.getElementById(id); + return actualErrorElement !== null; + }, "error message was not generated", 70); - waitsFor(function() { - return errorFile.loaded; - }, "failed to load expected outout", 10000); - - runs(function() { - var errorTxt = errorFile.text - .replace("{path}", "") - .replace("{pathrel}", "") - .replace("{pathhref}", "http://localhost:8081/less/errors/") - .replace("{404status}", " (404)"); - expect(errorTxt).toEqual(actualErrorMsg); - if (errorTxt == actualErrorMsg) { - actualErrorElement.style.display = "none"; - } + runs(function() { + actualErrorMsg = actualErrorElement.innerText + .replace(/\n\d+/g, function(lineNo) { + return lineNo + " "; + }) + .replace(/\n\s*in /g, " in ") + .replace("\n\n", "\n"); + }); + + waitsFor(function() { + return errorFile.loaded; + }, "failed to load expected outout", 10000); + + runs(function() { + var errorTxt = errorFile.text + .replace("{path}", "") + .replace("{pathrel}", "") + .replace("{pathhref}", "http://localhost:8081/less/errors/") + .replace("{404status}", " (404)"); + expect(errorTxt).toEqual(actualErrorMsg); + if (errorTxt == actualErrorMsg) { + actualErrorElement.style.display = "none"; + } }); }); }; @@ -115,20 +152,23 @@ var testErrorSheetConsole = function(sheet) { .replace("{404status}", " (404)") .trim(); expect(errorTxt).toEqual(actualErrorMsg); - }); }); + }); }; var loadFile = function(href) { - var request = new XMLHttpRequest(), - response = { loaded: false, text: ""}; - request.open('GET', href, true); - request.onload = function(e) { - response.text = request.response.replace(/\r/g, ""); - response.loaded = true; - } - request.send(); - return response; + var request = new XMLHttpRequest(), + response = { + loaded: false, + text: "" + }; + request.open('GET', href, true); + request.onload = function(e) { + response.text = request.response.replace(/\r/g, ""); + response.loaded = true; + }; + request.send(); + return response; }; (function() { @@ -154,7 +194,7 @@ var loadFile = function(href) { function execJasmine() { setTimeout(function() { - jasmineEnv.execute(); + jasmineEnv.execute(); }, 3000); } diff --git a/test/browser/css/relative-urls/urls.css b/test/browser/css/relative-urls/urls.css index 96bbf604..891d5977 100644 --- a/test/browser/css/relative-urls/urls.css +++ b/test/browser/css/relative-urls/urls.css @@ -1,20 +1,20 @@ -@import "http://localhost:8081/browser/less/imports/modify-this.css"; -@import "http://localhost:8081/browser/less/imports/modify-again.css"; +@import "http://localhost:8081/test/browser/less/imports/modify-this.css"; +@import "http://localhost:8081/test/browser/less/imports/modify-again.css"; .modify { - my-url: url("http://localhost:8081/browser/less/imports/a.png"); + my-url: url("http://localhost:8081/test/browser/less/imports/a.png"); } .modify { - my-url: url("http://localhost:8081/browser/less/imports/b.png"); + my-url: url("http://localhost:8081/test/browser/less/imports/b.png"); } @font-face { src: url("/fonts/garamond-pro.ttf"); - src: local(Futura-Medium), url(http://localhost:8081/browser/less/relative-urls/fonts.svg#MyGeometricModern) format("svg"); + src: local(Futura-Medium), url(http://localhost:8081/test/browser/less/relative-urls/fonts.svg#MyGeometricModern) format("svg"); } #shorthands { background: url("http://www.lesscss.org/spec.html") no-repeat 0 4px; } #misc { - background-image: url(http://localhost:8081/browser/less/relative-urls/images/image.jpg); + background-image: url(http://localhost:8081/test/browser/less/relative-urls/images/image.jpg); } #data-uri { background: url(data:image/png;charset=utf-8;base64, @@ -28,8 +28,8 @@ background: transparent url('data:image/svg+xml, '); } .comma-delimited { - background: url(http://localhost:8081/browser/less/relative-urls/bg.jpg) no-repeat, url(http://localhost:8081/browser/less/relative-urls/bg.png) repeat-x top left, url(http://localhost:8081/browser/less/relative-urls/bg); + background: url(http://localhost:8081/test/browser/less/relative-urls/bg.jpg) no-repeat, url(http://localhost:8081/test/browser/less/relative-urls/bg.png) repeat-x top left, url(http://localhost:8081/test/browser/less/relative-urls/bg); } .values { - url: url('http://localhost:8081/browser/less/relative-urls/Trebuchet'); + url: url('http://localhost:8081/test/browser/less/relative-urls/Trebuchet'); } diff --git a/test/browser/css/urls.css b/test/browser/css/urls.css index 9a9f99e3..3da361b8 100644 --- a/test/browser/css/urls.css +++ b/test/browser/css/urls.css @@ -1,20 +1,20 @@ -@import "http://localhost:8081/browser/less/modify-this.css"; -@import "http://localhost:8081/browser/less/modify-again.css"; +@import "http://localhost:8081/test/browser/less/modify-this.css"; +@import "http://localhost:8081/test/browser/less/modify-again.css"; .modify { - my-url: url("http://localhost:8081/browser/less/a.png"); + my-url: url("http://localhost:8081/test/browser/less/a.png"); } .modify { - my-url: url("http://localhost:8081/browser/less/b.png"); + my-url: url("http://localhost:8081/test/browser/less/b.png"); } @font-face { src: url("/fonts/garamond-pro.ttf"); - src: local(Futura-Medium), url(http://localhost:8081/browser/less/fonts.svg#MyGeometricModern) format("svg"); + src: local(Futura-Medium), url(http://localhost:8081/test/browser/less/fonts.svg#MyGeometricModern) format("svg"); } #shorthands { background: url("http://www.lesscss.org/spec.html") no-repeat 0 4px; } #misc { - background-image: url(http://localhost:8081/browser/less/images/image.jpg); + background-image: url(http://localhost:8081/test/browser/less/images/image.jpg); } #data-uri { background: url(data:image/png;charset=utf-8;base64, @@ -28,10 +28,10 @@ background: transparent url('data:image/svg+xml, '); } .comma-delimited { - background: url(http://localhost:8081/browser/less/bg.jpg) no-repeat, url(http://localhost:8081/browser/less/bg.png) repeat-x top left, url(http://localhost:8081/browser/less/bg); + background: url(http://localhost:8081/test/browser/less/bg.jpg) no-repeat, url(http://localhost:8081/test/browser/less/bg.png) repeat-x top left, url(http://localhost:8081/test/browser/less/bg); } .values { - url: url('http://localhost:8081/browser/less/Trebuchet'); + url: url('http://localhost:8081/test/browser/less/Trebuchet'); } #data-uri { uri: url('http://localhost:8081/data/image.jpg'); diff --git a/test/browser/less/relative-urls/urls.less b/test/browser/less/relative-urls/urls.less index 7923d4c8..b33fdfa6 100644 --- a/test/browser/less/relative-urls/urls.less +++ b/test/browser/less/relative-urls/urls.less @@ -1,5 +1,5 @@ @import ".././imports/urls.less"; -@import "http://localhost:8081/browser/less/imports/urls2.less"; +@import "http://localhost:8081/test/browser/less/imports/urls2.less"; @font-face { src: url("/fonts/garamond-pro.ttf"); src: local(Futura-Medium), diff --git a/test/browser/less/rootpath-relative/urls.less b/test/browser/less/rootpath-relative/urls.less index 1c5ac888..1843fb22 100644 --- a/test/browser/less/rootpath-relative/urls.less +++ b/test/browser/less/rootpath-relative/urls.less @@ -1,5 +1,5 @@ @import "../imports/urls.less"; -@import "http://localhost:8081/browser/less/imports/urls2.less"; +@import "http://localhost:8081/test/browser/less/imports/urls2.less"; @font-face { src: url("/fonts/garamond-pro.ttf"); src: local(Futura-Medium), diff --git a/test/browser/less/rootpath/urls.less b/test/browser/less/rootpath/urls.less index 1c5ac888..1843fb22 100644 --- a/test/browser/less/rootpath/urls.less +++ b/test/browser/less/rootpath/urls.less @@ -1,5 +1,5 @@ @import "../imports/urls.less"; -@import "http://localhost:8081/browser/less/imports/urls2.less"; +@import "http://localhost:8081/test/browser/less/imports/urls2.less"; @font-face { src: url("/fonts/garamond-pro.ttf"); src: local(Futura-Medium), diff --git a/test/browser/less/urls.less b/test/browser/less/urls.less index e640693b..596e5e7b 100644 --- a/test/browser/less/urls.less +++ b/test/browser/less/urls.less @@ -1,5 +1,5 @@ @import "imports/urls.less"; -@import "http://localhost:8081/browser/less/imports/urls2.less"; +@import "http://localhost:8081/test/browser/less/imports/urls2.less"; @font-face { src: url("/fonts/garamond-pro.ttf"); src: local(Futura-Medium), diff --git a/test/browser/phantom-runner.js b/test/browser/phantom-runner.js index 0325ba5b..8233deb8 100644 --- a/test/browser/phantom-runner.js +++ b/test/browser/phantom-runner.js @@ -2,31 +2,35 @@ var webpage = require('webpage'); var server = require('webserver').create(); var system = require('system'); var fs = require('fs'); -var host, port = 8081; +var host; +var port = 8081; -var listening = server.listen(port, function (request, response) { - //console.log("Requested "+request.url); - - var filename = ("test/" + request.url.slice(1)).replace(/[\\\/]/g, fs.separator); - - if (!fs.exists(filename) || !fs.isFile(filename)) { - response.statusCode = 404; - response.write("

File Not Found

File:"+filename+"

"); - response.close(); - return; - } +var listening = server.listen(port, function(request, response) { + //console.log("Requested " + request.url); - // we set the headers here - response.statusCode = 200; - response.headers = {"Cache": "no-cache", "Content-Type": "text/html"}; - - response.write(fs.read(filename)); - + var filename = ("test/" + request.url.slice(1)).replace(/[\\\/]/g, fs.separator); + + if (!fs.exists(filename) || !fs.isFile(filename)) { + response.statusCode = 404; + response.write("

File Not Found

File:" + filename + "

"); response.close(); + return; + } + + // we set the headers here + response.statusCode = 200; + response.headers = { + "Cache": "no-cache", + "Content-Type": "text/html" + }; + + response.write(fs.read(filename)); + + response.close(); }); if (!listening) { - console.log("could not create web server listening on port " + port); - phantom.exit(); + console.log("could not create web server listening on port " + port); + phantom.exit(); } /** @@ -42,100 +46,105 @@ if (!listening) { * @param timeOutMillis the max amount of time to wait. If not specified, 3 sec is used. * @param timeOutErrorMessage the error message if time out occurs */ + function waitFor(testFx, onReady, timeOutMillis, timeOutErrorMessage) { - var maxtimeOutMillis = timeOutMillis ? timeOutMillis : 10001, //< Default Max Timeout is 10s - start = new Date().getTime(), - condition = false, - interval = setInterval(function() { - if ( (new Date().getTime() - start < maxtimeOutMillis) && !condition ) { - // If not time-out yet and condition not yet fulfilled - condition = (typeof(testFx) === "string" ? eval(testFx) : testFx()); //< defensive code - } else { - if(!condition) { - // If condition still not fulfilled (timeout but condition is 'false') - console.log(timeOutErrorMessage || "'waitFor()' timeout"); - phantom.exit(1); - } else { - // Condition fulfilled (timeout and/or condition is 'true') - typeof(onReady) === "string" ? eval(onReady) : onReady(); //< Do what it's supposed to do once the condition is fulfilled - clearInterval(interval); //< Stop this interval - } - } - }, 100); //< repeat check every 100ms -}; + var maxtimeOutMillis = timeOutMillis ? timeOutMillis : 10001, //< Default Max Timeout is 10s + start = new Date().getTime(), + condition = false, + interval = setInterval(function() { + if ((new Date().getTime() - start < maxtimeOutMillis) && !condition) { + // If not time-out yet and condition not yet fulfilled + condition = (typeof(testFx) === "string" ? eval(testFx) : testFx()); //< defensive code + } else { + if (!condition) { + // If condition still not fulfilled (timeout but condition is 'false') + console.log(timeOutErrorMessage || "'waitFor()' timeout"); + phantom.exit(1); + } else { + // Condition fulfilled (timeout and/or condition is 'true') + typeof(onReady) === "string" ? eval(onReady) : onReady(); //< Do what it's supposed to do once the condition is fulfilled + clearInterval(interval); //< Stop this interval + } + } + }, 100); //< repeat check every 100ms +} function testPage(url) { - var page = webpage.create(); - page.open(url, function (status) { - if (status !== "success") { - console.log("Unable to access network - " + status); - phantom.exit(); - } else { - waitFor(function(){ - return page.evaluate(function(){ - return document.body && document.body.querySelector && - document.body.querySelector('.symbolSummary .pending') === null && - document.body.querySelector('.results') !== null; - }); - }, function(){ - page.onConsoleMessage = function (msg) { - console.log(msg); - }; - var exitCode = page.evaluate(function(){ - console.log(''); - console.log(document.body.querySelector('.description').innerText); - var list = document.body.querySelectorAll('.results > #details > .specDetail.failed'); - if (list && list.length > 0) { - console.log(''); - console.log(list.length + ' test(s) FAILED:'); - for (var i = 0; i < list.length; ++i) { - var el = list[i], - desc = el.querySelector('.description'), - msg = el.querySelector('.resultMessage.fail'); - console.log(''); - console.log(desc.innerText); - console.log(msg.innerText); - console.log(''); - } - return 1; - } else { - console.log(document.body.querySelector('.alert > .passingAlert.bar').innerText); - return 0; - } - }); - testFinished(exitCode); - }, - 10000, // 10 second timeout - "Test failed waiting for jasmine results on page: " + url); - } - }); + var page = webpage.create(); + page.open(url, function(status) { + if (status !== "success") { + console.log("Unable to access network - " + status); + phantom.exit(); + } else { + waitFor(function() { + return page.evaluate(function() { + return document.body && document.body.querySelector && + document.body.querySelector('.symbolSummary .pending') === null && + document.body.querySelector('.results') !== null; + }); + }, function() { + page.onConsoleMessage = function(msg) { + console.log(msg); + }; + var exitCode = page.evaluate(function() { + console.log(''); + console.log(document.body.querySelector('.description').innerText); + var list = document.body.querySelectorAll('.results > #details > .specDetail.failed'); + if (list && list.length > 0) { + console.log(''); + console.log(list.length + ' test(s) FAILED:'); + for (var i = 0; i < list.length; ++i) { + var el = list[i], + desc = el.querySelector('.description'), + msg = el.querySelector('.resultMessage.fail'); + console.log(''); + console.log(desc.innerText); + console.log(msg.innerText); + console.log(''); + } + return 1; + } else { + console.log(document.body.querySelector('.alert > .passingAlert.bar').innerText); + return 0; + } + }); + testFinished(exitCode); + }, + 10000, // 10 second timeout + "Test failed waiting for jasmine results on page: " + url); + } + }); } function scanDirectory(path, regex) { - var files = []; - fs.list(path).forEach(function (file) { - if (file.match(regex)) { - files.push(file); - } - }); - return files; -}; + var files = []; + fs.list(path).forEach(function(file) { + if (file.match(regex)) { + files.push(file); + } + }); + return files; +} var totalTests = 0, - totalFailed = 0, - totalDone = 0; + totalFailed = 0, + totalDone = 0; function testFinished(failed) { - if (failed) { totalFailed++; } - totalDone++; - if (totalDone === totalTests) { phantom.exit(totalFailed > 0 ? 1 : 0); } + if (failed) { + totalFailed++; + } + totalDone++; + if (totalDone === totalTests) { + phantom.exit(totalFailed > 0 ? 1 : 0); + } } if (system.args.length != 2 && system.args[1] != "--no-tests") { - var files = scanDirectory("test/browser/", /^test-runner-.+\.htm$/); - totalTests = files.length; - console.log("found " + files.length + " tests"); - files.forEach(function(file) { - testPage("http://localhost:8081/browser/" + file); - }); + var files = scanDirectory("test/browser/", /^test-runner-.+\.htm$/); + totalTests = files.length; + console.log("found " + files.length + " tests"); + files.forEach(function(file) { + testPage("http://localhost:8081/browser/" + file); + }); } \ No newline at end of file diff --git a/test/browser/runner-browser-options.js b/test/browser/runner-browser-options.js new file mode 100644 index 00000000..3d73323e --- /dev/null +++ b/test/browser/runner-browser-options.js @@ -0,0 +1,42 @@ +var less = {}; + +// There originally run inside describe method. However, since they have not +// been inside it, they run at jasmine compile time (not runtime). It all +// worked cause less.js was in async mode and custom phantom runner had +// different setup then grunt-contrib-jasmine. They have been created before +// less.js run, even as they have been defined in spec. + +// test inline less in style tags by grabbing an assortment of less files and doing `@import`s +var testFiles = ['charsets', 'colors', 'comments', 'css-3', 'strings', 'media', 'mixins'], + testSheets = []; + +// setup style tags with less and link tags pointing to expected css output +for (var i = 0; i < testFiles.length; i++) { + var file = testFiles[i], + lessPath = '/test/less/' + file + '.less', + cssPath = '/test/css/' + file + '.css', + lessStyle = document.createElement('style'), + cssLink = document.createElement('link'), + lessText = '@import "' + lessPath + '";'; + + lessStyle.type = 'text/less'; + lessStyle.id = file; + lessStyle.href = file; + + if (lessStyle.styleSheet) { + lessStyle.styleSheet.cssText = lessText; + } else { + lessStyle.innerHTML = lessText; + } + + cssLink.rel = 'stylesheet'; + cssLink.type = 'text/css'; + cssLink.href = cssPath; + cssLink.id = 'expected-' + file; + + var head = document.getElementsByTagName('head')[0]; + + head.appendChild(lessStyle); + head.appendChild(cssLink); + testSheets[i] = lessStyle; +} \ No newline at end of file diff --git a/test/browser/runner-browser-spec.js b/test/browser/runner-browser-spec.js new file mode 100644 index 00000000..ead27b4a --- /dev/null +++ b/test/browser/runner-browser-spec.js @@ -0,0 +1,12 @@ +describe("less.js browser behaviour", function() { + testLessEqualsInDocument(); + + it("has some log messages", function() { + expect(logMessages.length).toBeGreaterThan(0); + }); + + for (var i = 0; i < testFiles.length; i++) { + var sheet = testSheets[i]; + testSheet(sheet); + } +}); diff --git a/test/browser/runner-browser.js b/test/browser/runner-browser.js deleted file mode 100644 index e34b1790..00000000 --- a/test/browser/runner-browser.js +++ /dev/null @@ -1,42 +0,0 @@ -describe("less.js browser behaviour", function() { - testLessEqualsInDocument(); - - it("has some log messages", function() { - expect(logMessages.length).toBeGreaterThan(0); - }); - - // test inline less in style tags by grabbing an assortment of less files and doing `@import`s - var testFiles = ['charsets', 'colors', 'comments', 'css-3', 'strings', 'media', 'mixins'], - testSheets = []; - - // setup style tags with less and link tags pointing to expected css output - for (var i = 0; i < testFiles.length; i++) { - var file = testFiles[i], - lessPath = '/less/' + file + '.less', - cssPath = '/css/' + file + '.css', - lessStyle = document.createElement('style'), - cssLink = document.createElement('link'), - lessText = '@import "' + lessPath + '";'; - lessStyle.type = 'text/less'; - lessStyle.id = file; - if (lessStyle.styleSheet) { - lessStyle.styleSheet.cssText = lessText; - } else { - lessStyle.innerHTML = lessText; - } - cssLink.rel = 'stylesheet'; - cssLink.type = 'text/css'; - cssLink.href = cssPath; - cssLink.id = 'expected-' + file; - - var head = document.getElementsByTagName('head')[0]; - head.appendChild(lessStyle); - head.appendChild(cssLink); - testSheets[i] = lessStyle; - } - - for (var i = 0; i < testFiles.length; i++) { - var sheet = testSheets[i]; - testSheet(sheet); - } -}); \ No newline at end of file diff --git a/test/browser/runner-errors-options.js b/test/browser/runner-errors-options.js new file mode 100644 index 00000000..acbcdf96 --- /dev/null +++ b/test/browser/runner-errors-options.js @@ -0,0 +1,3 @@ +var less = {}; +less.strictUnits = true; + diff --git a/test/browser/runner-errors-spec.js b/test/browser/runner-errors-spec.js new file mode 100644 index 00000000..0b720676 --- /dev/null +++ b/test/browser/runner-errors-spec.js @@ -0,0 +1,4 @@ +describe("less.js error tests", function() { + testLessErrorsInDocument(); +}); + diff --git a/test/browser/runner-errors.js b/test/browser/runner-errors.js deleted file mode 100644 index 93994a57..00000000 --- a/test/browser/runner-errors.js +++ /dev/null @@ -1,5 +0,0 @@ -less.strictUnits = true; - -describe("less.js error tests", function() { - testLessErrorsInDocument(); -}); \ No newline at end of file diff --git a/test/browser/runner-legacy-options.js b/test/browser/runner-legacy-options.js new file mode 100644 index 00000000..9e4b9592 --- /dev/null +++ b/test/browser/runner-legacy-options.js @@ -0,0 +1,4 @@ +var less = {}; +less.strictMath = false; +less.strictUnits = false; + diff --git a/test/browser/runner-legacy.js b/test/browser/runner-legacy-spec.js similarity index 58% rename from test/browser/runner-legacy.js rename to test/browser/runner-legacy-spec.js index e60c84c8..6ba7bfae 100644 --- a/test/browser/runner-legacy.js +++ b/test/browser/runner-legacy-spec.js @@ -1,6 +1,3 @@ -less.strictMath = false; -less.strictUnits = false; - describe("less.js legacy tests", function() { testLessEqualsInDocument(); -}); \ No newline at end of file +}); diff --git a/test/browser/runner-main-options.js b/test/browser/runner-main-options.js new file mode 100644 index 00000000..2433fa9c --- /dev/null +++ b/test/browser/runner-main-options.js @@ -0,0 +1,14 @@ +var less = {}; +less.functions = { + add: function(a, b) { + return new(less.tree.Dimension)(a.value + b.value); + }, + increment: function(a) { + return new(less.tree.Dimension)(a.value + 1); + }, + _color: function(str) { + if (str.value === "evil red") { + return new(less.tree.Color)("600"); + } + } +}; \ No newline at end of file diff --git a/test/browser/runner-main-spec.js b/test/browser/runner-main-spec.js new file mode 100644 index 00000000..b5261e75 --- /dev/null +++ b/test/browser/runner-main-spec.js @@ -0,0 +1,3 @@ +describe("less.js main tests", function() { + testLessEqualsInDocument(); +}); diff --git a/test/browser/runner-main.js b/test/browser/runner-main.js deleted file mode 100644 index d9637c01..00000000 --- a/test/browser/runner-main.js +++ /dev/null @@ -1,15 +0,0 @@ -less.functions = { - add: function (a, b) { - return new(less.tree.Dimension)(a.value + b.value); - }, - increment: function (a) { - return new(less.tree.Dimension)(a.value + 1); - }, - _color: function (str) { - if (str.value === "evil red") { return new(less.tree.Color)("600") } - } -}; - -describe("less.js main tests", function() { - testLessEqualsInDocument(); -}); \ No newline at end of file diff --git a/test/browser/runner-modify-vars-options.js b/test/browser/runner-modify-vars-options.js new file mode 100644 index 00000000..2799ad55 --- /dev/null +++ b/test/browser/runner-modify-vars-options.js @@ -0,0 +1,2 @@ +/* exported less */ +var less = {}; \ No newline at end of file diff --git a/test/browser/runner-modify-vars-spec.js b/test/browser/runner-modify-vars-spec.js new file mode 100644 index 00000000..ee100356 --- /dev/null +++ b/test/browser/runner-modify-vars-spec.js @@ -0,0 +1,43 @@ +var alreadyRun = false; + +describe("less.js modify vars", function() { + beforeEach(function() { + // simulating "setUp" or "beforeAll" method + var lessOutputObj; + if (alreadyRun) + return; + + alreadyRun = true; + + // wait until the sheet is compiled first time + waitsFor(function() { + lessOutputObj = document.getElementById("less:test-less-simple"); + return lessOutputObj !== null; + }, "first generation of less:test-less-simple", 7000); + + // modify variables + runs(function() { + lessOutputObj.type = "not compiled yet"; + less.modifyVars({ + var1: "green", + var2: "purple" + }); + }); + + // wait until variables are modified + waitsFor(function() { + lessOutputObj = document.getElementById("less:test-less-simple"); + return lessOutputObj !== null && lessOutputObj.type === "text/css"; + }, "second generation of less:test-less-simple", 7000); + + }); + + testLessEqualsInDocument(); + it("Should log only 2 XHR requests", function() { + var xhrLogMessages = logMessages.filter(function(item) { + var filename = item.replace(/^.*[\\\/]/, ''); + return (/XHR: Getting '/).test(filename).magenta; + }); + expect(xhrLogMessages.length).toEqual(2); + }); +}); \ No newline at end of file diff --git a/test/browser/runner-modify-vars.js b/test/browser/runner-modify-vars.js deleted file mode 100644 index 4918d8f8..00000000 --- a/test/browser/runner-modify-vars.js +++ /dev/null @@ -1,14 +0,0 @@ - -setTimeout(function(){ - less.modifyVars({var1: "green", var2: "purple"}); -}, 1000); - -describe("less.js modify vars", function() { - testLessEqualsInDocument(); - it("Should log only 2 XHR requests", function() { - var xhrLogMessages = logMessages.filter(function(item) { - return /XHR: Getting '/.test(item); - }) - expect(xhrLogMessages.length).toEqual(2); - }); -}); \ No newline at end of file diff --git a/test/browser/runner-no-js-errors-options.js b/test/browser/runner-no-js-errors-options.js new file mode 100644 index 00000000..2a464944 --- /dev/null +++ b/test/browser/runner-no-js-errors-options.js @@ -0,0 +1,4 @@ +var less = {}; + +less.strictUnits = true; +less.javascriptEnabled = false; diff --git a/test/browser/runner-no-js-errors-spec.js b/test/browser/runner-no-js-errors-spec.js new file mode 100644 index 00000000..38aefe29 --- /dev/null +++ b/test/browser/runner-no-js-errors-spec.js @@ -0,0 +1,4 @@ +describe("less.js javascript disabled error tests", function() { + testLessErrorsInDocument(); +}); + diff --git a/test/browser/runner-no-js-errors.js b/test/browser/runner-no-js-errors.js deleted file mode 100644 index 7c53aa98..00000000 --- a/test/browser/runner-no-js-errors.js +++ /dev/null @@ -1,6 +0,0 @@ -less.strictUnits = true; -less.javascriptEnabled = false; - -describe("less.js javascript disabled error tests", function() { - testLessErrorsInDocument(); -}); \ No newline at end of file diff --git a/test/browser/runner-production-options.js b/test/browser/runner-production-options.js new file mode 100644 index 00000000..f8189d6a --- /dev/null +++ b/test/browser/runner-production-options.js @@ -0,0 +1,3 @@ +var less = {}; +less.env = "production"; + diff --git a/test/browser/runner-production.js b/test/browser/runner-production-spec.js similarity index 84% rename from test/browser/runner-production.js rename to test/browser/runner-production-spec.js index 94c0c8da..8c8d8f42 100644 --- a/test/browser/runner-production.js +++ b/test/browser/runner-production-spec.js @@ -1,7 +1,5 @@ -less.env = "production"; - describe("less.js production behaviour", function() { it("doesn't log any messages", function() { expect(logMessages.length).toEqual(0); }); -}); \ No newline at end of file +}); diff --git a/test/browser/runner-relative-urls-options.js b/test/browser/runner-relative-urls-options.js new file mode 100644 index 00000000..a20eb591 --- /dev/null +++ b/test/browser/runner-relative-urls-options.js @@ -0,0 +1,3 @@ +var less = {}; +less.relativeUrls = true; + diff --git a/test/browser/runner-relative-urls.js b/test/browser/runner-relative-urls-spec.js similarity index 76% rename from test/browser/runner-relative-urls.js rename to test/browser/runner-relative-urls-spec.js index 4e47f639..b13911ee 100644 --- a/test/browser/runner-relative-urls.js +++ b/test/browser/runner-relative-urls-spec.js @@ -1,4 +1,3 @@ -less.relativeUrls = true; describe("less.js browser test - relative url's", function() { testLessEqualsInDocument(); -}); \ No newline at end of file +}); diff --git a/test/browser/runner-rootpath-options.js b/test/browser/runner-rootpath-options.js new file mode 100644 index 00000000..5e475bb4 --- /dev/null +++ b/test/browser/runner-rootpath-options.js @@ -0,0 +1,3 @@ +var less = {}; +less.rootpath = "https://www.github.com/"; + diff --git a/test/browser/runner-rootpath-relative-options.js b/test/browser/runner-rootpath-relative-options.js new file mode 100644 index 00000000..f97baa4b --- /dev/null +++ b/test/browser/runner-rootpath-relative-options.js @@ -0,0 +1,4 @@ +var less = {}; +less.rootpath = "https://www.github.com/cloudhead/less.js/"; +less.relativeUrls = true; + diff --git a/test/browser/runner-rootpath-relative.js b/test/browser/runner-rootpath-relative-spec.js similarity index 54% rename from test/browser/runner-rootpath-relative.js rename to test/browser/runner-rootpath-relative-spec.js index b318b42b..cd905739 100644 --- a/test/browser/runner-rootpath-relative.js +++ b/test/browser/runner-rootpath-relative-spec.js @@ -1,5 +1,3 @@ -less.rootpath = "https://www.github.com/cloudhead/less.js/"; -less.relativeUrls = true; describe("less.js browser test - rootpath and relative url's", function() { testLessEqualsInDocument(); -}); \ No newline at end of file +}); diff --git a/test/browser/runner-rootpath.js b/test/browser/runner-rootpath-spec.js similarity index 67% rename from test/browser/runner-rootpath.js rename to test/browser/runner-rootpath-spec.js index 2c7edd7d..b7b9ba1d 100644 --- a/test/browser/runner-rootpath.js +++ b/test/browser/runner-rootpath-spec.js @@ -1,4 +1,3 @@ -less.rootpath = "https://www.github.com/"; describe("less.js browser test - rootpath url's", function() { testLessEqualsInDocument(); -}); \ No newline at end of file +}); diff --git a/test/browser/template.htm b/test/browser/template.htm index 41be7c0e..6e85f846 100644 --- a/test/browser/template.htm +++ b/test/browser/template.htm @@ -1,11 +1,11 @@ - - - - - - - - - - + + + + + + + + + + \ No newline at end of file diff --git a/test/browser/test-runner-template.tmpl b/test/browser/test-runner-template.tmpl new file mode 100644 index 00000000..0aeaf041 --- /dev/null +++ b/test/browser/test-runner-template.tmpl @@ -0,0 +1,43 @@ + + + + + Jasmine Spec Runner + + + <% var generateScriptTags = function(allScripts) { allScripts.forEach(function(script){ %> + + <% }); }; %> + + + <% scripts.src.forEach(function(fullLessName) { + var pathParts = fullLessName.split('/'); + var fullCssName = fullLessName.replace(/less/g, 'css'); + var lessName = pathParts[pathParts.length - 1]; + var name = lessName.split('.')[0]; %> + + + + <% }); %> + + + <% css.forEach(function(style){ %> + + <% }) %> + + + <% generateScriptTags([].concat(scripts.polyfills, scripts.jasmine)); %> + + + <% generateScriptTags([].concat(scripts.vendor, scripts.helpers)); %> + + <% generateScriptTags(scripts.specs); %> + + + <% generateScriptTags([].concat(scripts.reporters, scripts.start)); %> + + + + + +