<commit_before>// Generated on 2016-04-21 using generator-angular 0.15.1
'use strict';

// # Globbing
// for performance reasons we're only matching one level down:
// 'test/spec/{,*/}*.js'
// use this if you want to recursively match all subfolders:
// 'test/spec/**/*.js'

module.exports = function (grunt) {

  // Time how long tasks take. Can help when optimizing build times
  require('time-grunt')(grunt);

  // Automatically load required Grunt tasks
  require('jit-grunt')(grunt, {
    useminPrepare: 'grunt-usemin',
    ngtemplates: 'grunt-angular-templates',
    cdnify: 'grunt-google-cdn'
  });

  // Configurable paths for the application
  var appConfig = {
    app: require('./bower.json').appPath || 'app',
    dist: 'dist'
  };

  // Define the configuration for all the tasks
  grunt.initConfig({

    // Project settings
    yeoman: appConfig,

    // Watches files for changes and runs tasks based on the changed files
    watch: {
      bower: {
        files: ['bower.json'],
        tasks: ['wiredep']
      },
      js: {
        files: ['<%= yeoman.app %>/scripts/{,*/}*.js'],
        tasks: ['newer:jshint:all', 'newer:jscs:all'],
        options: {
          livereload: '<%= connect.options.livereload %>'
        }
      },
      jsTest: {
        files: ['test/spec/{,*/}*.js'],
        tasks: ['newer:jshint:test', 'newer:jscs:test', 'karma']
      },
      compass: {
        files: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'],
        tasks: ['compass:server', 'postcss:server']
      },
      gruntfile: {
        files: ['Gruntfile.js']
      },
      livereload: {
        options: {
          livereload: '<%= connect.options.livereload %>'
        },
        files: [
          '<%= yeoman.app %>/{,*/}*.html',
          '.tmp/styles/{,*/}*.css',
          '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}'
        ]
      }
    },

    // The actual grunt server settings
    connect: {
      options: {
        port: 9000,
        // Change this to '0.0.0.0' to access the server from outside.
        hostname: 'localhost',
        livereload: 35729
      },
      livereload: {
        options: {
          open: true,
          middleware: function (connect) {
            return [
              connect.static('.tmp'),
              connect().use(
                '/bower_components',
                connect.static('./bower_components')
              ),
              connect().use(
                '/app/styles',
                connect.static('./app/styles')
              ),
              connect.static(appConfig.app)
            ];
          }
        }
      },
      test: {
        options: {
          port: 9001,
          middleware: function (connect) {
            return [
              connect.static('.tmp'),
              connect.static('test'),
              connect().use(
                '/bower_components',
                connect.static('./bower_components')
              ),
              connect.static(appConfig.app)
            ];
          }
        }
      },
      dist: {
        options: {
          open: true,
          base: '<%= yeoman.dist %>'
        }
      }
    },

    // Make sure there are no obvious mistakes
    jshint: {
      options: {
        jshintrc: '.jshintrc',
        reporter: require('jshint-stylish')
      },
      all: {
        src: [
          'Gruntfile.js',
          '<%= yeoman.app %>/scripts/{,*/}*.js'
        ]
      },
      test: {
        options: {
          jshintrc: 'test/.jshintrc'
        },
        src: ['test/spec/{,*/}*.js']
      }
    },

    // Make sure code styles are up to par
    jscs: {
      options: {
        config: '.jscsrc',
        verbose: true
      },
      all: {
        src: [
          'Gruntfile.js',
          '<%= yeoman.app %>/scripts/{,*/}*.js'
        ]
      },
      test: {
        src: ['test/spec/{,*/}*.js']
      }
    },

    // Empties folders to start fresh
    clean: {
      dist: {
        files: [{
          dot: true,
          src: [
            '.tmp',
            '<%= yeoman.dist %>/{,*/}*',
            '!<%= yeoman.dist %>/.git{,*/}*'
          ]
        }]
      },
      server: '.tmp'
    },

    // Add vendor prefixed styles
    postcss: {
      options: {
        processors: [
          require('autoprefixer-core')({browsers: ['last 1 version']})
        ]
      },
      server: {
        options: {
          map: true
        },
        files: [{
          expand: true,
          cwd: '.tmp/styles/',
          src: '{,*/}*.css',
          dest: '.tmp/styles/'
        }]
      },
      dist: {
        files: [{
          expand: true,
          cwd: '.tmp/styles/',
          src: '{,*/}*.css',
          dest: '.tmp/styles/'
        }]
      }
    },

    // Automatically inject Bower components into the app
    wiredep: {
      app: {
        src: ['<%= yeoman.app %>/index.html'],
        ignorePath:  /\.\.\//
      },
      test: {
        devDependencies: true,
        src: '<%= karma.unit.configFile %>',
        ignorePath:  /\.\.\//,
        fileTypes:{
          js: {
            block: /(([\s\t]*)\/{2}\s*?bower:\s*?(\S*))(\n|\r|.)*?(\/{2}\s*endbower)/gi,
              detect: {
                js: /'(.*\.js)'/gi
              },
              replace: {
                js: '\'{{filePath}}\','
              }
            }
          }
      },
      sass: {
        src: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'],
        ignorePath: /(\.\.\/){1,2}bower_components\//
      }
    },

    // Compiles Sass to CSS and generates necessary files if requested
    compass: {
      options: {
        sassDir: '<%= yeoman.app %>/styles',
        cssDir: '.tmp/styles',
        generatedImagesDir: '.tmp/images/generated',
        imagesDir: '<%= yeoman.app %>/images',
        javascriptsDir: '<%= yeoman.app %>/scripts',
        fontsDir: '<%= yeoman.app %>/styles/fonts',
        importPath: './bower_components',
        httpImagesPath: '/images',
        httpGeneratedImagesPath: '/images/generated',
        httpFontsPath: '/styles/fonts',
        relativeAssets: false,
        assetCacheBuster: false,
        raw: 'Sass::Script::Number.precision = 10\n'
      },
      dist: {
        options: {
          generatedImagesDir: '<%= yeoman.dist %>/images/generated'
        }
      },
      server: {
        options: {
          sourcemap: true
        }
      }
    },

    // Renames files for browser caching purposes
    filerev: {
      dist: {
        src: [
          '<%= yeoman.dist %>/scripts/{,*/}*.js',
          '<%= yeoman.dist %>/styles/{,*/}*.css',
          '<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}',
          '<%= yeoman.dist %>/styles/fonts/*'
        ]
      }
    },

    // Reads HTML for usemin blocks to enable smart builds that automatically
    // concat, minify and revision files. Creates configurations in memory so
    // additional tasks can operate on them
    useminPrepare: {
      html: '<%= yeoman.app %>/index.html',
      options: {
        dest: '<%= yeoman.dist %>',
        flow: {
          html: {
            steps: {
              js: ['concat', 'uglifyjs'],
              css: ['cssmin']
            },
            post: {}
          }
        }
      }
    },

    // Performs rewrites based on filerev and the useminPrepare configuration
    usemin: {
      html: ['<%= yeoman.dist %>/{,*/}*.html'],
      css: ['<%= yeoman.dist %>/styles/{,*/}*.css'],
      js: ['<%= yeoman.dist %>/scripts/{,*/}*.js'],
      options: {
        assetsDirs: [
          '<%= yeoman.dist %>',
          '<%= yeoman.dist %>/images',
          '<%= yeoman.dist %>/styles'
        ],
        patterns: {
          js: [[/(images\/[^''""]*\.(png|jpg|jpeg|gif|webp|svg))/g, 'Replacing references to images']]
        }
      }
    },

    // The following *-min tasks will produce minified files in the dist folder
    // By default, your `index.html`'s <!-- Usemin block --> will take care of
    // minification. These next options are pre-configured if you do not wish
    // to use the Usemin blocks.
    // cssmin: {
    //   dist: {
    //     files: {
    //       '<%= yeoman.dist %>/styles/main.css': [
    //         '.tmp/styles/{,*/}*.css'
    //       ]
    //     }
    //   }
    // },
    // uglify: {
    //   dist: {
    //     files: {
    //       '<%= yeoman.dist %>/scripts/scripts.js': [
    //         '<%= yeoman.dist %>/scripts/scripts.js'
    //       ]
    //     }
    //   }
    // },
    // concat: {
    //   dist: {}
    // },

    imagemin: {
      dist: {
        files: [{
          expand: true,
          cwd: '<%= yeoman.app %>/images',
          src: '{,*/}*.{png,jpg,jpeg,gif}',
          dest: '<%= yeoman.dist %>/images'
        }]
      }
    },

    svgmin: {
      dist: {
        files: [{
          expand: true,
          cwd: '<%= yeoman.app %>/images',
          src: '{,*/}*.svg',
          dest: '<%= yeoman.dist %>/images'
        }]
      }
    },

    htmlmin: {
      dist: {
        options: {
          collapseWhitespace: true,
          conservativeCollapse: true,
          collapseBooleanAttributes: true,
          removeCommentsFromCDATA: true
        },
        files: [{
          expand: true,
          cwd: '<%= yeoman.dist %>',
          src: ['*.html'],
          dest: '<%= yeoman.dist %>'
        }]
      }
    },

    ngtemplates: {
      dist: {
        options: {
          module: 'timelinerApp',
          htmlmin: '<%= htmlmin.dist.options %>',
          usemin: 'scripts/scripts.js'
        },
        cwd: '<%= yeoman.app %>',
        src: 'views/{,*/}*.html',
        dest: '.tmp/templateCache.js'
      }
    },

    // ng-annotate tries to make the code safe for minification automatically
    // by using the Angular long form for dependency injection.
    ngAnnotate: {
      dist: {
        files: [{
          expand: true,
          cwd: '.tmp/concat/scripts',
          src: '*.js',
          dest: '.tmp/concat/scripts'
        }]
      }
    },

    // Replace Google CDN references
    cdnify: {
      dist: {
        html: ['<%= yeoman.dist %>/*.html']
      }
    },

    // Copies remaining files to places other tasks can use
    copy: {
      dist: {
        files: [{
          expand: true,
          dot: true,
          cwd: '<%= yeoman.app %>',
          dest: '<%= yeoman.dist %>',
          src: [
            '*.{ico,png,txt}',
            '*.html',
            'images/{,*/}*.{webp}',
            'styles/fonts/{,*/}*.*'
          ]
        }, {
          expand: true,
          cwd: '.tmp/images',
          dest: '<%= yeoman.dist %>/images',
          src: ['generated/*']
        }, {
          expand: true,
          cwd: '.',
          src: 'bower_components/bootstrap-sass-official/assets/fonts/bootstrap/*',
          dest: '<%= yeoman.dist %>'
        }]
      },
      styles: {
        expand: true,
        cwd: '<%= yeoman.app %>/styles',
        dest: '.tmp/styles/',
        src: '{,*/}*.css'
      }
    },

    // Run some tasks in parallel to speed up the build process
    concurrent: {
      server: [
        'compass:server'
      ],
      test: [
        'compass'
      ],
      dist: [
        'compass:dist',
        'imagemin',
        'svgmin'
      ]
    },

    // Test settings
    karma: {
      unit: {
        configFile: 'test/karma.conf.js',
        singleRun: true
      }
    }
  });

  grunt.registerTask('serve', 'Compile then start a connect web server', function (target) {
    if (target === 'dist') {
      return grunt.task.run(['build', 'connect:dist:keepalive']);
    }

    grunt.task.run([
      'clean:server',
      'wiredep',
      'concurrent:server',
      'postcss:server',
      'connect:livereload',
      'watch'
    ]);
  });

  grunt.registerTask('server', 'DEPRECATED TASK. Use the "serve" task instead', function (target) {
    grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.');
    grunt.task.run(['serve:' + target]);
  });

  grunt.registerTask('test', [
    'clean:server',
    'wiredep',
    'concurrent:test',
    'postcss',
    'connect:test',
    'karma'
  ]);

  grunt.registerTask('build', [
    'clean:dist',
    'wiredep',
    'useminPrepare',
    'concurrent:dist',
    'postcss',
    'ngtemplates',
    'concat',
    'ngAnnotate',
    'copy:dist',
    'cdnify',
    'cssmin',
    'uglify',
    'filerev',
    'usemin',
    'htmlmin'
  ]);

  grunt.registerTask('default', [
    'newer:jshint',
    'newer:jscs',
    'test',
    'build'
  ]);
};
<commit_msg>Closes learning-layers/timeliner#43, added deep watching.<commit_after>// Generated on 2016-04-21 using generator-angular 0.15.1
'use strict';

// # Globbing
// for performance reasons we're only matching one level down:
// 'test/spec/{,*/}*.js'
// use this if you want to recursively match all subfolders:
// 'test/spec/**/*.js'

module.exports = function (grunt) {

  // Time how long tasks take. Can help when optimizing build times
  require('time-grunt')(grunt);

  // Automatically load required Grunt tasks
  require('jit-grunt')(grunt, {
    useminPrepare: 'grunt-usemin',
    ngtemplates: 'grunt-angular-templates',
    cdnify: 'grunt-google-cdn'
  });

  // Configurable paths for the application
  var appConfig = {
    app: require('./bower.json').appPath || 'app',
    dist: 'dist'
  };

  // Define the configuration for all the tasks
  grunt.initConfig({

    // Project settings
    yeoman: appConfig,

    // Watches files for changes and runs tasks based on the changed files
    watch: {
      bower: {
        files: ['bower.json'],
        tasks: ['wiredep']
      },
      js: {
        files: ['<%= yeoman.app %>/scripts/**/*.js'],
        tasks: ['newer:jshint:all', 'newer:jscs:all'],
        options: {
          livereload: '<%= connect.options.livereload %>'
        }
      },
      jsTest: {
        files: ['test/spec/{,*/}*.js'],
        tasks: ['newer:jshint:test', 'newer:jscs:test', 'karma']
      },
      compass: {
        files: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'],
        tasks: ['compass:server', 'postcss:server']
      },
      gruntfile: {
        files: ['Gruntfile.js']
      },
      livereload: {
        options: {
          livereload: '<%= connect.options.livereload %>'
        },
        files: [
          '<%= yeoman.app %>/**/*.html',
          '.tmp/styles/{,*/}*.css',
          '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}'
        ]
      }
    },

    // The actual grunt server settings
    connect: {
      options: {
        port: 9000,
        // Change this to '0.0.0.0' to access the server from outside.
        hostname: 'localhost',
        livereload: 35729
      },
      livereload: {
        options: {
          open: true,
          middleware: function (connect) {
            return [
              connect.static('.tmp'),
              connect().use(
                '/bower_components',
                connect.static('./bower_components')
              ),
              connect().use(
                '/app/styles',
                connect.static('./app/styles')
              ),
              connect.static(appConfig.app)
            ];
          }
        }
      },
      test: {
        options: {
          port: 9001,
          middleware: function (connect) {
            return [
              connect.static('.tmp'),
              connect.static('test'),
              connect().use(
                '/bower_components',
                connect.static('./bower_components')
              ),
              connect.static(appConfig.app)
            ];
          }
        }
      },
      dist: {
        options: {
          open: true,
          base: '<%= yeoman.dist %>'
        }
      }
    },

    // Make sure there are no obvious mistakes
    jshint: {
      options: {
        jshintrc: '.jshintrc',
        reporter: require('jshint-stylish')
      },
      all: {
        src: [
          'Gruntfile.js',
          '<%= yeoman.app %>/scripts/{,*/}*.js'
        ]
      },
      test: {
        options: {
          jshintrc: 'test/.jshintrc'
        },
        src: ['test/spec/{,*/}*.js']
      }
    },

    // Make sure code styles are up to par
    jscs: {
      options: {
        config: '.jscsrc',
        verbose: true
      },
      all: {
        src: [
          'Gruntfile.js',
          '<%= yeoman.app %>/scripts/{,*/}*.js'
        ]
      },
      test: {
        src: ['test/spec/{,*/}*.js']
      }
    },

    // Empties folders to start fresh
    clean: {
      dist: {
        files: [{
          dot: true,
          src: [
            '.tmp',
            '<%= yeoman.dist %>/{,*/}*',
            '!<%= yeoman.dist %>/.git{,*/}*'
          ]
        }]
      },
      server: '.tmp'
    },

    // Add vendor prefixed styles
    postcss: {
      options: {
        processors: [
          require('autoprefixer-core')({browsers: ['last 1 version']})
        ]
      },
      server: {
        options: {
          map: true
        },
        files: [{
          expand: true,
          cwd: '.tmp/styles/',
          src: '{,*/}*.css',
          dest: '.tmp/styles/'
        }]
      },
      dist: {
        files: [{
          expand: true,
          cwd: '.tmp/styles/',
          src: '{,*/}*.css',
          dest: '.tmp/styles/'
        }]
      }
    },

    // Automatically inject Bower components into the app
    wiredep: {
      app: {
        src: ['<%= yeoman.app %>/index.html'],
        ignorePath:  /\.\.\//
      },
      test: {
        devDependencies: true,
        src: '<%= karma.unit.configFile %>',
        ignorePath:  /\.\.\//,
        fileTypes:{
          js: {
            block: /(([\s\t]*)\/{2}\s*?bower:\s*?(\S*))(\n|\r|.)*?(\/{2}\s*endbower)/gi,
              detect: {
                js: /'(.*\.js)'/gi
              },
              replace: {
                js: '\'{{filePath}}\','
              }
            }
          }
      },
      sass: {
        src: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'],
        ignorePath: /(\.\.\/){1,2}bower_components\//
      }
    },

    // Compiles Sass to CSS and generates necessary files if requested
    compass: {
      options: {
        sassDir: '<%= yeoman.app %>/styles',
        cssDir: '.tmp/styles',
        generatedImagesDir: '.tmp/images/generated',
        imagesDir: '<%= yeoman.app %>/images',
        javascriptsDir: '<%= yeoman.app %>/scripts',
        fontsDir: '<%= yeoman.app %>/styles/fonts',
        importPath: './bower_components',
        httpImagesPath: '/images',
        httpGeneratedImagesPath: '/images/generated',
        httpFontsPath: '/styles/fonts',
        relativeAssets: false,
        assetCacheBuster: false,
        raw: 'Sass::Script::Number.precision = 10\n'
      },
      dist: {
        options: {
          generatedImagesDir: '<%= yeoman.dist %>/images/generated'
        }
      },
      server: {
        options: {
          sourcemap: true
        }
      }
    },

    // Renames files for browser caching purposes
    filerev: {
      dist: {
        src: [
          '<%= yeoman.dist %>/scripts/{,*/}*.js',
          '<%= yeoman.dist %>/styles/{,*/}*.css',
          '<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}',
          '<%= yeoman.dist %>/styles/fonts/*'
        ]
      }
    },

    // Reads HTML for usemin blocks to enable smart builds that automatically
    // concat, minify and revision files. Creates configurations in memory so
    // additional tasks can operate on them
    useminPrepare: {
      html: '<%= yeoman.app %>/index.html',
      options: {
        dest: '<%= yeoman.dist %>',
        flow: {
          html: {
            steps: {
              js: ['concat', 'uglifyjs'],
              css: ['cssmin']
            },
            post: {}
          }
        }
      }
    },

    // Performs rewrites based on filerev and the useminPrepare configuration
    usemin: {
      html: ['<%= yeoman.dist %>/{,*/}*.html'],
      css: ['<%= yeoman.dist %>/styles/{,*/}*.css'],
      js: ['<%= yeoman.dist %>/scripts/{,*/}*.js'],
      options: {
        assetsDirs: [
          '<%= yeoman.dist %>',
          '<%= yeoman.dist %>/images',
          '<%= yeoman.dist %>/styles'
        ],
        patterns: {
          js: [[/(images\/[^''""]*\.(png|jpg|jpeg|gif|webp|svg))/g, 'Replacing references to images']]
        }
      }
    },

    // The following *-min tasks will produce minified files in the dist folder
    // By default, your `index.html`'s <!-- Usemin block --> will take care of
    // minification. These next options are pre-configured if you do not wish
    // to use the Usemin blocks.
    // cssmin: {
    //   dist: {
    //     files: {
    //       '<%= yeoman.dist %>/styles/main.css': [
    //         '.tmp/styles/{,*/}*.css'
    //       ]
    //     }
    //   }
    // },
    // uglify: {
    //   dist: {
    //     files: {
    //       '<%= yeoman.dist %>/scripts/scripts.js': [
    //         '<%= yeoman.dist %>/scripts/scripts.js'
    //       ]
    //     }
    //   }
    // },
    // concat: {
    //   dist: {}
    // },

    imagemin: {
      dist: {
        files: [{
          expand: true,
          cwd: '<%= yeoman.app %>/images',
          src: '{,*/}*.{png,jpg,jpeg,gif}',
          dest: '<%= yeoman.dist %>/images'
        }]
      }
    },

    svgmin: {
      dist: {
        files: [{
          expand: true,
          cwd: '<%= yeoman.app %>/images',
          src: '{,*/}*.svg',
          dest: '<%= yeoman.dist %>/images'
        }]
      }
    },

    htmlmin: {
      dist: {
        options: {
          collapseWhitespace: true,
          conservativeCollapse: true,
          collapseBooleanAttributes: true,
          removeCommentsFromCDATA: true
        },
        files: [{
          expand: true,
          cwd: '<%= yeoman.dist %>',
          src: ['*.html'],
          dest: '<%= yeoman.dist %>'
        }]
      }
    },

    ngtemplates: {
      dist: {
        options: {
          module: 'timelinerApp',
          htmlmin: '<%= htmlmin.dist.options %>',
          usemin: 'scripts/scripts.js'
        },
        cwd: '<%= yeoman.app %>',
        src: 'views/{,*/}*.html',
        dest: '.tmp/templateCache.js'
      }
    },

    // ng-annotate tries to make the code safe for minification automatically
    // by using the Angular long form for dependency injection.
    ngAnnotate: {
      dist: {
        files: [{
          expand: true,
          cwd: '.tmp/concat/scripts',
          src: '*.js',
          dest: '.tmp/concat/scripts'
        }]
      }
    },

    // Replace Google CDN references
    cdnify: {
      dist: {
        html: ['<%= yeoman.dist %>/*.html']
      }
    },

    // Copies remaining files to places other tasks can use
    copy: {
      dist: {
        files: [{
          expand: true,
          dot: true,
          cwd: '<%= yeoman.app %>',
          dest: '<%= yeoman.dist %>',
          src: [
            '*.{ico,png,txt}',
            '*.html',
            'images/{,*/}*.{webp}',
            'styles/fonts/{,*/}*.*'
          ]
        }, {
          expand: true,
          cwd: '.tmp/images',
          dest: '<%= yeoman.dist %>/images',
          src: ['generated/*']
        }, {
          expand: true,
          cwd: '.',
          src: 'bower_components/bootstrap-sass-official/assets/fonts/bootstrap/*',
          dest: '<%= yeoman.dist %>'
        }]
      },
      styles: {
        expand: true,
        cwd: '<%= yeoman.app %>/styles',
        dest: '.tmp/styles/',
        src: '{,*/}*.css'
      }
    },

    // Run some tasks in parallel to speed up the build process
    concurrent: {
      server: [
        'compass:server'
      ],
      test: [
        'compass'
      ],
      dist: [
        'compass:dist',
        'imagemin',
        'svgmin'
      ]
    },

    // Test settings
    karma: {
      unit: {
        configFile: 'test/karma.conf.js',
        singleRun: true
      }
    }
  });

  grunt.registerTask('serve', 'Compile then start a connect web server', function (target) {
    if (target === 'dist') {
      return grunt.task.run(['build', 'connect:dist:keepalive']);
    }

    grunt.task.run([
      'clean:server',
      'wiredep',
      'concurrent:server',
      'postcss:server',
      'connect:livereload',
      'watch'
    ]);
  });

  grunt.registerTask('server', 'DEPRECATED TASK. Use the "serve" task instead', function (target) {
    grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.');
    grunt.task.run(['serve:' + target]);
  });

  grunt.registerTask('test', [
    'clean:server',
    'wiredep',
    'concurrent:test',
    'postcss',
    'connect:test',
    'karma'
  ]);

  grunt.registerTask('build', [
    'clean:dist',
    'wiredep',
    'useminPrepare',
    'concurrent:dist',
    'postcss',
    'ngtemplates',
    'concat',
    'ngAnnotate',
    'copy:dist',
    'cdnify',
    'cssmin',
    'uglify',
    'filerev',
    'usemin',
    'htmlmin'
  ]);

  grunt.registerTask('default', [
    'newer:jshint',
    'newer:jscs',
    'test',
    'build'
  ]);
};
<|endoftext|>


<commit_before>/* eslint no-underscore-dangle: 0 */

import React from 'react';
import PropTypes from 'prop-types';
import global from 'global';
import { baseFonts } from '@storybook/components';

import marksy from 'marksy';

import PropTable from './PropTable';
import Node from './Node';
import { Pre } from './markdown';

global.STORYBOOK_REACT_CLASSES = global.STORYBOOK_REACT_CLASSES || [];
const { STORYBOOK_REACT_CLASSES } = global;

const getName = type => type.displayName || type.name;

const stylesheet = {
  button: {
    base: {
      fontFamily: 'sans-serif',
      fontSize: '12px',
      display: 'block',
      position: 'fixed',
      border: 'none',
      background: '#28c',
      color: '#fff',
      padding: '5px 15px',
      cursor: 'pointer',
    },
    topRight: {
      top: 0,
      right: 0,
      borderRadius: '0 0 0 5px',
    },
  },
  info: {
    position: 'fixed',
    background: 'white',
    top: 0,
    bottom: 0,
    left: 0,
    right: 0,
    padding: '0 40px',
    overflow: 'auto',
    zIndex: 99999,
  },
  children: {
    position: 'relative',
    zIndex: 0,
  },
  infoBody: {
    ...baseFonts,
    fontWeight: 300,
    lineHeight: 1.45,
    fontSize: '15px',
    border: '1px solid #eee',
    padding: '20px 40px 40px',
    borderRadius: '2px',
    boxShadow: '0px 2px 3px rgba(0, 0, 0, 0.05)',
    backgroundColor: '#fff',
    marginTop: '50px',
  },
  infoContent: {
    marginBottom: 0,
  },
  infoStory: {},
  jsxInfoContent: {
    borderTop: '1px solid #eee',
    margin: '20px 0 0 0',
  },
  header: {
    h1: {
      margin: 0,
      padding: 0,
      fontSize: '35px',
    },
    h2: {
      margin: '0 0 10px 0',
      padding: 0,
      fontWeight: 400,
      fontSize: '22px',
    },
    body: {
      borderBottom: '1px solid #eee',
      paddingTop: 10,
      marginBottom: 10,
    },
  },
  source: {
    h1: {
      margin: '20px 0 0 0',
      padding: '0 0 5px 0',
      fontSize: '25px',
      borderBottom: '1px solid #EEE',
    },
  },
  propTableHead: {
    margin: '20px 0 0 0',
  },
};

export default class Story extends React.Component {
  constructor(...args) {
    super(...args);
    this.state = {
      open: false,
      stylesheet: this.props.styles(JSON.parse(JSON.stringify(stylesheet))),
    };
    this.marksy = marksy(this.props.marksyConf);
  }

  componentWillReceiveProps(nextProps) {
    this.setState({
      stylesheet: nextProps.styles(JSON.parse(JSON.stringify(stylesheet))),
    });
  }

  _renderStory() {
    return
{this.props.children}
;
  }

  _renderInline() {
    return (

        {this._renderInlineHeader()}
        {this._renderStory()}

            {this._getInfoContent()}
            {this._getComponentDescription()}
            {this._getSourceCode()}
            {this._getPropTables()}

    );
  }

  _renderInlineHeader() {
    const infoHeader = this._getInfoHeader();

    return (
      infoHeader && (

{infoHeader}

      )
    );
  }

  _renderOverlay() {
    const buttonStyle = {
      ...this.state.stylesheet.button.base,
      ...this.state.stylesheet.button.topRight,
    };

    const infoStyle = Object.assign({}, this.state.stylesheet.info);
    if (!this.state.open) {
      infoStyle.display = 'none';
    }

    const openOverlay = () => {
      this.setState({ open: true });
      return false;
    };

    const closeOverlay = () => {
      this.setState({ open: false });
      return false;
    };

    return (

{this.props.children}

        <button type="button" style={buttonStyle} onClick={openOverlay}>
          Show Info
        </button>

          <button type="button" style={buttonStyle} onClick={closeOverlay}>
            ×
          </button>

              {this._getInfoHeader()}
              {this._getInfoContent()}
              {this._getComponentDescription()}
              {this._getSourceCode()}
              {this._getPropTables()}

    );
  }

  _getInfoHeader() {
    if (!this.props.context || !this.props.showHeader) {
      return null;
    }

    return (

{this.props.context.kind}

{this.props.context.story}

    );
  }

  _getInfoContent() {
    if (!this.props.info) {
      return '';
    }

    if (React.isValidElement(this.props.info)) {
      return (

          {this.props.info}

      );
    }

    const lines = this.props.info.split('\n');
    while (lines[0].trim() === '') {
      lines.shift();
    }
    let padding = 0;
    const matches = lines[0].match(/^ */);
    if (matches) {
      padding = matches[0].length;
    }
    const source = lines.map(s => s.slice(padding)).join('\n');
    return
{this.marksy(source).tree}
;
  }

  _getComponentDescription() {
    let retDiv = null;

    if (Object.keys(STORYBOOK_REACT_CLASSES).length) {
      Object.keys(STORYBOOK_REACT_CLASSES).forEach(key => {
        if (STORYBOOK_REACT_CLASSES[key].name === this.props.context.story) {
          retDiv =
{STORYBOOK_REACT_CLASSES[key].docgenInfo.description}
;
        }
      });
    }

    return retDiv;
  }

  _getSourceCode() {
    if (!this.props.showSource) {
      return null;
    }

    const {
      maxPropsIntoLine,
      maxPropObjectKeys,
      maxPropArrayLength,
      maxPropStringLength,
    } = this.props;

    return (

Story Source

          {React.Children.map(this.props.children, (root, idx) => (
            <Node
              key={idx}
              node={root}
              depth={0}
              maxPropsIntoLine={maxPropsIntoLine}
              maxPropObjectKeys={maxPropObjectKeys}
              maxPropArrayLength={maxPropArrayLength}
              maxPropStringLength={maxPropStringLength}
            />
          ))}

    );
  }

  _getPropTables() {
    const types = new Map();

    if (this.props.propTables === null) {
      return null;
    }

    if (!this.props.children) {
      return null;
    }

    if (this.props.propTables) {
      this.props.propTables.forEach(type => {
        types.set(type, true);
      });
    }

    // depth-first traverse and collect types
    const extract = children => {
      if (!children) {
        return;
      }
      if (Array.isArray(children)) {
        children.forEach(extract);
        return;
      }
      if (children.props && children.props.children) {
        extract(children.props.children);
      }
      if (
        typeof children === 'string' ||
        typeof children.type === 'string' ||
        (Array.isArray(this.props.propTablesExclude) && // also ignore excluded types
          ~this.props.propTablesExclude.indexOf(children.type)) // eslint-disable-line no-bitwise
      ) {
        return;
      }
      if (children.type && !types.has(children.type)) {
        types.set(children.type, true);
      }
    };

    // extract components from children
    extract(this.props.children);

    const array = Array.from(types.keys());
    array.sort((a, b) => getName(a) > getName(b));

    const { maxPropObjectKeys, maxPropArrayLength, maxPropStringLength } = this.props;
    const propTables = array.map((type, i) => (
      // eslint-disable-next-line react/no-array-index-key
      <div key={`${getName(type)}_${i}`}>

"{getName(type)}" Component

        <PropTable
          type={type}
          maxPropObjectKeys={maxPropObjectKeys}
          maxPropArrayLength={maxPropArrayLength}
          maxPropStringLength={maxPropStringLength}
        />

    ));

    if (!propTables || propTables.length === 0) {
      return null;
    }

    return (

Prop Types

        {propTables}

    );
  }

  render() {
    if (this.props.showInline) {
      return this._renderInline();
    }

    return this._renderOverlay();
  }
}

Story.displayName = 'Story';

Story.propTypes = {
  context: PropTypes.shape({
    kind: PropTypes.string,
    story: PropTypes.string,
  }),
  info: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
  propTables: PropTypes.arrayOf(PropTypes.func),
  propTablesExclude: PropTypes.arrayOf(PropTypes.func),
  showInline: PropTypes.bool,
  showHeader: PropTypes.bool,
  showSource: PropTypes.bool,
  styles: PropTypes.func.isRequired,
  children: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
  marksyConf: PropTypes.object, // eslint-disable-line react/forbid-prop-types
  maxPropsIntoLine: PropTypes.number.isRequired,
  maxPropObjectKeys: PropTypes.number.isRequired,
  maxPropArrayLength: PropTypes.number.isRequired,
  maxPropStringLength: PropTypes.number.isRequired,
};
Story.defaultProps = {
  context: null,
  info: '',
  children: null,
  propTables: null,
  propTablesExclude: [],
  showInline: false,
  showHeader: true,
  showSource: true,
  marksyConf: {},
};
<commit_msg>Fix whitespace on info add-on card<commit_after>/* eslint no-underscore-dangle: 0 */

import React from 'react';
import PropTypes from 'prop-types';
import global from 'global';
import { baseFonts } from '@storybook/components';

import marksy from 'marksy';

import PropTable from './PropTable';
import Node from './Node';
import { Pre } from './markdown';

global.STORYBOOK_REACT_CLASSES = global.STORYBOOK_REACT_CLASSES || [];
const { STORYBOOK_REACT_CLASSES } = global;

const getName = type => type.displayName || type.name;

const stylesheet = {
  button: {
    base: {
      fontFamily: 'sans-serif',
      fontSize: '12px',
      display: 'block',
      position: 'fixed',
      border: 'none',
      background: '#28c',
      color: '#fff',
      padding: '5px 15px',
      cursor: 'pointer',
    },
    topRight: {
      top: 0,
      right: 0,
      borderRadius: '0 0 0 5px',
    },
  },
  info: {
    position: 'fixed',
    background: 'white',
    top: 0,
    bottom: 0,
    left: 0,
    right: 0,
    padding: '0 40px',
    overflow: 'auto',
    zIndex: 99999,
  },
  children: {
    position: 'relative',
    zIndex: 0,
  },
  infoBody: {
    ...baseFonts,
    fontWeight: 300,
    lineHeight: 1.45,
    fontSize: '15px',
    border: '1px solid #eee',
    padding: '20px 40px 40px',
    borderRadius: '2px',
    boxShadow: '0px 2px 3px rgba(0, 0, 0, 0.05)',
    backgroundColor: '#fff',
    marginTop: '20px',
    marginBottom: '20px',
  },
  infoContent: {
    marginBottom: 0,
  },
  infoStory: {},
  jsxInfoContent: {
    borderTop: '1px solid #eee',
    margin: '20px 0 0 0',
  },
  header: {
    h1: {
      margin: 0,
      padding: 0,
      fontSize: '35px',
    },
    h2: {
      margin: '0 0 10px 0',
      padding: 0,
      fontWeight: 400,
      fontSize: '22px',
    },
    body: {
      borderBottom: '1px solid #eee',
      paddingTop: 10,
      marginBottom: 10,
    },
  },
  source: {
    h1: {
      margin: '20px 0 0 0',
      padding: '0 0 5px 0',
      fontSize: '25px',
      borderBottom: '1px solid #EEE',
    },
  },
  propTableHead: {
    margin: '20px 0 0 0',
  },
};

export default class Story extends React.Component {
  constructor(...args) {
    super(...args);
    this.state = {
      open: false,
      stylesheet: this.props.styles(JSON.parse(JSON.stringify(stylesheet))),
    };
    this.marksy = marksy(this.props.marksyConf);
  }

  componentWillReceiveProps(nextProps) {
    this.setState({
      stylesheet: nextProps.styles(JSON.parse(JSON.stringify(stylesheet))),
    });
  }

  _renderStory() {
    return
{this.props.children}
;
  }

  _renderInline() {
    return (

        {this._renderInlineHeader()}
        {this._renderStory()}

            {this._getInfoContent()}
            {this._getComponentDescription()}
            {this._getSourceCode()}
            {this._getPropTables()}

    );
  }

  _renderInlineHeader() {
    const infoHeader = this._getInfoHeader();

    return (
      infoHeader && (

{infoHeader}

      )
    );
  }

  _renderOverlay() {
    const buttonStyle = {
      ...this.state.stylesheet.button.base,
      ...this.state.stylesheet.button.topRight,
    };

    const infoStyle = Object.assign({}, this.state.stylesheet.info);
    if (!this.state.open) {
      infoStyle.display = 'none';
    }

    const openOverlay = () => {
      this.setState({ open: true });
      return false;
    };

    const closeOverlay = () => {
      this.setState({ open: false });
      return false;
    };

    return (

{this.props.children}

        <button type="button" style={buttonStyle} onClick={openOverlay}>
          Show Info
        </button>

          <button type="button" style={buttonStyle} onClick={closeOverlay}>
            ×
          </button>

              {this._getInfoHeader()}
              {this._getInfoContent()}
              {this._getComponentDescription()}
              {this._getSourceCode()}
              {this._getPropTables()}

    );
  }

  _getInfoHeader() {
    if (!this.props.context || !this.props.showHeader) {
      return null;
    }

    return (

{this.props.context.kind}

{this.props.context.story}

    );
  }

  _getInfoContent() {
    if (!this.props.info) {
      return '';
    }

    if (React.isValidElement(this.props.info)) {
      return (

          {this.props.info}

      );
    }

    const lines = this.props.info.split('\n');
    while (lines[0].trim() === '') {
      lines.shift();
    }
    let padding = 0;
    const matches = lines[0].match(/^ */);
    if (matches) {
      padding = matches[0].length;
    }
    const source = lines.map(s => s.slice(padding)).join('\n');
    return
{this.marksy(source).tree}
;
  }

  _getComponentDescription() {
    let retDiv = null;

    if (Object.keys(STORYBOOK_REACT_CLASSES).length) {
      Object.keys(STORYBOOK_REACT_CLASSES).forEach(key => {
        if (STORYBOOK_REACT_CLASSES[key].name === this.props.context.story) {
          retDiv =
{STORYBOOK_REACT_CLASSES[key].docgenInfo.description}
;
        }
      });
    }

    return retDiv;
  }

  _getSourceCode() {
    if (!this.props.showSource) {
      return null;
    }

    const {
      maxPropsIntoLine,
      maxPropObjectKeys,
      maxPropArrayLength,
      maxPropStringLength,
    } = this.props;

    return (

Story Source

          {React.Children.map(this.props.children, (root, idx) => (
            <Node
              key={idx}
              node={root}
              depth={0}
              maxPropsIntoLine={maxPropsIntoLine}
              maxPropObjectKeys={maxPropObjectKeys}
              maxPropArrayLength={maxPropArrayLength}
              maxPropStringLength={maxPropStringLength}
            />
          ))}

    );
  }

  _getPropTables() {
    const types = new Map();

    if (this.props.propTables === null) {
      return null;
    }

    if (!this.props.children) {
      return null;
    }

    if (this.props.propTables) {
      this.props.propTables.forEach(type => {
        types.set(type, true);
      });
    }

    // depth-first traverse and collect types
    const extract = children => {
      if (!children) {
        return;
      }
      if (Array.isArray(children)) {
        children.forEach(extract);
        return;
      }
      if (children.props && children.props.children) {
        extract(children.props.children);
      }
      if (
        typeof children === 'string' ||
        typeof children.type === 'string' ||
        (Array.isArray(this.props.propTablesExclude) && // also ignore excluded types
          ~this.props.propTablesExclude.indexOf(children.type)) // eslint-disable-line no-bitwise
      ) {
        return;
      }
      if (children.type && !types.has(children.type)) {
        types.set(children.type, true);
      }
    };

    // extract components from children
    extract(this.props.children);

    const array = Array.from(types.keys());
    array.sort((a, b) => getName(a) > getName(b));

    const { maxPropObjectKeys, maxPropArrayLength, maxPropStringLength } = this.props;
    const propTables = array.map((type, i) => (
      // eslint-disable-next-line react/no-array-index-key
      <div key={`${getName(type)}_${i}`}>

"{getName(type)}" Component

        <PropTable
          type={type}
          maxPropObjectKeys={maxPropObjectKeys}
          maxPropArrayLength={maxPropArrayLength}
          maxPropStringLength={maxPropStringLength}
        />

    ));

    if (!propTables || propTables.length === 0) {
      return null;
    }

    return (

Prop Types

        {propTables}

    );
  }

  render() {
    if (this.props.showInline) {
      return this._renderInline();
    }

    return this._renderOverlay();
  }
}

Story.displayName = 'Story';

Story.propTypes = {
  context: PropTypes.shape({
    kind: PropTypes.string,
    story: PropTypes.string,
  }),
  info: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
  propTables: PropTypes.arrayOf(PropTypes.func),
  propTablesExclude: PropTypes.arrayOf(PropTypes.func),
  showInline: PropTypes.bool,
  showHeader: PropTypes.bool,
  showSource: PropTypes.bool,
  styles: PropTypes.func.isRequired,
  children: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
  marksyConf: PropTypes.object, // eslint-disable-line react/forbid-prop-types
  maxPropsIntoLine: PropTypes.number.isRequired,
  maxPropObjectKeys: PropTypes.number.isRequired,
  maxPropArrayLength: PropTypes.number.isRequired,
  maxPropStringLength: PropTypes.number.isRequired,
};
Story.defaultProps = {
  context: null,
  info: '',
  children: null,
  propTables: null,
  propTablesExclude: [],
  showInline: false,
  showHeader: true,
  showSource: true,
  marksyConf: {},
};
<|endoftext|>


<commit_before>module.exports = function (grunt) {
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
        concat: {
            js: {
                options: {
                    separator: ';\n\n'
                },
                src: ['src/js/**/*.js'],
                dest: 'tmp/<%= pkg.name %>.js'
            },
            less: {
                options: {
                    separator: '\n'
                },
                src: ['src/less/**/*.less'],
                dest: 'tmp/style.less'
            }
        },
        uglify: {
            options: {
                banner: '/* <%= pkg.desc %>. <%= grunt.template.today("yyyy-mm-dd") %> */\n'
            },
            build: {
                src: 'tmp/<%= pkg.name %>.js',
                dest: 'build/release/js/<%= pkg.name %>-<%= pkg.version %>.js'
            }
        },
        template: {
            dev: {
                options: {
                    data: {
                        scripts: '<script src="js/app.js"></script>' +
                            '<script src="js/dnd.js"></script>' +
                            '<script src="js/ajax.js"></script>' +
                            '<script src="js/keyboard.js"></script>' +
                            '<script src="js/handwriting.js"></script>' +
                            '<script src="js/recognition.js"></script>' +
                            '<script src="js/slider.js"></script>' +
                            '<script src="js/colorpicker.js"></script>',
                        css: '<link type="text/css" rel="stylesheet" href="css/common.css">' +
                            '<link type="text/css" rel="stylesheet" href="css/keyboard.css">' +
                            '<link type="text/css" rel="stylesheet" href="css/recognition.css">' +
                            '<link type="text/css" rel="stylesheet" href="css/colorpicker.css">',
                        year: '<%= grunt.template.today("yyyy") %>'
                    }
                },
                files: {
                    'build/development/index.html': ['src/index.html'],
                    'build/development/keyboard.html': ['src/keyboard.html'],
                    'build/development/recognition.html': ['src/recognition.html']
                }
            },
            pro: {
                options: {
                    data: {
                        scripts: '<script src="js/<%= pkg.name %>-<%= pkg.version %>.js"></script>',
                        css: '<link type="text/css" rel="stylesheet" href="css/style.css">',
                        year: '<%= grunt.template.today("yyyy") %>'
                    }
                },
                files: {
                    'build/release/index.html': ['src/index.html'],
                    'build/release/keyboard.html': ['src/keyboard.html'],
                    'build/release/recognition.html': ['src/recognition.html']
                }
            }
        },
        less: {
            dev: {
                files: {
                    "build/development/css/common.css": "src/less/common.less",
                    "build/development/css/keyboard.css": "src/less/keyboard.less",
                    "build/development/css/recognition.css": "src/less/recognition.less",
                    "build/development/css/colorpicker.css": "src/less/colorpicker.less"
                }
            },
            pro: {
                options: {
                    yuicompress: true
                },
                files: {
                    "build/release/css/style.css": "tmp/style.less"
                }
            }
        },
        copy: {
            dev: {
                files: [
                    {expand: true, cwd: 'src/assets/', src: ['**'], dest: 'build/development/'},
                    {expand: true, cwd: 'src/data/', src: ['**'], dest: 'build/development/data/'},
                    {expand: true, cwd: 'src/js/', src: ['**'], dest: 'build/development/js/'},
                    {expand: true, cwd: 'src/lib/', src: ['**'], dest: 'build/development/lib/'}
                ]
            },
            pro: {
                files: [
                    {expand: true, cwd: 'src/data/', src: ['**'], dest: 'build/release/'},
                    {expand: true, cwd: 'src/assets/', src: ['**'], dest: 'build/release/'},
                    {expand: true, cwd: 'src/lib/', src: ['**'], dest: 'build/release/lib/'}
                ]
            }
        }
    });
    grunt.loadNpmTasks('grunt-contrib-concat');
    grunt.loadNpmTasks('grunt-contrib-uglify');
    grunt.loadNpmTasks('grunt-template');
    grunt.loadNpmTasks('grunt-contrib-less');
    grunt.loadNpmTasks('grunt-contrib-copy');
    grunt.registerTask('default', ['template:dev', 'less:dev', 'copy:dev']);
    grunt.registerTask('pro', ['concat:js', 'uglify', 'concat:less', 'less:pro', 'template:pro', 'copy:pro']);
};<commit_msg>release build fix<commit_after>module.exports = function (grunt) {
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
        concat: {
            js: {
                options: {
                    separator: ';\n\n'
                },
                src: ['src/js/**/*.js'],
                dest: 'tmp/<%= pkg.name %>.js'
            },
            less: {
                options: {
                    separator: '\n'
                },
                src: ['src/less/**/*.less'],
                dest: 'tmp/style.less'
            }
        },
        uglify: {
            options: {
                banner: '/* <%= pkg.desc %>. <%= grunt.template.today("yyyy-mm-dd") %> */\n'
            },
            build: {
                src: 'tmp/<%= pkg.name %>.js',
                dest: 'build/release/js/<%= pkg.name %>-<%= pkg.version %>.js'
            }
        },
        template: {
            dev: {
                options: {
                    data: {
                        scripts: '<script src="js/app.js"></script>' +
                            '<script src="js/dnd.js"></script>' +
                            '<script src="js/ajax.js"></script>' +
                            '<script src="js/keyboard.js"></script>' +
                            '<script src="js/handwriting.js"></script>' +
                            '<script src="js/recognition.js"></script>' +
                            '<script src="js/slider.js"></script>' +
                            '<script src="js/colorpicker.js"></script>',
                        css: '<link type="text/css" rel="stylesheet" href="css/common.css">' +
                            '<link type="text/css" rel="stylesheet" href="css/keyboard.css">' +
                            '<link type="text/css" rel="stylesheet" href="css/recognition.css">' +
                            '<link type="text/css" rel="stylesheet" href="css/colorpicker.css">',
                        year: '<%= grunt.template.today("yyyy") %>'
                    }
                },
                files: {
                    'build/development/index.html': ['src/index.html'],
                    'build/development/keyboard.html': ['src/keyboard.html'],
                    'build/development/recognition.html': ['src/recognition.html']
                }
            },
            pro: {
                options: {
                    data: {
                        scripts: '<script src="js/<%= pkg.name %>-<%= pkg.version %>.js"></script>',
                        css: '<link type="text/css" rel="stylesheet" href="css/style.css">',
                        year: '<%= grunt.template.today("yyyy") %>'
                    }
                },
                files: {
                    'build/release/index.html': ['src/index.html'],
                    'build/release/keyboard.html': ['src/keyboard.html'],
                    'build/release/recognition.html': ['src/recognition.html']
                }
            }
        },
        less: {
            dev: {
                files: {
                    "build/development/css/common.css": "src/less/common.less",
                    "build/development/css/keyboard.css": "src/less/keyboard.less",
                    "build/development/css/recognition.css": "src/less/recognition.less",
                    "build/development/css/colorpicker.css": "src/less/colorpicker.less"
                }
            },
            pro: {
                options: {
                    yuicompress: true
                },
                files: {
                    "build/release/css/style.css": "tmp/style.less"
                }
            }
        },
        copy: {
            dev: {
                files: [
                    {expand: true, cwd: 'src/assets/', src: ['**'], dest: 'build/development/'},
                    {expand: true, cwd: 'src/data/', src: ['**'], dest: 'build/development/data/'},
                    {expand: true, cwd: 'src/js/', src: ['**'], dest: 'build/development/js/'},
                    {expand: true, cwd: 'src/lib/', src: ['**'], dest: 'build/development/lib/'}
                ]
            },
            pro: {
                files: [
                    {expand: true, cwd: 'src/data/', src: ['**'], dest: 'build/release/data/'},
                    {expand: true, cwd: 'src/assets/', src: ['**'], dest: 'build/release/'},
                    {expand: true, cwd: 'src/lib/', src: ['**'], dest: 'build/release/lib/'}
                ]
            }
        }
    });
    grunt.loadNpmTasks('grunt-contrib-concat');
    grunt.loadNpmTasks('grunt-contrib-uglify');
    grunt.loadNpmTasks('grunt-template');
    grunt.loadNpmTasks('grunt-contrib-less');
    grunt.loadNpmTasks('grunt-contrib-copy');
    grunt.registerTask('default', ['template:dev', 'less:dev', 'copy:dev']);
    grunt.registerTask('pro', ['concat:js', 'uglify', 'concat:less', 'less:pro', 'template:pro', 'copy:pro']);
};<|endoftext|>


<commit_before>// Generated on 2014-04-07 using generator-webapp 0.4.7
'use strict';

// # Globbing
// for performance reasons we're only matching one level down:
// 'test/spec/{,*/}*.js'
// use this if you want to recursively match all subfolders:
// 'test/spec/**/*.js'

module.exports = function (grunt) {

  // Load grunt tasks automatically
  require('load-grunt-tasks')(grunt);

  grunt.loadNpmTasks('grunt-remove-logging');
  grunt.loadNpmTasks('grunt-ftp-deploy');
  grunt.loadNpmTasks('grunt-appcache');

  // Time how long tasks take. Can help when optimizing build times
  require('time-grunt')(grunt);

  // Define the configuration for all the tasks
  grunt.initConfig({

    // Project settings
    yeoman: {
      // Configurable paths
      app: 'app',
      dist: 'dist'
    },

    // Watches files for changes and runs tasks based on the changed files
    watch: {
      js: {
        files: ['<%= yeoman.app %>/scripts/{,*/}*.js'],
        tasks: [],
        options: {
          livereload: true
        }
      },

      jstest: {
        files: ['test/spec/{,*/}*.js'],
        tasks: ['test:watch']
      },

      gruntfile: {
        files: ['Gruntfile.js']
      },

      compass: {
        files: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'],
        tasks: ['compass:server', 'autoprefixer']
      },

      styles: {
        files: ['<%= yeoman.app %>/styles/{,*/}*.css'],
        tasks: ['newer:copy:styles', 'autoprefixer']
      },

      livereload: {
        options: {
          livereload: '<%= connect.options.livereload %>'
        },
        files: [
          '<%= yeoman.app %>/{,*/}*.html',
          '.tmp/styles/{,*/}*.css',
          '<%= yeoman.app %>/images/{,*/}*.{gif,jpeg,jpg,png,svg,webp}'
        ]
      }
    },

    // The actual grunt server settings
    connect: {
      options: {
        port: 9000,
        livereload: 35729,
        // Change this to '0.0.0.0' to access the server from outside
        hostname: '0.0.0.0'
      },
      livereload: {
        options: {
          open: true,
          base: [
            '.tmp',
            '<%= yeoman.app %>',
            '../'
          ]
        }
      },
      test: {
        options: {
          port: 9001,
          base: [
            '.tmp',
            'test',
            '<%= yeoman.app %>'
          ]
        }
      },
      dist: {
        options: {
          open: true,
          base: '<%= yeoman.dist %>',
          livereload: false
        }
      }
    },

    // Empties folders to start fresh
    clean: {
      options: { force: true },
      dist: {
        files: [{
          dot: true,
          src: [
            '.tmp',
            '<%= yeoman.dist %>/*',
            '!<%= yeoman.dist %>/.git*'
          ]
        }]
      },
      server: '.tmp'
    },

    // Make sure code styles are up to par and there are no obvious mistakes
    jshint: {
      options: {
        jshintrc: '.jshintrc',
        reporter: require('jshint-stylish')
      },
      all: [
        'Gruntfile.js',
        '<%= yeoman.app %>/scripts/{,*/}*.js',
        '!<%= yeoman.app %>/scripts/vendor/*',
        'test/spec/{,*/}*.js'
      ]
    },

    // Mocha testing framework configuration options
    mocha: {
      all: {
        options: {
          run: true,
          urls: ['http://<%= connect.test.options.hostname %>:<%= connect.test.options.port %>/index.html']
        }
      }
    },

    // Compiles Sass to CSS and generates necessary files if requested
    compass: {
      options: {
        sassDir: '<%= yeoman.app %>/styles',
        cssDir: '.tmp/styles',
        generatedImagesDir: '.tmp/images/generated',
        imagesDir: '<%= yeoman.app %>/images',
        javascriptsDir: '<%= yeoman.app %>/scripts',
        fontsDir: '<%= yeoman.app %>/fonts',
        importPath: '<%= yeoman.app %>/bower_components',
        httpImagesPath: '/images',
        httpGeneratedImagesPath: '/images/generated',
        httpFontsPath: '../fonts',
        relativeAssets: false,
        assetCacheBuster: false
      },
      dist: {
        options: {
          generatedImagesDir: '<%= yeoman.dist %>/images/generated'
        }
      },
      server: {
        options: {
          debugInfo: true
        }
      }
    },

    // Add vendor prefixed styles
    autoprefixer: {
      options: {
        browsers: ['last 1 version']
      },
      dist: {
        files: [{
          expand: true,
          cwd: '.tmp/styles/',
          src: '{,*/}*.css',
          dest: '.tmp/styles/'
        }]
      }
    },

    // Automatically inject Bower components into the HTML file
    'bower-install': {
      app: {
        html: '<%= yeoman.app %>/index.html',
        ignorePath: '<%= yeoman.app %>/'
      }
    },

    // Renames files for browser caching purposes
    rev: {
      dist: {
        files: {
          src: [
            '<%= yeoman.dist %>/scripts/{,*/}*.js',
            '<%= yeoman.dist %>/styles/{,*/}*.css',
          ]
        }
      }
    },

    // Reads HTML for usemin blocks to enable smart builds that automatically
    // concat, minify and revision files. Creates configurations in memory so
    // additional tasks can operate on them
    useminPrepare: {
      options: {
        dest: '<%= yeoman.dist %>'
      },
      html: '<%= yeoman.app %>/index.html'
    },

    // Performs rewrites based on rev and the useminPrepare configuration
    usemin: {
      options: {
        assetsDirs: ['<%= yeoman.dist %>']
      },
      html: ['<%= yeoman.dist %>/{,*/}*.html'],
      css: ['<%= yeoman.dist %>/styles/{,*/}*.css']
    },

    // The following *-min tasks produce minified files in the dist folder
    imagemin: {
      dist: {
        files: [{
          expand: true,
          cwd: '<%= yeoman.app %>/images',
          src: '{,*/}*.{gif,jpeg,jpg,png}',
          dest: '<%= yeoman.dist %>/images'
        }]
      }
    },

    svgmin: {
      dist: {
        files: [{
          expand: true,
          cwd: '<%= yeoman.app %>/images',
          src: '{,*/}*.svg',
          dest: '<%= yeoman.dist %>/images'
        }]
      }
    },

    htmlmin: {
      dist: {
        options: {
          collapseBooleanAttributes: true,
          collapseWhitespace: true,
          removeAttributeQuotes: true,
          removeCommentsFromCDATA: true,
          removeEmptyAttributes: true,
          removeOptionalTags: true,
          removeRedundantAttributes: true,
          useShortDoctype: true
        },
        files: [{
          expand: true,
          cwd: '<%= yeoman.dist %>',
          src: '{,*/}*.html',
          dest: '<%= yeoman.dist %>'
        }]
      }
    },

    // By default, your `index.html`'s <!-- Usemin block --> will take care of
    // minification. These next options are pre-configured if you do not wish
    // to use the Usemin blocks.
    // cssmin: {
    //     dist: {
    //         files: {
    //             '<%= yeoman.dist %>/styles/main.css': [
    //                 '.tmp/styles/{,*/}*.css',
    //                 '<%= yeoman.app %>/styles/{,*/}*.css'
    //             ]
    //         }
    //     }
    // },
    // uglify: {
    //     dist: {
    //         files: {
    //             '<%= yeoman.dist %>/scripts/scripts.js': [
    //                 '<%= yeoman.dist %>/scripts/scripts.js'
    //             ]
    //         }
    //     }
    // },
    // concat: {
    //     dist: {}
    // },

    // Copies remaining files to places other tasks can use
    copy: {
      dist: {
        files: [{
          expand: true,
          dot: true,
          cwd: '<%= yeoman.app %>',
          dest: '<%= yeoman.dist %>',
          src: [
            '*.{ico,png,txt}',
            '.htaccess',
            'images/{,*/}*.webp',
            '{,*/}*.html',
            'fonts/{,*/}*.*',
            'sounds/{,*/}*.*',
            'bower_components/' + (this.includeCompass ? 'sass-' : '') + 'bootstrap/' + (this.includeCompass ? 'fonts/' : 'dist/fonts/') +'*.*'
          ]
        }]
      },
      styles: {
        expand: true,
        dot: true,
        cwd: '<%= yeoman.app %>/styles',
        dest: '.tmp/styles/',
        src: '{,*/}*.css'
      }
    },

    // Generates a custom Modernizr build that includes only the tests you
    // reference in your app
    modernizr: {
      devFile: '<%= yeoman.app %>/bower_components/modernizr/modernizr.js',
      outputFile: '<%= yeoman.dist %>/bower_components/modernizr/modernizr.js',
      files: [
        '<%= yeoman.dist %>/scripts/{,*/}*.js',
        '<%= yeoman.dist %>/styles/{,*/}*.css',
        '!<%= yeoman.dist %>/scripts/vendor/*'
      ],
      uglify: true
    },

    // Run some tasks in parallel to speed up build process
    concurrent: {
      server: [
        'compass:server',
        'copy:styles'
      ],
      test: [
        'copy:styles'
      ],
      dist: [
        'compass',
        'copy:styles',
        'imagemin',
        'svgmin'
      ]
    },

    'ftp-deploy': {
      build: {
        auth: {
          host: 'gouchstudio.com',
          port: 21,
          authKey: 'key1'
        },
        src: '<%= yeoman.dist %>',
        dest: '/smash',
        exclusions: []
      }
    },

    appcache: {
      options: {
        basePath: '<%= yeoman.dist %>/'
      },
      all: {
        dest: '<%= yeoman.dist %>/smash.appcache',
        cache: '<%= yeoman.dist %>/**/*',
        network: '*'
      }
    },

    removelogging: {
      dist: {
        src: '<%= yeoman.dist %>/**/*.js',
        options: {
          // see below for options. this is optional.
        }
      }
    },
  });

  grunt.registerTask('serve', function (target) {
    if (target === 'dist') {
      return grunt.task.run(['build', 'connect:dist:keepalive']);
    }

    grunt.task.run([
      'clean:server',
      'concurrent:server',
      'autoprefixer',
      'connect:livereload',
      'watch'
    ]);
  });

  grunt.registerTask('server', function () {
    grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.');
    grunt.task.run(['serve']);
  });

  grunt.registerTask('test', function(target) {
    if (target !== 'watch') {
      grunt.task.run([
        'clean:server',
        'concurrent:test',
        'autoprefixer',
      ]);
    }

    grunt.task.run([
      'connect:test',
      'mocha'
    ]);
  });

  grunt.registerTask('build', [
    'clean:dist',
    'useminPrepare',
    'concurrent:dist',
    'autoprefixer',
    'concat',
    'removelogging',
    'cssmin',
    'uglify',
    'copy:dist',
    // 'modernizr',
    'rev',
    'usemin',
    'htmlmin',
    'appcache',
    'ftp-deploy'
  ]);

  grunt.registerTask('default', [
    'newer:jshint',
    'test',
    'build'
  ]);

};
<commit_msg>fixes issue with images different folders in GRuntfile<commit_after>// Generated on 2014-04-07 using generator-webapp 0.4.7
'use strict';

// # Globbing
// for performance reasons we're only matching one level down:
// 'test/spec/{,*/}*.js'
// use this if you want to recursively match all subfolders:
// 'test/spec/**/*.js'

module.exports = function (grunt) {

  // Load grunt tasks automatically
  require('load-grunt-tasks')(grunt);

  grunt.loadNpmTasks('grunt-remove-logging');
  grunt.loadNpmTasks('grunt-ftp-deploy');
  grunt.loadNpmTasks('grunt-appcache');

  // Time how long tasks take. Can help when optimizing build times
  require('time-grunt')(grunt);

  // Define the configuration for all the tasks
  grunt.initConfig({

    // Project settings
    yeoman: {
      // Configurable paths
      app: 'app',
      dist: 'dist'
    },

    // Watches files for changes and runs tasks based on the changed files
    watch: {
      js: {
        files: ['<%= yeoman.app %>/scripts/{,*/}*.js'],
        tasks: [],
        options: {
          livereload: true
        }
      },

      jstest: {
        files: ['test/spec/{,*/}*.js'],
        tasks: ['test:watch']
      },

      gruntfile: {
        files: ['Gruntfile.js']
      },

      compass: {
        files: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'],
        tasks: ['compass:server', 'autoprefixer']
      },

      styles: {
        files: ['<%= yeoman.app %>/styles/{,*/}*.css'],
        tasks: ['newer:copy:styles', 'autoprefixer']
      },

      livereload: {
        options: {
          livereload: '<%= connect.options.livereload %>'
        },
        files: [
          '<%= yeoman.app %>/{,*/}*.html',
          '.tmp/styles/{,*/}*.css',
          '<%= yeoman.app %>/images/{,*/}*.{gif,jpeg,jpg,png,svg,webp}'
        ]
      }
    },

    // The actual grunt server settings
    connect: {
      options: {
        port: 9000,
        livereload: 35729,
        // Change this to '0.0.0.0' to access the server from outside
        hostname: '0.0.0.0'
      },
      livereload: {
        options: {
          open: true,
          base: [
            '.tmp',
            '<%= yeoman.app %>',
            '../'
          ]
        }
      },
      test: {
        options: {
          port: 9001,
          base: [
            '.tmp',
            'test',
            '<%= yeoman.app %>'
          ]
        }
      },
      dist: {
        options: {
          open: true,
          base: '<%= yeoman.dist %>',
          livereload: false
        }
      }
    },

    // Empties folders to start fresh
    clean: {
      options: { force: true },
      dist: {
        files: [{
          dot: true,
          src: [
            '.tmp',
            '<%= yeoman.dist %>/*',
            '!<%= yeoman.dist %>/.git*'
          ]
        }]
      },
      server: '.tmp'
    },

    // Make sure code styles are up to par and there are no obvious mistakes
    jshint: {
      options: {
        jshintrc: '.jshintrc',
        reporter: require('jshint-stylish')
      },
      all: [
        'Gruntfile.js',
        '<%= yeoman.app %>/scripts/{,*/}*.js',
        '!<%= yeoman.app %>/scripts/vendor/*',
        'test/spec/{,*/}*.js'
      ]
    },

    // Mocha testing framework configuration options
    mocha: {
      all: {
        options: {
          run: true,
          urls: ['http://<%= connect.test.options.hostname %>:<%= connect.test.options.port %>/index.html']
        }
      }
    },

    // Compiles Sass to CSS and generates necessary files if requested
    compass: {
      options: {
        sassDir: '<%= yeoman.app %>/styles',
        cssDir: '.tmp/styles',
        generatedImagesDir: '.tmp/images/generated',
        imagesDir: '<%= yeoman.app %>/images',
        javascriptsDir: '<%= yeoman.app %>/scripts',
        fontsDir: '<%= yeoman.app %>/fonts',
        importPath: '<%= yeoman.app %>/bower_components',
        httpImagesPath: '/images',
        httpGeneratedImagesPath: '/images/generated',
        httpFontsPath: '../fonts',
        relativeAssets: false,
        assetCacheBuster: false
      },
      dist: {
        options: {
          generatedImagesDir: '<%= yeoman.dist %>/images/generated'
        }
      },
      server: {
        options: {
          debugInfo: true
        }
      }
    },

    // Add vendor prefixed styles
    autoprefixer: {
      options: {
        browsers: ['last 1 version']
      },
      dist: {
        files: [{
          expand: true,
          cwd: '.tmp/styles/',
          src: '{,*/}*.css',
          dest: '.tmp/styles/'
        }]
      }
    },

    // Automatically inject Bower components into the HTML file
    'bower-install': {
      app: {
        html: '<%= yeoman.app %>/index.html',
        ignorePath: '<%= yeoman.app %>/'
      }
    },

    // Renames files for browser caching purposes
    rev: {
      dist: {
        files: {
          src: [
            '<%= yeoman.dist %>/scripts/{,*/}*.js',
            '<%= yeoman.dist %>/styles/{,*/}*.css',
          ]
        }
      }
    },

    // Reads HTML for usemin blocks to enable smart builds that automatically
    // concat, minify and revision files. Creates configurations in memory so
    // additional tasks can operate on them
    useminPrepare: {
      options: {
        dest: '<%= yeoman.dist %>'
      },
      html: '<%= yeoman.app %>/index.html'
    },

    // Performs rewrites based on rev and the useminPrepare configuration
    usemin: {
      options: {
        assetsDirs: ['<%= yeoman.dist %>']
      },
      html: ['<%= yeoman.dist %>/{,*/}*.html'],
      css: ['<%= yeoman.dist %>/styles/{,*/}*.css']
    },

    // The following *-min tasks produce minified files in the dist folder
    imagemin: {
      dist: {
        files: [{
          expand: true,
          flatten: false,
          cwd: '<%= yeoman.app %>/images',
          src: '{,*/}*.{gif,jpeg,jpg,png}',
          dest: '<%= yeoman.dist %>/images'
        }]
      }
    },

    svgmin: {
      dist: {
        files: [{
          expand: true,
          flatten: false,
          cwd: '<%= yeoman.app %>/images',
          src: '{,*/}*.svg',
          dest: '<%= yeoman.dist %>/images'
        }]
      }
    },

    htmlmin: {
      dist: {
        options: {
          collapseBooleanAttributes: true,
          collapseWhitespace: true,
          removeAttributeQuotes: true,
          removeCommentsFromCDATA: true,
          removeEmptyAttributes: true,
          removeOptionalTags: true,
          removeRedundantAttributes: true,
          useShortDoctype: true
        },
        files: [{
          expand: true,
          cwd: '<%= yeoman.dist %>',
          src: '{,*/}*.html',
          dest: '<%= yeoman.dist %>'
        }]
      }
    },

    // By default, your `index.html`'s <!-- Usemin block --> will take care of
    // minification. These next options are pre-configured if you do not wish
    // to use the Usemin blocks.
    // cssmin: {
    //     dist: {
    //         files: {
    //             '<%= yeoman.dist %>/styles/main.css': [
    //                 '.tmp/styles/{,*/}*.css',
    //                 '<%= yeoman.app %>/styles/{,*/}*.css'
    //             ]
    //         }
    //     }
    // },
    // uglify: {
    //     dist: {
    //         files: {
    //             '<%= yeoman.dist %>/scripts/scripts.js': [
    //                 '<%= yeoman.dist %>/scripts/scripts.js'
    //             ]
    //         }
    //     }
    // },
    // concat: {
    //     dist: {}
    // },

    // Copies remaining files to places other tasks can use
    copy: {
      dist: {
        files: [{
          expand: true,
          dot: true,
          cwd: '<%= yeoman.app %>',
          dest: '<%= yeoman.dist %>',
          src: [
            '*.{ico,png,txt}',
            '.htaccess',
            'images/{,*/}*.webp',
            '{,*/}*.html',
            'fonts/{,*/}*.*',
            'sounds/{,*/}*.*',
            'bower_components/' + (this.includeCompass ? 'sass-' : '') + 'bootstrap/' + (this.includeCompass ? 'fonts/' : 'dist/fonts/') +'*.*'
          ]
        }]
      },
      styles: {
        expand: true,
        dot: true,
        cwd: '<%= yeoman.app %>/styles',
        dest: '.tmp/styles/',
        src: '{,*/}*.css'
      }
    },

    // Generates a custom Modernizr build that includes only the tests you
    // reference in your app
    modernizr: {
      devFile: '<%= yeoman.app %>/bower_components/modernizr/modernizr.js',
      outputFile: '<%= yeoman.dist %>/bower_components/modernizr/modernizr.js',
      files: [
        '<%= yeoman.dist %>/scripts/{,*/}*.js',
        '<%= yeoman.dist %>/styles/{,*/}*.css',
        '!<%= yeoman.dist %>/scripts/vendor/*'
      ],
      uglify: true
    },

    // Run some tasks in parallel to speed up build process
    concurrent: {
      server: [
        'compass:server',
        'copy:styles'
      ],
      test: [
        'copy:styles'
      ],
      dist: [
        'compass',
        'copy:styles',
        'imagemin',
        'svgmin'
      ]
    },

    'ftp-deploy': {
      build: {
        auth: {
          host: 'gouchstudio.com',
          port: 21,
          authKey: 'key1'
        },
        src: '<%= yeoman.dist %>',
        dest: '/smash',
        exclusions: []
      }
    },

    appcache: {
      options: {
        basePath: '<%= yeoman.dist %>/'
      },
      all: {
        dest: '<%= yeoman.dist %>/smash.appcache',
        cache: '<%= yeoman.dist %>/**/*',
        network: '*'
      }
    },

    removelogging: {
      dist: {
        src: '<%= yeoman.dist %>/**/*.js',
        options: {
          // see below for options. this is optional.
        }
      }
    },
  });

  grunt.registerTask('serve', function (target) {
    if (target === 'dist') {
      return grunt.task.run(['build', 'connect:dist:keepalive']);
    }

    grunt.task.run([
      'clean:server',
      'concurrent:server',
      'autoprefixer',
      'connect:livereload',
      'watch'
    ]);
  });

  grunt.registerTask('server', function () {
    grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.');
    grunt.task.run(['serve']);
  });

  grunt.registerTask('test', function(target) {
    if (target !== 'watch') {
      grunt.task.run([
        'clean:server',
        'concurrent:test',
        'autoprefixer',
      ]);
    }

    grunt.task.run([
      'connect:test',
      'mocha'
    ]);
  });

  grunt.registerTask('build', [
    'clean:dist',
    'useminPrepare',
    'concurrent:dist',
    'autoprefixer',
    'concat',
    'removelogging',
    'cssmin',
    'uglify',
    'copy:dist',
    // 'modernizr',
    'rev',
    'usemin',
    'htmlmin',
    'appcache',
    'ftp-deploy'
  ]);

  grunt.registerTask('default', [
    'newer:jshint',
    'test',
    'build'
  ]);

};
<|endoftext|>


<commit_before>const MJMLParser = require('../lib/index.js')
require('mjml')
const components = require('mjml-core').components
const chai = require('chai')
const displayDiff = require('./test-utils').displayDiff
const omitDeepLodash = require('./test-utils').omitDeepLodash

/*
  If test fails, run it with --debug to log the details of the diff
*/

const mjml1 = `
<mjml>
  <mj-body>
    <mj-section background-color="#CCCCCC" full-width="full-width">
      <mj-button href="<% dynamic %>" pouf="$2">
        &end

        Blu & end $1
        &
        lorem
      </mj-button>
      <mj-button href="http://mjml.io?encodedUrl=http%3A%2F%2Fmjml.io&coin=coi">
        Blu
      </mj-button>
      <mj-button href="&é(§&è!çà)">http%3A%2F%2Fmjml.io</mj-button>
      <mj-raw>
        <coin color="#CCCCCC">bla</coin>
      </mj-raw>
    </mj-section>
  </mj-body>
</mjml>
`

const validJson1 = {
  "file": ".",
  "line": 2,
  "includedIn": [],
  "tagName": "mjml",
  "children": [
    {
      "file": ".",
      "line": 3,
      "includedIn": [],
      "tagName": "mj-body",
      "children": [
        {
          "file": ".",
          "line": 4,
          "includedIn": [],
          "tagName": "mj-section",
          "attributes": {
            "background-color": "#CCCCCC",
            "full-width": "full-width"
          },
          "children": [
            {
              "file": ".",
              "line": 5,
              "includedIn": [],
              "tagName": "mj-button",
              "attributes": {
                "href": "<% dynamic %>",
                "pouf": "$2"
              },
              "content": "&end
\n        Blu & end $1\n        &\n        lorem"
            },
            {
              "file": ".",
              "line": 11,
              "includedIn": [],
              "tagName": "mj-button",
              "attributes": {
                "href": "http://mjml.io?encodedUrl=http%3A%2F%2Fmjml.io&coin=coi"
              },
              "content": "Blu"
            },
            {
              "file": ".",
              "line": 14,
              "includedIn": [],
              "tagName": "mj-button",
              "attributes": {
                "href": "&é(§&è!çà)"
              },
              "content": "http%3A%2F%2Fmjml.io"
            },
            {
              "file": ".",
              "line": 15,
              "includedIn": [],
              "tagName": "mj-raw",
              "content": "<coin color=\"#CCCCCC\">bla</coin>",
              "attributes": {}
            }
          ]
        }
      ],
      "attributes": {}
    }
  ],
  "attributes": {}
}

const mjml2 = `
<mjml>
  <mj-body>
    <mj-text-test-wrapper>
      <mj-text>MJML</mj-text>
      <mj-text attr="val">FTW</mj-text>
    </mj-text-test-wrapper>
    <mj-text-test-wrapper>
      <mj-text attr="val">FTW</mj-text>
      <mj-text>MJML</mj-text>
    </mj-text-test-wrapper>
  </mj-body>
</mjml>
`

const validJson2 = {
  "file": ".",
  "line": 2,
  "includedIn": [],
  "tagName": "mjml",
  "children": [
    {
      "file": ".",
      "line": 3,
      "includedIn": [],
      "tagName": "mj-body",
      "children": [
        {
          "file": ".",
          "line": 4,
          "includedIn": [],
          "tagName": "mj-text-test-wrapper",
          "children": [
            {
              "file": ".",
              "line": 5,
              "includedIn": [],
              "tagName": "mj-text",
              "content": "MJML",
              "attributes": {}
            },
            {
              "file": ".",
              "line": 6,
              "includedIn": [],
              "tagName": "mj-text",
              "attributes": {
                "attr": "val"
              },
              "content": "FTW"
            }
          ],
          "attributes": {}
        },
        {
          "file": ".",
          "line": 8,
          "includedIn": [],
          "tagName": "mj-text-test-wrapper",
          "children": [
            {
              "file": ".",
              "line": 9,
              "includedIn": [],
              "tagName": "mj-text",
              "attributes": {
                "attr": "val"
              },
              "content": "FTW"
            },
            {
              "file": ".",
              "line": 10,
              "includedIn": [],
              "tagName": "mj-text",
              "content": "MJML",
              "attributes": {}
            }
          ],
          "attributes": {}
        }
      ],
      "attributes": {}
    }
  ],
  "attributes": {}
}

const mjml3 = `
<mjml>
  <mj-head>
    <mj-attributes>
      <mj-text color="blue" />
      <mj-text font-size="40px" />
    </mj-attributes>
  </mj-head>
  <mj-body>
    <mj-section>
      <mj-column>
        <mj-text>
          Hello !
        </mj-text>
      </mj-column>
    </mj-section>
  </mj-body>
</mjml>
`

const validJson3 = {
  "file": ".",
  "line": 2,
  "includedIn": [],
  "tagName": "mjml",
  "children": [
    {
      "file": ".",
      "line": 3,
      "includedIn": [],
      "tagName": "mj-head",
      "children": [
        {
          "file": ".",
          "line": 4,
          "includedIn": [],
          "tagName": "mj-attributes",
          "children": [
            {
              "file": ".",
              "line": 5,
              "includedIn": [],
              "tagName": "mj-text",
              "attributes": {
                "color": "blue"
              }
            },
            {
              "file": ".",
              "line": 6,
              "includedIn": [],
              "tagName": "mj-text",
              "attributes": {
                "font-size": "40px"
              }
            }
          ],
          "attributes": {}
        }
      ],
      "attributes": {}
    },
    {
      "file": ".",
      "line": 9,
      "includedIn": [],
      "tagName": "mj-body",
      "children": [
        {
          "file": ".",
          "line": 10,
          "includedIn": [],
          "tagName": "mj-section",
          "children": [
            {
              "file": ".",
              "line": 11,
              "includedIn": [],
              "tagName": "mj-column",
              "children": [
                {
                  "file": ".",
                  "line": 12,
                  "includedIn": [],
                  "tagName": "mj-text",
                  "content": "Hello !",
                  "attributes": {}
                }
              ],
              "attributes": {}
            }
          ],
          "attributes": {}
        }
      ],
      "attributes": {}
    }
  ],
  "attributes": {}
}

const parse = mjml => MJMLParser(mjml, {
  keepComments: true,
  components,
  filePath: '.'
})

if (process.argv.indexOf('--debug') !== -1) {
  displayDiff(validJson1, omitDeepLodash(parse(mjml1), 'absoluteFilePath'))
  displayDiff(validJson2, omitDeepLodash(parse(mjml2), 'absoluteFilePath'))
  displayDiff(validJson3, omitDeepLodash(parse(mjml3), 'absoluteFilePath'))
}

chai.expect(validJson1, 'Special characters test failed')
    .to.deep.equal(omitDeepLodash(parse(mjml1), 'absoluteFilePath'))

chai.expect(validJson2, 'Similar tags test failed')
    .to.deep.equal(omitDeepLodash(parse(mjml2), 'absoluteFilePath'))

chai.expect(validJson3, 'Self closing tags test failed')
    .to.deep.equal(omitDeepLodash(parse(mjml3), 'absoluteFilePath'))
<commit_msg>add test case<commit_after>const MJMLParser = require('../lib/index.js')
require('mjml')
const components = require('mjml-core').components
const chai = require('chai')
const displayDiff = require('./test-utils').displayDiff
const omitDeepLodash = require('./test-utils').omitDeepLodash

/*
  If test fails, run it with --debug to log the details of the diff
*/

const mjml1 = `
<mjml>
  <mj-body>
    <mj-section background-color="#CCCCCC" full-width="full-width">
      <mj-button href="<% dynamic %>" pouf="$2">
        &end

        Blu & end $1
        &
        lorem
      </mj-button>
      <mj-button href="http://mjml.io?encodedUrl=http%3A%2F%2Fmjml.io&coin=coi">
        Blu
      </mj-button>
      <mj-button href="&é(§&è!çà)">http%3A%2F%2Fmjml.io</mj-button>
      <mj-raw>
        <coin color="#CCCCCC">bla</coin>
      </mj-raw>
    </mj-section>
  </mj-body>
</mjml>
`

const validJson1 = {
  "file": ".",
  "line": 2,
  "includedIn": [],
  "tagName": "mjml",
  "children": [
    {
      "file": ".",
      "line": 3,
      "includedIn": [],
      "tagName": "mj-body",
      "children": [
        {
          "file": ".",
          "line": 4,
          "includedIn": [],
          "tagName": "mj-section",
          "attributes": {
            "background-color": "#CCCCCC",
            "full-width": "full-width"
          },
          "children": [
            {
              "file": ".",
              "line": 5,
              "includedIn": [],
              "tagName": "mj-button",
              "attributes": {
                "href": "<% dynamic %>",
                "pouf": "$2"
              },
              "content": "&end
\n        Blu & end $1\n        &\n        lorem"
            },
            {
              "file": ".",
              "line": 11,
              "includedIn": [],
              "tagName": "mj-button",
              "attributes": {
                "href": "http://mjml.io?encodedUrl=http%3A%2F%2Fmjml.io&coin=coi"
              },
              "content": "Blu"
            },
            {
              "file": ".",
              "line": 14,
              "includedIn": [],
              "tagName": "mj-button",
              "attributes": {
                "href": "&é(§&è!çà)"
              },
              "content": "http%3A%2F%2Fmjml.io"
            },
            {
              "file": ".",
              "line": 15,
              "includedIn": [],
              "tagName": "mj-raw",
              "content": "<coin color=\"#CCCCCC\">bla</coin>",
              "attributes": {}
            }
          ]
        }
      ],
      "attributes": {}
    }
  ],
  "attributes": {}
}

const mjml2 = `
<mjml>
  <mj-body>
    <mj-text-test-wrapper>
      <mj-text>MJML</mj-text>
      <mj-text attr="val">FTW</mj-text>
    </mj-text-test-wrapper>
    <mj-text-test-wrapper>
      <mj-text attr="val">FTW</mj-text>
      <mj-text>MJML</mj-text>
    </mj-text-test-wrapper>
  </mj-body>
</mjml>
`

const validJson2 = {
  "file": ".",
  "line": 2,
  "includedIn": [],
  "tagName": "mjml",
  "children": [
    {
      "file": ".",
      "line": 3,
      "includedIn": [],
      "tagName": "mj-body",
      "children": [
        {
          "file": ".",
          "line": 4,
          "includedIn": [],
          "tagName": "mj-text-test-wrapper",
          "children": [
            {
              "file": ".",
              "line": 5,
              "includedIn": [],
              "tagName": "mj-text",
              "content": "MJML",
              "attributes": {}
            },
            {
              "file": ".",
              "line": 6,
              "includedIn": [],
              "tagName": "mj-text",
              "attributes": {
                "attr": "val"
              },
              "content": "FTW"
            }
          ],
          "attributes": {}
        },
        {
          "file": ".",
          "line": 8,
          "includedIn": [],
          "tagName": "mj-text-test-wrapper",
          "children": [
            {
              "file": ".",
              "line": 9,
              "includedIn": [],
              "tagName": "mj-text",
              "attributes": {
                "attr": "val"
              },
              "content": "FTW"
            },
            {
              "file": ".",
              "line": 10,
              "includedIn": [],
              "tagName": "mj-text",
              "content": "MJML",
              "attributes": {}
            }
          ],
          "attributes": {}
        }
      ],
      "attributes": {}
    }
  ],
  "attributes": {}
}

const mjml3 = `
<mjml>
  <mj-head>
    <mj-attributes>
      <mj-text color="blue" />
      <mj-text font-size="40px" />
    </mj-attributes>
  </mj-head>
  <mj-body>
    <mj-section>
      <mj-column>
        <mj-text>
          Hello !
        </mj-text>
      </mj-column>
    </mj-section>
  </mj-body>
</mjml>
`

const validJson3 = {
  "file": ".",
  "line": 2,
  "includedIn": [],
  "tagName": "mjml",
  "children": [
    {
      "file": ".",
      "line": 3,
      "includedIn": [],
      "tagName": "mj-head",
      "children": [
        {
          "file": ".",
          "line": 4,
          "includedIn": [],
          "tagName": "mj-attributes",
          "children": [
            {
              "file": ".",
              "line": 5,
              "includedIn": [],
              "tagName": "mj-text",
              "attributes": {
                "color": "blue"
              }
            },
            {
              "file": ".",
              "line": 6,
              "includedIn": [],
              "tagName": "mj-text",
              "attributes": {
                "font-size": "40px"
              }
            }
          ],
          "attributes": {}
        }
      ],
      "attributes": {}
    },
    {
      "file": ".",
      "line": 9,
      "includedIn": [],
      "tagName": "mj-body",
      "children": [
        {
          "file": ".",
          "line": 10,
          "includedIn": [],
          "tagName": "mj-section",
          "children": [
            {
              "file": ".",
              "line": 11,
              "includedIn": [],
              "tagName": "mj-column",
              "children": [
                {
                  "file": ".",
                  "line": 12,
                  "includedIn": [],
                  "tagName": "mj-text",
                  "content": "Hello !",
                  "attributes": {}
                }
              ],
              "attributes": {}
            }
          ],
          "attributes": {}
        }
      ],
      "attributes": {}
    }
  ],
  "attributes": {}
}

// Input that matches most of the CDATAs regex but not all, potentially resulting in regex timeout
const mjml4 = `
<mj-section>
  <mj-text font-family="Arial" />
  <mj-column background-color="#ffffff" css-class="column1"></mj-column>
</mj-section>
`

const validJson4 = {
  "file": ".",
  "line": 2,
  "includedIn": [],
  "tagName": "mj-section",
  "children": [
    {
      "file": ".",
      "line": 3,
      "includedIn": [],
      "tagName": "mj-text",
      "attributes": {
        "font-family": "Arial"
      }
    },
    {
      "file": ".",
      "line": 4,
      "includedIn": [],
      "tagName": "mj-column",
      "attributes": {
        "background-color": "#ffffff",
        "css-class": "column1"
      }
    }
  ],
  "attributes": {}
}

const parse = mjml => MJMLParser(mjml, {
  keepComments: true,
  components,
  filePath: '.'
})

if (process.argv.indexOf('--debug') !== -1) {
  displayDiff(validJson1, omitDeepLodash(parse(mjml1), 'absoluteFilePath'))
  displayDiff(validJson2, omitDeepLodash(parse(mjml2), 'absoluteFilePath'))
  displayDiff(validJson3, omitDeepLodash(parse(mjml3), 'absoluteFilePath'))
  displayDiff(validJson4, omitDeepLodash(parse(mjml4), 'absoluteFilePath'))
}

chai.expect(validJson1, 'Special characters test failed')
    .to.deep.equal(omitDeepLodash(parse(mjml1), 'absoluteFilePath'))

chai.expect(validJson2, 'Similar tags test failed')
    .to.deep.equal(omitDeepLodash(parse(mjml2), 'absoluteFilePath'))

chai.expect(validJson3, 'Self closing tags test failed')
    .to.deep.equal(omitDeepLodash(parse(mjml3), 'absoluteFilePath'))

chai.expect(validJson4, 'Regex timeout test failed')
    .to.deep.equal(omitDeepLodash(parse(mjml4), 'absoluteFilePath'))
<|endoftext|>