<commit_before>module.exports = function(grunt) {
  'use strict';

  // load all packages matching the `grunt-*` pattern
  require('load-grunt-tasks')(grunt);

  grunt.initConfig({
    jsonlint: {
      test: {
        src: ['data-page.json', 'data-permalink.json']
      }
    },

    shell: {
      build: {
        command: function(json, src, dest) {
          return 'tumblr-theme-parser -d ' + json + ' < ' + src + ' > ' + dest;
        }
      }
    }
  });

  // By default, running `grunt` renders the frontpage.
  grunt.registerTask('default', ['build:page']);

  // Renders theme.html into a sample content of your choice.
  //
  // arg1: can be either `page` or `permalink`
  grunt.registerTask('build', 'Renders `theme.html` into a sample content.', function(arg1) {
    if (arguments.length === 0) {
      grunt.fail.warn('No arguments set.', 3);
    } else {
      switch (arg1) {
        case 'page':
          grunt.task.run('shell:build:data-page.json:theme.html:compiled-theme.html');
          break;
        case 'permalink':
          grunt.task.run('shell:build:data-permalink.json:theme.html:compiled-theme.html');
          break;
        default:
          grunt.fail.warn('Invalid argument specified for `build`.', 3);
          break;
      }
    }
  });

  // Runs a validation of the JSON files
  grunt.registerTask('test', 'Runs a validation of the JSON files.', 'jsonlint:test');
};
<commit_msg>Add grunt globalConfig<commit_after>module.exports = function(grunt) {
  var globalConfig = {
    src: 'src',
    dest: 'dist'
  };

  // load all packages matching the `grunt-*` pattern
  require('load-grunt-tasks')(grunt);

  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    globalConfig: globalConfig,

    jsonlint: {
      test: {
        src: ['data-page.json', 'data-permalink.json']
      }
    },

    shell: {
      build: {
        command: function(json, src, dest) {
          return 'tumblr-theme-parser -d ' + json + ' < ' + src + ' > ' + dest;
        }
      }
    }
  });

  // By default, running `grunt` renders the frontpage.
  grunt.registerTask('default', ['build:page']);

  // Renders theme.html into a sample content of your choice.
  //
  // arg1: can be either `page` or `permalink`
  grunt.registerTask('build', 'Renders `theme.html` into a sample content.', function(arg1) {
    if (arguments.length === 0) {
      grunt.fail.warn('No arguments set.', 3);
    } else {
      switch (arg1) {
        case 'page':
          grunt.task.run('shell:build:data-page.json:theme.html:compiled-theme.html');
          break;
        case 'permalink':
          grunt.task.run('shell:build:data-permalink.json:theme.html:compiled-theme.html');
          break;
        default:
          grunt.fail.warn('Invalid argument specified for `build`.', 3);
          break;
      }
    }
  });

  // Runs a validation of the JSON files
  grunt.registerTask('test', 'Runs a validation of the JSON files.', 'jsonlint:test');
};
<|endoftext|>


<commit_before>module.exports = function(grunt) {
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),

    jshint: {
      files: [
        'app/**/*.js',
        'admin/**/*.js',
        'test/**/*.js',
        'shared/**/*.js',
        'server/**/*.js'
      ],
      options: {
        globals: {
          window: true,
          document: true,
          navigator: true,
          console: true,
          module: true,
          require: true,
          FastClick: true,
          $: true,
          Backbone: true,
          Parse: true,
          Handlebars: true,
          _: true,
          google: true,
          parseAppId: true,
          parseJSKey: true
        },
        undef: true,
        debug: true,
        '-W030': true
      }
    },

    // A simple Grunt invoker for mocha tests
    simplemocha: {
      options: {
        timeout: 3000,
        ignoreLeaks: false,
        ui: 'bdd',
        reporter: 'list'
      },

      all: {
        src: 'test/**/*.js'
      }
    },

    // run the 'default' task when any watched files change
    watch: {
      files: [
        'Gruntfile.js',
        'app/**/*',
        'admin/**/*',
        'shared/**/*',
        'server/**/*',
        'vendor/**/*',
        'test/**/*'
      ],
      tasks: ['default']
    },

    sass: {
      // generate app-specific css file
      // sass performs both the processing and concatenation steps for css
      app: {
        src: 'app/css/app.scss',
        dest: 'tmp/linksf.css'
      },

      admin: {
        src: 'admin/css/admin.scss',
        dest: 'tmp/linksf_admin.css'
      }
    },

    // Browserify
    browserify: {
      options: {
        // We want `.hbs` files to be `require`able. We use the hbsfy transform
        // to precompile Handlebars templates and let us require the compiled
        // JavaScript functions.
        transform: ['hbsfy']
      },
      app: {
        // The entry point where Browserify will begin searching the AST for `require` calls
        src: 'app/js/app.js',

        // The built file
        dest: 'tmp/app.js',

        options: {
          // We want alias mappings because we'd rather
          // `require('views/index_view')` than `require('./views/index_view.js')`
          //
          // There is a tradeoff for convenient here - all aliased files are exported
          // into the built app.js file, even if they're never explicitly
          // `require`d from the entry point tree.
          aliasMappings: [
            { cwd: 'shared/js/lib',         src: '*.js',  dest: 'shared/lib' },
            { cwd: 'shared/js/lib',         src: '*.js',  dest: 'cloud/lib' },
            { cwd: 'shared/js/models',      src: '*.js',  dest: 'shared/models' },
            { cwd: 'shared/js/models',      src: '*.js',  dest: 'cloud/models' },
            { cwd: 'shared/js/collections', src: '*.js',  dest: 'shared/collections' },
            { cwd: 'shared/js/views',       src: '*.js',  dest: 'shared/views' },
            { cwd: 'shared/js/templates',   src: '*.hbs', dest: 'shared/templates' },
            { cwd: 'app/js/lib',            src: '*.js',  dest: 'lib' },
            { cwd: 'app/js/models',         src: '*.js',  dest: 'models' },
            { cwd: 'app/js/collections',    src: '*.js',  dest: 'collections' },
            { cwd: 'app/js/routers',        src: '*.js',  dest: 'routers' },
            { cwd: 'app/js/templates',      src: '*.hbs', dest: 'templates' },
            { cwd: 'app/js/views',          src: '*.js',  dest: 'views' }
          ]
        }
      },
      // See browserify:app above
      admin: {
        src: 'admin/js/admin.js',
        dest: 'tmp/admin.js',
        options: {
          aliasMappings: [
            { cwd: 'shared/js/lib',         src: '*.js',  dest: 'shared/lib' },
            { cwd: 'shared/js/lib',         src: '*.js',  dest: 'cloud/lib' },
            { cwd: 'shared/js/models',      src: '*.js',  dest: 'shared/models' },
            { cwd: 'shared/js/models',      src: '*.js',  dest: 'cloud/models' },
            { cwd: 'shared/js/collections', src: '*.js',  dest: 'shared/collections' },
            { cwd: 'shared/js/views',       src: '*.js',  dest: 'shared/views' },
            { cwd: 'shared/js/templates',   src: '*.hbs', dest: 'shared/templates' },
            { cwd: 'admin/js/lib',          src: '*.js',  dest: 'lib' },
            { cwd: 'admin/js/models',       src: '*.js',  dest: 'models' },
            { cwd: 'admin/js/collections',  src: '*.js',  dest: 'collections' },
            { cwd: 'admin/js/routers',      src: '*.js',  dest: 'routers' },
            { cwd: 'admin/js/templates',    src: '*.hbs', dest: 'templates' },
            { cwd: 'admin/js/views',        src: '*.js',  dest: 'views' }
          ]
        }
      }
    },

    // Minify css files
    cssmin: {
      app: {
        src: 'tmp/linksf.css',
        dest: 'tmp/linksf.css'
      },
      admin: {
        src: 'tmp/linksf_admin.css',
        dest: 'tmp/linksf_admin.css'
      },
    },

    // Concatenate files together.
    concat: {
      // Not a target, just a variable that we can interpolate in elsewhere.
      shared_js: [
        'vendor/js/jquery-2.0.3.js',
        'vendor/js/jquery.serialize-object.js',
        'vendor/js/underscore.js',
        'vendor/js/backbone-1.0.0.js',
        'vendor/js/parse-1.2.12.js'
      ],

      shared_js_minified: [
        'vendor/js/jquery-2.0.3.min.js',
        'vendor/js/jquery.serialize-object.min.js',
        'vendor/js/underscore.min.js',
        'vendor/js/backbone-1.0.0.min.js',
        'vendor/js/parse-1.2.12.min.js'
      ],

      // App also uses the vendored fastclick library
      // App uses backbone filters for tracking
      app: {
        src: [
          '<%= concat.shared_js %>',
          'vendor/js/backbone_filters.js',
          'vendor/js/bootstrap-button.js',
          'vendor/js/fastclick.js',
          'tmp/app.js'
        ],
        dest: 'tmp/linksf.js'
      },

      // Concat minified files
      app_min: {
        src: [
          '<%= concat.shared_js_minified %>',
          'vendor/js/backbone_filters.js',
          'vendor/js/bootstrap-button.min.js',
          'vendor/js/fastclick.min.js',
          'tmp/app.min.js'
        ],
        dest: 'tmp/linksf.js'
      },

      // Admin uses backbone filters for authentication and autosize for text entry
      admin: {
        src: [
          '<%= concat.shared_js %>',
          'vendor/js/backbone_filters.js',
          'vendor/js/jquery.autosize.js',
          'vendor/js/bootstrap.js',
          'tmp/admin.js'
        ],
        dest: 'tmp/linksf_admin.js'
      },

      admin_min: {
        src: [
          '<%= concat.shared_js_minified %>',
          'vendor/js/backbone_filters.min.js',
          'vendor/js/jquery.autosize.min.js',
          'vendor/js/bootstrap.min.js',
          'tmp/admin.min.js'
        ],
        dest: 'tmp/linksf_admin.js'
      }
    },

    uglify: {
      options: {
        mangle: false,
        preserveComments: false,
        report: 'min'
      },

      vendor: {
        files: {
          // These libraries are not distributed with minified versions, so we
          // minify them ourselves.
          'vendor/js/jquery.serialize-object.min.js': 'vendor/js/jquery.serialize-object.js',
          'vendor/js/backbone_filters.min.js': 'vendor/js/backbone_filters.js',
          'vendor/js/bootstrap-button.min.js': 'vendor/js/bootstrap-button.js'
        }
      },
      app: {files: {'tmp/app.min.js': 'tmp/app.js'}},
      admin: {files: {'tmp/admin.min.js': 'tmp/admin.js'}}
    },

    clean: {
      build: {
        src: 'build/*',
        filter: function(filepath) { return filepath !== 'build/.gitkeep'; }
      }
    },

    cachebuster: {
      dist: {
        files: {
          src: [
            'tmp/linksf.js',
            'tmp/linksf.css',
            'tmp/linksf_admin.js',
            'tmp/linksf_admin.css'
          ],
        },
        options: {
          complete: function(hashes) {
            // ugly but the only way to tag each file with a key
            var keyMap = {
                'tmp/linksf.js': 'linksf_js',
                'tmp/linksf.css': 'linksf_css',
                'tmp/linksf_admin.js': 'linksf_admin_js',
                'tmp/linksf_admin.css': 'linksf_admin_css'
               },
               context = {},
               Handlebars = require('handlebars'),
               template,
               output;

            context.parseAppId = process.env.PARSE_APP_ID;
            context.parseJSKey = process.env.PARSE_JS_KEY;

            Object.keys(hashes).forEach(function(key) {
              var matches = key.match(/^tmp\/(.*)(\..*)$/),
                  filename = matches[1],
                  extension = matches[2],
                  outputFile = 'build/' + filename + '-' + hashes[key] + extension;

              grunt.file.copy(key, outputFile);
              context[keyMap[key]] = outputFile;
            });

            template = Handlebars.compile(grunt.file.read('app/index.html'));
            output = template(context);
            grunt.file.write('index.html', output);

            template = Handlebars.compile(grunt.file.read('admin/admin.html'));
            output = template(context);
            grunt.file.write('admin.html', output);
          }
        }
      }
    }
  });

  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-contrib-sass');
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-cssmin');
  grunt.loadNpmTasks('grunt-simple-mocha');
  grunt.loadNpmTasks('grunt-cachebuster');
  grunt.loadNpmTasks('grunt-browserify');
  grunt.loadNpmTasks('grunt-contrib-clean');

  grunt.loadTasks('tasks');

  grunt.registerTask('build:development', [
    'clean',
    'jshint',
    'simplemocha',
    'sass',
    'browserify',
    'concat:app',
    'concat:admin',
    'configure:development',
    'cachebuster'
  ]);

  grunt.registerTask('build:production', [
    'clean',
    'jshint',
    'simplemocha',
    'sass',
    'cssmin',
    'browserify',
    'uglify',
    'concat:app_min',
    'concat:admin_min',
    'configure:production',
    'cachebuster'
  ]);

  grunt.registerTask('default', [
    'build:development'
  ]);
};
<commit_msg>stop inserting Parse tokens in cachebuster step<commit_after>module.exports = function(grunt) {
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),

    jshint: {
      files: [
        'app/**/*.js',
        'admin/**/*.js',
        'test/**/*.js',
        'shared/**/*.js',
        'server/**/*.js'
      ],
      options: {
        globals: {
          window: true,
          document: true,
          navigator: true,
          console: true,
          module: true,
          require: true,
          FastClick: true,
          $: true,
          Backbone: true,
          Parse: true,
          Handlebars: true,
          _: true,
          google: true,
          parseAppId: true,
          parseJSKey: true
        },
        undef: true,
        debug: true,
        '-W030': true
      }
    },

    // A simple Grunt invoker for mocha tests
    simplemocha: {
      options: {
        timeout: 3000,
        ignoreLeaks: false,
        ui: 'bdd',
        reporter: 'list'
      },

      all: {
        src: 'test/**/*.js'
      }
    },

    // run the 'default' task when any watched files change
    watch: {
      files: [
        'Gruntfile.js',
        'app/**/*',
        'admin/**/*',
        'shared/**/*',
        'server/**/*',
        'vendor/**/*',
        'test/**/*'
      ],
      tasks: ['default']
    },

    sass: {
      // generate app-specific css file
      // sass performs both the processing and concatenation steps for css
      app: {
        src: 'app/css/app.scss',
        dest: 'tmp/linksf.css'
      },

      admin: {
        src: 'admin/css/admin.scss',
        dest: 'tmp/linksf_admin.css'
      }
    },

    // Browserify
    browserify: {
      options: {
        // We want `.hbs` files to be `require`able. We use the hbsfy transform
        // to precompile Handlebars templates and let us require the compiled
        // JavaScript functions.
        transform: ['hbsfy']
      },
      app: {
        // The entry point where Browserify will begin searching the AST for `require` calls
        src: 'app/js/app.js',

        // The built file
        dest: 'tmp/app.js',

        options: {
          // We want alias mappings because we'd rather
          // `require('views/index_view')` than `require('./views/index_view.js')`
          //
          // There is a tradeoff for convenient here - all aliased files are exported
          // into the built app.js file, even if they're never explicitly
          // `require`d from the entry point tree.
          aliasMappings: [
            { cwd: 'shared/js/lib',         src: '*.js',  dest: 'shared/lib' },
            { cwd: 'shared/js/lib',         src: '*.js',  dest: 'cloud/lib' },
            { cwd: 'shared/js/models',      src: '*.js',  dest: 'shared/models' },
            { cwd: 'shared/js/models',      src: '*.js',  dest: 'cloud/models' },
            { cwd: 'shared/js/collections', src: '*.js',  dest: 'shared/collections' },
            { cwd: 'shared/js/views',       src: '*.js',  dest: 'shared/views' },
            { cwd: 'shared/js/templates',   src: '*.hbs', dest: 'shared/templates' },
            { cwd: 'app/js/lib',            src: '*.js',  dest: 'lib' },
            { cwd: 'app/js/models',         src: '*.js',  dest: 'models' },
            { cwd: 'app/js/collections',    src: '*.js',  dest: 'collections' },
            { cwd: 'app/js/routers',        src: '*.js',  dest: 'routers' },
            { cwd: 'app/js/templates',      src: '*.hbs', dest: 'templates' },
            { cwd: 'app/js/views',          src: '*.js',  dest: 'views' }
          ]
        }
      },
      // See browserify:app above
      admin: {
        src: 'admin/js/admin.js',
        dest: 'tmp/admin.js',
        options: {
          aliasMappings: [
            { cwd: 'shared/js/lib',         src: '*.js',  dest: 'shared/lib' },
            { cwd: 'shared/js/lib',         src: '*.js',  dest: 'cloud/lib' },
            { cwd: 'shared/js/models',      src: '*.js',  dest: 'shared/models' },
            { cwd: 'shared/js/models',      src: '*.js',  dest: 'cloud/models' },
            { cwd: 'shared/js/collections', src: '*.js',  dest: 'shared/collections' },
            { cwd: 'shared/js/views',       src: '*.js',  dest: 'shared/views' },
            { cwd: 'shared/js/templates',   src: '*.hbs', dest: 'shared/templates' },
            { cwd: 'admin/js/lib',          src: '*.js',  dest: 'lib' },
            { cwd: 'admin/js/models',       src: '*.js',  dest: 'models' },
            { cwd: 'admin/js/collections',  src: '*.js',  dest: 'collections' },
            { cwd: 'admin/js/routers',      src: '*.js',  dest: 'routers' },
            { cwd: 'admin/js/templates',    src: '*.hbs', dest: 'templates' },
            { cwd: 'admin/js/views',        src: '*.js',  dest: 'views' }
          ]
        }
      }
    },

    // Minify css files
    cssmin: {
      app: {
        src: 'tmp/linksf.css',
        dest: 'tmp/linksf.css'
      },
      admin: {
        src: 'tmp/linksf_admin.css',
        dest: 'tmp/linksf_admin.css'
      },
    },

    // Concatenate files together.
    concat: {
      // Not a target, just a variable that we can interpolate in elsewhere.
      shared_js: [
        'vendor/js/jquery-2.0.3.js',
        'vendor/js/jquery.serialize-object.js',
        'vendor/js/underscore.js',
        'vendor/js/backbone-1.0.0.js',
        'vendor/js/parse-1.2.12.js'
      ],

      shared_js_minified: [
        'vendor/js/jquery-2.0.3.min.js',
        'vendor/js/jquery.serialize-object.min.js',
        'vendor/js/underscore.min.js',
        'vendor/js/backbone-1.0.0.min.js',
        'vendor/js/parse-1.2.12.min.js'
      ],

      // App also uses the vendored fastclick library
      // App uses backbone filters for tracking
      app: {
        src: [
          '<%= concat.shared_js %>',
          'vendor/js/backbone_filters.js',
          'vendor/js/bootstrap-button.js',
          'vendor/js/fastclick.js',
          'tmp/app.js'
        ],
        dest: 'tmp/linksf.js'
      },

      // Concat minified files
      app_min: {
        src: [
          '<%= concat.shared_js_minified %>',
          'vendor/js/backbone_filters.js',
          'vendor/js/bootstrap-button.min.js',
          'vendor/js/fastclick.min.js',
          'tmp/app.min.js'
        ],
        dest: 'tmp/linksf.js'
      },

      // Admin uses backbone filters for authentication and autosize for text entry
      admin: {
        src: [
          '<%= concat.shared_js %>',
          'vendor/js/backbone_filters.js',
          'vendor/js/jquery.autosize.js',
          'vendor/js/bootstrap.js',
          'tmp/admin.js'
        ],
        dest: 'tmp/linksf_admin.js'
      },

      admin_min: {
        src: [
          '<%= concat.shared_js_minified %>',
          'vendor/js/backbone_filters.min.js',
          'vendor/js/jquery.autosize.min.js',
          'vendor/js/bootstrap.min.js',
          'tmp/admin.min.js'
        ],
        dest: 'tmp/linksf_admin.js'
      }
    },

    uglify: {
      options: {
        mangle: false,
        preserveComments: false,
        report: 'min'
      },

      vendor: {
        files: {
          // These libraries are not distributed with minified versions, so we
          // minify them ourselves.
          'vendor/js/jquery.serialize-object.min.js': 'vendor/js/jquery.serialize-object.js',
          'vendor/js/backbone_filters.min.js': 'vendor/js/backbone_filters.js',
          'vendor/js/bootstrap-button.min.js': 'vendor/js/bootstrap-button.js'
        }
      },
      app: {files: {'tmp/app.min.js': 'tmp/app.js'}},
      admin: {files: {'tmp/admin.min.js': 'tmp/admin.js'}}
    },

    clean: {
      build: {
        src: 'build/*',
        filter: function(filepath) { return filepath !== 'build/.gitkeep'; }
      }
    },

    cachebuster: {
      dist: {
        files: {
          src: [
            'tmp/linksf.js',
            'tmp/linksf.css',
            'tmp/linksf_admin.js',
            'tmp/linksf_admin.css'
          ],
        },
        options: {
          complete: function(hashes) {
            var context = {},
                Handlebars = require('handlebars'),
                template,
                output,
                keyMap = {
                  'tmp/linksf.js': 'linksf_js',
                  'tmp/linksf.css': 'linksf_css',
                  'tmp/linksf_admin.js': 'linksf_admin_js',
                  'tmp/linksf_admin.css': 'linksf_admin_css'
                };

            Object.keys(hashes).forEach(function(key) {
              var matches = key.match(/^tmp\/(.*)(\..*)$/),
                  filename = matches[1],
                  extension = matches[2],
                  outputFile = 'build/' + filename + '-' + hashes[key] + extension;

              grunt.file.copy(key, outputFile);
              context[keyMap[key]] = outputFile;
            });

            template = Handlebars.compile(grunt.file.read('app/index.html'));
            output = template(context);
            grunt.file.write('index.html', output);

            template = Handlebars.compile(grunt.file.read('admin/admin.html'));
            output = template(context);
            grunt.file.write('admin.html', output);
          }
        }
      }
    }
  });

  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-contrib-sass');
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-cssmin');
  grunt.loadNpmTasks('grunt-simple-mocha');
  grunt.loadNpmTasks('grunt-cachebuster');
  grunt.loadNpmTasks('grunt-browserify');
  grunt.loadNpmTasks('grunt-contrib-clean');

  grunt.loadTasks('tasks');

  grunt.registerTask('build:development', [
    'clean',
    'jshint',
    'simplemocha',
    'sass',
    'browserify',
    'concat:app',
    'concat:admin',
    'configure:development',
    'cachebuster'
  ]);

  grunt.registerTask('build:production', [
    'clean',
    'jshint',
    'simplemocha',
    'sass',
    'cssmin',
    'browserify',
    'uglify',
    'concat:app_min',
    'concat:admin_min',
    'configure:production',
    'cachebuster'
  ]);

  grunt.registerTask('default', [
    'build:development'
  ]);
};
<|endoftext|>


<commit_before>'use strict';

describe('Dropdown directive', function () {
    var scope, element, html;

    beforeEach(module('talend.widget'));
    beforeEach(module('htmlTemplates'));

    var clickDropdownToggle = function (elm) {
        elm = elm || element;
        elm.find('.dropdown-action').eq(0).click();
    };

    var clickDropdownItem = function (elm) {
        elm = elm || element;
        elm.find('a[role="menuitem"]').eq(0).click();
    };

    beforeEach(function () {
        jasmine.clock().install();
    });
    afterEach(function () {
        jasmine.clock().uninstall();

        scope.$destroy();
        element.remove();
    });

    describe('closeable dropdown', function () {

        beforeEach(inject(function ($rootScope, $compile) {

            scope = $rootScope.$new();

            html = '<talend-dropdown id="dropdown1">' +
                '
' +
                '
' +
                '
{{ column.id }}
' +
                '
{{ column.type }}
' +
                '
' +
                '
' +
                '
Hide Column
' +
                '

' +
                '
Split first Space
' +
                '
Uppercase
' +
                '
' +
                '
' +
                '</talend-dropdown>';
            element = $compile(html)(scope);
            scope.$digest();
        }));

        it('should show dropdown-menu on dropdown-action click', function () {
            //given
            var menu = element.find('.dropdown-menu').eq(0);
            expect(menu.hasClass('show-menu')).toBe(false);

            //when
            clickDropdownToggle();

            //then
            expect(menu.hasClass('show-menu')).toBe(true);
        });

        it('should focus on dropdown menu when it is shown', function () {
            //given
            var menu = element.find('.dropdown-menu').eq(0)[0];
            var body = angular.element('body');
            body.append(element);
            expect(document.activeElement).not.toBe(menu);

            //when
            clickDropdownToggle();

            //then
            expect(document.activeElement).not.toBe(element.find('.dropdown-menu').eq(0)[0]);
        });

        it('should hide dropdown-menu on dropdown-action click when menu is visible', function () {
            //given
            var menu = element.find('.dropdown-menu').eq(0);
            menu.addClass('show-menu');

            //when
            clickDropdownToggle();

            //then
            expect(menu.hasClass('show-menu')).toBe(false);
        });

        it('should hide dropdown-menu on item click', function () {
            //given
            var menu = element.find('.dropdown-menu').eq(0);
            menu.addClass('show-menu');

            //when
            clickDropdownItem();

            //then
            expect(menu.hasClass('show-menu')).toBe(false);
        });

        it('should register window scroll handler on open', inject(function ($window) {
            //given
            expect($._data(angular.element($window)[0], 'events')).not.toBeDefined();

            //when
            clickDropdownToggle();

            //then
            expect($._data(angular.element($window)[0], 'events')).toBeDefined();
            expect($._data(angular.element($window)[0], 'events').scroll.length).toBe(1);
        }));

        it('should unregister window scroll on close', inject(function ($window) {
            //given
            clickDropdownToggle();
            expect($._data(angular.element($window)[0], 'events').scroll.length).toBe(1);

            //when
            clickDropdownToggle();

            //then
            expect($._data(angular.element($window)[0], 'events')).not.toBeDefined();
        }));

        it('should hide dropdown-menu on body mousedown', function () {
            //given
            var menu = element.find('.dropdown-menu').eq(0);
            menu.addClass('show-menu');

            //when
            angular.element('body').mousedown();

            //then
            expect(menu.hasClass('show-menu')).toBe(false);
        });

        it('should unregister body mousedown on element remove', function () {
            //given
            expect($._data(angular.element('body')[0], 'events').mousedown.length).toBe(1);

            console.log('data:'+$._data(angular.element('body')[0], 'events').mousedown[0]);

            //when
            element.remove();

            var asset = $._data(angular.element('body')[0], 'events');
            //then
            expect(asset.mousedown.length).toBe(0);
        });

        it('should stop mousedown propagation on dropdown-menu mousedown', function () {
            //given
            var bodyMouseDown = false;
            angular.element('body').mousedown(function () {
                bodyMouseDown = true;
            });

            //when
            element.find('.dropdown-menu').mousedown();

            //then
            expect(bodyMouseDown).toBe(false);
        });

        it('should hide dropdown menu on ESC', function () {
            //given
            var menu = element.find('.dropdown-menu').eq(0);
            menu.addClass('show-menu');

            var event = angular.element.Event('keydown');
            event.keyCode = 27;

            //when
            menu.trigger(event);

            //then
            expect(menu.hasClass('show-menu')).toBe(false);
        });

        it('should not hide dropdown menu on not ESC keydown', function () {
            //given
            var menu = element.find('.dropdown-menu').eq(0);
            menu.addClass('show-menu');

            var event = angular.element.Event('keydown');
            event.keyCode = 13;

            //when
            menu.trigger(event);

            //then
            expect(menu.hasClass('show-menu')).toBe(true);
        });

        it('should focus on dropdown action when menu is hidden by ESC', function () {
            //given
            var action = element.find('.dropdown-action').eq(0);
            var menu = element.find('.dropdown-menu').eq(0);
            angular.element('body').append(element);
            expect(document.activeElement).not.toBe(menu[0]);

            clickDropdownToggle();

            var event = angular.element.Event('keydown');
            event.keyCode = 27;

            //when
            menu.trigger(event);
            jasmine.clock().tick(100);

            //then
            expect(document.activeElement).toBe(action[0]);
        });

        it('should show menu on first click then close menu on the second click', function () {
            //given
            var menu = element.find('.dropdown-menu').eq(0);
            expect(menu.hasClass('show-menu')).toBe(false);

            //when
            clickDropdownToggle();
            expect(menu.hasClass('show-menu')).toBe(true);

            clickDropdownToggle();
            //then
            expect(menu.hasClass('show-menu')).toBe(false);
        });
    });

    describe('not closeable on click dropdown', function () {
        beforeEach(inject(function ($rootScope, $compile) {
            scope = $rootScope.$new();

            html = '<talend-dropdown id="dropdown1" close-on-select="false">' +
                '
' +
                '
' +
                '
{{ column.id }}
' +
                '
{{ column.type }}
' +
                '
' +
                '
' +
                '
Hide Column
' +
                '

' +
                '
Split first Space
' +
                '
Uppercase
' +
                '
' +
                '
' +
                '</talend-dropdown>';
            element = $compile(html)(scope);
            scope.$digest();
        }));

        it('should not hide dropdown-menu on item click if closeOnSelect is false', function () {
            //given
            var menu = element.find('.dropdown-menu').eq(0);
            menu.addClass('show-menu');

            //when
            clickDropdownItem();

            //then
            expect(menu.hasClass('show-menu')).toBe(true);
        });
    });

    describe('with onOpen action', function () {
        beforeEach(inject(function ($rootScope, $compile) {
            scope = $rootScope.$new();
            scope.onOpen = function () {
            };
            spyOn(scope, 'onOpen').and.returnValue(true);

            html = '<talend-dropdown id="dropdown1" on-open="onOpen()">' +
                '
' +
                '
' +
                '
{{ column.id }}
' +
                '
{{ column.type }}
' +
                '
' +
                '
' +
                '
Hide Column
' +
                '

' +
                '
Split first Space
' +
                '
Uppercase
' +
                '
' +
                '
' +
                '</talend-dropdown>';
            element = $compile(html)(scope);
            scope.$digest();
        }));

        it('should call action on open click', function () {
            //given
            expect(scope.onOpen).not.toHaveBeenCalled();

            //when
            clickDropdownToggle();

            //then
            expect(scope.onOpen).toHaveBeenCalled();
        });
    });
});<commit_msg>[TDP-173] fix TU<commit_after>'use strict';

describe('Dropdown directive', function () {
    var scope, element, html;

    beforeEach(module('talend.widget'));
    beforeEach(module('htmlTemplates'));

    var clickDropdownToggle = function (elm) {
        elm = elm || element;
        elm.find('.dropdown-action').eq(0).click();
    };

    var clickDropdownItem = function (elm) {
        elm = elm || element;
        elm.find('a[role="menuitem"]').eq(0).click();
    };

    beforeEach(function () {
        jasmine.clock().install();
    });
    afterEach(function () {
        jasmine.clock().uninstall();

        scope.$destroy();
        element.remove();
    });

    describe('closeable dropdown', function () {

        beforeEach(inject(function ($rootScope, $compile) {

            scope = $rootScope.$new();

            html = '<talend-dropdown id="dropdown1">' +
                '
' +
                '
' +
                '
{{ column.id }}
' +
                '
{{ column.type }}
' +
                '
' +
                '
' +
                '
Hide Column
' +
                '

' +
                '
Split first Space
' +
                '
Uppercase
' +
                '
' +
                '
' +
                '</talend-dropdown>';
            element = $compile(html)(scope);
            scope.$digest();
        }));

        it('should show dropdown-menu on dropdown-action click', function () {
            //given
            var menu = element.find('.dropdown-menu').eq(0);
            expect(menu.hasClass('show-menu')).toBe(false);

            //when
            clickDropdownToggle();

            //then
            expect(menu.hasClass('show-menu')).toBe(true);
        });

        it('should focus on dropdown menu when it is shown', function () {
            //given
            var menu = element.find('.dropdown-menu').eq(0)[0];
            var body = angular.element('body');
            body.append(element);
            expect(document.activeElement).not.toBe(menu);

            //when
            clickDropdownToggle();

            //then
            expect(document.activeElement).not.toBe(element.find('.dropdown-menu').eq(0)[0]);
        });

        it('should hide dropdown-menu on dropdown-action click when menu is visible', function () {
            //given
            var menu = element.find('.dropdown-menu').eq(0);
            menu.addClass('show-menu');

            //when
            clickDropdownToggle();

            //then
            expect(menu.hasClass('show-menu')).toBe(false);
        });

        it('should hide dropdown-menu on item click', function () {
            //given
            var menu = element.find('.dropdown-menu').eq(0);
            menu.addClass('show-menu');

            //when
            clickDropdownItem();

            //then
            expect(menu.hasClass('show-menu')).toBe(false);
        });

        it('should register window scroll handler on open', inject(function ($window) {
            //given
            expect($._data(angular.element($window)[0], 'events')).not.toBeDefined();

            //when
            clickDropdownToggle();

            //then
            expect($._data(angular.element($window)[0], 'events')).toBeDefined();
            expect($._data(angular.element($window)[0], 'events').scroll.length).toBe(1);
        }));

        it('should unregister window scroll on close', inject(function ($window) {
            //given
            clickDropdownToggle();
            expect($._data(angular.element($window)[0], 'events').scroll.length).toBe(1);

            //when
            clickDropdownToggle();

            //then
            expect($._data(angular.element($window)[0], 'events')).not.toBeDefined();
        }));

        it('should hide dropdown-menu on body mousedown', function () {
            //given
            var menu = element.find('.dropdown-menu').eq(0);
            menu.addClass('show-menu');

            //when
            angular.element('body').mousedown();

            //then
            expect(menu.hasClass('show-menu')).toBe(false);
        });

        it('should unregister body mousedown on element remove', function () {
            //given
            expect($._data(angular.element('body')[0], 'events').mousedown.length).toBe(1);

            //when
            element.remove();

            var asset = $._data(angular.element('body')[0], 'events');
            //then
            expect($._data(angular.element('body')[0], 'events')).not.toBeDefined();
        });

        it('should stop mousedown propagation on dropdown-menu mousedown', function () {
            //given
            var bodyMouseDown = false;
            angular.element('body').mousedown(function () {
                bodyMouseDown = true;
            });

            //when
            element.find('.dropdown-menu').mousedown();

            //then
            expect(bodyMouseDown).toBe(false);
        });

        it('should hide dropdown menu on ESC', function () {
            //given
            var menu = element.find('.dropdown-menu').eq(0);
            menu.addClass('show-menu');

            var event = angular.element.Event('keydown');
            event.keyCode = 27;

            //when
            menu.trigger(event);

            //then
            expect(menu.hasClass('show-menu')).toBe(false);
        });

        it('should not hide dropdown menu on not ESC keydown', function () {
            //given
            var menu = element.find('.dropdown-menu').eq(0);
            menu.addClass('show-menu');

            var event = angular.element.Event('keydown');
            event.keyCode = 13;

            //when
            menu.trigger(event);

            //then
            expect(menu.hasClass('show-menu')).toBe(true);
        });

        it('should focus on dropdown action when menu is hidden by ESC', function () {
            //given
            var action = element.find('.dropdown-action').eq(0);
            var menu = element.find('.dropdown-menu').eq(0);
            angular.element('body').append(element);
            expect(document.activeElement).not.toBe(menu[0]);

            clickDropdownToggle();

            var event = angular.element.Event('keydown');
            event.keyCode = 27;

            //when
            menu.trigger(event);
            jasmine.clock().tick(100);

            //then
            expect(document.activeElement).toBe(action[0]);
        });

        it('should show menu on first click then close menu on the second click', function () {
            //given
            var menu = element.find('.dropdown-menu').eq(0);
            expect(menu.hasClass('show-menu')).toBe(false);

            //when
            clickDropdownToggle();
            expect(menu.hasClass('show-menu')).toBe(true);

            clickDropdownToggle();
            //then
            expect(menu.hasClass('show-menu')).toBe(false);
        });
    });

    describe('not closeable on click dropdown', function () {
        beforeEach(inject(function ($rootScope, $compile) {
            scope = $rootScope.$new();

            html = '<talend-dropdown id="dropdown1" close-on-select="false">' +
                '
' +
                '
' +
                '
{{ column.id }}
' +
                '
{{ column.type }}
' +
                '
' +
                '
' +
                '
Hide Column
' +
                '

' +
                '
Split first Space
' +
                '
Uppercase
' +
                '
' +
                '
' +
                '</talend-dropdown>';
            element = $compile(html)(scope);
            scope.$digest();
        }));

        it('should not hide dropdown-menu on item click if closeOnSelect is false', function () {
            //given
            var menu = element.find('.dropdown-menu').eq(0);
            menu.addClass('show-menu');

            //when
            clickDropdownItem();

            //then
            expect(menu.hasClass('show-menu')).toBe(true);
        });
    });

    describe('with onOpen action', function () {
        beforeEach(inject(function ($rootScope, $compile) {
            scope = $rootScope.$new();
            scope.onOpen = function () {
            };
            spyOn(scope, 'onOpen').and.returnValue(true);

            html = '<talend-dropdown id="dropdown1" on-open="onOpen()">' +
                '
' +
                '
' +
                '
{{ column.id }}
' +
                '
{{ column.type }}
' +
                '
' +
                '
' +
                '
Hide Column
' +
                '

' +
                '
Split first Space
' +
                '
Uppercase
' +
                '
' +
                '
' +
                '</talend-dropdown>';
            element = $compile(html)(scope);
            scope.$digest();
        }));

        it('should call action on open click', function () {
            //given
            expect(scope.onOpen).not.toHaveBeenCalled();

            //when
            clickDropdownToggle();

            //then
            expect(scope.onOpen).toHaveBeenCalled();
        });
    });
});<|endoftext|>


<commit_before>import template from './customer-threshold.html';

const customerThreshold = {
  template: template,
  bindings: {
    horizontal: '<',
    priceEstimate: '=',
    toggleHardLimit: '&',
  },
  controller: class CustomerThresholdController {
    // @ngInject
    constructor(ENV, customerUtils) {
      this.currency = ENV.currency;
      this.isHardLimit = customerUtils.isHardLimit(this.priceEstimate);
    }

    validateThreshold() {
      let isValid = this.priceEstimate.threshold >= this.priceEstimate.total;
      this.thresholdForm.threshold.$setValidity('exceedsThreshold', isValid);
    }
  }
};

export default customerThreshold;
<commit_msg>Initialize isHardLimit in the right place [WAL-881]<commit_after>import template from './customer-threshold.html';

const customerThreshold = {
  template: template,
  bindings: {
    horizontal: '<',
    priceEstimate: '=',
    toggleHardLimit: '&',
  },
  controller: class CustomerThresholdController {
    // @ngInject
    constructor(ENV, customerUtils) {
      this.currency = ENV.currency;
      this.customerUtils = customerUtils;
    }

    $onInit() {
      this.isHardLimit = this.customerUtils.isHardLimit(this.priceEstimate);
    }

    validateThreshold() {
      let isValid = this.priceEstimate.threshold >= this.priceEstimate.total;
      this.thresholdForm.threshold.$setValidity('exceedsThreshold', isValid);
    }
  }
};

export default customerThreshold;
<|endoftext|>


<commit_before>/**
* Behaviors that do not require the composition lifecycle callbacks when replacing
* their binding context.
*/
export const lifecycleOptionalBehaviors = ['focus', 'if', 'repeat', 'show', 'with'];

function behaviorRequiresLifecycle(instruction) {
  let t = instruction.type;
  let name = t.elementName !== null ? t.elementName : t.attributeName;
  return lifecycleOptionalBehaviors.indexOf(name) === -1 && (t.handlesAttached || t.handlesBind || t.handlesCreated || t.handlesDetached || t.handlesUnbind)
    || t.viewFactory && viewsRequireLifecycle(t.viewFactory)
    || instruction.viewFactory && viewsRequireLifecycle(instruction.viewFactory);
}

function targetRequiresLifecycle(instruction) {
  // check each behavior instruction.
  let behaviors = instruction.behaviorInstructions;
  if (behaviors) {
    let i = behaviors.length;
    while (i--) {
      if (behaviorRequiresLifecycle(behaviors[i])) {
        return true;
      }
    }
  }

  // check the instruction's view factory (if it has one).
  return instruction.viewFactory && viewsRequireLifecycle(instruction.viewFactory);
}

export function viewsRequireLifecycle(viewFactory) {
  // already analyzed?
  if ('_viewsRequireLifecycle' in viewFactory) {
    return viewFactory._viewsRequireLifecycle;
  }

  // access inner view factory.
  if (viewFactory.viewFactory) {
    viewFactory._viewsRequireLifecycle = viewsRequireLifecycle(viewFactory.viewFactory);
    return viewFactory._viewsRequireLifecycle;
  }

  // template uses animation?
  if (viewFactory.template.querySelector('.au-animate')) {
    viewFactory._viewsRequireLifecycle = true;
    return true;
  }

  // target instructions require lifecycle?
  for (let id in viewFactory.instructions) {
    if (targetRequiresLifecycle(viewFactory.instructions[id])) {
      viewFactory._viewsRequireLifecycle = true;
      return true;
    }
  }

  // safe to skip lifecycle.
  viewFactory._viewsRequireLifecycle = false;
  return false;
}
<commit_msg>fix(analyze-view-factory): prevent infinite recursion<commit_after>/**
* Behaviors that do not require the composition lifecycle callbacks when replacing
* their binding context.
*/
export const lifecycleOptionalBehaviors = ['focus', 'if', 'repeat', 'show', 'with'];

function behaviorRequiresLifecycle(instruction) {
  let t = instruction.type;
  let name = t.elementName !== null ? t.elementName : t.attributeName;
  return lifecycleOptionalBehaviors.indexOf(name) === -1 && (t.handlesAttached || t.handlesBind || t.handlesCreated || t.handlesDetached || t.handlesUnbind)
    || t.viewFactory && viewsRequireLifecycle(t.viewFactory)
    || instruction.viewFactory && viewsRequireLifecycle(instruction.viewFactory);
}

function targetRequiresLifecycle(instruction) {
  // check each behavior instruction.
  let behaviors = instruction.behaviorInstructions;
  if (behaviors) {
    let i = behaviors.length;
    while (i--) {
      if (behaviorRequiresLifecycle(behaviors[i])) {
        return true;
      }
    }
  }

  // check the instruction's view factory (if it has one).
  return instruction.viewFactory && viewsRequireLifecycle(instruction.viewFactory);
}

export function viewsRequireLifecycle(viewFactory) {
  // already analyzed?
  if ('_viewsRequireLifecycle' in viewFactory) {
    return viewFactory._viewsRequireLifecycle;
  }

  // set prop to avoid infinite recursion.
  viewFactory._viewsRequireLifecycle = false;

  // access inner view factory.
  if (viewFactory.viewFactory) {
    viewFactory._viewsRequireLifecycle = viewsRequireLifecycle(viewFactory.viewFactory);
    return viewFactory._viewsRequireLifecycle;
  }

  // template uses animation?
  if (viewFactory.template.querySelector('.au-animate')) {
    viewFactory._viewsRequireLifecycle = true;
    return true;
  }

  // target instructions require lifecycle?
  for (let id in viewFactory.instructions) {
    if (targetRequiresLifecycle(viewFactory.instructions[id])) {
      viewFactory._viewsRequireLifecycle = true;
      return true;
    }
  }

  // safe to skip lifecycle.
  viewFactory._viewsRequireLifecycle = false;
  return false;
}
<|endoftext|>


<commit_before>module.exports = function(grunt) {
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    wiredep: {
      target: {
        // Point to the files that should be updated when
        // you run `grunt wiredep`
        src: [
          'views/layouts/*'
        ]
      }
    },
    sass: {
      dist: {
        options: {
          loadPath: 'static/bower_components/bootstrap-sass/lib'
        },
        files: [{
          expand: true,
          cwd: 'static/scss',
          src: ['*.scss'],
          dest: 'static/css',
          ext: '.css'
        }]
      }
    },
    watch: {
      wiredep: {
        files: 'bower.json',
        tasks: ['shell:bower', 'wiredep'],
        options: {
          debounceDelay: 250
        }
      },
      livereload: {
        files: '**/*',
        options: {
          debounceDelay: 250,
          livereload: true
        }
      },
      static: {
        files: 'static/**/*',
        tasks: ['sass'],
        options: {
          debounceDelay: 250
        }
      },
      go: {
        files: './**/*.go',
        tasks: ['shell:fmt'],
        options: {
          debounceDelay: 250
        }
      }
    },
    shell: {
        bower: {
            command: 'bower install'
        },
        fmt: {
            command: 'go fmt ./...'
        }
    }
  });

  grunt.loadNpmTasks('grunt-shell');
  grunt.loadNpmTasks('grunt-wiredep');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-contrib-sass');
  grunt.registerTask('default', ['shell:bower']);
};
<commit_msg>Fix grunt watch.<commit_after>module.exports = function(grunt) {
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    wiredep: {
      target: {
        // Point to the files that should be updated when
        // you run `grunt wiredep`
        src: [
          'views/layouts/*'
        ]
      }
    },
    sass: {
      dist: {
        options: {
          loadPath: 'static/bower_components/bootstrap-sass/lib'
        },
        files: [{
          expand: true,
          cwd: 'static/scss',
          src: ['*.scss'],
          dest: 'static/css',
          ext: '.css'
        }]
      }
    },
    watch: {
      wiredep: {
        files: 'bower.json',
        tasks: ['shell:bower', 'wiredep'],
        options: {
          debounceDelay: 250
        }
      },
      livereload: {
        files: [
          '**/*.go',
          'static/**/*',
          'views/**/*'
        ],
        options: {
          debounceDelay: 250,
          livereload: true
        }
      },
      static: {
        files: 'static/**/*',
        tasks: ['sass'],
        options: {
          debounceDelay: 250
        }
      },
      go: {
        files: '**/*.go',
        tasks: ['shell:fmt'],
        options: {
          debounceDelay: 250
        }
      }
    },
    shell: {
        bower: {
            command: 'bower install'
        },
        fmt: {
            command: 'go fmt ./...'
        }
    }
  });

  grunt.loadNpmTasks('grunt-shell');
  grunt.loadNpmTasks('grunt-wiredep');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-contrib-sass');
  grunt.registerTask('default', ['shell:bower']);
};
<|endoftext|>


<commit_before>module.exports = function(grunt) {

    grunt.loadNpmTasks('grunt-contrib-connect');

    // Project configuration.
    grunt.initConfig({

        connect: {
            server: {
                options: {
                port: 8000,
                base: '.',
                keepalive: true
                }
            }
        }

        ,

        pkg: grunt.file.readJSON('package.json'),
        uglify: {
            options: {
                banner: '/*! <%= pkg.name %> - v<%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %> */\n',
                sourceMap: true
            },
            build: {
                src: 'src/angular-intro.js',
                dest: 'build/angular-intro.min.js'
            }
        },
        jshint: {
            lib: {
                options: {},
                src: ['src/*.js']
            },
        },
        watch: {
            scripts: {
                files: 'src/*.js',
                tasks: ['jshint', 'uglify'],
                options: {
                    interrupt: true
                },
            },
            gruntfile: {
                files: 'Gruntfile.js'
            }
        },
    });

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

    // Default task(s).
    grunt.registerTask('default', ['jshint', 'uglify']);

    // Test
    grunt.registerTask('test', ['jshint']);
};
<commit_msg>Harmonize formatting (removed empty lines and dangling commas).<commit_after>module.exports = function(grunt) {

    grunt.loadNpmTasks('grunt-contrib-connect');

    // Project configuration.
    grunt.initConfig({
        connect: {
            server: {
                options: {
                port: 8000,
                base: '.',
                keepalive: true
                }
            }
        },
        pkg: grunt.file.readJSON('package.json'),
        uglify: {
            options: {
                banner: '/*! <%= pkg.name %> - v<%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %> */\n',
                sourceMap: true
            },
            build: {
                src: 'src/angular-intro.js',
                dest: 'build/angular-intro.min.js'
            }
        },
        jshint: {
            lib: {
                options: {},
                src: ['src/*.js']
            }
        },
        watch: {
            scripts: {
                files: 'src/*.js',
                tasks: ['jshint', 'uglify'],
                options: {
                    interrupt: true
                }
            },
            gruntfile: {
                files: 'Gruntfile.js'
            }
        }
    });

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

    // Default task(s).
    grunt.registerTask('default', ['jshint', 'uglify']);

    // Test
    grunt.registerTask('test', ['jshint']);
};
<|endoftext|>


<commit_before>const { stripIndent, source } = require('common-tags')

function template3({ profile, schools, jobs, projects, skills }) {
  return stripIndent`
    \\documentclass{article}
    \\usepackage{fullpage}
    \\usepackage{amsmath}
    \\usepackage{amssymb}
    \\textheight=10in
    \\pagestyle{empty}
    \\raggedright

    ${generateResumeDefinitions()}

    \\begin{document}

        \\vspace*{-40pt}

        ${generateProfileSection(profile)}

        \\vspace*{2mm}

        ${generateEducationSection(schools)}

        \\vspace*{2mm}

        ${generateExperienceSection(jobs)}

        ${generateSkillsSection(skills)}

        \\vspace*{2mm}

        ${generateProjectsSection(projects)}

    \\end{document}
  `
}

function generateProfileSection(profile) {
  if (!profile) {
    return ''
  }

  const { fullName, email, phoneNumber, address, link } = profile

  let line1 = fullName ? `{\\Huge \\scshape {${fullName}}}` : ''
  let line2 = [address, email, phoneNumber, link].filter(Boolean).join(' $\\cdot$ ')

  if (line1 && line2) {
    line1 += '\\\\'
    line2 += '\\\\'
  }

  return stripIndent`
    %==== Profile ====%
    \\vspace*{-10pt}
    \\begin{center}
      ${line1}
      ${line2}
    \\end{center}
  `
}

function generateEducationSection(schools) {
  if (!schools) {
    return ''
  }

  return source`
    %==== Education ====%
    \\header{Education}
      ${schools.map((school) => {
        const { name, location, degree, major, gpa, graduationDate } = school

        let line1 = ''
        let line2 = ''

        if (name) {
          line1 += `\\textbf{${name}}`
        }

        if (location) {
          line1 += `\\hfill ${location}`
        }

        if (degree) {
          line2 += degree
        }

        if (major) {
          line2 += degree ? ` ${major}` : `Degree in ${major}`
        }

        if (gpa) {
          line2 += ` \\textit{GPA: ${gpa}}`
        }

        if (graduationDate) {
          const gradLine = `Grad: ${graduationDate}`
          line2 += line2 ? ` \\hfill ${gradLine}` : gradLine
        }

        if (line1) {
          line1 += '\\\\'
        }

        if (line2) {
          line2 += '\\\\'
        }

        line2.trim()

        return stripIndent`
          ${line1}
          ${line2}
          \\vspace{1mm}
        `
      })}
  `
}

function generateExperienceSection(jobs) {
  if (!jobs) {
    return ''
  }

  return source`
    %==== Experience ====%
    \\header{Experience}
    \\vspace{1mm}

    ${jobs.map((job) => {
      const { name, title, location, startDate, endDate, duties } = job

      let line1 = ''
      let line2 = ''
      let dutyLines = ''

      if (name) {
        line1 += `\\textbf{${name}}`
      }

      if (location) {
        line1 += ` \\hfill ${location}`
      }

      if (title) {
        line2 += `\\textit{${title}}`
      }

      if (startDate && endDate) {
        line2 += ` \\hfill ${startDate} | ${endDate}`
      } else if (startDate) {
        line2 += ` \\hfill ${startDate} | Present`
      } else if (endDate) {
        line2 += ` \\hfill ${endDate}`
      }

      if (line1) line1 += '\\\\'
      if (line2) line2 += '\\\\'

      if (duties) {
        dutyLines = source`
          \\vspace{-1mm}
          \\begin{itemize} \\itemsep 1pt
            ${duties.map(duty => `\\item ${duty}`)}
          \\end{itemize}
        `
      }

      return stripIndent`
        ${line1}
        ${line2}
        ${dutyLines}
      `
    })}
  `
}

function generateSkillsSection(skills) {
  if (!skills) {
    return ''
  }

  const { languages, frameworks, miscellaneous } = skills

  return `
    \\header{Skills}
    \\begin{tabular}{ l l }
      ${languages ? `Programming Languages: & ${languages} \\\\` : ''}
      ${frameworks ? `Frameworks/Libraries: & ${frameworks} \\\\` : ''}
      ${miscellaneous ? `Miscellaneous: & ${miscellaneous} \\\\` : ''}
    \\end{tabular}
  `
}

function generateProjectsSection(projects) {
  if (!projects) {
    return ''
  }

  return source`
    \\header{Projects}
    ${projects.map((project) => {
      if (Object.keys(project) === 0) {
        return ''
      }

      const { name, description, technologies, link } = project

      let line1 = ''
      let line2 = description || ''

      if (name) {
        line1 += `{\\textbf{${name}}`
      }

      if (technologies) {
        line1 += ` \\sl ${technologies}} `
      }

      if (link) {
        line1 += `\\hfill ${link}`
      }

      if (line1) {
        line1 += '\\\\'
      }

      if (line2) {
        line2 += '\\\\'
      }

      return stripIndent`
        ${line1}
        ${line2}
        \\vspace*{2mm}
      `
    })}
  `
}

function generateResumeDefinitions() {
  return stripIndent`
    %\\renewcommand{\\encodingdefault}{cg}
    %\\renewcommand{\\rmdefault}{lgrcmr}

    \\def\\bull{\\vrule height 0.8ex width .7ex depth -.1ex }

    % DEFINITIONS FOR RESUME %%%%%%%%%%%%%%%%%%%%%%%

    \\newcommand{\\area} [2] {
        \\vspace*{-9pt}
        \\begin{verse}
            \\textbf{#1}   #2
        \\end{verse}
    }

    \\newcommand{\\lineunder} {
        \\vspace*{-8pt} \\\\
        \\hspace*{-18pt} \\hrulefill \\\\
    }

    \\newcommand{\\header} [1] {
        {\\hspace*{-18pt}\\vspace*{6pt} \\textsc{#1}}
        \\vspace*{-6pt} \\lineunder
    }

    \\newcommand{\\employer} [3] {
        { \\textbf{#1} (#2)\\\\ \\underline{\\textbf{\\emph{#3}}}\\\\  }
    }

    \\newcommand{\\contact} [3] {
        \\vspace*{-10pt}
        \\begin{center}
            {\\Huge \\scshape {#1}}\\\\
            #2 \\\\ #3
        \\end{center}
        \\vspace*{-8pt}
    }

    \\newenvironment{achievements}{
        \\begin{list}
            {$\\bullet$}{\\topsep 0pt \\itemsep -2pt}}{\\vspace*{4pt}
        \\end{list}
    }

    \\newcommand{\\schoolwithcourses} [4] {
        \\textbf{#1} #2 $\\bullet$ #3\\\\
        #4 \\\\
        \\vspace*{5pt}
    }

    \\newcommand{\\school} [4] {
        \\textbf{#1} #2 $\\bullet$ #3\\\\
        #4 \\\\
    }
        % END RESUME DEFINITIONS %%%%%%%%%%%%%%%%%%%%%%%
  `
}

module.exports = template3
<commit_msg>update template3 for new skills section<commit_after>const { stripIndent, source } = require('common-tags')

function template3({ profile, schools, jobs, projects, skills }) {
  return stripIndent`
    \\documentclass{article}
    \\usepackage{fullpage}
    \\usepackage{amsmath}
    \\usepackage{amssymb}
    \\textheight=10in
    \\pagestyle{empty}
    \\raggedright

    ${generateResumeDefinitions()}

    \\begin{document}

        \\vspace*{-40pt}

        ${generateProfileSection(profile)}

        \\vspace*{2mm}

        ${generateEducationSection(schools)}

        \\vspace*{2mm}

        ${generateExperienceSection(jobs)}

        ${generateSkillsSection(skills)}

        \\vspace*{2mm}

        ${generateProjectsSection(projects)}

    \\end{document}
  `
}

function generateProfileSection(profile) {
  if (!profile) {
    return ''
  }

  const { fullName, email, phoneNumber, address, link } = profile

  let line1 = fullName ? `{\\Huge \\scshape {${fullName}}}` : ''
  let line2 = [address, email, phoneNumber, link].filter(Boolean).join(' $\\cdot$ ')

  if (line1 && line2) {
    line1 += '\\\\'
    line2 += '\\\\'
  }

  return stripIndent`
    %==== Profile ====%
    \\vspace*{-10pt}
    \\begin{center}
      ${line1}
      ${line2}
    \\end{center}
  `
}

function generateEducationSection(schools) {
  if (!schools) {
    return ''
  }

  return source`
    %==== Education ====%
    \\header{Education}
      ${schools.map((school) => {
        const { name, location, degree, major, gpa, graduationDate } = school

        let line1 = ''
        let line2 = ''

        if (name) {
          line1 += `\\textbf{${name}}`
        }

        if (location) {
          line1 += `\\hfill ${location}`
        }

        if (degree) {
          line2 += degree
        }

        if (major) {
          line2 += degree ? ` ${major}` : `Degree in ${major}`
        }

        if (gpa) {
          line2 += ` \\textit{GPA: ${gpa}}`
        }

        if (graduationDate) {
          const gradLine = `Grad: ${graduationDate}`
          line2 += line2 ? ` \\hfill ${gradLine}` : gradLine
        }

        if (line1) {
          line1 += '\\\\'
        }

        if (line2) {
          line2 += '\\\\'
        }

        line2.trim()

        return stripIndent`
          ${line1}
          ${line2}
          \\vspace{1mm}
        `
      })}
  `
}

function generateExperienceSection(jobs) {
  if (!jobs) {
    return ''
  }

  return source`
    %==== Experience ====%
    \\header{Experience}
    \\vspace{1mm}

    ${jobs.map((job) => {
      const { name, title, location, startDate, endDate, duties } = job

      let line1 = ''
      let line2 = ''
      let dutyLines = ''

      if (name) {
        line1 += `\\textbf{${name}}`
      }

      if (location) {
        line1 += ` \\hfill ${location}`
      }

      if (title) {
        line2 += `\\textit{${title}}`
      }

      if (startDate && endDate) {
        line2 += ` \\hfill ${startDate} | ${endDate}`
      } else if (startDate) {
        line2 += ` \\hfill ${startDate} | Present`
      } else if (endDate) {
        line2 += ` \\hfill ${endDate}`
      }

      if (line1) line1 += '\\\\'
      if (line2) line2 += '\\\\'

      if (duties) {
        dutyLines = source`
          \\vspace{-1mm}
          \\begin{itemize} \\itemsep 1pt
            ${duties.map(duty => `\\item ${duty}`)}
          \\end{itemize}
        `
      }

      return stripIndent`
        ${line1}
        ${line2}
        ${dutyLines}
      `
    })}
  `
}

function generateSkillsSection(skills) {
  if (!skills) {
    return ''
  }

  return source`
    \\header{Skills}
    \\begin{tabular}{ l l }
      ${skills.map(skill => `${skill.name}: & ${skill.details} \\\\`)}
    \\end{tabular}
  `
}

function generateProjectsSection(projects) {
  if (!projects) {
    return ''
  }

  return source`
    \\header{Projects}
    ${projects.map((project) => {
      if (Object.keys(project) === 0) {
        return ''
      }

      const { name, description, technologies, link } = project

      let line1 = ''
      let line2 = description || ''

      if (name) {
        line1 += `{\\textbf{${name}}`
      }

      if (technologies) {
        line1 += ` \\sl ${technologies}} `
      }

      if (link) {
        line1 += `\\hfill ${link}`
      }

      if (line1) {
        line1 += '\\\\'
      }

      if (line2) {
        line2 += '\\\\'
      }

      return stripIndent`
        ${line1}
        ${line2}
        \\vspace*{2mm}
      `
    })}
  `
}

function generateResumeDefinitions() {
  return stripIndent`
    %\\renewcommand{\\encodingdefault}{cg}
    %\\renewcommand{\\rmdefault}{lgrcmr}

    \\def\\bull{\\vrule height 0.8ex width .7ex depth -.1ex }

    % DEFINITIONS FOR RESUME %%%%%%%%%%%%%%%%%%%%%%%

    \\newcommand{\\area} [2] {
        \\vspace*{-9pt}
        \\begin{verse}
            \\textbf{#1}   #2
        \\end{verse}
    }

    \\newcommand{\\lineunder} {
        \\vspace*{-8pt} \\\\
        \\hspace*{-18pt} \\hrulefill \\\\
    }

    \\newcommand{\\header} [1] {
        {\\hspace*{-18pt}\\vspace*{6pt} \\textsc{#1}}
        \\vspace*{-6pt} \\lineunder
    }

    \\newcommand{\\employer} [3] {
        { \\textbf{#1} (#2)\\\\ \\underline{\\textbf{\\emph{#3}}}\\\\  }
    }

    \\newcommand{\\contact} [3] {
        \\vspace*{-10pt}
        \\begin{center}
            {\\Huge \\scshape {#1}}\\\\
            #2 \\\\ #3
        \\end{center}
        \\vspace*{-8pt}
    }

    \\newenvironment{achievements}{
        \\begin{list}
            {$\\bullet$}{\\topsep 0pt \\itemsep -2pt}}{\\vspace*{4pt}
        \\end{list}
    }

    \\newcommand{\\schoolwithcourses} [4] {
        \\textbf{#1} #2 $\\bullet$ #3\\\\
        #4 \\\\
        \\vspace*{5pt}
    }

    \\newcommand{\\school} [4] {
        \\textbf{#1} #2 $\\bullet$ #3\\\\
        #4 \\\\
    }
        % END RESUME DEFINITIONS %%%%%%%%%%%%%%%%%%%%%%%
  `
}

module.exports = template3
<|endoftext|>


<commit_before>module.exports = function(grunt) {
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
        concat: {
            options: {
                // define a string to put between each file in the concatenated output
                separator: ';'
            },
            dist: {
                // the files to concatenate
                src: ['src/js/**/*.js'],
                // the location of the resulting JS file
                dest: '<%= pkg.name %>.js'
            }
        },
        karma: {
            unit: {
                configFile: 'karma.conf.js',
                options: {
                    files: ['test/lib/**/*.js', 'src/js/**/*.js', 'test/unit/**/*.js']
                },
                background: true
            },
            travis: {
                configFile: 'karma.conf.js',
                singleRun: true,
                browsers: ['PhantomJS']
            }
        },
        watch: {
            karma: {
                files: ['test/lib/**/*.js', 'src/js/**/*.js', 'test/unit/**/*.js'],
                tasks: ['karma:unit:run']
            }
        }
    });

    grunt.loadNpmTasks('grunt-contrib-watch');
    grunt.loadNpmTasks('grunt-karma');
    grunt.loadNpmTasks('grunt-contrib-jshint');
    grunt.loadNpmTasks('grunt-contrib-concat');

    //Default task
    grunt.registerTask('devmode', ['karma:unit', 'watch']);
    grunt.registerTask('test', ['karma:travis', 'watch']);

}<commit_msg>Update<commit_after>module.exports = function(grunt) {
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
        concat: {
            options: {
                // define a string to put between each file in the concatenated output
                separator: ';'
            },
            dist: {
                // the files to concatenate
                src: ['src/js/**/*.js'],
                // the location of the resulting JS file
                dest: '<%= pkg.name %>.js'
            }
        },
        // Test settings
        karma: {
            options: {
                configFile: 'karma.conf.js',
                browsers: ['PhantomJS']
            },
            unit: {
                singleRun: true,
                options: {
                    reporters: ['dots', 'coverage']
                }
            },
            server: {
                autoWatch: true
            }
        }
    });

    grunt.loadNpmTasks('grunt-contrib-watch');
    grunt.loadNpmTasks('grunt-karma');
    grunt.loadNpmTasks('grunt-contrib-jshint');
    grunt.loadNpmTasks('grunt-contrib-concat');

    //Default task
    grunt.registerTask('devmode', ['karma:unit', 'watch']);
    grunt.registerTask('test', ['karma:travis', 'watch']);

    grunt.registerTask('test', [
        'karma:unit'
    ]);

    grunt.registerTask('default', [
        'test'
    ]);

}<|endoftext|>


<commit_before>const { stripIndent, source } = require('common-tags')

function template4({ profile, schools, jobs, projects, skills }) {
  return stripIndent`
    ${generateCommentHeader()}

    \\begin{document}
    ${generateProfileSection(profile)}
    ${generateEducationSection(schools)}
    ${generateExperienceSection(jobs)}
    \\cvsection{Skills}
    \\begin{cventries}
    \\cventry
      {}
      {\\def\\arraystretch{1.15}{\\begin{tabular}{ l l }
        Languages: & {\\skill{ Java, JavaScript, Ruby, Python, HTML, CSS}} \\\\
        Libraries: & {\\skill{ Node.js, Koa, Express, React, Redux, Bootstrap, Materialize}} \\\\
      \\end{tabular}}}
      {}
      {}
      {}
    \\end{cventries}

    \\vspace{-7mm}

    \\cvsection{Projects}
    \\begin{cventries}
      \\cventry
        {A webapp for generating LaTeX resumes from form data (including this one).}
        {LaTeX Resume Generator}
        {Node.js, Koa, React, Redux}
        {https://latexresu.me}
        {}

      \\vspace{-5mm}

      \\cventry
        {A modern speedcubing app with a scrambler, timer, and analyzer for cubing statistics.}
        {Flow Timer}
        {Node.js, Koa, React, Redux}
        {https://flowtimer.com}
        {}

      \\vspace{-5mm}

      \\cventry
        {A web app that lets you view a collage of images/videos from a subreddit.}
        {Reddit Image Scraper}
        {Ruby, Sinatra}
        {https://reddit-scraper.herokuapp.com}
        {}

      \\vspace{-5mm}

      \\cventry
        {A cognitive, anagram-recognition game where the player must quickly find the answer.}
        {Anagrams}
        {HTML, CSS, JavaScript}
        {A cognitive, anagram-recognition game where the player must quickly find the answer.}
        {}
    \\end{cventries}

    \\end{document}
  `
}

function generateProfileSection(profile) {
  if (!profile) {
    return ''
  }

  const { fullName, email, phoneNumber, address, link } = profile

  let nameLine = ''

  if (fullName) {
    const names = fullName.split(' ')
    let nameStart = ''
    let nameEnd = ''

    if (names.length === 1) {
      nameStart = names[0]
    } else if (names.length === 2) {
      nameStart = names[0]
      nameEnd = names[1]
    } else {
      nameStart = names.slice(0, names.length - 1).join(' ')
      nameEnd = names[names.length - 1]
    }

    nameLine = `\\headerfirstnamestyle{${nameStart}} \\headerlastnamestyle{${nameEnd}} \\\\`
  }

  const emailLine = email ? `{\\faEnvelope\\ ${email}}` : ''
  const phoneLine = phoneNumber ? `{\\faMobile\\ ${phoneNumber}}` : ''
  const addressLine = address ? `{\\faMapMarker\\ ${address}}` : ''
  const linkLine = link ? `{\\faLink\\ ${link}}` : ''
  const info = [emailLine, phoneLine, addressLine, linkLine].filter(Boolean).join(' | ')

  return stripIndent`
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %     Profile
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    \\begin{center}
    ${nameLine}
    \\vspace{2mm}
    ${info}
    \\end{center}
  `
}

function generateEducationSection(schools) {
  if (!schools) {
    return ''
  }

  return source`
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %     Education
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    \\cvsection{Education}
    \\begin{cventries}
    ${schools.map((school) => {
      const { name, location, degree, major, gpa, graduationDate } = school

      let degreeLine = ''

      if (degree && major) {
        degreeLine = `${degree} in ${major}`
      } else if (degree || major) {
        degreeLine = degree || major
      }

      return stripIndent`
        \\cventry
          {${degreeLine}}
          {${name || ''}}
          {${location || ''}}
          {${graduationDate || ''}}
          {${gpa ? `GPA: ${gpa}` : ''} \\vspace{1mm}}
      `
    })}
    \\end{cventries}
  `
}

function generateExperienceSection(jobs) {
  if (!jobs) {
    return ''
  }

  return source`
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %     Experience
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    \\cvsection{Experience}
    \\begin{cventries}
    ${jobs.map((job) => {
      const { name, title, location, startDate, endDate, duties } = job

      let dateRange = ''
      let dutyLines = ''

      if (startDate && endDate) {
        dateRange = `${startDate} – ${endDate}`
      } else if (startDate) {
        dateRange = `${startDate} – Present`
      } else {
        dateRange = endDate
      }

      if (duties) {
        dutyLines = source`
          \\begin{cvitems}
            ${duties.map(duty => `\\item {${duty}}`)}
          \\end{cvitems}
        `
      }

      return stripIndent`
        \\cventry
          {${title || ''}}
          {${name || ''}}
          {${location || ''}}
          {${dateRange}}
          {${dutyLines}}
      `
    })}
    \\end{cventries}
  `
}

function generateCommentHeader() {
  return stripIndent`
    %!TEX TS-program = xelatex
    %!TEX encoding = UTF-8 Unicode
    % Awesome CV LaTeX Template
    %
    % This template has been downloaded from:
    % https://github.com/posquit0/Awesome-CV
    %
    % Author:
    % Claud D. Park <posquit0.bj@gmail.com>
    % http://www.posquit0.com
    %
    % Template license:
    % CC BY-SA 4.0 (https://creativecommons.org/licenses/by-sa/4.0/)
    %

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %     Configuration
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %%% Themes: Awesome-CV
    \\documentclass[11pt, a4paper]{awesome-cv}

    %%% Override a directory location for fonts(default: 'fonts/')
    \\fontdir[fonts/]

    %%% Configure a directory location for sections
    \\newcommand*{\\sectiondir}{resume/}

    %%% Override color
    % Awesome Colors: awesome-emerald, awesome-skyblue, awesome-red, awesome-pink, awesome-orange
    %                 awesome-nephritis, awesome-concrete, awesome-darknight
    %% Color for highlight
    % Define your custom color if you don't like awesome colors
    \\colorlet{awesome}{awesome-red}
    %\\definecolor{awesome}{HTML}{CA63A8}
    %% Colors for text
    %\\definecolor{darktext}{HTML}{414141}
    %\\definecolor{text}{HTML}{414141}
    %\\definecolor{graytext}{HTML}{414141}
    %\\definecolor{lighttext}{HTML}{414141}

    %%% Override a separator for social informations in header(default: ' | ')
    %\\headersocialsep[\\quad\\textbar\\quad]
  `
}

module.exports = template4
<commit_msg>add skills section to template4<commit_after>const { stripIndent, source } = require('common-tags')

function template4({ profile, schools, jobs, projects, skills }) {
  return stripIndent`
    ${generateCommentHeader()}

    \\begin{document}
    ${generateProfileSection(profile)}
    ${generateEducationSection(schools)}
    ${generateExperienceSection(jobs)}
    ${generateSkillsSection(skills)}
    \\cvsection{Projects}
    \\begin{cventries}
      \\cventry
        {A webapp for generating LaTeX resumes from form data (including this one).}
        {LaTeX Resume Generator}
        {Node.js, Koa, React, Redux}
        {https://latexresu.me}
        {}

      \\vspace{-5mm}

      \\cventry
        {A modern speedcubing app with a scrambler, timer, and analyzer for cubing statistics.}
        {Flow Timer}
        {Node.js, Koa, React, Redux}
        {https://flowtimer.com}
        {}

      \\vspace{-5mm}

      \\cventry
        {A web app that lets you view a collage of images/videos from a subreddit.}
        {Reddit Image Scraper}
        {Ruby, Sinatra}
        {https://reddit-scraper.herokuapp.com}
        {}

      \\vspace{-5mm}

      \\cventry
        {A cognitive, anagram-recognition game where the player must quickly find the answer.}
        {Anagrams}
        {HTML, CSS, JavaScript}
        {A cognitive, anagram-recognition game where the player must quickly find the answer.}
        {}
    \\end{cventries}

    \\end{document}
  `
}

function generateProfileSection(profile) {
  if (!profile) {
    return ''
  }

  const { fullName, email, phoneNumber, address, link } = profile

  let nameLine = ''

  if (fullName) {
    const names = fullName.split(' ')
    let nameStart = ''
    let nameEnd = ''

    if (names.length === 1) {
      nameStart = names[0]
    } else if (names.length === 2) {
      nameStart = names[0]
      nameEnd = names[1]
    } else {
      nameStart = names.slice(0, names.length - 1).join(' ')
      nameEnd = names[names.length - 1]
    }

    nameLine = `\\headerfirstnamestyle{${nameStart}} \\headerlastnamestyle{${nameEnd}} \\\\`
  }

  const emailLine = email ? `{\\faEnvelope\\ ${email}}` : ''
  const phoneLine = phoneNumber ? `{\\faMobile\\ ${phoneNumber}}` : ''
  const addressLine = address ? `{\\faMapMarker\\ ${address}}` : ''
  const linkLine = link ? `{\\faLink\\ ${link}}` : ''
  const info = [emailLine, phoneLine, addressLine, linkLine].filter(Boolean).join(' | ')

  return stripIndent`
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %     Profile
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    \\begin{center}
    ${nameLine}
    \\vspace{2mm}
    ${info}
    \\end{center}
  `
}

function generateEducationSection(schools) {
  if (!schools) {
    return ''
  }

  return source`
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %     Education
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    \\cvsection{Education}
    \\begin{cventries}
    ${schools.map((school) => {
      const { name, location, degree, major, gpa, graduationDate } = school

      let degreeLine = ''

      if (degree && major) {
        degreeLine = `${degree} in ${major}`
      } else if (degree || major) {
        degreeLine = degree || major
      }

      return stripIndent`
        \\cventry
          {${degreeLine}}
          {${name || ''}}
          {${location || ''}}
          {${graduationDate || ''}}
          {${gpa ? `GPA: ${gpa}` : ''} \\vspace{1mm}}
      `
    })}
    \\end{cventries}
  `
}

function generateExperienceSection(jobs) {
  if (!jobs) {
    return ''
  }

  return source`
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %     Experience
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    \\cvsection{Experience}
    \\begin{cventries}
    ${jobs.map((job) => {
      const { name, title, location, startDate, endDate, duties } = job

      let dateRange = ''
      let dutyLines = ''

      if (startDate && endDate) {
        dateRange = `${startDate} – ${endDate}`
      } else if (startDate) {
        dateRange = `${startDate} – Present`
      } else {
        dateRange = endDate
      }

      if (duties) {
        dutyLines = source`
          \\begin{cvitems}
            ${duties.map(duty => `\\item {${duty}}`)}
          \\end{cvitems}
        `
      }

      return stripIndent`
        \\cventry
          {${title || ''}}
          {${name || ''}}
          {${location || ''}}
          {${dateRange}}
          {${dutyLines}}
      `
    })}
    \\end{cventries}
  `
}

function generateSkillsSection(skills) {
  if (!skills) {
    return ''
  }

  return source`
      \\cvsection{Skills}
      \\begin{cventries}
      \\cventry
        {}
        {\\def\\arraystretch{1.15}{\\begin{tabular}{ l l }
          ${skills.map((skill) => {
            const { name, details } = skill
            const nameLine = name ? `${name}: ` : ''
            const detailsLine = `{\\skill{ ${details || ''}}}`

            return `${nameLine} & ${detailsLine} \\\\`
          })}
        \\end{tabular}}}
        {}
        {}
        {}
      \\end{cventries}

      \\vspace{-7mm}
  `
}

function generateCommentHeader() {
  return stripIndent`
    %!TEX TS-program = xelatex
    %!TEX encoding = UTF-8 Unicode
    % Awesome CV LaTeX Template
    %
    % This template has been downloaded from:
    % https://github.com/posquit0/Awesome-CV
    %
    % Author:
    % Claud D. Park <posquit0.bj@gmail.com>
    % http://www.posquit0.com
    %
    % Template license:
    % CC BY-SA 4.0 (https://creativecommons.org/licenses/by-sa/4.0/)
    %

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %     Configuration
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %%% Themes: Awesome-CV
    \\documentclass[11pt, a4paper]{awesome-cv}

    %%% Override a directory location for fonts(default: 'fonts/')
    \\fontdir[fonts/]

    %%% Configure a directory location for sections
    \\newcommand*{\\sectiondir}{resume/}

    %%% Override color
    % Awesome Colors: awesome-emerald, awesome-skyblue, awesome-red, awesome-pink, awesome-orange
    %                 awesome-nephritis, awesome-concrete, awesome-darknight
    %% Color for highlight
    % Define your custom color if you don't like awesome colors
    \\colorlet{awesome}{awesome-red}
    %\\definecolor{awesome}{HTML}{CA63A8}
    %% Colors for text
    %\\definecolor{darktext}{HTML}{414141}
    %\\definecolor{text}{HTML}{414141}
    %\\definecolor{graytext}{HTML}{414141}
    %\\definecolor{lighttext}{HTML}{414141}

    %%% Override a separator for social informations in header(default: ' | ')
    %\\headersocialsep[\\quad\\textbar\\quad]
  `
}

module.exports = template4
<|endoftext|>