From bcbe14c3f26bc407604e5fc5bc864356f2d9f1b6 Mon Sep 17 00:00:00 2001 From: Nishad Bakshi Date: Tue, 19 Jul 2016 13:55:37 +1000 Subject: [PATCH 1/2] Initial conversion of default template --- app/templates/_babelrc | 4 + app/templates/_jshintrc | 3 +- app/templates/bower.json | 41 +- app/templates/gulp.config.js | 24 +- app/templates/gulpfile.js | 868 +++--------------- app/templates/jsconfig.json | 8 + app/templates/karma.conf.js | 130 ++- app/templates/package.json | 118 ++- app/templates/spec.bundle.js | 29 + app/templates/ui/app/app.component.js | 8 + app/templates/ui/app/app.html | 4 + app/templates/ui/app/app.js | 37 +- app/templates/ui/app/common/common.js | 11 + .../ui/app/common/root/root.component.js | 12 + .../ui/app/common/root/root.controller.js | 14 + .../ui/app/{ => common}/root/root.html | 22 +- app/templates/ui/app/common/root/root.js | 12 + app/templates/ui/app/components/components.js | 23 + .../app/components/create/create.component.js | 11 + .../components/create/create.controller.js | 153 +++ .../create/create.controller.spec.js | 97 ++ .../app/{ => components}/create/create.html | 46 +- .../ui/app/components/create/create.js | 16 + .../app/components/detail/detail.component.js | 14 + .../components/detail/detail.controller.js | 101 ++ .../detail/detail.controller.spec.js | 75 ++ .../app/{ => components}/detail/detail.html | 18 +- .../ui/app/components/detail/detail.js | 19 + .../detail/similar-component.html} | 4 +- .../components/detail/similar.component.js | 29 + .../ui/app/components/detail/similar.js | 10 + .../ui/app/components/error/error.js | 16 + .../error/errorInterceptor.service.js | 30 + .../app/{ => components}/landing/landing.html | 0 .../login/login-component.html} | 10 +- .../components/login/login-full.component.js | 12 + .../components/login/login-full.controller.js | 24 + .../{ => components}/login/login-full.html | 2 +- .../{ => components}/login/login-modal.html | 0 .../app/components/login/login.component.js | 16 + .../app/components/login/login.controller.js | 40 + .../ui/app/components/login/login.js | 26 + .../ui/app/components/login/login.service.js | 187 ++++ .../login/login.service.spec.js | 43 +- .../login/loginInterceptor.service.js | 64 ++ .../message-board-component.html | 4 + .../message-board/message-board.component.js | 35 + .../components/message-board/message-board.js | 13 + .../message-board/message-board.service.js | 28 + .../search/search-results.html | 0 .../app/components/search/search.component.js | 11 + .../components/search/search.controller.js | 36 + .../search/search.controller.spec.js | 201 ++++ .../ui/app/components/search/search.html | 23 + .../ui/app/components/search/search.js | 19 + .../components/search/snippet.component.js | 30 + .../app/{ => components}/search/snippet.html | 6 +- .../ui/app/components/search/snippet.js | 11 + .../app/components/user/mlUser.component.js | 28 + .../app/components/user/profile.component.js | 12 + .../app/components/user/profile.controller.js | 68 ++ .../user/profile.controller.spec.js | 55 +- .../ui/app/{ => components}/user/profile.html | 22 +- .../app/components/user/user-component.html | 16 + app/templates/ui/app/components/user/user.js | 21 + .../ui/app/components/user/user.service.js | 63 ++ .../user/user.service.spec.js | 25 +- .../ui/app/create/create.controller.js | 78 -- .../ui/app/create/create.controller.spec.js | 59 -- app/templates/ui/app/create/create.module.js | 5 - .../ui/app/detail/detail.controller.js | 46 - .../ui/app/detail/detail.controller.spec.js | 37 - app/templates/ui/app/detail/detail.module.js | 8 - .../ui/app/detail/similar-directive.js | 32 - app/templates/ui/app/detail/similar.module.js | 6 - app/templates/ui/app/error/error.module.js | 4 - .../ui/app/error/errorInterceptor.service.js | 36 - .../ui/app/login/login.controller.js | 64 -- app/templates/ui/app/login/login.directive.js | 23 - app/templates/ui/app/login/login.module.js | 4 - app/templates/ui/app/login/login.service.js | 201 ---- .../ui/app/login/loginInterceptor.service.js | 69 -- .../app/message-board/message-board-dir.html | 4 - .../message-board/message-board.directive.js | 45 - .../app/message-board/message-board.module.js | 5 - .../message-board/message-board.service.js | 31 - app/templates/ui/app/root/root.controller.js | 18 - app/templates/ui/app/root/root.module.js | 5 - app/templates/ui/app/route/routes.js | 71 +- .../ui/app/search/search.controller.js | 30 - .../ui/app/search/search.controller.spec.js | 58 -- app/templates/ui/app/search/search.html | 23 - app/templates/ui/app/search/search.module.js | 5 - .../ui/app/search/snippet.directive.js | 38 - app/templates/ui/app/search/snippet.module.js | 5 - .../ui/app/user/profile.controller.js | 85 -- app/templates/ui/app/user/user-dir.html | 16 - app/templates/ui/app/user/user.directive.js | 38 - app/templates/ui/app/user/user.module.js | 5 - app/templates/ui/app/user/user.service.js | 52 -- app/templates/ui/index.html | 38 +- app/templates/ui/robots.txt | 3 - app/templates/ui/specs.html | 64 -- app/templates/ui/styles/default.less | 4 +- app/templates/ui/styles/main.less | 8 +- app/templates/webpack/webpack.config.js | 77 ++ app/templates/webpack/webpack.dev.config.js | 19 + app/templates/webpack/webpack.dist.config.js | 26 + 108 files changed, 2338 insertions(+), 2265 deletions(-) create mode 100644 app/templates/_babelrc create mode 100644 app/templates/jsconfig.json create mode 100644 app/templates/spec.bundle.js create mode 100644 app/templates/ui/app/app.component.js create mode 100644 app/templates/ui/app/app.html create mode 100644 app/templates/ui/app/common/common.js create mode 100644 app/templates/ui/app/common/root/root.component.js create mode 100644 app/templates/ui/app/common/root/root.controller.js rename app/templates/ui/app/{ => common}/root/root.html (73%) create mode 100644 app/templates/ui/app/common/root/root.js create mode 100644 app/templates/ui/app/components/components.js create mode 100644 app/templates/ui/app/components/create/create.component.js create mode 100644 app/templates/ui/app/components/create/create.controller.js create mode 100644 app/templates/ui/app/components/create/create.controller.spec.js rename app/templates/ui/app/{ => components}/create/create.html (60%) create mode 100644 app/templates/ui/app/components/create/create.js create mode 100644 app/templates/ui/app/components/detail/detail.component.js create mode 100644 app/templates/ui/app/components/detail/detail.controller.js create mode 100644 app/templates/ui/app/components/detail/detail.controller.spec.js rename app/templates/ui/app/{ => components}/detail/detail.html (50%) create mode 100644 app/templates/ui/app/components/detail/detail.js rename app/templates/ui/app/{detail/similar-directive.html => components/detail/similar-component.html} (74%) create mode 100644 app/templates/ui/app/components/detail/similar.component.js create mode 100644 app/templates/ui/app/components/detail/similar.js create mode 100644 app/templates/ui/app/components/error/error.js create mode 100644 app/templates/ui/app/components/error/errorInterceptor.service.js rename app/templates/ui/app/{ => components}/landing/landing.html (100%) rename app/templates/ui/app/{login/login-dir.html => components/login/login-component.html} (60%) create mode 100644 app/templates/ui/app/components/login/login-full.component.js create mode 100644 app/templates/ui/app/components/login/login-full.controller.js rename app/templates/ui/app/{ => components}/login/login-full.html (55%) rename app/templates/ui/app/{ => components}/login/login-modal.html (100%) create mode 100644 app/templates/ui/app/components/login/login.component.js create mode 100644 app/templates/ui/app/components/login/login.controller.js create mode 100644 app/templates/ui/app/components/login/login.js create mode 100644 app/templates/ui/app/components/login/login.service.js rename app/templates/ui/app/{ => components}/login/login.service.spec.js (77%) create mode 100644 app/templates/ui/app/components/login/loginInterceptor.service.js create mode 100644 app/templates/ui/app/components/message-board/message-board-component.html create mode 100644 app/templates/ui/app/components/message-board/message-board.component.js create mode 100644 app/templates/ui/app/components/message-board/message-board.js create mode 100644 app/templates/ui/app/components/message-board/message-board.service.js rename app/templates/ui/app/{ => components}/search/search-results.html (100%) create mode 100644 app/templates/ui/app/components/search/search.component.js create mode 100644 app/templates/ui/app/components/search/search.controller.js create mode 100644 app/templates/ui/app/components/search/search.controller.spec.js create mode 100644 app/templates/ui/app/components/search/search.html create mode 100644 app/templates/ui/app/components/search/search.js create mode 100644 app/templates/ui/app/components/search/snippet.component.js rename app/templates/ui/app/{ => components}/search/snippet.html (53%) create mode 100644 app/templates/ui/app/components/search/snippet.js create mode 100644 app/templates/ui/app/components/user/mlUser.component.js create mode 100644 app/templates/ui/app/components/user/profile.component.js create mode 100644 app/templates/ui/app/components/user/profile.controller.js rename app/templates/ui/app/{ => components}/user/profile.controller.spec.js (58%) rename app/templates/ui/app/{ => components}/user/profile.html (63%) create mode 100644 app/templates/ui/app/components/user/user-component.html create mode 100644 app/templates/ui/app/components/user/user.js create mode 100644 app/templates/ui/app/components/user/user.service.js rename app/templates/ui/app/{ => components}/user/user.service.spec.js (77%) delete mode 100644 app/templates/ui/app/create/create.controller.js delete mode 100644 app/templates/ui/app/create/create.controller.spec.js delete mode 100644 app/templates/ui/app/create/create.module.js delete mode 100644 app/templates/ui/app/detail/detail.controller.js delete mode 100644 app/templates/ui/app/detail/detail.controller.spec.js delete mode 100644 app/templates/ui/app/detail/detail.module.js delete mode 100644 app/templates/ui/app/detail/similar-directive.js delete mode 100644 app/templates/ui/app/detail/similar.module.js delete mode 100644 app/templates/ui/app/error/error.module.js delete mode 100644 app/templates/ui/app/error/errorInterceptor.service.js delete mode 100644 app/templates/ui/app/login/login.controller.js delete mode 100644 app/templates/ui/app/login/login.directive.js delete mode 100644 app/templates/ui/app/login/login.module.js delete mode 100644 app/templates/ui/app/login/login.service.js delete mode 100644 app/templates/ui/app/login/loginInterceptor.service.js delete mode 100644 app/templates/ui/app/message-board/message-board-dir.html delete mode 100644 app/templates/ui/app/message-board/message-board.directive.js delete mode 100644 app/templates/ui/app/message-board/message-board.module.js delete mode 100644 app/templates/ui/app/message-board/message-board.service.js delete mode 100644 app/templates/ui/app/root/root.controller.js delete mode 100644 app/templates/ui/app/root/root.module.js delete mode 100644 app/templates/ui/app/search/search.controller.js delete mode 100644 app/templates/ui/app/search/search.controller.spec.js delete mode 100644 app/templates/ui/app/search/search.html delete mode 100644 app/templates/ui/app/search/search.module.js delete mode 100644 app/templates/ui/app/search/snippet.directive.js delete mode 100644 app/templates/ui/app/search/snippet.module.js delete mode 100644 app/templates/ui/app/user/profile.controller.js delete mode 100644 app/templates/ui/app/user/user-dir.html delete mode 100644 app/templates/ui/app/user/user.directive.js delete mode 100644 app/templates/ui/app/user/user.module.js delete mode 100644 app/templates/ui/app/user/user.service.js delete mode 100644 app/templates/ui/robots.txt delete mode 100644 app/templates/ui/specs.html create mode 100644 app/templates/webpack/webpack.config.js create mode 100644 app/templates/webpack/webpack.dev.config.js create mode 100644 app/templates/webpack/webpack.dist.config.js diff --git a/app/templates/_babelrc b/app/templates/_babelrc new file mode 100644 index 00000000..c71d3666 --- /dev/null +++ b/app/templates/_babelrc @@ -0,0 +1,4 @@ +{ + "plugins": ["transform-runtime"], + "presets": ["es2015", "stage-0"] +} diff --git a/app/templates/_jshintrc b/app/templates/_jshintrc index d80a4fff..d4ce9de7 100644 --- a/app/templates/_jshintrc +++ b/app/templates/_jshintrc @@ -1,6 +1,7 @@ { "browser": true, "camelcase": true, + "esversion": 6, "curly": true, "devel": true, "eqeqeq": true, @@ -11,7 +12,7 @@ "quotmark": "single", "undef": true, "unused": "vars", - "strict": true, + "strict": "implied", "trailing": true, "globals": { "angular": false, diff --git a/app/templates/bower.json b/app/templates/bower.json index 4b604d2c..d253c8a8 100644 --- a/app/templates/bower.json +++ b/app/templates/bower.json @@ -1,42 +1,7 @@ { "name": "@sample-app-name", "version": "0.0.0", - "dependencies": { - "jquery": "~2.1.4", - "angular": "~1.4.4", - "angular-bootstrap": "^1.1", - "angular-cookies": "~1.4.4", - "angular-highlightjs": "~0.4.3", - "angular-mocks": "~1.4.4", - "angular-ui-router": "~0.2.15", - "angular-ui-tinymce": "~0.0.9", - "angular-animate": "~1.4.4", - "ngtoast": "^2.0.0", - "angular-x2js": "https://github.com/janmichaelyu/angular-x2js.git", - "bootstrap": "~3.3.5", - "font-awesome": "~4.6.0", - "highlightjs":"~8.7.0", - "lodash": "~3.10.1", - "ml-search-ng": "~0.2.0", - "ml-utils": "withjam/ml-utils", - "ng-json-explorer": "8c2a0f9104", - "vkbeautify-wrapper": "*", - "highcharts": "^4.2", - "angular-google-maps": "2.3.2", - "tinymce-dist": "4.3.12" - }, - "overrides": { - "angular-highlightjs": { - "dependencies": {"angular" : ">1.0.8", "highlightjs":"~8.7.0"} - } - }, - "devDependencies": { - "angular-mocks": "~1.4.4", - "sinon": "http://sinonjs.org/releases/sinon-1.16.1.js", - "bardjs": "~0.1.8" - }, - "private": true, - "resolutions": { - "angular": "~1.4.4" - } + "dependencies": {}, + "overrides": {}, + "devDependencies": {} } diff --git a/app/templates/gulp.config.js b/app/templates/gulp.config.js index fa67009b..286f87e9 100644 --- a/app/templates/gulp.config.js +++ b/app/templates/gulp.config.js @@ -12,20 +12,24 @@ module.exports = function() { var temp = './.tmp/'; var _ = require('lodash'); var wiredep = require('wiredep'); + var bower = { json: require('./bower.json'), directory: './bower_components/', ignorePath: '..' }; + var getWiredepDefaultOptions = function() { return { bowerJson: bower.json, directory: bower.directory, ignorePath: bower.ignorePath, - exclude: [ 'requirejs', 'angularjs', 'font-awesome.css' ] + exclude: ['requirejs', 'angularjs', 'font-awesome.css'] }; }; - var bowerFiles = wiredep(_.merge(getWiredepDefaultOptions(), { devDependencies: true })).js; + var bowerFiles = wiredep(_.merge(getWiredepDefaultOptions(), { + devDependencies: true + })).js; var nodeModules = 'node_modules'; var config = { @@ -94,7 +98,9 @@ module.exports = function() { /** * plato */ - plato: {js: clientApp + '**/*.js'}, + plato: { + js: clientApp + '**/*.js' + }, /** * browser sync @@ -183,9 +189,15 @@ module.exports = function() { // dir: report + 'coverage', reporters: [ // reporters not supporting the `file` property - {type: 'html', subdir: 'report-html'}, - {type: 'lcov', subdir: 'report-lcov'}, - {type: 'text-summary'} //, subdir: '.', file: 'text-summary.txt'} + { + type: 'html', + subdir: 'report-html' + }, { + type: 'lcov', + subdir: 'report-lcov' + }, { + type: 'text-summary' + } //, subdir: '.', file: 'text-summary.txt'} ] }, preprocessors: {} diff --git a/app/templates/gulpfile.js b/app/templates/gulpfile.js index 147a39a7..08de65d2 100644 --- a/app/templates/gulpfile.js +++ b/app/templates/gulpfile.js @@ -1,4 +1,4 @@ -/*jshint node: true */ +/*jshint node: true, strict: true*/ 'use strict'; @@ -12,6 +12,12 @@ var gulp = require('gulp'); var path = require('path'); var username = require('username'); +var webpack = require('webpack'); +var webpackDevMiddelware = require('webpack-dev-middleware'); +var webpachHotMiddelware = require('webpack-hot-middleware'); +var colorsSupported = require('supports-color'); +var historyApiFallback = require('connect-history-api-fallback'); + /* jshint ignore:start */ var _ = require('lodash'); var $ = require('gulp-load-plugins')({ @@ -19,14 +25,6 @@ var $ = require('gulp-load-plugins')({ }); /* jshint ignore:end */ -var _s = require('underscore.string'), - q = require('q'), - spawn = require('child_process').spawn; - -var encoding = { - encoding: 'utf8' -}; - /** * yargs variables can be passed in to alter the behavior, when present. * Example: gulp serve-dev @@ -76,188 +74,115 @@ gulp.task('plato', function(done) { startPlatoVisualizer(done); }); -/** - * Compile less to css - * @return {Stream} - */ -gulp.task('styles', ['clean-styles'], function() { - log('Compiling Less --> CSS'); - - var less = $.less().on('error', function(e) { - $.util.log($.util.colors.red(e)); - this.emit('end', e); - }); - - return gulp - .src(config.mainLess) - .pipe($.plumber()) // exit gracefully if something fails after this - .pipe(less) - .pipe($.autoprefixer({ - browsers: ['last 2 version', '> 5%'] - })) - .pipe(gulp.dest(config.temp)) - .pipe($.if(args.verbose, $.print())); -}); - -/** - * Copy fonts - * @return {Stream} - */ -gulp.task('fonts', ['clean-fonts'], function() { - log('Copying fonts'); - - return gulp - .src(config.fonts) - .pipe(gulp.dest(config.client + 'fonts')) - .pipe($.if(args.verbose, $.print())) - .pipe(gulp.dest(config.build + 'fonts')) - .pipe($.if(args.verbose, $.print())); -}); +var root = 'ui'; +// helper method for resolving paths +var resolveToApp = function(glob) { + return path.join(root, 'app', glob); // app/{glob} +}; -/** -- * Copy static data like lang.json -- * @return {Stream} -- */ -gulp.task('statics', function() { - log('Copying statics'); +var resolveToComponents = function(glob) { + return path.join(root, 'app/components', glob); // app/components/{glob} +}; - return gulp - .src(config.staticdata) - .pipe(gulp.dest(config.build)) - .pipe($.if(args.verbose, $.print())); -}); +// map of all paths +var paths = { + js: resolveToComponents('**/*!(.spec.js).js'), // exclude spec files + html: [ + resolveToApp('**/*.html'), + path.join(root, 'index.html') + ], + entry: [ + 'babel-polyfill', + path.join(__dirname, root, 'app/app.js') + ], + output: root, + blankTemplates: path.join(__dirname, 'generator', 'component/**/*.**'), + dest: path.join(__dirname, 'dist') +}; -/** - * Copy tinymce files - * @return {Stream} - */ -gulp.task('tinymce', function() { - log('Copying tinymce files'); - return gulp - .src(config.tinymce, { - base: './bower_components/tinymce-dist' - }) - .pipe(gulp.dest(config.build + 'js/')) - .pipe($.if(args.verbose, $.print())); -}); +// use webpack.config.js to build modules +gulp.task('build', ['clean', 'test'], function(cb) { + var config = require('./webpack/webpack.dist.config'); + config.entry.app = paths.entry; -/** - * Compress images - * @return {Stream} - */ -gulp.task('images', ['clean-images'], function() { - log('Compressing and copying images'); + webpack(config, function(err, stats) { + if (err) { + throw new $.util.PluginError('webpack', err); + } - return gulp - .src(config.images) - .pipe($.imagemin({ - optimizationLevel: 4 - })) - .pipe(gulp.dest(config.build + 'images')) - .pipe($.if(args.verbose, $.print())); -}); + $.util.log('[webpack]', stats.toString({ + colors: colorsSupported, + chunks: false, + errorDetails: true + })); -/** - * Watch for less file changes - * @return {Stream} - */ -gulp.task('less-watcher', function() { - return gulp.watch([config.less], ['styles']); + cb(); + }); }); -/** - * Create $templateCache from the html templates - * @return {Stream} - */ -gulp.task('templatecache', ['clean-code'], function() { - log('Creating an AngularJS $templateCache'); - - return gulp - .src(config.htmltemplates) - .pipe($.if(args.verbose, $.bytediff.start())) - .pipe($.htmlmin()) - .pipe($.if(args.verbose, $.bytediff.stop(bytediffFormatter))) - .pipe($.angularTemplatecache( - config.templateCache.file, - config.templateCache.options - )) - .pipe(gulp.dest(config.temp)) - .pipe($.if(args.verbose, $.print())); -}); /** - * Wire-up the bower dependencies - * @return {Stream} + * Creates a sample local.json; can be used as model for dev.json and prod.json */ -gulp.task('wiredep', function() { - log('Wiring the bower dependencies into the html'); - - var wiredep = require('wiredep').stream; - var options = config.getWiredepDefaultOptions(); - - // Only include stubs if flag is enabled - var js = args.stubs ? [].concat(config.js, config.stubsjs) : config.js; +gulp.task('init-local', function() { + //copy from slushfile - config gulp - with modifications to use config instead + log('Creating local.json sample document with values drawn from gulp.config.js'); + try { + var configJSON = {}; + configJSON['ml-version'] = config.marklogic.version; + configJSON['ml-host'] = config.marklogic.host; + configJSON['ml-admin-user'] = config.marklogic.username; + configJSON['ml-admin-pass'] = config.marklogic.password; + configJSON['ml-app-user'] = config.marklogic.username; + configJSON['ml-app-pass'] = config.marklogic.password; + configJSON['ml-http-port'] = config.marklogic.httpPort; + configJSON['node-port'] = config.defaultPort; + + if (config.marklogic.version < 8) { + configJSON['ml-xcc-port'] = config.marklogic.xccPort; + } - return gulp - .src(config.index) - .pipe(wiredep(options)) - .pipe(inject(js, '', config.jsOrder)) - .pipe(gulp.dest(config.client)) - .pipe($.if(args.verbose, $.print())); + var configString = JSON.stringify(configJSON, null, 2) + '\n'; + fs.writeFileSync('local.json', configString, { + encoding: 'utf8' + }); + } catch (e) { + log('failed to write local.json: ' + e.message); + } }); /** - * Inject dependencies into index.html - * @return {Stream} + * Updates ecosystem.json */ -gulp.task('inject', ['wiredep', 'styles', 'templatecache'], function() { - log('Wire up css into the html, after files are ready'); - - return gulp - .src(config.index) - .pipe(inject(config.css)) - .pipe(gulp.dest(config.client)) - .pipe($.if(args.verbose, $.print())); -}); +gulp.task('add-deploy-target', function(done) { + log('Update ecosystem.json targets or create new ones!'); -/** - * Initialize config files for local environment - */ -gulp.task('init-local', function(done) { - init('local', done); -}); + var ecosystem = 'ecosystem.json'; -/** - * Initialize config files for dev environment - */ -gulp.task('init-dev', function(done) { - init('dev', done); -}); + if (!fs.existsSync(ecosystem)) { + try { + var configJSON = {}; + configJSON.deploy = {}; -/** - * Initialize config files for prod environment - */ -gulp.task('init-prod', function(done) { - init('prod', done); -}); + var configString = JSON.stringify(configJSON, null, 2) + '\n'; -/** - * Updates ecosystem.json - */ -gulp.task('add-deploy-target', function(done) { - log('Update ecosystem.json targets or create new ones!'); + fs.writeFileSync('ecosystem.json', configString, { + encoding: 'utf8' + }); + } catch (e) { + console.log('failed to write ecosystem.json: ' + e.message); + } + } - var properties = fs.readFileSync('deploy/build.properties', encoding); + var properties = fs.readFileSync('deploy/build.properties', { + encoding: 'utf8' + }); var name = properties.match(/app-name=(.*)/)[1]; var gitUrl = 'https://github.com/'; var folderPath = '/space/projects/' + name; - var ecosystem = 'ecosystem.json'; - - ecosystemMustExist(ecosystem, name); - $.git.exec({ args: 'config --get remote.origin.url', quiet: true @@ -301,22 +226,11 @@ gulp.task('add-deploy-target', function(done) { name: 'folder', message: 'Where do you want to store the project on your target server?', default: folderPath - }, { - type: 'list', - name: 'local', - message: 'Is there any sensitive information or credentials here that shouldn\'t go in source control?', - choices: ['no', 'yes'], - default: 0 }]; gulp.src(ecosystem) .pipe($.prompt.prompt(questions, function(answers) { - if (answers.local === 'yes') { - ecosystem = 'local.ecosystem.json'; - ecosystemMustExist(ecosystem, name); - } - gulp.src(ecosystem) .pipe($.jsonEditor(function(json) { json.deploy[answers.targetName] = { @@ -326,7 +240,7 @@ gulp.task('add-deploy-target', function(done) { 'ref': 'origin/' + answers.branch, 'repo': answers.gitUrl, 'path': answers.folder, - 'post-deploy': 'npm install && bower install && gulp build' + 'post-deploy': 'npm install; bower install; gulp build' }; return json; // must return JSON object. })) @@ -335,222 +249,17 @@ gulp.task('add-deploy-target', function(done) { }); }); -function ecosystemMustExist(ecosystem, name) { - if (!fs.existsSync(ecosystem)) { - try { - var configJSON = { - 'apps': [{ - 'name': name, - 'script': './node-server/node-app.js', - 'watch': true, - 'restart_delay': 4000, - 'env': { - 'NODE_ENV': 'local' - }, - 'env_local': { - 'NODE_ENV': 'local' - }, - 'env_dev': { - 'NODE_ENV': 'dev' - }, - 'env_prod': { - 'NODE_ENV': 'prod' - } - }], - 'deploy': {} - }; - - var configString = JSON.stringify(configJSON, null, 2) + '\n'; - - fs.writeFileSync(ecosystem, configString, encoding); - } catch (e) { - console.log('failed to write ecosystem.json: ' + e.message); - } - } -} - -/** - * Run the spec runner - * @param {Function} done - callback when complete - */ -gulp.task('serve-specs', ['build-specs'], function(done) { - log('run the spec runner'); - serve('local' /* env */ , true /* specRunner */ ); - done(); -}); - -/** - * Inject all the spec files into the specs.html - * @return {Stream} - */ -gulp.task('build-specs', ['templatecache'], function() { - log('building the spec runner'); - - var wiredep = require('wiredep').stream; - var templateCache = config.temp + config.templateCache.file; - var options = config.getWiredepDefaultOptions(); - var specs = config.specs; - - if (args.startServers) { - specs = [].concat(specs, config.serverIntegrationSpecs); - } - options.devDependencies = true; - - return gulp - .src(config.specRunner) - .pipe(wiredep(options)) - .pipe(inject(config.js, '', config.jsOrder)) - .pipe(inject(config.testlibraries, 'testlibraries')) - .pipe(inject(config.specHelpers, 'spechelpers')) - .pipe(inject(specs, 'specs', ['**/*'])) - .pipe(inject(templateCache, 'templates')) - .pipe(gulp.dest(config.client)) - .pipe($.if(args.verbose, $.print())); -}); - -/** - * Build everything - * This is separate so we can run tests on - * optimize before handling image or fonts - * @param {Function} done - callback when complete - */ -gulp.task('build', ['optimize', 'images', 'fonts', 'statics', 'tinymce'], function(done) { - log('Building everything'); - - var msg = { - title: 'gulp build', - subtitle: 'Deployed to the dist folder', - message: 'Ready to run `gulp serve-dev` or `gulp serve-prod`' - }; - log(msg); - notify(msg); - done(); -}); - -/** - * Optimize all files, move to a build folder, - * and inject them into the new index.html - * @return {Stream} - */ -gulp.task('optimize', ['inject', 'test'], function() { - log('Optimizing the js, css, and html'); - - // Filters are named for the gulp-useref path - var restore = { - restore: true - }; - var cssFilter = $.filter('**/*.css', restore); - var jsAppFilter = $.filter('**/' + config.optimized.app, restore); - var jslibFilter = $.filter('**/' + config.optimized.lib, restore); - - var templateCache = config.temp + config.templateCache.file; - - var combined = gulp - .src(config.index) - .pipe($.plumber()) - .pipe(inject(templateCache, 'templates')) - // Apply the concat and file replacement with useref - .pipe($.useref({ - searchPath: './' - })) - // Get the css - .pipe(cssFilter) - // Take inventory of the css file names for future rev numbers - .pipe($.rev()) - .pipe($.sourcemaps.init()) - .pipe($.cssnano({ - safe: true - })) - // write sourcemap for css - .pipe($.sourcemaps.write('.')) - .pipe(cssFilter.restore) - // Get the custom javascript - .pipe(jsAppFilter) - // Take inventory of the js app file name for future rev numbers - .pipe($.rev()) - .pipe($.sourcemaps.init()) - .pipe($.ngAnnotate({ - add: true - })) - .pipe($.uglify()) - // write sourcemap for js app - .pipe($.sourcemaps.write('.')) - .pipe(jsAppFilter.restore) - // Get the vendor javascript - .pipe(jslibFilter) - // Take inventory of the js lib file name for future rev numbers - .pipe($.rev()) - .pipe($.sourcemaps.init()) - .pipe($.uglify()) // another option is to override wiredep to use min files - // write sourcemap for js lib - .pipe($.sourcemaps.write('.')) - .pipe(jslibFilter.restore) - // Rename the recorded file names in the steam, and in the html to append rev numbers - .pipe($.revReplace()) - // copy result to dist/, and print some logging.. - .pipe(gulp.dest(config.build)) - .pipe($.if(args.verbose, $.print())); - - combined.on('error', console.error.bind(console)); - - return combined; -}); - /** * Remove all files from the build, temp, and reports folders * @return {Stream} */ -gulp.task('clean', ['clean-fonts'], function() { - var files = [].concat(config.build, config.temp, config.report); - return clean(files); -}); - -/** - * Remove all fonts from the build folder - * @return {Stream} - */ -gulp.task('clean-fonts', function() { - var files = [].concat( - config.build + 'fonts/**/*.*', - config.client + 'fonts/**/*.*' - ); - return clean(files); -}); - -/** - * Remove all images from the build folder - * @return {Stream} - */ -gulp.task('clean-images', function() { - return clean(config.build + 'images/**/*.*'); +gulp.task('clean', function(cb) { + del([paths.dest]).then(function(paths) { + $.util.log('[clean]', paths); + cb(); + }); }); -/** - * Remove all styles from the build and temp folders - * @return {Stream} - */ -gulp.task('clean-styles', function() { - var files = [].concat( - config.temp + '**/*.css', - config.build + 'styles/**/*.css', - config.build + 'styles/**/*.css.map' - ); - return clean(files); -}); - -/** - * Remove all js and html from the build and temp folders - * @return {Stream} - */ -gulp.task('clean-code', function() { - var files = [].concat( - config.temp + '**/*.js', - config.build + 'js/**/*.js', - config.build + 'js/**/*.js.map', - config.build + '**/*.html' - ); - return clean(files); -}); /** * Run specs once and exit @@ -558,7 +267,7 @@ gulp.task('clean-code', function() { * gulp test --startServers * @param {Function} done - callback when complete */ -gulp.task('test', ['vet', 'templatecache'], function(done) { +gulp.task('test', ['vet'], function(done) { startTests(true /*singleRun*/ , done); }); @@ -579,7 +288,7 @@ gulp.task('autotest', function(done) { * --nosync * @return {Stream} */ -gulp.task('serve-local', ['inject', 'fonts'], function() { +gulp.task('serve-local', function() { return serve('local' /*env*/ ); }); @@ -634,260 +343,6 @@ gulp.task('bump', function() { .pipe($.if(args.verbose, $.print())); }); -/** - * When files change, log it - * @param {Object} event - event that fired - */ -function changeEvent(event) { - var srcPattern = new RegExp('/.*(?=/' + config.source + ')/'); - log('File ' + event.path.replace(srcPattern, '') + ' ' + event.type); -} - -/** - * Delete all files in a given path - * @param {Array} files - array of paths to delete - * @return {Stream} - */ -function clean(files) { - log('Cleaning: ' + $.util.colors.blue(files)); - return del(files) - .then(function(paths) { - if (args.verbose) { - log(paths.map(function(path) { - return path.replace(__dirname + '/', 'rm '); - })); - } - }); -} - -/** - * Initialize config files for given env - * @param {String} env The environment name (local, dev, prod) - * @param {Function} done - callback when complete - */ -function init(env, done) { - if (fs.existsSync(env + '.json')) { - log('NOTE: ' + env + '.json already exists, change manually if needed.'); - if (fs.existsSync('deploy/' + env + '.properties')) { - log('NOTE: deploy/' + env + '.properties already exists, change manually too.'); - } else { - log('WARN: deploy/' + env + '.properties is missing!'); - } - done(); - } else { - //copy from slushfile - config gulp - with modifications to use config instead - var inquirer = require('inquirer'); - - run('./ml', [env, 'info', '--format=json']).then(function(output) { - var localJson = fs.existsSync('local.json') ? JSON.parse(fs.readFileSync('local.json', 'utf8')) : {}; - - var localAppName = localJson['app-name']; - var localMlVersion = localJson['ml-version']; - var localMlHost = localJson['ml-host']; - var localMlAdminUser = localJson['ml-admin-user']; - var localMlAppUser = localJson['ml-app-user']; - var localMlAppPass = localJson['ml-app-pass']; - var localMlHttpPort = localJson['ml-http-port']; - var localMlXccPort = localJson['ml-xcc-port']; - var localNodePort = localJson['node-port'] || 9070; - var localGuestAccess = ['false', 'true'].indexOf(localJson['guest-access']); - var localDisallowUpdates = ['false', 'true'].indexOf(localJson['disallow-updates']); - var localAppUsersOnly = ['false', 'true'].indexOf(localJson['appusers-only']); - - var properties = JSON.parse(output).properties || {}; - - var mlVersion = ['8', '7', '6', '5'].indexOf(localMlVersion || properties['ml.server-version'] || '8'); - var marklogicHost = properties['ml.' + env + '-server'] || localMlHost || 'localhost'; - var marklogicAdminUser = properties['ml.user'] || localMlAdminUser || 'admin'; - var appName = properties['ml.app-name'] || localAppName; - var appUserName = properties['ml.default-user'] || localMlAppUser; - var appUserPass = unescape(properties['ml.appuser-password']) || localMlAppPass; - var appPort = localMlHttpPort || properties['ml.app-port'] || 8040; - var xccPort = localMlXccPort || properties['ml.xcc-port'] || 8041; - - var prompts = [{ - type: 'list', - name: 'mlVersion', - message: 'MarkLogic version?', - choices: ['8', '7', '6', '5'], - default: mlVersion > 0 ? mlVersion : 0 - }, { - type: 'input', - name: 'marklogicHost', - message: 'MarkLogic Host?', - default: marklogicHost - }, { - type: 'input', - name: 'marklogicAdminUser', - message: 'MarkLogic Admin User?', - default: marklogicAdminUser - }, { - type: 'input', - name: 'marklogicAdminPass', - message: 'Note: consider keeping the following blank, ' + - 'you will be asked to enter it at appropriate commands.\n? MarkLogic Admin Password?', - default: '' - }, { - type: 'input', - name: 'appPort', - message: 'MarkLogic App/Rest port?', - default: appPort - }, { - type: 'input', - name: 'xccPort', - message: 'XCC port?', - default: xccPort, - when: function(answers) { - return answers.mlVersion < 8; - } - }, { - type: 'input', - name: 'nodePort', - message: 'Node app port?', - default: localNodePort - }, { - type: 'list', - name: 'guestAccess', - message: 'Allow anonymous users to search data?', - choices: ['false', 'true'], - default: localGuestAccess > 0 ? localGuestAccess : 0 - }, { - type: 'list', - name: 'disallowUpdates', - message: 'Disallow proxying update requests?', - choices: ['false', 'true'], - default: localDisallowUpdates > 0 ? localDisallowUpdates : 0 - }, { - type: 'list', - name: 'appUsersOnly', - message: 'Only allow access to users created for this app? Note: disallows admin users.', - choices: ['false', 'true'], - default: localAppUsersOnly > 0 ? localAppUsersOnly : 0 - }]; - - if (typeof appName === 'undefined') { - prompts.unshift({ - type: 'input', - name: 'name', - message: 'Name for the app?' - }); - } - - inquirer.prompt(prompts, function(settings) { - if (typeof appName === 'undefined') { - settings.nameDashed = _s.slugify(settings.name); - } else { - settings.nameDashed = _s.slugify(appName); - } - - try { - var configJSON = {}; - configJSON['app-name'] = settings.nameDashed; - configJSON['ml-version'] = settings.mlVersion; - configJSON['ml-host'] = settings.marklogicHost; - configJSON['ml-admin-user'] = settings.marklogicAdminUser; - configJSON['ml-admin-pass'] = settings.marklogicAdminPass; - configJSON['ml-app-user'] = appUserName || (settings.nameDashed + '-user'); - configJSON['ml-app-pass'] = appUserPass || ''; - configJSON['ml-http-port'] = settings.appPort; - - if (settings.mlVersion < 8) { - configJSON['ml-xcc-port'] = settings.xccPort; - } - - configJSON['node-port'] = settings.nodePort; - configJSON['guest-access'] = settings.guestAccess; - configJSON['disallow-updates'] = settings.disallowUpdates; - configJSON['appusers-only'] = settings.appUsersOnly; - - var configString = JSON.stringify(configJSON, null, 2) + '\n'; - fs.writeFileSync(env + '.json', configString, encoding); - log('Created ' + env + '.json.'); - - if (fs.existsSync('deploy/' + env + '.properties')) { - log('NOTE: deploy/' + env + '.properties already exists, change manually please!'); - } else { - var envProperties = '#################################################################\n' + - '# This file contains overrides to values in build.properties\n' + - '# These only affect your local environment and should not be checked in\n' + - '#################################################################\n' + - '\n' + - 'server-version=' + settings.mlVersion + '\n' + - '\n' + - '#\n' + - '# The ports used by your application\n' + - '#\n' + - 'app-port=' + settings.appPort + '\n'; - if (settings.mlVersion < 8) { - envProperties += 'xcc-port=' + settings.xccPort + '\n'; - } else { - envProperties += '# Taking advantage of not needing a XCC Port for ML8\n' + - 'xcc-port=${app-port}\n' + - 'install-xcc=false\n'; - } - - envProperties += '\n' + - '#\n' + - '# the uris or IP addresses of your servers\n' + - '# WARNING: if you are running these scripts on WINDOWS you may need to change localhost to 127.0.0.1\n' + - '# There have been reported issues with dns resolution when localhost wasn\'t in the hosts file.\n' + - '#\n' + - env + '-server=' + settings.marklogicHost + '\n' + - 'content-forests-per-host=3\n' + - '\n' + - '#\n' + - '# Admin username/password that will exist on the local/dev/prod servers\n' + - '#\n' + - 'user=' + settings.marklogicAdminUser + '\n' + - 'password=' + settings.marklogicAdminPass + '\n'; - - fs.writeFileSync('deploy/' + env + '.properties', envProperties, encoding); - log('Created deploy/' + env + '.properties.'); - } - done(); - } catch (e) { - log('Failed to write ' + env + ' config files: ' + e.message); - done(); - } - }); - }); - } -} -// bypass Roxy bug that causes special XML chars to get escaped as entities -function unescape(s) { - return s.replace(''', '\'').replace('"', '"').replace('<', '<').replace('>', '>').replace('&', '&').replace('{{', '{').replace('}}', '}'); -} - -/** - * Inject files in a sorted sequence at a specified inject label - * @param {Array} src glob pattern for source files - * @param {String} label The label name - * @param {Array} order glob pattern for sort order of the files - * @return {Stream} - */ -function inject(src, label, order) { - var options = { - read: false - }; - if (label) { - options.name = 'inject:' + label; - } - - return $.inject(orderSrc(src, order), options); -} - -/** - * Order a stream - * @param {Stream} src The gulp.src stream - * @param {Array} order Glob array pattern - * @return {Stream} The ordered stream - */ -function orderSrc(src, order) { - return gulp - .src(src) - .pipe($.if(order, $.order(order))); -} - /** * serve the code * --debug-brk or --debug @@ -954,7 +409,7 @@ function getNodeOptions(env) { delayTime: 1, env: { 'PORT': port, - 'NODE_ENV': env, + 'NODE_ENV': isDevMode(env) ? 'dev' : 'build', 'APP_PORT': port, 'ML_HOST': args['ml-host'] || process.env.ML_HOST || envJson['ml-host'] || config.marklogic.host, 'ML_APP_USER': args['ml-app-user'] || process.env.ML_APP_USER || envJson['ml-app-user'] || config.marklogic.user, @@ -967,11 +422,17 @@ function getNodeOptions(env) { }; } -/** - * Start BrowserSync - * --nosync will avoid browserSync - */ -function startBrowserSync(env, specRunner) { +function startBrowserSync(env) { + var config = require('./webpack/webpack.dev.config'); + config.entry.app = [ + // this modules required to make HRM working + // it responsible for all this webpack magic + 'webpack-hot-middleware/client?reload=true', + // application entry point + ].concat(paths.entry); + + var compiler = webpack(config); + var nodeOptions = getNodeOptions(env); if (args.nosync || browserSync.active) { @@ -980,23 +441,27 @@ function startBrowserSync(env, specRunner) { log('Starting BrowserSync on port ' + nodeOptions.env.APP_PORT); - // If build: watches the files, builds, and restarts browser-sync. - // If dev: watches less, compiles it to css, browser-sync handles reload - if (isDevMode(env)) { - gulp.watch([config.less], ['styles']) - .on('change', changeEvent); - } else { - gulp.watch([config.less, config.js, config.html], ['optimize', browserSync.reload]) - .on('change', changeEvent); - } - var options = { proxy: 'localhost:' + nodeOptions.env.APP_PORT, port: 3000, - files: isDevMode(env) ? [ - config.client + '**/*.*', - '!' + config.less, - config.temp + '**/*.css' + notify: true, + reloadDelay: 0, //1000 + ui: false, + open: true, + /*server: { + baseDir: root + },*/ + middleware: isDevMode(env) ? [ + historyApiFallback(), + webpackDevMiddelware(compiler, { + stats: { + colors: colorsSupported, + chunks: false, + modules: false + }, + publicPath: config.output.publicPath + }), + webpachHotMiddelware(compiler) ] : [], ghostMode: { // these are the defaults t,f,t,t clicks: true, @@ -1007,14 +472,8 @@ function startBrowserSync(env, specRunner) { injectChanges: true, logFileChanges: true, logLevel: 'debug', - logPrefix: 'gulp-patterns', - notify: true, - reloadDelay: 0, //1000 - ui: false + logPrefix: 'gulp-patterns' }; - if (specRunner) { - options.startPath = config.specRunnerFile; - } browserSync(options); } @@ -1064,7 +523,7 @@ function startTests(singleRun, done) { if (args.startServers) { log('Starting servers'); var savedEnv = process.env; - savedEnv.NODE_ENV = 'local'; + savedEnv.NODE_ENV = 'dev'; savedEnv.PORT = 8888; child = fork(config.nodeServer); } else { @@ -1099,29 +558,6 @@ function startTests(singleRun, done) { } } -/** - * Formatter for bytediff to display the size changes after processing - * @param {Object} data - byte data - * @return {String} Difference in bytes, formatted - */ -function bytediffFormatter(data) { - var difference = (data.savings > 0) ? ' smaller.' : ' larger.'; - return data.fileName + ' went from ' + - (data.startSize / 1000).toFixed(2) + ' kB to ' + - (data.endSize / 1000).toFixed(2) + ' kB and is ' + - formatPercent(1 - data.percent, 2) + '%' + difference; -} - -/** - * Format a number as a percentage - * @param {Number} num Number to format as a percent - * @param {Number} precision Precision of the decimal - * @return {String} Formatted perentage - */ -function formatPercent(num, precision) { - return (num * 100).toFixed(precision); -} - /** * Log a message or series of messages using chalk's blue color. * Can pass in a string, object or array. @@ -1138,50 +574,4 @@ function log(msg) { } } -/** - * Show OS level notification using node-notifier - */ -function notify(options) { - var notifier = require('node-notifier'); - var notifyOptions = { - sound: 'Bottle', - contentImage: path.join(__dirname, 'gulp.png'), - icon: path.join(__dirname, 'gulp.png') - }; - _.assign(notifyOptions, options); - notifier.notify(notifyOptions); -} - -function run(cmd, args, verbose) { - var d = q.defer(); - var output = ''; - - console.log('Spawning ' + cmd + ' ' + args.join(' ')); - var child = spawn(cmd, args, { - stdio: [ - 0, // Use parents stdin for child - 'pipe', // Pipe child's stdout to parent (default) - 'pipe' // Pipe child's stderr to parent (default) - ] - }); - - child.on('close', function() { - console.log('done running ' + cmd); - d.resolve(output); - }); - - child.stdout.on('data', function(chunk) { - if (verbose) { - console.log(chunk.toString()); - } - output += chunk.toString(); - }); - - child.stderr.on('data', function(data) { - console.log(data.toString()); - }); - - return d.promise; -} - module.exports = gulp; diff --git a/app/templates/jsconfig.json b/app/templates/jsconfig.json new file mode 100644 index 00000000..69ca405e --- /dev/null +++ b/app/templates/jsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "target": "ES6" + }, + "exclude": [ + "node_modules" + ] +} \ No newline at end of file diff --git a/app/templates/karma.conf.js b/app/templates/karma.conf.js index 6635f9b0..8f992aa4 100644 --- a/app/templates/karma.conf.js +++ b/app/templates/karma.conf.js @@ -1,66 +1,130 @@ /*jshint node: true */ - -'use strict'; -/*global module*/ -// Karma configuration -// http://karma-runner.github.io/0.10/config/configuration-file.html - module.exports = function(config) { - var gulpConfig = require('./gulp.config')(); - config.set({ - // base path that will be used to resolve all patterns (eg. files, exclude) - basePath: './', + // base path used to resolve all patterns + basePath: '', // frameworks to use - // some available frameworks: https://npmjs.org/browse/keyword/karma-adapter + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter frameworks: ['mocha', 'chai', 'sinon', 'chai-sinon'], - // list of files / patterns to load in the browser - files: gulpConfig.karma.files, + // list of files/patterns to load in the browser + files: [{ + pattern: 'spec.bundle.js', + watched: false + }], + + // files to exclude + exclude: [], + + plugins: [ + require('karma-chai'), + require('karma-chrome-launcher'), + require('karma-mocha'), + require('karma-mocha-reporter'), + require('karma-coverage'), + require('karma-notify-reporter'), + require('karma-sourcemap-loader'), + require('karma-chai-sinon'), + require('karma-sinon'), + require('karma-webpack'), + require('karma-phantomjs-launcher') + ], - // list of files to exclude - exclude: gulpConfig.karma.exclude, + // preprocess matching files before serving them to the browser + // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor + preprocessors: { + 'spec.bundle.js': ['webpack', 'sourcemap'] + }, - proxies: { - '/': 'http://localhost:8888/' + webpack: { + devtool: 'inline-source-map', + module: { + /*\ + preLoaders: [{ + test: /\.js$/, + loader: 'isparta' + }], + */ + loaders: [ + // transpile all files except modules and source files which are need be tested + { + test: /\.js$/, + exclude: [/app((?!\.spec).)*$/, /node_modules/], + loader: 'ng-annotate!babel' + }, + // transpile and instrument only testable files with isparta + // this allows us to generate a coverage report of all files + { + test: /\.js$/, + include: [/app((?!\.spec).)*$/], + loader: 'isparta' + }, { + test: /\.html$/, + loader: 'html' + }, { + test: /\.styl$/, + loader: 'style!css!stylus' + }, { + test: /\.less$/, + loader: 'css!less' + }, { + test: /\.css$/, + loader: 'style!css' + }, { + test: /\.(jpe?g|png|gif)$/i, + loader: 'file' + }, { + test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, + loader: 'url-loader?limit=10000&mimetype=application/font-woff' + }, { + test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/, + loader: 'file' + } + ] + } }, - // preprocess matching files before serving them to the browser - // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor - preprocessors: gulpConfig.karma.preprocessors, + webpackServer: { + noInfo: true // prevent console spamming when running in Karma! + }, - // test results reporter to use - // possible values: 'dots', 'progress', 'coverage' // available reporters: https://npmjs.org/browse/keyword/karma-reporter - reporters: ['progress', 'coverage', 'notify'], + reporters: ['mocha', 'coverage', 'notify'], coverageReporter: { - dir: gulpConfig.karma.coverage.dir, - reporters: gulpConfig.karma.coverage.reporters + // dir: report + 'coverage', + reporters: [ + // reporters not supporting the `file` property + { + type: 'html', + subdir: 'report-html' + }, { + type: 'lcov', + subdir: 'report-lcov' + }, { + type: 'text-summary' + } //, subdir: '.', file: 'text-summary.txt'} + ] }, - // web server port port: 9876, - // enable / disable colors in the output (reporters and logs) + // enable colors in the output colors: true, // level of logging - // possible values: config.LOG_DISABLE || config.LOG_ERROR || - // config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG logLevel: config.LOG_INFO, - // enable / disable watching file and executing tests whenever any file changes + // toggle whether to watch files and rerun tests upon incurring changes autoWatch: true, // start these browsers // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher - // browsers: ['Chrome', 'ChromeCanary', 'FirefoxAurora', 'Safari', 'PhantomJS'], browsers: ['PhantomJS'], - // Continuous Integration mode - // if true, Karma captures browsers, runs the tests and exits + // if true, Karma runs tests once and exits singleRun: false }); }; diff --git a/app/templates/package.json b/app/templates/package.json index 6a241cf7..75ddd3ca 100644 --- a/app/templates/package.json +++ b/app/templates/package.json @@ -2,19 +2,72 @@ "name": "@sample-app-name", "version": "0.0.0", "dependencies": { + "angular": "^1.5.0", + "angular-animate": "~1.5.x", + "angular-cookies": "~1.5.x", + "angular-google-maps": "2.3.2", + "angular-highlightjs": "^0.6.1", + "angular-mocks": "~1.5.x", + "angular-ui-bootstrap": "^1.1", + "angular-ui-router": "^1.0.0-beta.1", + "angular-ui-tinymce": "~0.0.9", + "angular-x2js": "https://github.com/janmichaelyu/angular-x2js.git", + "bardjs": "^0.1.8", "body-parser": "^1.14.0", + "bootstrap": "~3.3.5", + "exports-loader": "^0.6.3", "express": "^4.4.1", "express-session": "^1.5.0", - "helmet": "^2.0.0", - "morgan": "^1.6.0" + "extract-text-webpack-plugin": "^1.0.1", + "file-loader": "^0.9.0", + "font-awesome": "^4.6.3", + "gulp-jshint": "^2.0.1", + "helmet": "^2.1.1", + "highcharts": "^4.2", + "highlightjs": "~8.7.0", + "html-loader": "^0.4.3", + "jquery": "~2.1.4", + "jshint": "^2.9.2", + "jshint-stylish": "^2.0.0", + "karma-chai-sinon": "^0.1.5", + "karma-sinon": "^1.0.5", + "less": "^2.7.1", + "less-loader": "^2.2.3", + "lodash": "~3.10.1", + "ml-common-ng": "https://github.com/joemfb/ml-common-ng.git", + "ml-search-ng": "https://github.com/joemfb/ml-search-ng.git", + "ml-utils": "https://github.com/withjam/ml-utils.git", + "morgan": "^1.6.0", + "ng-json-explorer": "odra/ng-json-explorer", + "ng-toast": "^2.0.0", + "normalize.css": "^3.0.3", + "script-loader": "^0.7.0", + "sinon": "^1.17.4", + "sinon-chai": "^2.8.0", + "tinymce": "4.3.12", + "url-loader": "^0.5.7", + "vkbeautify": "*", + "x2js": "^2.0.1" }, "devDependencies": { - "browser-sync": "^2.7.12", - "chai": "^3.0.0", - "del": "^2.0.2", - "glob": "^6.0.0", + "angular-mocks": "^1.5.0", + "babel-core": "^6.7.7", + "babel-loader": "^6.2.4", + "babel-plugin-transform-runtime": "^6.7.5", + "babel-polyfill": "^6.7.4", + "babel-preset-es2015": "^6.6.0", + "babel-preset-stage-0": "^6.5.0", + "babel-register": "^6.7.2", + "babel-runtime": "^6.6.1", + "browser-sync": "^2.11.1", + "chai": "^3.4.0", + "connect-history-api-fallback": "^1.1.0", + "css-loader": "^0.19.0", + "del": "^2.2.0", + "esprima": "^2.7.2", + "fs-walk": "0.0.1", "gulp": "^3.8.0", - "gulp-angular-templatecache": "^1.9.1", + "gulp-angular-templatecache": "^1.6.0", "gulp-autoprefixer": "^2.2.0", "gulp-bump": "^1.0.0", "gulp-bytediff": "^1.0.0", @@ -23,12 +76,12 @@ "gulp-filter": "^3.0.1", "gulp-flatten": "0.2.0", "gulp-git": "^1.7.2", + "gulp-header": "1.8.2", "gulp-htmlmin": "^1.1.0", "gulp-if": "^2.0.0", "gulp-imagemin": "^2.2.1", "gulp-inject": "^3.0.0", "gulp-jscs": "^3.0.0", - "gulp-jshint": "^2.0.0", "gulp-json-editor": "^2.2.1", "gulp-less": "^3.0.3", "gulp-load-plugins": "^1.0.0", @@ -36,8 +89,8 @@ "gulp-nodemon": "^2.0.3", "gulp-order": "^1.1.1", "gulp-plumber": "^1.0.1", - "gulp-prompt": "^0.2.0", "gulp-print": "^2.0.1", + "gulp-prompt": "^0.2.0", "gulp-rename": "^1.2.0", "gulp-rev": "^7.0.0", "gulp-rev-replace": "^0.4.2", @@ -45,33 +98,38 @@ "gulp-task-listing": "^1.0.1", "gulp-uglify": "^1.4.1", "gulp-useref": "^3.0.0", - "gulp-util": "^3.0.5", - "inquirer": "^0.12.0", - "jasmine-core": "^2.3.4", - "jshint": "^2.0.0", - "jshint-stylish": "^2.0.0", - "karma": "^0.13.0", + "gulp-util": "^3.0.7", + "html-webpack-plugin": "^1.7.0", + "image-webpack-loader": "~1.8.0", + "isparta-loader": "^2.0.0", + "karma": "^0.13.9", "karma-chai": "^0.1.0", - "karma-chai-sinon": "^0.1.5", - "karma-coverage": "^0.5.0", - "karma-jasmine": "^0.3.0", + "karma-chrome-launcher": "^0.2.0", + "karma-coverage": "^1.1.0", "karma-mocha": "^0.2.0", - "karma-ng-html2js-preprocessor": "^0.2.0", - "karma-notify-reporter": "^0.1.1", + "karma-mocha-reporter": "^1.0.2", + "karma-notify-reporter": "^1.0.1", "karma-phantomjs-launcher": "^1.0.0", - "karma-sinon": "^1.0.4", - "lodash": "^3.9.3", - "mocha": "^2.2.5", - "node-notifier": "^4.2.3", + "karma-sourcemap-loader": "^0.3.4", + "karma-webpack": "^1.5.1", + "lodash": "^4.11.1", + "mocha": "^2.3.0", + "ng-annotate-loader": "0.0.10", + "node-libs-browser": "^0.5.0", "phantomjs-prebuilt": "^2.0.0", - "plato": "^1.5.0", - "q": "^1.0.0", - "sinon": "^1.15.3", - "sinon-chai": "^2.8.0", - "underscore.string": "^3.2.2", + "plato": "^1.6.0", + "raw-loader": "^0.5.1", + "run-sequence": "^1.1.0", + "style-loader": "^0.12.2", + "stylus": "^0.54.5", + "stylus-loader": "^2.1.1", + "supports-color": "^3.1.2", "username": "^1.0.1", + "webpack": "^1.9.5", + "webpack-dev-middleware": "^1.6.1", + "webpack-hot-middleware": "^2.6.0", "wiredep": "^3.0.0", - "yargs": "^3.26.0" + "yargs": "^3.9.0" }, "engines": { "node": ">=0.8.0" diff --git a/app/templates/spec.bundle.js b/app/templates/spec.bundle.js new file mode 100644 index 00000000..58ac6796 --- /dev/null +++ b/app/templates/spec.bundle.js @@ -0,0 +1,29 @@ +/* + * When testing with Webpack and ES6, we have to do some + * preliminary setup. Because we are writing our tests also in ES6, + * we must transpile those as well, which is handled inside + * `karma.conf.js` via the `karma-webpack` plugin. This is the entry + * file for the Webpack tests. Similarly to how Webpack creates a + * `bundle.js` file for the compressed app source files, when we + * run our tests, Webpack, likewise, compiles and bundles those tests here. + */ + +import angular from 'angular'; + +// Built by the core Angular team for mocking dependencies +import mocks from 'angular-mocks'; + +import 'script!bardjs/dist/bard'; + +// We use the context method on `require` which Webpack created +// in order to signify which files we actually want to require or import. +// Below, `context` will be a/an function/object with file names as keys. +// Using that regex, we scan within `client/app` and target +// all files ending with `.spec.js` and trace its path. +// By passing in true, we permit this process to occur recursively. +let context = require.context('./ui/app', true, /\.js/); + +// Get all files, for each file, call the context function +// that will require the file and load it here. Context will +// loop and require those spec files here. +context.keys().forEach(context); diff --git a/app/templates/ui/app/app.component.js b/app/templates/ui/app/app.component.js new file mode 100644 index 00000000..5ab7caee --- /dev/null +++ b/app/templates/ui/app/app.component.js @@ -0,0 +1,8 @@ +import template from './app.html'; + +let appComponent = { + template +}; + +export +default appComponent; diff --git a/app/templates/ui/app/app.html b/app/templates/ui/app/app.html new file mode 100644 index 00000000..f284be10 --- /dev/null +++ b/app/templates/ui/app/app.html @@ -0,0 +1,4 @@ + +
+
+
diff --git a/app/templates/ui/app/app.js b/app/templates/ui/app/app.js index 00ba4536..628efb96 100644 --- a/app/templates/ui/app/app.js +++ b/app/templates/ui/app/app.js @@ -1,23 +1,18 @@ -(function () { - 'use strict'; +import angular from 'angular'; - angular.module('app', [ - 'ml.common', - 'ml.search', - 'ml.search.tpls', - 'ml.utils', - 'ngJsonExplorer', - 'app.create', - 'app.detail', - 'app.error', - 'app.login', - 'app.root', - 'app.search', - 'app.user', - 'ui.bootstrap', - 'ui.router', - 'ui.tinymce', - 'ngToast' - ]); +import Common from './common/common'; +import Components from './components/components'; +import AppComponent from './app.component'; -}()); +import routes from './route/routes'; + +import 'normalize.css'; +import '../styles/main.less'; + +angular.module('app', [ + routes, + Common, + Components, + 'ml.search.tpls' + ]) + .component('app', AppComponent); diff --git a/app/templates/ui/app/common/common.js b/app/templates/ui/app/common/common.js new file mode 100644 index 00000000..f2d3c41b --- /dev/null +++ b/app/templates/ui/app/common/common.js @@ -0,0 +1,11 @@ +import angular from 'angular'; +import Root from './root/root'; + +let commonModule = angular.module('app.common', [ + Root +]) + +.name; + +export +default commonModule; diff --git a/app/templates/ui/app/common/root/root.component.js b/app/templates/ui/app/common/root/root.component.js new file mode 100644 index 00000000..e6f17de3 --- /dev/null +++ b/app/templates/ui/app/common/root/root.component.js @@ -0,0 +1,12 @@ +import RootCtrl from './root.controller'; + +import template from './root.html'; + +const rootComponent = { + bindings: {}, + controller: RootCtrl, + template: template +}; + +export +default rootComponent; diff --git a/app/templates/ui/app/common/root/root.controller.js b/app/templates/ui/app/common/root/root.controller.js new file mode 100644 index 00000000..11d8ca02 --- /dev/null +++ b/app/templates/ui/app/common/root/root.controller.js @@ -0,0 +1,14 @@ +RootCtrl.$inject = ['messageBoardService', 'userService', '$scope']; + +function RootCtrl(messageBoardService, userService, $scope) { + var ctrl = this; + ctrl.currentYear = new Date().getUTCFullYear(); + ctrl.messageBoardService = messageBoardService; + + $scope.$watch(userService.currentUser, function(newValue) { + ctrl.currentUser = newValue; + }); +} + +export +default RootCtrl; diff --git a/app/templates/ui/app/root/root.html b/app/templates/ui/app/common/root/root.html similarity index 73% rename from app/templates/ui/app/root/root.html rename to app/templates/ui/app/common/root/root.html index be7b94f5..29b37d18 100644 --- a/app/templates/ui/app/root/root.html +++ b/app/templates/ui/app/common/root/root.html @@ -15,7 +15,7 @@ @@ -23,8 +23,8 @@
- -
+ +
@@ -36,17 +36,17 @@
@@ -64,9 +64,9 @@
- \ No newline at end of file + diff --git a/app/templates/ui/app/common/root/root.js b/app/templates/ui/app/common/root/root.js new file mode 100644 index 00000000..e0522765 --- /dev/null +++ b/app/templates/ui/app/common/root/root.js @@ -0,0 +1,12 @@ +import angular from 'angular'; + +import RootComponent from './root.component'; +import MessageBoard from '../../components/message-board/message-board'; + +const Root = angular + .module('app.root', [MessageBoard]) + .component('root', RootComponent) + .name; + +export +default Root; diff --git a/app/templates/ui/app/components/components.js b/app/templates/ui/app/components/components.js new file mode 100644 index 00000000..06ffe0e6 --- /dev/null +++ b/app/templates/ui/app/components/components.js @@ -0,0 +1,23 @@ +import angular from 'angular'; +import Login from './login/login'; +import MessageBoard from './message-board/message-board'; +import User from './user/user'; +import Search from './search/search'; +import ErrorModule from './error/error'; +import Detail from './detail/detail'; +import Create from './create/create'; + +let componentModule = angular.module('app.components', [ + Login, + MessageBoard, + User, + Search, + ErrorModule, + Detail, + Create +]) + +.name; + +export +default componentModule; diff --git a/app/templates/ui/app/components/create/create.component.js b/app/templates/ui/app/components/create/create.component.js new file mode 100644 index 00000000..8d601dd1 --- /dev/null +++ b/app/templates/ui/app/components/create/create.component.js @@ -0,0 +1,11 @@ +import template from './create.html'; +import controller from './create.controller'; + +const component = { + bindings: {}, + controller: controller, + template: template +}; + +export +default component; diff --git a/app/templates/ui/app/components/create/create.controller.js b/app/templates/ui/app/components/create/create.controller.js new file mode 100644 index 00000000..b711cad3 --- /dev/null +++ b/app/templates/ui/app/components/create/create.controller.js @@ -0,0 +1,153 @@ +class CreateCtrl { + constructor($scope, mlRest, $state, userService, toast) { + this.person = { + isActive: true, + balance: 0, + picture: 'http://placehold.it/32x32', + age: 0, + eyeColor: null, + name: null, + gender: null, + company: null, + email: null, + phone: null, + address: null, + about: null, + registered: null, + location: { + latitude: 0, + longitude: 0 + }, + tags: [], + friends: [], + greeting: null, + favoriteFruit: null + }; + + this.newTag = null; + this.currentUser = null; + this.editorOptions = { + plugins: 'advlist autolink link image lists charmap print preview' + }; + + this.$scope = $scope; + this.mlRest = mlRest; + this.$state = $state; + this.userService = userService; + this.toast = toast; + + this.$scope.$watch(this.userService.currentUser, (newValue, oldValue) => { + this.currentUser = newValue; + }); + } + + submit() { + this.mlRest.createDocument(this.person, { + format: 'json', + directory: '/content/', + extension: '.json', + collection: ['data', 'data/people'] + // TODO: add read/update permissions here like this: + // 'perm:sample-role': 'read', + // 'perm:sample-role': 'update' + }).then((response) => { + this.toast.success('Record created.'); + this.$state.go('root.view', { + uri: response.replace(/(.*\?uri=)/, '') + }); + }); + } + + addTag() { + if (this.newTag && this.newTag !== '' && this.person.tags.indexOf(this.newTag) < 0) { + this.person.tags.push(this.newTag); + } + this.newTag = null; + } + + removeTag(index) { + this.person.tags.splice(index, 1); + } +} + +CreateCtrl.$inject = ['$scope', 'MLRest', '$state', 'userService', 'ngToast']; + +/* +//es5 style + +import angular from 'angular'; + +function CreateCtrl($scope, mlRest, $state, userService, toast) { + var ctrl = this; + + ctrl.person = null; + + angular.extend(ctrl, { + person: { + isActive: true, + balance: 0, + picture: 'http://placehold.it/32x32', + age: 0, + eyeColor: null, + name: null, + gender: null, + company: null, + email: null, + phone: null, + address: null, + about: null, + registered: null, + location: { + latitude: 0, + longitude: 0 + }, + tags: [], + friends: [], + greeting: null, + favoriteFruit: null + }, + newTag: null, + currentUser: null, + editorOptions: { + plugins: 'advlist autolink link image lists charmap print preview' + }, + submit: submit, + addTag: addTag, + removeTag: removeTag + }); + + function submit() { + mlRest.createDocument(ctrl.person, { + format: 'json', + directory: '/content/', + extension: '.json', + collection: ['data', 'data/people'] + // TODO: add read/update permissions here like this: + // 'perm:sample-role': 'read', + // 'perm:sample-role': 'update' + }).then(function(response) { + toast.success('Record created.'); + $state.go('root.view', { + uri: response.replace(/(.*\?uri=)/, '') + }); + }); + } + + function addTag() { + if (ctrl.newTag && ctrl.newTag !== '' && ctrl.person.tags.indexOf(ctrl.newTag) < 0) { + ctrl.person.tags.push(ctrl.newTag); + } + ctrl.newTag = null; + } + + function removeTag(index) { + ctrl.person.tags.splice(index, 1); + } + + $scope.$watch(userService.currentUser, function(newValue) { + ctrl.currentUser = newValue; + }); +} +*/ +export +default CreateCtrl; diff --git a/app/templates/ui/app/components/create/create.controller.spec.js b/app/templates/ui/app/components/create/create.controller.spec.js new file mode 100644 index 00000000..81a2c342 --- /dev/null +++ b/app/templates/ui/app/components/create/create.controller.spec.js @@ -0,0 +1,97 @@ +/* jshint -W117, -W030 */ +import Module from './create'; + +import Controller from './create.controller'; +import Component from './create.component'; +import Template from './create.html'; + +describe('Create', () => { + let $rootScope, controller, scope; + let $q, MLRest, $state, userService, toast; + + var nextState; + + beforeEach(window.module(Module)); + + beforeEach(inject( + (_$controller_, _$q_, _$rootScope_, _MLRest_, _$state_, _userService_, _ngToast_) => { + $rootScope = _$rootScope_; + scope = $rootScope.$new(); + $q = _$q_; + MLRest = _MLRest_; + $state = _$state_; + userService = _userService_; + toast = _ngToast_; + + sinon.stub(MLRest, 'createDocument', () => $q.when('/?uri=blah')); + sinon.stub($state, 'go', function(state, params) { + nextState = { + state: state, + params: params + }; + }); + })); + + beforeEach(() => { + // stub the current user + scope = $rootScope.$new(); + controller = new Controller(scope, MLRest, $state, userService, toast); + }); + + describe('Module', () => { + // top-level specs: i.e., routes, injection, naming + }); + + describe('Controller', () => { + // controller specs + it('should be created successfully', function() { + expect(controller).to.be.defined; + }); + + it('has a person property [REMOVE]', () => { // erase if removing this.person from the controller + expect(controller).to.have.property('person'); + }); + + it('should add tags', function() { + var tagValue = 'testTag'; + expect(controller.person.tags.length).to.eq(0); + controller.newTag = tagValue; + controller.addTag(); + expect(controller.person.tags.length).to.eq(1); + expect(controller.person.tags[0]).to.eq(tagValue); + expect(controller.newTag).to.eq.null; + }); + + it('should show the detail view when submitted', function() { + controller.submit(); + $rootScope.$apply(); + expect(nextState).to.deep.eq({ + state: 'root.view', + params: { + uri: 'blah' + } + }); + }); + }); + + describe('Template', () => { + // template specs + // tip: use regex to ensure correct bindings are used e.g., {{ }} + it('has person in template [REMOVE]', () => { + expect(Template).to.match(/\s?\$ctrl\.person\s?/g); + }); + }); + + describe('Component', () => { + // component/directive specs + let component = Component; + + it('includes the intended template', () => { + expect(component.template).to.equal(Template); + }); + + it('invokes the right controller', () => { + expect(component.controller).to.equal(Controller); + }); + }); +}); diff --git a/app/templates/ui/app/create/create.html b/app/templates/ui/app/components/create/create.html similarity index 60% rename from app/templates/ui/app/create/create.html rename to app/templates/ui/app/components/create/create.html index e2f6fdf6..3438aad2 100644 --- a/app/templates/ui/app/create/create.html +++ b/app/templates/ui/app/components/create/create.html @@ -1,4 +1,4 @@ -
+

Create a Document

@@ -7,97 +7,97 @@

Create a Document

- +
- +
- +
- +
- +
- +
- +
- +
- +
- +
- +
- +
- +
- +
- +
- - + +
-
+
{{tag}} - +
@@ -105,14 +105,14 @@

Create a Document

- Cancel - + Cancel +
diff --git a/app/templates/ui/app/components/create/create.js b/app/templates/ui/app/components/create/create.js new file mode 100644 index 00000000..f31900bb --- /dev/null +++ b/app/templates/ui/app/components/create/create.js @@ -0,0 +1,16 @@ +import angular from 'angular'; +import uiRouter from 'angular-ui-router'; + +import CreateComponent from './create.component'; +import User from '../user/user'; + +//run globally since not in an es6 module +import 'script!ml-common-ng/dist/ml-common-ng'; +import 'script!ng-toast'; + +const module = angular.module('app.create', ['ml.common', User, 'ngToast', uiRouter]) + .component('create', CreateComponent) + .name; + +export +default module; diff --git a/app/templates/ui/app/components/detail/detail.component.js b/app/templates/ui/app/components/detail/detail.component.js new file mode 100644 index 00000000..5734bcbd --- /dev/null +++ b/app/templates/ui/app/components/detail/detail.component.js @@ -0,0 +1,14 @@ +import controller from './detail.controller'; + +import template from './detail.html'; + +const component = { + bindings: { + doc: '<' + }, + controller: controller, + template: template +}; + +export +default component; diff --git a/app/templates/ui/app/components/detail/detail.controller.js b/app/templates/ui/app/components/detail/detail.controller.js new file mode 100644 index 00000000..0757bd11 --- /dev/null +++ b/app/templates/ui/app/components/detail/detail.controller.js @@ -0,0 +1,101 @@ +import X2JS from 'x2js'; +import vkbeautify from 'vkbeautify'; +/* +function DetailCtrl($stateParams) { + var this = this; + + var uri = $stateParams.uri; + var this.doc = this.this.doc; + + var contentType = this.doc.headers('content-type'); + + var x2js = new X2JS(); + */ +/* jscs: disable */ +//if (contentType.lastIndexOf('application/json', 0) === 0) { +/*jshint camelcase: false */ +/* + this.xml = vkbeautify.xml(x2js.js2xml(this.doc.data)); + this.json = this.doc.data; + this.type = 'json'; + } else if (contentType.lastIndexOf('application/xml', 0) === 0) { + this.xml = vkbeautify.xml(this.doc.data); + /*jshint camelcase: false */ +/* +this.json = x2js.xml2js(this.doc.data); +this.type = 'xml'; +/* jscs: enable */ +/* + } else if (contentType.lastIndexOf('text/plain', 0) === 0) { + this.xml = this.doc.data; + this.json = { + 'this.document': this.doc.data + }; + this.type = 'text'; + } else if (contentType.lastIndexOf('application', 0) === 0) { + this.xml = 'Binary object'; + this.json = { + 'this.document type': 'Binary object' + }; + this.type = 'binary'; + } else { + this.xml = 'Error occured determining this.document type.'; + this.json = { + 'Error': 'Error occured determining this.document type.' + }; + } + + angular.extend(this, { + this.doc: this.doc.data, + uri: uri + }); +} +*/ + + +class DetailCtrl { + constructor($stateParams) { + this.$stateParams = $stateParams; + this.uri = $stateParams.uri; + } + $onInit() { + var contentType = this.doc.headers('content-type'); + + var x2js = new X2JS(); + /* jscs: disable */ + if (contentType.lastIndexOf('application/json', 0) === 0) { + /*jshint camelcase: false */ + this.xml = vkbeautify.xml(x2js.js2xml(this.doc.data)); + this.json = this.doc.data; + this.type = 'json'; + } else if (contentType.lastIndexOf('application/xml', 0) === 0) { + this.xml = vkbeautify.xml(this.doc.data); + /*jshint camelcase: false */ + this.json = x2js.xml2js(this.doc.data); + this.type = 'xml'; + /* jscs: enable */ + } else if (contentType.lastIndexOf('text/plain', 0) === 0) { + this.xml = this.doc.data; + this.json = { + 'this.document': this.doc.data + }; + this.type = 'text'; + } else if (contentType.lastIndexOf('application', 0) === 0) { + this.xml = 'Binary object'; + this.json = { + 'this.document type': 'Binary object' + }; + this.type = 'binary'; + } else { + this.xml = 'Error occured determining this.document type.'; + this.json = { + 'Error': 'Error occured determining this.document type.' + }; + } + } +} + +DetailCtrl.$inject = ['$stateParams']; + +export +default DetailCtrl; diff --git a/app/templates/ui/app/components/detail/detail.controller.spec.js b/app/templates/ui/app/components/detail/detail.controller.spec.js new file mode 100644 index 00000000..158910ba --- /dev/null +++ b/app/templates/ui/app/components/detail/detail.controller.spec.js @@ -0,0 +1,75 @@ +/* jshint -W117, -W030 */ +import Module from './detail'; + +import Controller from './detail.controller'; +import Component from './detail.component'; +import Template from './detail.html'; + +describe('Detail', () => { + let $rootScope, $stateParams, controller, scope; + let doc; + + beforeEach(window.module(Module)); + + beforeEach(inject((_$rootScope_, _$stateParams_) => { + $rootScope = _$rootScope_; + $stateParams = _$stateParams_; + scope = $rootScope.$new(); + + })); + + beforeEach(() => { + // stub the current user + $stateParams.uri = '/test/'; + + controller = new Controller($stateParams); + + var headers = 'application/json'; + doc = { + headers: headers, + data: { + name: 'hi' + } + }; + + controller.doc = doc; + }); + + describe('Module', () => { + // top-level specs: i.e., routes, injection, naming + }); + + describe('Controller', () => { + // controller specs + it('should be created successfully', function() { + expect(controller).to.be.defined; + }); + + it('should have the doc data we gave it', function() { + expect(controller.doc.headers).to.eq('application/json'); + expect(controller.doc.data).to.eq(doc.data); + }); + + it('should have correct uri', function() { + expect(controller.$stateParams.uri).to.eq('/test/'); + }); + }); + + describe('Template', () => { + // template specs + // tip: use regex to ensure correct bindings are used e.g., {{ }} + }); + + describe('Component', () => { + // component/directive specs + let component = Component; + + it('includes the intended template', () => { + expect(component.template).to.equal(Template); + }); + + it('invokes the right controller', () => { + expect(component.controller).to.equal(Controller); + }); + }); +}); diff --git a/app/templates/ui/app/detail/detail.html b/app/templates/ui/app/components/detail/detail.html similarity index 50% rename from app/templates/ui/app/detail/detail.html rename to app/templates/ui/app/components/detail/detail.html index b5de742e..ea4766d7 100644 --- a/app/templates/ui/app/detail/detail.html +++ b/app/templates/ui/app/components/detail/detail.html @@ -2,24 +2,24 @@
- {{ctrl.type | uppercase}} + {{$ctrl.type | uppercase}}
- -
{{ctrl.xml}}
+ +
{{$ctrl.xml}}
- Download + Download
- + JSON
- +
- + XML
-
{{ctrl.xml}}
+
{{$ctrl.xml}}
@@ -29,7 +29,7 @@
Similar
- +
diff --git a/app/templates/ui/app/components/detail/detail.js b/app/templates/ui/app/components/detail/detail.js new file mode 100644 index 00000000..ee4e0f80 --- /dev/null +++ b/app/templates/ui/app/components/detail/detail.js @@ -0,0 +1,19 @@ +import angular from 'angular'; +import uiRouter from 'angular-ui-router'; + +import SimilarModule from './similar'; +import DetailComponent from './detail.component'; + +import 'script!ng-json-explorer/dist/angular-json-explorer.min.js'; +import 'ng-json-explorer/dist/angular-json-explorer.css'; + +const module = angular.module('app.detail', [ + SimilarModule, + uiRouter, + 'ngJsonExplorer' + ]) + .component('detail', DetailComponent) + .name; + +export +default module; diff --git a/app/templates/ui/app/detail/similar-directive.html b/app/templates/ui/app/components/detail/similar-component.html similarity index 74% rename from app/templates/ui/app/detail/similar-directive.html rename to app/templates/ui/app/components/detail/similar-component.html index 901f0846..2f70fdbd 100644 --- a/app/templates/ui/app/detail/similar-directive.html +++ b/app/templates/ui/app/components/detail/similar-component.html @@ -1,8 +1,8 @@
-
\ No newline at end of file +
diff --git a/app/templates/ui/app/components/detail/similar.component.js b/app/templates/ui/app/components/detail/similar.component.js new file mode 100644 index 00000000..50dd7be0 --- /dev/null +++ b/app/templates/ui/app/components/detail/similar.component.js @@ -0,0 +1,29 @@ +import template from './similar-component.html'; + +const component = { + bindings: { + uri: '@', + limit: '@' + }, + controller: MlSimilar, + template: template +}; + +export +default component; + +MlSimilar.$inject = ['MLRest']; + +function MlSimilar(mlRest) { + var ctrl = this; + + mlRest.extension('extsimilar', { + method: 'GET', + params: { + 'rs:uri': ctrl.uri, + 'rs:limit': ctrl.limit ? ctrl.limit : 10 + } + }).then(function(response) { + ctrl.similar = response.data.similar; + }); +} diff --git a/app/templates/ui/app/components/detail/similar.js b/app/templates/ui/app/components/detail/similar.js new file mode 100644 index 00000000..fb688610 --- /dev/null +++ b/app/templates/ui/app/components/detail/similar.js @@ -0,0 +1,10 @@ +import angular from 'angular'; + +import mlSimilar from './similar.component'; + +const module = angular.module('app.similar', []) + .component('mlSimilar', mlSimilar) + .name; + +export +default module; diff --git a/app/templates/ui/app/components/error/error.js b/app/templates/ui/app/components/error/error.js new file mode 100644 index 00000000..61c3dd2e --- /dev/null +++ b/app/templates/ui/app/components/error/error.js @@ -0,0 +1,16 @@ +import angular from 'angular'; + +import messageBoard from '../message-board/message-board'; +import ErrorInterceptor from './errorInterceptor.service'; + +const module = angular.module('app.error', [messageBoard]) + .factory('errorInterceptor', ErrorInterceptor) + .config(['$httpProvider', + function($httpProvider) { + $httpProvider.interceptors.push('errorInterceptor'); + } + ]) + .name; + +export +default module; diff --git a/app/templates/ui/app/components/error/errorInterceptor.service.js b/app/templates/ui/app/components/error/errorInterceptor.service.js new file mode 100644 index 00000000..7be2460b --- /dev/null +++ b/app/templates/ui/app/components/error/errorInterceptor.service.js @@ -0,0 +1,30 @@ +ErrorInterceptor.$inject = ['$q', '$injector', 'ngToast']; + +function ErrorInterceptor($q, $injector, toast) { + var errorInterceptor = { + responseError: function(rejection) { + if (rejection.status === 500 || rejection.status === 400) { + var messageBoardService = $injector.get('messageBoardService'); + var msg; + if (rejection.data && rejection.data.errorResponse) { + msg = { + title: rejection.data.errorResponse.status, + body: rejection.data.errorResponse.message + }; + } else { + msg = { + title: (rejection.status === 400) ? 'Bad Request' : 'Internal Server Error' + }; + } + messageBoardService.message(msg); + var toastMsg = '' + msg.title + '

' + msg.body + '

'; + toast.danger(toastMsg); + } + return $q.reject(rejection); + } + }; + return errorInterceptor; +} + +export +default ErrorInterceptor; diff --git a/app/templates/ui/app/landing/landing.html b/app/templates/ui/app/components/landing/landing.html similarity index 100% rename from app/templates/ui/app/landing/landing.html rename to app/templates/ui/app/components/landing/landing.html diff --git a/app/templates/ui/app/login/login-dir.html b/app/templates/ui/app/components/login/login-component.html similarity index 60% rename from app/templates/ui/app/login/login-dir.html rename to app/templates/ui/app/components/login/login-component.html index ba640235..a491de73 100644 --- a/app/templates/ui/app/login/login-dir.html +++ b/app/templates/ui/app/components/login/login-component.html @@ -1,13 +1,13 @@
-
-
Username and/or Password Incorrect
+ +
Username and/or Password Incorrect
- +
- +
- +
diff --git a/app/templates/ui/app/components/login/login-full.component.js b/app/templates/ui/app/components/login/login-full.component.js new file mode 100644 index 00000000..d130aa88 --- /dev/null +++ b/app/templates/ui/app/components/login/login-full.component.js @@ -0,0 +1,12 @@ +import controller from './login-full.controller'; + +import template from './login-full.html'; + +const component = { + bindings: {}, + controller: controller, + template: template +}; + +export +default component; diff --git a/app/templates/ui/app/components/login/login-full.controller.js b/app/templates/ui/app/components/login/login-full.controller.js new file mode 100644 index 00000000..46a49465 --- /dev/null +++ b/app/templates/ui/app/components/login/login-full.controller.js @@ -0,0 +1,24 @@ +LoginFullCtrl.$inject = ['$state', '$stateParams']; + +function LoginFullCtrl($state, $stateParams) { + var ctrl = this; + angular.extend(ctrl, { + showCancel: $stateParams.state !== 'root.landing', + toState: toState + }); + + function toState(user) { + if (user) { + var params = null; + if ($stateParams.params && /^\{.*\}$/.test($stateParams.params)) { + params = JSON.parse($stateParams.params); + } + $state.go($stateParams.state || 'root.landing', params); + } else { + $state.go('root.landing'); + } + } +} + +export +default LoginFullCtrl; diff --git a/app/templates/ui/app/login/login-full.html b/app/templates/ui/app/components/login/login-full.html similarity index 55% rename from app/templates/ui/app/login/login-full.html rename to app/templates/ui/app/components/login/login-full.html index f7301298..38265c9e 100644 --- a/app/templates/ui/app/login/login-full.html +++ b/app/templates/ui/app/components/login/login-full.html @@ -1,7 +1,7 @@
diff --git a/app/templates/ui/app/login/login-modal.html b/app/templates/ui/app/components/login/login-modal.html similarity index 100% rename from app/templates/ui/app/login/login-modal.html rename to app/templates/ui/app/components/login/login-modal.html diff --git a/app/templates/ui/app/components/login/login.component.js b/app/templates/ui/app/components/login/login.component.js new file mode 100644 index 00000000..61f7adda --- /dev/null +++ b/app/templates/ui/app/components/login/login.component.js @@ -0,0 +1,16 @@ +import controller from './login.controller'; + +import template from './login-component.html'; + +const component = { + bindings: { + showCancel: '=', + mode: '@', + callback: '&' + }, + controller: controller, + template: template +}; + +export +default component; diff --git a/app/templates/ui/app/components/login/login.controller.js b/app/templates/ui/app/components/login/login.controller.js new file mode 100644 index 00000000..f8fb98e2 --- /dev/null +++ b/app/templates/ui/app/components/login/login.controller.js @@ -0,0 +1,40 @@ +LoginCtrl.$inject = ['$scope', 'loginService']; + +function LoginCtrl($scope, loginService) { + var ctrl = this; + angular.extend(ctrl, { + username: null, + password: null, + login: login, + loginService: loginService, + logout: logout, + cancel: cancel + }); + + function login() { + loginService.login(ctrl.username, ctrl.password).then(function(user) { + callback(user); + }); + } + + function logout() { + loginService.logout().then(function() { + callback(); + }); + } + + function cancel() { + callback(); + } + + function callback(user) { + if (ctrl.callback && !loginService.loginError()) { + ctrl.callback({ + user: user + }); + } + } +} + +export +default LoginCtrl; diff --git a/app/templates/ui/app/components/login/login.js b/app/templates/ui/app/components/login/login.js new file mode 100644 index 00000000..a45c3c6e --- /dev/null +++ b/app/templates/ui/app/components/login/login.js @@ -0,0 +1,26 @@ +import angular from 'angular'; +import uiBootstrap from 'angular-ui-bootstrap'; +import uiRouter from 'angular-ui-router'; + +import messageBoard from '../message-board/message-board'; + +import loginService from './login.service'; +import AuthInterceptor from './loginInterceptor.service'; +import LoginFullComponent from './login-full.component'; +import LoginComponent from './login.component'; + +const login = angular + .module('app.login', [uiBootstrap, uiRouter, messageBoard]) + .component('loginFull', LoginFullComponent) + .component('login', LoginComponent) + .factory('loginService', loginService) + .factory('authInterceptor', AuthInterceptor) + .config(['$httpProvider', + function($httpProvider) { + $httpProvider.interceptors.push('authInterceptor'); + } + ]) + .name; + +export +default login; diff --git a/app/templates/ui/app/components/login/login.service.js b/app/templates/ui/app/components/login/login.service.js new file mode 100644 index 00000000..5c2dd936 --- /dev/null +++ b/app/templates/ui/app/components/login/login.service.js @@ -0,0 +1,187 @@ +function LoginService($http, $uibModal, $q, $rootScope, $state, + $stateParams, messageBoardService) { + + var _loginMode = 'full'; // 'modal', 'top-right', or 'full' + var _loginError; + var _toStateName; + var _toStateParams; + var _isAuthenticated; + var _protectedRoutes = []; + var deregisterLoginSuccess; + + function loginMode(mode) { + if (mode === undefined) { + return _loginMode; + } + _loginMode = mode; + } + + function failLogin(response) { + if (response.status === 401) { + _loginError = true; + } + } + + function loginError() { + return _loginError; + } + + function getAuthenticatedStatus() { + if (_isAuthenticated) { + return _isAuthenticated; + } + + return $http.get('/api/user/status', {}).then(function(response) { + if (response.data.authenticated === false) { + _isAuthenticated = false; + } else { + loginSuccess(response); + } + return isAuthenticated(); + }); + } + + function loginSuccess(response) { + _loginError = null; + _isAuthenticated = true; + $rootScope.$broadcast('loginService:login-success', response.data); + } + + function login(username, password) { + return $http.post('/api/user/login', { + 'username': username, + 'password': password + }).then(function(response) { + loginSuccess(response); + return response; + }, failLogin); + } + + function loginPrompt() { + var d = $q.defer(); + if (_loginMode === 'modal') { + $uibModal.open({ + controller: ['$uibModalInstance', + function($uibModalInstance) { + var ctrl = this; + ctrl.showCancel = $state.current.name !== 'root.landing'; + ctrl.close = function(user) { + if (user) { + d.resolve(user); + } else { + d.reject(); + $state.go('root.landing'); + } + return $uibModalInstance.close(); + }; + } + ], + controllerAs: 'ctrl', + templateUrl: '/app/login/login-modal.html', + backdrop: 'static', + keyboard: false + }); + } else if (_loginMode === 'top-right') { + messageBoardService.message({ + title: 'Please sign-in to see content.' + }); + var deregisterMsgBoard = $rootScope.$on('loginService:login-success', function(e, user) { + messageBoardService.message(null); + d.resolve(user); + deregisterMsgBoard(); + }); + } else { + $state.go('root.login', { + 'state': _toStateName || $state.current.name, + 'params': JSON.stringify((_toStateParams || $stateParams)) + }).then(function() { + d.reject(); + }); + } + return d.promise; + } + + function logout() { + return $http.get('/api/user/logout').then(function(response) { + $rootScope.$broadcast('loginService:logout-success', response); + _loginError = null; + _isAuthenticated = false; + $state.reload(); + return response; + }); + } + + function isAuthenticated() { + return _isAuthenticated; + } + + function protectedRoutes(routes) { + if (routes === undefined) { + return _protectedRoutes; + } + _protectedRoutes = routes; + } + + function routeIsProtected(route) { + return _protectedRoutes.indexOf(route) > -1; + } + + function blockRoute(event, next, nextParams) { + event.preventDefault(); + loginPrompt(); + if (_loginMode !== 'full') { + if (deregisterLoginSuccess) { + deregisterLoginSuccess(); + deregisterLoginSuccess = null; + } + deregisterLoginSuccess = $rootScope.$on('loginService:login-success', function() { + deregisterLoginSuccess(); + deregisterLoginSuccess = null; + $state.go(next.name, nextParams); + }); + } + } + + $rootScope.$on('$stateChangeStart', function(event, next, nextParams) { + if (next.name !== 'root.login') { + _toStateName = next.name; + _toStateParams = nextParams; + } + + if (routeIsProtected(next.name)) { + var auth = getAuthenticatedStatus(); + + if (angular.isFunction(auth.then)) { + auth.then(function() { + if (!isAuthenticated()) { + //this does NOT block requests in a timely fashion... + blockRoute(event, next, nextParams); + } + }); + } else { + if (!auth) { + blockRoute(event, next, nextParams); + } + } + + } + }); + + return { + login: login, + logout: logout, + loginPrompt: loginPrompt, + loginError: loginError, + loginMode: loginMode, + isAuthenticated: isAuthenticated, + getAuthenticatedStatus: getAuthenticatedStatus, + protectedRoutes: protectedRoutes + }; +} + +LoginService.$inject = ['$http', '$uibModal', '$q', '$rootScope', '$state', + '$stateParams', 'messageBoardService' +]; + +export +default LoginService; diff --git a/app/templates/ui/app/login/login.service.spec.js b/app/templates/ui/app/components/login/login.service.spec.js similarity index 77% rename from app/templates/ui/app/login/login.service.spec.js rename to app/templates/ui/app/components/login/login.service.spec.js index a6e7c547..f052f4f5 100644 --- a/app/templates/ui/app/login/login.service.spec.js +++ b/app/templates/ui/app/components/login/login.service.spec.js @@ -1,8 +1,7 @@ /* jshint -W117, -W030 */ -(function () { - 'use strict'; +(function() { - describe('Service: loginService', function () { + describe('Service: loginService', function() { var service; var modalOpened = false; @@ -23,9 +22,15 @@ }); bard.mockService($state, { - current: { name: 'root.search', params: {} }, + current: { + name: 'root.search', + params: {} + }, go: function(stateName, stateParams) { - this.current = { name: stateName, params: stateParams }; + this.current = { + name: stateName, + params: stateParams + }; return $q.when(); } }); @@ -33,19 +38,21 @@ bard.mockService($uibModal, { open: function() { modalOpened = true; - return { result: angular.noop }; + return { + result: angular.noop + }; } }); }); - beforeEach(inject(function (_loginService_) { + beforeEach(inject(function(_loginService_) { service = _loginService_; modalOpened = false; })); - it('should be defined', function () { + it('should be defined', function() { expect(service).to.be.defined; }); @@ -68,7 +75,7 @@ }); }); - describe('Service: loginService - should be authenticated if logged in already', function () { + describe('Service: loginService - should be authenticated if logged in already', function() { var service; var modalOpened = false; @@ -89,9 +96,15 @@ }); bard.mockService($state, { - current: { name: 'root.search', params: {} }, + current: { + name: 'root.search', + params: {} + }, go: function(stateName, stateParams) { - this.current = { name: stateName, params: stateParams }; + this.current = { + name: stateName, + params: stateParams + }; return $q.when(); } }); @@ -99,13 +112,15 @@ bard.mockService($uibModal, { open: function() { modalOpened = true; - return { result: angular.noop }; + return { + result: angular.noop + }; } }); }); - beforeEach(inject(function (_loginService_) { + beforeEach(inject(function(_loginService_) { service = _loginService_; modalOpened = false; @@ -116,7 +131,7 @@ $rootScope.$apply(); })); - it('should be defined', function () { + it('should be defined', function() { expect(service).to.be.defined; }); diff --git a/app/templates/ui/app/components/login/loginInterceptor.service.js b/app/templates/ui/app/components/login/loginInterceptor.service.js new file mode 100644 index 00000000..25169e77 --- /dev/null +++ b/app/templates/ui/app/components/login/loginInterceptor.service.js @@ -0,0 +1,64 @@ +AuthInterceptor.$inject = ['$q', '$rootScope', '$injector']; + +function AuthInterceptor($q, $rootScope, $injector) { + var waitingForLoginDefer; + var waitingLineForLoginDefer = 0; + + function reduceWaitingLine() { + waitingLineForLoginDefer--; + if (waitingLineForLoginDefer <= 0) { + waitingForLoginDefer = null; + } + } + $rootScope.$on('$stateChangeSuccess', function() { + waitingLineForLoginDefer = 0; + waitingForLoginDefer = null; + }); + + var authInterceptor = { + request: function(config) { + if (/\/v1\//.test(config.url) && waitingForLoginDefer) { + // hold off on additional REST API calls until login is resolved + waitingLineForLoginDefer++; + return waitingForLoginDefer.promise.then(function() { + reduceWaitingLine(); + return config; + }, + function() { + reduceWaitingLine(); + }); + } + return config; + }, + responseError: function(rejection) { + // Not logged in or session has expired + var userService = $injector.get('userService'); + if (rejection.status === 419 || rejection.status === 401 && + !waitingForLoginDefer && !userService.currentUser() && + !/\/api\/user\/login/.test(rejection.config.url)) { + var loginService = $injector.get('loginService'); + var $http = $injector.get('$http'); + waitingForLoginDefer = $q.defer(); + waitingLineForLoginDefer = 1; + + // We use login method that logs the user in using the current credentials and + // returns a promise + loginService.loginPrompt().then( + waitingForLoginDefer.resolve, waitingForLoginDefer.reject); + // When the session recovered, make the same backend call again and chain the request + return waitingForLoginDefer.promise.then(function() { + reduceWaitingLine(); + return $http(rejection.config); + }, + function() { + reduceWaitingLine(); + }); + } + return $q.reject(rejection); + } + }; + return authInterceptor; +} + +export +default AuthInterceptor; diff --git a/app/templates/ui/app/components/message-board/message-board-component.html b/app/templates/ui/app/components/message-board/message-board-component.html new file mode 100644 index 00000000..484e518f --- /dev/null +++ b/app/templates/ui/app/components/message-board/message-board-component.html @@ -0,0 +1,4 @@ +
+

{{$ctrl.msg.title || $ctrl.msg}}

+

{{$ctrl.msg.body}}

+
diff --git a/app/templates/ui/app/components/message-board/message-board.component.js b/app/templates/ui/app/components/message-board/message-board.component.js new file mode 100644 index 00000000..9e1fa5bf --- /dev/null +++ b/app/templates/ui/app/components/message-board/message-board.component.js @@ -0,0 +1,35 @@ +import template from './message-board-component.html'; + +const messageBoardComponent = { + bindings: { + msg: '=' + }, + controller: MessageBoardController, + template: template, +}; + +MessageBoardController.$inject = ['$scope']; + +function MessageBoardController($scope) { + var ctrl = this; + angular.extend(ctrl, { + isObject: isObject, + collapseRaw: collapseRaw, + expandRaw: expandRaw + }); + + function isObject(item) { + return _.isObject(item); + } + + function collapseRaw() { + ctrl.showRaw = false; + } + + function expandRaw() { + ctrl.showRaw = true; + } +} + +export +default messageBoardComponent; diff --git a/app/templates/ui/app/components/message-board/message-board.js b/app/templates/ui/app/components/message-board/message-board.js new file mode 100644 index 00000000..d413cd62 --- /dev/null +++ b/app/templates/ui/app/components/message-board/message-board.js @@ -0,0 +1,13 @@ +import angular from 'angular'; + +import MessageBoardService from './message-board.service'; +import MessageBoardComponent from './message-board.component'; + +const messageBoard = angular + .module('app.messageBoard', []) + .factory('messageBoardService', MessageBoardService) + .component('messageBoard', MessageBoardComponent) + .name; + +export +default messageBoard; diff --git a/app/templates/ui/app/components/message-board/message-board.service.js b/app/templates/ui/app/components/message-board/message-board.service.js new file mode 100644 index 00000000..d2b328f3 --- /dev/null +++ b/app/templates/ui/app/components/message-board/message-board.service.js @@ -0,0 +1,28 @@ +MessageBoardService.$inject = ['$rootScope']; + +function MessageBoardService($rootScope) { + var _message = null; + + function message(msg) { + if (msg === undefined) { + return _message; + } + _message = msg; + if (message) { + $rootScope.$broadcast('message-board:set', _message); + } else { + $rootScope.$broadcast('message-board:cleared'); + } + } + + $rootScope.$on('$stateChangeSuccess', function() { + message(null); + }); + + return { + message: message + }; +} + +export +default MessageBoardService; diff --git a/app/templates/ui/app/search/search-results.html b/app/templates/ui/app/components/search/search-results.html similarity index 100% rename from app/templates/ui/app/search/search-results.html rename to app/templates/ui/app/components/search/search-results.html diff --git a/app/templates/ui/app/components/search/search.component.js b/app/templates/ui/app/components/search/search.component.js new file mode 100644 index 00000000..38c8a7ef --- /dev/null +++ b/app/templates/ui/app/components/search/search.component.js @@ -0,0 +1,11 @@ +import template from './search.html'; +import controller from './search.controller'; + +const component = { + bindings: {}, + controller: controller, + template: template +}; + +export +default component; diff --git a/app/templates/ui/app/components/search/search.controller.js b/app/templates/ui/app/components/search/search.controller.js new file mode 100644 index 00000000..afc44619 --- /dev/null +++ b/app/templates/ui/app/components/search/search.controller.js @@ -0,0 +1,36 @@ +/* globals MLSearchController: false */ +//import statement is more readable than a global but results in larger interface files since it needs to be included again +//import MLSearchController from 'exports?MLSearchController!ml-search-ng/dist/ml-search-ng'; + +import searchResultsTemplate from './search-results.html'; + +class SearchCtrl extends MLSearchController { + constructor($scope, $location, userService, searchFactory) { + + super($scope, $location, searchFactory.newContext()); + + this.$scope = $scope; + this.$location = $location; + this.userService = userService; + this.searchFactory = searchFactory; + + this.init(); + + //retrieve and insert searchResults template + this.searchResultsTemplate = searchResultsTemplate; + + this.$scope.$watch(this.userService.currentUser, (newValue, oldValue) => { + this.currentUser = newValue; + }); + } + + setSnippet(type) { + this.mlSearch.setSnippet(type); + this.search(); + } +} + +SearchCtrl.$inject = ['$scope', '$location', 'userService', 'MLSearchFactory']; + +export +default SearchCtrl; diff --git a/app/templates/ui/app/components/search/search.controller.spec.js b/app/templates/ui/app/components/search/search.controller.spec.js new file mode 100644 index 00000000..7437358a --- /dev/null +++ b/app/templates/ui/app/components/search/search.controller.spec.js @@ -0,0 +1,201 @@ +/* jshint -W117, -W030 */ +import Module from './search'; + +import Controller from './search.controller'; +import Component from './search.component'; +import Template from './search.html'; + +//import MLSearchController from 'exports?MLSearchController!ml-search-ng/dist/ml-search-ng'; + +describe('Search', () => { + + let sandbox, $httpBackend; + let $rootScope, controller, scope; + let $q, $location, MLSearchFactory, userService, MLRest; + + var currentUser = null; + /* + var results = [{ + uri: 'abc' + }, { + uri: 'def' + }]; + */ + beforeEach(window.module(Module)); + //jscs:disable maximumLineLength + beforeEach(inject((_$q_, _$rootScope_, _$location_, _MLSearchFactory_, _userService_, _$httpBackend_, _MLRest_) => { + //jscs:enable maximumLineLength + $q = _$q_; + $rootScope = _$rootScope_; + scope = $rootScope.$new(); + + $location = _$location_; + MLSearchFactory = _MLSearchFactory_; + userService = _userService_; + $httpBackend = _$httpBackend_; + MLRest = _MLRest_; + + sandbox = sinon.sandbox.create(); + + MLRest = sinon.stub(MLRest, 'search', function() { + return $q.when({ + data: { + results: [{ + metadata: 'test', + uri: 'abc' + }, { + metadata: 'test', + uri: 'def' + }] + } + }); + }); + + sandbox.stub(userService, 'currentUser', () => $q.when(currentUser)); + + /* + //atempts to mock up search/mlRest method + sandbox.stub(MLSearchController.prototype, 'search', () => { + return [{ + metadata: 'test', + uri: 'abc' + }, { + metadata: 'test', + uri: 'def' + }]; + }); + + server.respondWith('GET', /./, [200, { + 'Content-Type': 'application/json' + }, `{ + data: { + results: [{ + metadata: 'test', + uri: 'abc' + }, { + metadata: 'test', + uri: 'def' + }] + } + }`]); + */ + + })); + + beforeEach(() => { + controller = new Controller(scope, $location, userService, MLSearchFactory); + }); + + afterEach(() => { + sandbox.restore(); + }); + + describe('Module', () => { + // top-level specs: i.e., routes, injection, naming + }); + + describe('Controller', () => { + // controller specs + it('should be created successfully', function() { + expect(controller).to.be.defined; + }); + + it('should update the current user if it changes', function() { + expect(controller.currentUser).to.not.be.defined; + }); + + /* + //test currently not working + it('should run a search', function() { + + + controller.search('stuff'); + $rootScope.$digest(); + + //$httpBackend.flush(); + + expect(controller.response.results).to.eq(results); + }); + */ + }); + + describe('Template', () => { + // template specs + // tip: use regex to ensure correct bindings are used e.g., {{ }} + }); + + describe('Component', () => { + // component/directive specs + let component = Component; + + it('includes the intended template', () => { + expect(component.template).to.equal(Template); + }); + + it('invokes the right controller', () => { + expect(component.controller).to.equal(Controller); + }); + }); +}); + +//Original test +/* jshint -W117, -W030 */ +/* +(function() { + 'use strict'; + + describe('Controller: SearchCtrl', function() { + + var controller; + + var currentUser = null; + + var results = [{ + uri: 'abc' + }, { + uri: 'def' + }]; + + beforeEach(function() { + bard.appModule('app.search'); + bard.inject('$controller', '$q', '$rootScope', '$location', + 'userService', 'MLSearchFactory', 'MLRest'); + + bard.mockService(userService, { + currentUser: $q.when(currentUser) + }); + + + bard.mockService(MLRest, { + search: $q.when({ + data: { + results: results + } + }) + }); + + }); + + beforeEach(function() { + controller = $controller('SearchCtrl', { + $scope: $rootScope.$new() + }); + $rootScope.$apply(); + }); + + it('should be created successfully', function() { + expect(controller).to.be.defined; + }); + + it('should update the current user if it changes', function() { + expect(controller.currentUser).to.not.be.defined; + }); + + it('should run a search', function() { + controller.search('stuff'); + $rootScope.$apply(); + expect(controller.response.results).to.eq(results); + }); + }); +}()); +*/ diff --git a/app/templates/ui/app/components/search/search.html b/app/templates/ui/app/components/search/search.html new file mode 100644 index 00000000..09518dc9 --- /dev/null +++ b/app/templates/ui/app/components/search/search.html @@ -0,0 +1,23 @@ +
Please log in to see content
+
+
+ +
+
+ + diff --git a/app/templates/ui/app/components/search/search.js b/app/templates/ui/app/components/search/search.js new file mode 100644 index 00000000..7f773751 --- /dev/null +++ b/app/templates/ui/app/components/search/search.js @@ -0,0 +1,19 @@ +import angular from 'angular'; + +//run dependencies that haven't been designed for npm/imports globally as if they were in a script tag and load if necessary +import 'script!ml-common-ng/dist/ml-common-ng'; +import 'script!ml-search-ng/dist/ml-search-ng-tpls'; +import 'script!ml-search-ng/dist/ml-search-ng'; + +import SearchComponent from './search.component'; + +import User from '../user/user'; +import Snippet from './snippet'; + +const Search = angular + .module('app.search', ['ml.search', User, Snippet]) + .component('search', SearchComponent) + .name; + +export +default Search; diff --git a/app/templates/ui/app/components/search/snippet.component.js b/app/templates/ui/app/components/search/snippet.component.js new file mode 100644 index 00000000..800246d3 --- /dev/null +++ b/app/templates/ui/app/components/search/snippet.component.js @@ -0,0 +1,30 @@ +SnippetController.$inject = ['$scope']; + +function SnippetController($scope) { + var ctrl = this; + ctrl.snippets = ['compact', 'detailed']; + + angular.extend(ctrl, { + setSnippetType: setSnippetType + }); + + function setSnippetType(type) { + ctrl.snippetType = type; + ctrl.setSnippet({ + type: type + }); + } +} + +import template from './snippet.html'; + +const component = { + bindings: { + setSnippet: '&' + }, + controller: SnippetController, + template: template +}; + +export +default component; diff --git a/app/templates/ui/app/search/snippet.html b/app/templates/ui/app/components/search/snippet.html similarity index 53% rename from app/templates/ui/app/search/snippet.html rename to app/templates/ui/app/components/search/snippet.html index d355693f..93600755 100644 --- a/app/templates/ui/app/search/snippet.html +++ b/app/templates/ui/app/components/search/snippet.html @@ -1,10 +1,10 @@
diff --git a/app/templates/ui/app/components/search/snippet.js b/app/templates/ui/app/components/search/snippet.js new file mode 100644 index 00000000..d43d8c21 --- /dev/null +++ b/app/templates/ui/app/components/search/snippet.js @@ -0,0 +1,11 @@ +import angular from 'angular'; + +import mlSnippet from './snippet.component'; + +const Snippet = + angular.module('app.snippet', []) + .component('mlSnippet', mlSnippet) + .name; + +export +default Snippet; diff --git a/app/templates/ui/app/components/user/mlUser.component.js b/app/templates/ui/app/components/user/mlUser.component.js new file mode 100644 index 00000000..833daf60 --- /dev/null +++ b/app/templates/ui/app/components/user/mlUser.component.js @@ -0,0 +1,28 @@ +import template from './user-component.html'; + +UserController.$inject = ['$scope', 'userService', 'loginService']; + +function UserController($scope, userService, loginService) { + var ctrl = this; + angular.extend(ctrl, { + username: null, + password: null, + loginService: loginService + }); + $scope.$watch(userService.currentUser, function(newValue) { + ctrl.currentUser = newValue; + }); +} + +const component = { + bindings: { + showCancel: '=', + mode: '@', + callback: '&' + }, + controller: UserController, + template: template +}; + +export +default component; diff --git a/app/templates/ui/app/components/user/profile.component.js b/app/templates/ui/app/components/user/profile.component.js new file mode 100644 index 00000000..61fd458e --- /dev/null +++ b/app/templates/ui/app/components/user/profile.component.js @@ -0,0 +1,12 @@ +import ProfileCtrl from './profile.controller'; + +import template from './profile.html'; + +const component = { + bindings: {}, + controller: ProfileCtrl, + template: template +}; + +export +default component; diff --git a/app/templates/ui/app/components/user/profile.controller.js b/app/templates/ui/app/components/user/profile.controller.js new file mode 100644 index 00000000..d1cdb6a7 --- /dev/null +++ b/app/templates/ui/app/components/user/profile.controller.js @@ -0,0 +1,68 @@ +ProfileCtrl.$inject = ['$scope', '$state', 'MLRest', 'userService', 'ngToast']; + +function ProfileCtrl($scope, $state, mlRest, userService, toast) { + var ctrl = this; + angular.extend(ctrl, { + user: null, + newEmail: '', + addEmail: addEmail, + removeEmail: removeEmail, + submit: submit + }); + + function addEmail() { + if (!ctrl.newEmail || hasEmailInputError()) { + return; + } + if (!ctrl.user.emails) { + ctrl.user.emails = []; + } + ctrl.user.emails.push(ctrl.newEmail.trim()); + ctrl.newEmail = ''; + } + + function removeEmail(index) { + ctrl.user.emails.splice(index, 1); + } + + function hasEmailInputError() { + try { + return $scope.profileForm.newEmail.$error.email === true; + } catch (e) { + return false; + } + } + + function submit(form) { + if (form.$valid) { + addEmail(); + + if (ctrl.user.emails) { + _.pull(ctrl.user.emails, ''); + } + + mlRest.updateDocument({ + user: { + 'fullname': ctrl.user.fullname, + 'emails': ctrl.user.emails + } + }, { + format: 'json', + uri: '/api/users/' + ctrl.user.name + '.json' + // TODO: add read/update permissions here like this: + // 'perm:sample-role': 'read', + // 'perm:sample-role': 'update' + }).then(function(data) { + toast.success('Submitted'); + $state.go('root'); + }); + } + } + + $scope.$watch(userService.currentUser, function(newValue) { + ctrl.user = newValue; + }); +} + +export +default ProfileCtrl; diff --git a/app/templates/ui/app/user/profile.controller.spec.js b/app/templates/ui/app/components/user/profile.controller.spec.js similarity index 58% rename from app/templates/ui/app/user/profile.controller.spec.js rename to app/templates/ui/app/components/user/profile.controller.spec.js index 7b05e913..2f52325d 100644 --- a/app/templates/ui/app/user/profile.controller.spec.js +++ b/app/templates/ui/app/components/user/profile.controller.spec.js @@ -1,23 +1,22 @@ /* jshint -W117, -W030 */ -(function () { - 'use strict'; +import Module from './user'; - describe('Controller: ProfileCtrl', function () { +import Controller from './profile.controller'; +(function() { + + describe('Controller: ProfileCtrl', function() { var controller; var currentUser = { - profile: { - emails: [] - } + emails: [] }; var currentState; beforeEach(function() { - bard.appModule('app.user'); - bard.inject('$controller', '$q', '$rootScope', 'MLRest', '$state', 'userService', - 'loginService'); + bard.appModule(Module); + bard.inject('$controller', '$q', '$rootScope', 'MLRest', '$state', 'userService'); bard.mockService(MLRest, { _default: $q.when([]), @@ -36,67 +35,67 @@ } }); - bard.mockService(loginService, { - getAuthenticatedStatus: $q.when() - }); - }); - beforeEach(function () { - controller = $controller('ProfileCtrl', { + beforeEach(function() { + controller = $controller(Controller, { $scope: $rootScope.$new() }); $rootScope.$apply(); }); - it('should be created successfully', function () { + it('should be created successfully', function() { expect(controller).to.be.defined; }); it('should not add a blank email', function() { var newEmail = ''; controller.newEmail = newEmail; - expect(controller.user.profile.emails.length).to.eq(0); + expect(controller.user.emails.length).to.eq(0); controller.addEmail(); $rootScope.$apply(); - expect(controller.user.profile.emails.length).to.eq(0); + expect(controller.user.emails.length).to.eq(0); }); it('should add a nonblank email', function() { var newEmail = 'test@test.com'; controller.newEmail = newEmail; - expect(controller.user.profile.emails.length).to.eq(0); + expect(controller.user.emails.length).to.eq(0); controller.addEmail(); $rootScope.$apply(); - expect(controller.user.profile.emails.length).to.eq(1); - expect(controller.user.profile.emails[0]).to.eq(newEmail); + expect(controller.user.emails.length).to.eq(1); + expect(controller.user.emails[0]).to.eq(newEmail); }); it('should remove an email', function() { - controller.user.profile.emails = [ + controller.user.emails = [ 'abc@def.com', 'def@ghi.com' ]; - expect(controller.user.profile.emails.length).to.eq(2); + expect(controller.user.emails.length).to.eq(2); controller.removeEmail(1); $rootScope.$apply(); - expect(controller.user.profile.emails.length).to.eq(1); - expect(controller.user.profile.emails[0]).to.eq('abc@def.com'); + expect(controller.user.emails.length).to.eq(1); + expect(controller.user.emails[0]).to.eq('abc@def.com'); }); it('should not update the profile if form errors', function() { - var form = {$valid:false}; + var form = { + $valid: false + }; controller.submit(form); $rootScope.$apply(); expect(currentState).to.not.be.defined; }); it('should update the profile', function() { - var form = {$valid:true}; + var form = { + $valid: true + }; controller.submit(form); $rootScope.$apply(); - expect(currentState).to.eq('root.landing'); + expect(currentState).to.eq('root'); }); }); diff --git a/app/templates/ui/app/user/profile.html b/app/templates/ui/app/components/user/profile.html similarity index 63% rename from app/templates/ui/app/user/profile.html rename to app/templates/ui/app/components/user/profile.html index f3ef3de5..774f44f5 100644 --- a/app/templates/ui/app/user/profile.html +++ b/app/templates/ui/app/components/user/profile.html @@ -1,7 +1,7 @@ -
+
-

Edit profile of {{ctrl.user.username}}

+

Edit your profile

@@ -10,7 +10,7 @@

Edit profile of {{ctrl.user.username}}

- +
@@ -21,10 +21,10 @@

Edit profile of {{ctrl.user.username}}

- +
-
@@ -34,21 +34,21 @@

Edit profile of {{ctrl.user.username}}

-
+
-
+
- Cancel - + Cancel +
diff --git a/app/templates/ui/app/components/user/user-component.html b/app/templates/ui/app/components/user/user-component.html new file mode 100644 index 00000000..6c42175a --- /dev/null +++ b/app/templates/ui/app/components/user/user-component.html @@ -0,0 +1,16 @@ +
+ + + +
+ +
+
diff --git a/app/templates/ui/app/components/user/user.js b/app/templates/ui/app/components/user/user.js new file mode 100644 index 00000000..987863ea --- /dev/null +++ b/app/templates/ui/app/components/user/user.js @@ -0,0 +1,21 @@ +import angular from 'angular'; +import ngSanitize from 'angular-sanitize'; + +import UserService from './user.service'; +import ProfileComponent from './profile.component'; +import MlUserComponent from './mlUser.component'; + +import Login from '../login/login'; + +//run globally since not in an es6 module +import 'script!ml-common-ng/dist/ml-common-ng'; +import 'script!ng-toast'; + +const user = angular.module('app.user', ['ml.common', 'ngToast', ngSanitize, Login]) + .factory('userService', UserService) + .component('profile', ProfileComponent) + .component('mlUser', MlUserComponent) + .name; + +export +default user; diff --git a/app/templates/ui/app/components/user/user.service.js b/app/templates/ui/app/components/user/user.service.js new file mode 100644 index 00000000..286cbf23 --- /dev/null +++ b/app/templates/ui/app/components/user/user.service.js @@ -0,0 +1,63 @@ +import _ from 'lodash'; + +UserService.$inject = ['$rootScope', 'loginService']; + +function UserService($rootScope, loginService) { + var _currentUser = null; + + function currentUser() { + return _currentUser; + } + + function getUser() { + if (_currentUser) { + return _currentUser; + } + + return loginService.getAuthenticatedStatus().then(currentUser); + } + + function updateUser(response) { + var data = response.data; + + if (data.authenticated === false) { + return null; + } + + _currentUser = { + name: data.username, + }; + + if (data.profile) { + _currentUser.hasProfile = true; + _currentUser.fullname = data.profile.fullname; + + if (_.isArray(data.profile.emails)) { + _currentUser.emails = data.profile.emails; + } else if (data.profile.emails) { + // wrap single value in array, needed for repeater + _currentUser.emails = [data.profile.emails]; + } + } + + return _currentUser; + } + + $rootScope.$on('loginService:login-success', function(e, user) { + updateUser({ + data: user + }); + }); + + $rootScope.$on('loginService:logout-success', function() { + _currentUser = null; + }); + + return { + currentUser: currentUser, + getUser: getUser + }; +} + +export +default UserService; diff --git a/app/templates/ui/app/user/user.service.spec.js b/app/templates/ui/app/components/user/user.service.spec.js similarity index 77% rename from app/templates/ui/app/user/user.service.spec.js rename to app/templates/ui/app/components/user/user.service.spec.js index 93378d93..c82c6205 100644 --- a/app/templates/ui/app/user/user.service.spec.js +++ b/app/templates/ui/app/components/user/user.service.spec.js @@ -1,6 +1,5 @@ /* jshint -W117, -W030 */ (function() { - 'use strict'; describe('Service: userService', function() { @@ -23,9 +22,15 @@ }); bard.mockService($state, { - current: { name: 'root.search', params: {} }, + current: { + name: 'root.search', + params: {} + }, go: function(stateName, stateParams) { - this.current = { name: stateName, params: stateParams }; + this.current = { + name: stateName, + params: stateParams + }; return $q.when(); }, reload: function() { @@ -34,7 +39,7 @@ }); bard.mockService(loginService, { - getAuthenticatedStatus: $q.when() + getAuthenticatedStatus: $q.when(), }); }); @@ -62,22 +67,26 @@ }); it('should update the current user when logged in using loginService', function(done) { - $rootScope.$broadcast('loginService:login-success', {data:_user}); + $rootScope.$broadcast('loginService:login-success', { + data: _user + }); $rootScope.$apply(service); done(); expect(service.currentUser().name).to.eq('bob'); }); - it('should not set user with invalid credentials', function () { + it('should not set user with invalid credentials', function() { _user.data.authenticated = false; - $rootScope.$broadcast('loginService:login-success', {data:_user}); + $rootScope.$broadcast('loginService:login-success', { + data: _user + }); $rootScope.$apply(service); expect(service.currentUser().name).to.eq(undefined); }); - it('should clear user after logout', function () { + it('should clear user after logout', function() { $rootScope.$broadcast('loginService:logout-success'); $rootScope.$apply(service); diff --git a/app/templates/ui/app/create/create.controller.js b/app/templates/ui/app/create/create.controller.js deleted file mode 100644 index 7e78999b..00000000 --- a/app/templates/ui/app/create/create.controller.js +++ /dev/null @@ -1,78 +0,0 @@ -(function () { - 'use strict'; - - angular.module('app.create') - .controller('CreateCtrl', CreateCtrl); - - CreateCtrl.$inject = ['$scope', 'MLRest', '$state', 'userService', 'ngToast']; - - function CreateCtrl($scope, mlRest, $state, userService, toast) { - var ctrl = this; - - angular.extend(ctrl, { - person: { - isActive: true, - balance: 0, - picture: 'http://placehold.it/32x32', - age: 0, - eyeColor: null, - name: null, - gender: null, - company: null, - email: null, - phone: null, - address: null, - about: null, - registered: null, - location: { - latitude: 0, - longitude: 0 - }, - tags: [], - friends: [], - greeting: null, - favoriteFruit: null - }, - newTag: null, - currentUser: null, - editorOptions: { - plugins : 'advlist autolink link image lists charmap print preview' - }, - submit: submit, - addTag: addTag, - removeTag: removeTag - }); - - function submit() { - mlRest.createDocument(ctrl.person, { - format: 'json', - directory: '/content/', - extension: '.json', - collection: ['data', 'data/people'] - // TODO: add read/update permissions here like this: - // 'perm:sample-role': 'read', - // 'perm:sample-role': 'update' - }).then(function(response) { - toast.success('Created'); - $state.go('root.view', { uri: response.replace(/(.*\?uri=)/, '') }); - }, function(response) { - toast.danger(response.data); - }); - } - - function addTag() { - if (ctrl.newTag && ctrl.newTag !== '' && ctrl.person.tags.indexOf(ctrl.newTag) < 0) { - ctrl.person.tags.push(ctrl.newTag); - } - ctrl.newTag = null; - } - - function removeTag(index) { - ctrl.person.tags.splice(index, 1); - } - - $scope.$watch(userService.currentUser, function(newValue) { - ctrl.currentUser = newValue; - }); - } -}()); diff --git a/app/templates/ui/app/create/create.controller.spec.js b/app/templates/ui/app/create/create.controller.spec.js deleted file mode 100644 index 24ddccf5..00000000 --- a/app/templates/ui/app/create/create.controller.spec.js +++ /dev/null @@ -1,59 +0,0 @@ -/* jshint -W117, -W030 */ -(function () { - 'use strict'; - - describe('Controller: CreateCtrl', function () { - - var controller; - var nextState; - - beforeEach(function() { - bard.appModule('app.create'); - bard.inject('$controller', '$q', '$rootScope', 'MLRest', '$state', 'userService'); - - bard.mockService(MLRest, { - createDocument: $q.when('/?uri=blah') - }); - - bard.mockService($state, { - go: function(state, params) { - nextState = { - state: state, - params: params - }; - } - }); - }); - - beforeEach(function () { - // stub the current user - controller = $controller('CreateCtrl', { $scope: $rootScope.$new() }); - $rootScope.$apply(); - }); - - it('should be created successfully', function () { - expect(controller).to.be.defined; - }); - - it('should add tags', function() { - var tagValue = 'testTag'; - expect(controller.person.tags.length).to.eq(0); - controller.newTag = tagValue; - controller.addTag(); - expect(controller.person.tags.length).to.eq(1); - expect(controller.person.tags[0]).to.eq(tagValue); - expect(controller.newTag).to.eq.null; - }); - - it('should show the detail view when submitted', function() { - controller.submit(); - $rootScope.$apply(); - expect(nextState).to.deep.eq({ - state: 'root.view', - params: { - uri: 'blah' - } - }); - }); - }); -}()); diff --git a/app/templates/ui/app/create/create.module.js b/app/templates/ui/app/create/create.module.js deleted file mode 100644 index 54f31d29..00000000 --- a/app/templates/ui/app/create/create.module.js +++ /dev/null @@ -1,5 +0,0 @@ -(function () { - 'use strict'; - - angular.module('app.create', ['ml.common', 'app.user', 'ngToast']); -}()); diff --git a/app/templates/ui/app/detail/detail.controller.js b/app/templates/ui/app/detail/detail.controller.js deleted file mode 100644 index 0be9d3bd..00000000 --- a/app/templates/ui/app/detail/detail.controller.js +++ /dev/null @@ -1,46 +0,0 @@ -/* global X2JS,vkbeautify */ -(function () { - 'use strict'; - angular.module('app.detail') - .controller('DetailCtrl', DetailCtrl); - - DetailCtrl.$inject = ['doc', '$stateParams']; - function DetailCtrl(doc, $stateParams) { - var ctrl = this; - - var uri = $stateParams.uri; - - var contentType = doc.headers('content-type'); - - var x2js = new X2JS(); - /* jscs: disable */ - if (contentType.lastIndexOf('application/json', 0) === 0) { - /*jshint camelcase: false */ - ctrl.xml = vkbeautify.xml(x2js.json2xml_str(doc.data)); - ctrl.json = doc.data; - ctrl.type = 'json'; - } else if (contentType.lastIndexOf('application/xml', 0) === 0) { - ctrl.xml = vkbeautify.xml(doc.data); - /*jshint camelcase: false */ - ctrl.json = x2js.xml_str2json(doc.data); - ctrl.type = 'xml'; - /* jscs: enable */ - } else if (contentType.lastIndexOf('text/plain', 0) === 0) { - ctrl.xml = doc.data; - ctrl.json = {'Document' : doc.data}; - ctrl.type = 'text'; - } else if (contentType.lastIndexOf('application', 0) === 0 ) { - ctrl.xml = 'Binary object'; - ctrl.json = {'Document type' : 'Binary object'}; - ctrl.type = 'binary'; - } else { - ctrl.xml = 'Error occured determining document type.'; - ctrl.json = {'Error' : 'Error occured determining document type.'}; - } - - angular.extend(ctrl, { - doc : doc.data, - uri : uri - }); - } -}()); diff --git a/app/templates/ui/app/detail/detail.controller.spec.js b/app/templates/ui/app/detail/detail.controller.spec.js deleted file mode 100644 index e2997317..00000000 --- a/app/templates/ui/app/detail/detail.controller.spec.js +++ /dev/null @@ -1,37 +0,0 @@ -/* jshint -W117, -W030 */ -(function () { - 'use strict'; - - describe('Controller: DetailCtrl', function () { - - var controller; - var doc; - - beforeEach(function() { - bard.appModule('app.detail'); - bard.inject('$controller', '$rootScope'); - }); - - beforeEach(function () { - // stub the document - var headers = function() { return 'application/json'; }; - doc = { - headers: headers, - data: { - name: 'hi' - } - }; - controller = $controller('DetailCtrl', { doc: doc }); - $rootScope.$apply(); - }); - - it('should be created successfully', function () { - expect(controller).to.be.defined; - }); - - it('should have the doc data we gave it', function() { - expect(controller.doc).to.eq(doc.data); - }); - - }); -}()); diff --git a/app/templates/ui/app/detail/detail.module.js b/app/templates/ui/app/detail/detail.module.js deleted file mode 100644 index 0173eeff..00000000 --- a/app/templates/ui/app/detail/detail.module.js +++ /dev/null @@ -1,8 +0,0 @@ -(function () { - 'use strict'; - - angular.module('app.detail', [ - 'app.similar', - 'ui.router' - ]); -}()); diff --git a/app/templates/ui/app/detail/similar-directive.js b/app/templates/ui/app/detail/similar-directive.js deleted file mode 100644 index 53a956ea..00000000 --- a/app/templates/ui/app/detail/similar-directive.js +++ /dev/null @@ -1,32 +0,0 @@ -(function () { - - 'use strict'; - - angular.module('app.similar') - .directive('mlSimilar', mlSimilar); - - mlSimilar.$inject = ['MLRest']; - - function mlSimilar(mlRest) { - return { - restrict: 'E', - templateUrl: 'app/detail/similar-directive.html', - scope: { uri: '@', limit: '@' }, - link: function(scope, iElement, iAttrs, ctrl) { - mlRest.extension('extsimilar', - { - method: 'GET', - params: - { - 'rs:uri': scope.uri, - 'rs:limit': scope.limit ? scope.limit : 10 - } - }) - .then(function(response) { - scope.similar = response.data.similar; - }); - } - }; - } - -}()); diff --git a/app/templates/ui/app/detail/similar.module.js b/app/templates/ui/app/detail/similar.module.js deleted file mode 100644 index 9bf5073f..00000000 --- a/app/templates/ui/app/detail/similar.module.js +++ /dev/null @@ -1,6 +0,0 @@ -(function() { - 'use strict'; - - angular.module('app.similar', []); - -}()); diff --git a/app/templates/ui/app/error/error.module.js b/app/templates/ui/app/error/error.module.js deleted file mode 100644 index a45fad3f..00000000 --- a/app/templates/ui/app/error/error.module.js +++ /dev/null @@ -1,4 +0,0 @@ -(function () { - 'use strict'; - angular.module('app.error', ['app.messageBoard']); -}()); diff --git a/app/templates/ui/app/error/errorInterceptor.service.js b/app/templates/ui/app/error/errorInterceptor.service.js deleted file mode 100644 index 7dc4eed5..00000000 --- a/app/templates/ui/app/error/errorInterceptor.service.js +++ /dev/null @@ -1,36 +0,0 @@ -(function() { - 'use strict'; - - angular.module('app.error') - .factory('errorInterceptor', ErrorInterceptor) - .config(['$httpProvider', function($httpProvider) { - $httpProvider.interceptors.push('errorInterceptor'); - }]); - - ErrorInterceptor.$inject = ['$q', '$injector', 'ngToast']; - function ErrorInterceptor($q, $injector, toast) { - var errorInterceptor = { - responseError: function(rejection) { - if (rejection.status === 500 || rejection.status === 400) { - var messageBoardService = $injector.get('messageBoardService'); - var msg; - if (rejection.data && rejection.data.errorResponse) { - msg = { - title: rejection.data.errorResponse.status, - body: rejection.data.errorResponse.message - }; - } else { - msg = { - title: (rejection.status === 400) ? 'Bad Request' : 'Internal Server Error' - }; - } - messageBoardService.message(msg); - var toastMsg = '' + msg.title + '

' + msg.body + '

'; - toast.danger(toastMsg); - } - return $q.reject(rejection); - } - }; - return errorInterceptor; - } -}()); diff --git a/app/templates/ui/app/login/login.controller.js b/app/templates/ui/app/login/login.controller.js deleted file mode 100644 index b96c33ed..00000000 --- a/app/templates/ui/app/login/login.controller.js +++ /dev/null @@ -1,64 +0,0 @@ -(function () { - 'use strict'; - - angular.module('app.login') - .controller('LoginCtrl', LoginCtrl) - .controller('LoginFullCtrl', LoginFullCtrl); - - LoginCtrl.$inject = ['$scope', 'loginService']; - function LoginCtrl($scope, loginService) { - var ctrl = this; - angular.extend(ctrl, { - username: null, - password: null, - login: login, - loginService: loginService, - logout: logout, - cancel: cancel - }); - - function login() { - loginService.login(ctrl.username, ctrl.password).then(function(user) { - callback(user); - }); - } - - function logout() { - loginService.logout().then(function() { - callback(); - }); - } - - function cancel() { - callback(); - } - - function callback(user) { - if ($scope.callback && !loginService.loginError()) { - $scope.callback({user: user}); - } - } - } - - LoginFullCtrl.$inject = ['$state', '$stateParams']; - function LoginFullCtrl($state, $stateParams) { - var ctrl = this; - angular.extend(ctrl, { - showCancel: $stateParams.state !== 'root.landing', - toState: toState - }); - - function toState(user) { - if (user) { - var params = null; - if ($stateParams.params && /^\{.*\}$/.test($stateParams.params)) { - params = JSON.parse($stateParams.params); - } - $state.go($stateParams.state || 'root.landing', params); - } else { - $state.go('root.landing'); - } - } - } - -}()); diff --git a/app/templates/ui/app/login/login.directive.js b/app/templates/ui/app/login/login.directive.js deleted file mode 100644 index 7087b564..00000000 --- a/app/templates/ui/app/login/login.directive.js +++ /dev/null @@ -1,23 +0,0 @@ -(function () { - - 'use strict'; - - angular.module('app.login') - .directive('login', LoginDirective); - - function LoginDirective() { - return { - restrict: 'EA', - controller: 'LoginCtrl', - controllerAs: 'ctrl', - replace: true, - scope: { - showCancel: '=', - mode: '@', - callback: '&' - }, - templateUrl: 'app/login/login-dir.html' - }; - } - -}()); diff --git a/app/templates/ui/app/login/login.module.js b/app/templates/ui/app/login/login.module.js deleted file mode 100644 index cba9264f..00000000 --- a/app/templates/ui/app/login/login.module.js +++ /dev/null @@ -1,4 +0,0 @@ -(function () { - 'use strict'; - angular.module('app.login', ['app.messageBoard', 'ml.common', 'ui.bootstrap', 'ui.router']); -}()); diff --git a/app/templates/ui/app/login/login.service.js b/app/templates/ui/app/login/login.service.js deleted file mode 100644 index ad932cdb..00000000 --- a/app/templates/ui/app/login/login.service.js +++ /dev/null @@ -1,201 +0,0 @@ -(function () { - 'use strict'; - - angular.module('app.login') - .factory('loginService', LoginService); - - LoginService.$inject = ['$http', '$uibModal', '$q', '$rootScope', '$state', - '$stateParams', 'messageBoardService']; - function LoginService($http, $uibModal, $q, $rootScope, $state, - $stateParams, messageBoardService) { - - var service = {}; - var _loginMode = 'full'; // 'modal', 'top-right', or 'full' - var _loginError; - var _toStateName; - var _toStateParams; - var _isAuthenticated; - var _userPrefix = ''; - var _protectedRoutes = []; - var deregisterLoginSuccess; - - function loginMode(mode) { - if (mode === undefined) { - return _loginMode; - } - _loginMode = mode; - } - - function failLogin(response) { - if (response.status > 200) { - _loginError = true; - } - } - - function loginError() { - return _loginError; - } - - function getAuthenticatedStatus() { - if (_isAuthenticated !== undefined) { - return _isAuthenticated; - } - - return $http.get('/api/user/status', {}).then(function(response) { - if (response.data.appUsersOnly) { - _userPrefix = response.data.appName + '-'; - } - if (response.data.authenticated === false) { - _isAuthenticated = false; - } - else - { - loginSuccess(response); - } - return service.isAuthenticated(); - }); - } - - function loginSuccess(response) { - _loginError = null; - _isAuthenticated = true; - $rootScope.$broadcast('loginService:login-success', response.data); - } - - function login(username, password) { - return $http.post('/api/user/login', { - 'username': _userPrefix + username, - 'password': password - }).then(function(response) { - loginSuccess(response); - return response; - }, failLogin); - } - - function loginPrompt() { - var d = $q.defer(); - if (_loginMode === 'modal') { - $uibModal.open({ - controller: ['$uibModalInstance', function($uibModalInstance) { - var ctrl = this; - ctrl.showCancel = $state.current.name !== 'root.landing'; - ctrl.close = function(user) { - if (user) { - d.resolve(user); - } else { - d.reject(); - $state.go('root.landing'); - } - return $uibModalInstance.close(); - }; - }], - controllerAs: 'ctrl', - templateUrl: '/app/login/login-modal.html', - backdrop: 'static', - keyboard: false - }); - } else if (_loginMode === 'top-right') { - messageBoardService.message({title: 'Please sign-in to see content.'}); - var deregisterMsgBoard = $rootScope.$on('loginService:login-success', function(e, user) { - messageBoardService.message(null); - d.resolve(user); - deregisterMsgBoard(); - }); - } else { - $state.go('root.login', - { - 'state': _toStateName || $state.current.name, - 'params': JSON.stringify((_toStateParams || $stateParams)) - }).then(function() { - d.reject(); - }); - } - return d.promise; - } - - function logout() { - return $http.get('/api/user/logout').then(function(response) { - $rootScope.$broadcast('loginService:logout-success', response); - _loginError = null; - _isAuthenticated = false; - $state.reload(); - return response; - }); - } - - function isAuthenticated() { - return _isAuthenticated; - } - - function protectedRoutes(routes) { - if (routes === undefined) { - return _protectedRoutes; - } - _protectedRoutes = routes; - } - - function routeIsProtected(route) { - return _protectedRoutes.indexOf(route) > -1; - } - - function blockRoute(event, next, nextParams) { - event.preventDefault(); - service.loginPrompt(); - if (_loginMode !== 'full') { - if (deregisterLoginSuccess) { - deregisterLoginSuccess(); - deregisterLoginSuccess = null; - } - deregisterLoginSuccess = $rootScope.$on('loginService:login-success', function() { - deregisterLoginSuccess(); - deregisterLoginSuccess = null; - $state.go(next.name, nextParams); - }); - } - } - - $rootScope.$on('$stateChangeStart', function(event, next, nextParams) { - if (next.name !== 'root.login') { - _toStateName = next.name; - _toStateParams = nextParams; - } - - if (routeIsProtected(next.name)) { - var auth = service.getAuthenticatedStatus(); - - if (angular.isFunction(auth.then)) { - auth.then(function() { - if (!service.isAuthenticated()) { - //this does NOT block requests in a timely fashion... - blockRoute(event, next, nextParams); - } - }); - } - else { - if (!auth) { - blockRoute(event, next, nextParams); - } - } - - } - }); - - $rootScope.$on('loginService:profile-changed', function() { - _isAuthenticated = undefined; - service.getAuthenticatedStatus(); - }); - - angular.extend(service, { - login: login, - logout: logout, - loginPrompt: loginPrompt, - loginError: loginError, - loginMode: loginMode, - isAuthenticated: isAuthenticated, - getAuthenticatedStatus: getAuthenticatedStatus, - protectedRoutes: protectedRoutes - }); - - return service; - } -}()); diff --git a/app/templates/ui/app/login/loginInterceptor.service.js b/app/templates/ui/app/login/loginInterceptor.service.js deleted file mode 100644 index 5dce5083..00000000 --- a/app/templates/ui/app/login/loginInterceptor.service.js +++ /dev/null @@ -1,69 +0,0 @@ -(function() { - 'use strict'; - - angular.module('app.login') - .factory('authInterceptor', AuthInterceptor) - .config(['$httpProvider', function($httpProvider) { - $httpProvider.interceptors.push('authInterceptor'); - }]); - - AuthInterceptor.$inject = ['$q', '$rootScope', '$injector']; - function AuthInterceptor($q, $rootScope, $injector) { - var waitingForLoginDefer; - var waitingLineForLoginDefer = 0; - function reduceWaitingLine() { - waitingLineForLoginDefer--; - if (waitingLineForLoginDefer <= 0) { - waitingForLoginDefer = null; - } - } - $rootScope.$on('$stateChangeSuccess', function() { - waitingLineForLoginDefer = 0; - waitingForLoginDefer = null; - }); - - var authInterceptor = { - request: function(config) { - if (/\/v1\//.test(config.url) && waitingForLoginDefer) { - // hold off on additional REST API calls until login is resolved - waitingLineForLoginDefer++; - return waitingForLoginDefer.promise.then(function() { - reduceWaitingLine(); - return config; - }, - function() { - reduceWaitingLine(); - }); - } - return config; - }, - responseError: function(rejection) { - // Not logged in or session has expired - var userService = $injector.get('userService'); - if (rejection.status === 419 || rejection.status === 401 && - !waitingForLoginDefer && !userService.currentUser() && - !/\/api\/user\/login/.test(rejection.config.url)) { - var loginService = $injector.get('loginService'); - var $http = $injector.get('$http'); - waitingForLoginDefer = $q.defer(); - waitingLineForLoginDefer = 1; - - // We use login method that logs the user in using the current credentials and - // returns a promise - loginService.loginPrompt().then( - waitingForLoginDefer.resolve, waitingForLoginDefer.reject); - // When the session recovered, make the same backend call again and chain the request - return waitingForLoginDefer.promise.then(function() { - reduceWaitingLine(); - return $http(rejection.config); - }, - function() { - reduceWaitingLine(); - }); - } - return $q.reject(rejection); - } - }; - return authInterceptor; - } -}()); diff --git a/app/templates/ui/app/message-board/message-board-dir.html b/app/templates/ui/app/message-board/message-board-dir.html deleted file mode 100644 index 13d1c899..00000000 --- a/app/templates/ui/app/message-board/message-board-dir.html +++ /dev/null @@ -1,4 +0,0 @@ -
-

{{msg.title || msg}}

-

{{msg.body}}

-
diff --git a/app/templates/ui/app/message-board/message-board.directive.js b/app/templates/ui/app/message-board/message-board.directive.js deleted file mode 100644 index 9407d98d..00000000 --- a/app/templates/ui/app/message-board/message-board.directive.js +++ /dev/null @@ -1,45 +0,0 @@ -(function () { - - 'use strict'; - - angular.module('app.messageBoard') - .directive('messageBoard', MessageBoardDirective) - .controller('MessageBoardController', MessageBoardController); - - function MessageBoardDirective() { - return { - restrict: 'E', - controller: 'MessageBoardController', - controllerAs: 'ctrl', - replace: true, - scope: { - msg: '=' - }, - templateUrl: 'app/message-board/message-board-dir.html' - }; - } - - MessageBoardController.$inject = ['$scope']; - - function MessageBoardController($scope) { - var ctrl = this; - angular.extend(ctrl, { - isObject: isObject, - collapseRaw: collapseRaw, - expandRaw: expandRaw - }); - - function isObject(item) { - return _.isObject(item); - } - - function collapseRaw() { - ctrl.showRaw = false; - } - - function expandRaw() { - ctrl.showRaw = true; - } - } - -}()); diff --git a/app/templates/ui/app/message-board/message-board.module.js b/app/templates/ui/app/message-board/message-board.module.js deleted file mode 100644 index ab61a11f..00000000 --- a/app/templates/ui/app/message-board/message-board.module.js +++ /dev/null @@ -1,5 +0,0 @@ -(function () { - 'use strict'; - - angular.module('app.messageBoard', []); -}()); diff --git a/app/templates/ui/app/message-board/message-board.service.js b/app/templates/ui/app/message-board/message-board.service.js deleted file mode 100644 index dbfa3a71..00000000 --- a/app/templates/ui/app/message-board/message-board.service.js +++ /dev/null @@ -1,31 +0,0 @@ -(function () { - 'use strict'; - - angular.module('app.messageBoard') - .factory('messageBoardService', MessageBoardService); - - MessageBoardService.$inject = ['$rootScope']; - function MessageBoardService($rootScope) { - var _message = null; - - function message(msg) { - if (msg === undefined) { - return _message; - } - _message = msg; - if (message) { - $rootScope.$broadcast('message-board:set', _message); - } else { - $rootScope.$broadcast('message-board:cleared'); - } - } - - $rootScope.$on('$stateChangeSuccess', function() { - message(null); - }); - - return { - message: message - }; - } -}()); diff --git a/app/templates/ui/app/root/root.controller.js b/app/templates/ui/app/root/root.controller.js deleted file mode 100644 index d1770458..00000000 --- a/app/templates/ui/app/root/root.controller.js +++ /dev/null @@ -1,18 +0,0 @@ -(function () { - 'use strict'; - - angular.module('app.root') - .controller('RootCtrl', RootCtrl); - - RootCtrl.$inject = ['messageBoardService', 'userService', '$scope']; - - function RootCtrl(messageBoardService, userService, $scope) { - var ctrl = this; - ctrl.currentYear = new Date().getUTCFullYear(); - ctrl.messageBoardService = messageBoardService; - - $scope.$watch(userService.currentUser, function(newValue) { - ctrl.currentUser = newValue; - }); - } -}()); diff --git a/app/templates/ui/app/root/root.module.js b/app/templates/ui/app/root/root.module.js deleted file mode 100644 index b90bbc34..00000000 --- a/app/templates/ui/app/root/root.module.js +++ /dev/null @@ -1,5 +0,0 @@ -(function () { - 'use strict'; - - angular.module('app.root', ['app.messageBoard']); -}()); diff --git a/app/templates/ui/app/route/routes.js b/app/templates/ui/app/route/routes.js index ba4346e4..4eef186c 100644 --- a/app/templates/ui/app/route/routes.js +++ b/app/templates/ui/app/route/routes.js @@ -1,21 +1,18 @@ -(function () { - 'use strict'; +import angular from 'angular'; +import uiRouter from 'angular-ui-router'; - angular.module('app') - .run(['loginService', function(loginService) { - loginService.protectedRoutes(['root.search', 'root.create', 'root.profile']); - }]) - .config(Config); - - Config.$inject = ['$stateProvider', '$urlMatcherFactoryProvider', - '$urlRouterProvider', '$locationProvider' - ]; +import landingPageTemplate from '../components/landing/landing.html'; - function Config( - $stateProvider, - $urlMatcherFactoryProvider, - $urlRouterProvider, - $locationProvider) { +let routesModule = angular.module('app.router', [ + uiRouter + ]) + .run(['loginService', + function(loginService) { + loginService.protectedRoutes(['root.search', 'root.create', 'root.profile']); + } + ]) + .config(($stateProvider, $urlMatcherFactoryProvider, $urlRouterProvider, $locationProvider) => { + 'ngInject'; $urlRouterProvider.otherwise('/'); $locationProvider.html5Mode(true); @@ -39,9 +36,7 @@ .state('root', { url: '', // abstract: true, - templateUrl: 'app/root/root.html', - controller: 'RootCtrl', - controllerAs: 'ctrl', + component: 'root', resolve: { user: function(userService) { return userService.getUser(); @@ -50,7 +45,7 @@ }) .state('root.landing', { url: '/', - templateUrl: 'app/landing/landing.html', + template: landingPageTemplate, navLabel: { text: 'Home', area: 'dashboard', @@ -59,9 +54,7 @@ }) .state('root.search', { url: '/search', - templateUrl: 'app/search/search.html', - controller: 'SearchCtrl', - controllerAs: 'ctrl', + component: 'search', navLabel: { text: 'Search', area: 'dashboard', @@ -70,14 +63,11 @@ }) .state('root.create', { url: '/create', - templateUrl: 'app/create/create.html', - controller: 'CreateCtrl', - controllerAs: 'ctrl', + component: 'create', navLabel: { text: 'Create', area: 'dashboard', - navClass: 'fa-wpforms', - edit: true + navClass: 'fa-wpforms' }, resolve: { stuff: function() { @@ -86,19 +76,19 @@ } }) .state('root.view', { - url: '/detail{uri:path}', + url: '/detail?{uri:path}', params: { uri: { value: null } }, - templateUrl: 'app/detail/detail.html', - controller: 'DetailCtrl', - controllerAs: 'ctrl', + component: 'detail', resolve: { doc: function(MLRest, $stateParams) { var uri = $stateParams.uri; - return MLRest.getDocument(uri, { format: 'json' }).then(function(response) { + return MLRest.getDocument(uri, { + format: 'json' + }).then(function(response) { return response; }); } @@ -106,15 +96,14 @@ }) .state('root.profile', { url: '/profile', - templateUrl: 'app/user/profile.html', - controller: 'ProfileCtrl', - controllerAs: 'ctrl' + component: 'profile' }) .state('root.login', { url: '/login?state¶ms', - templateUrl: 'app/login/login-full.html', - controller: 'LoginFullCtrl', - controllerAs: 'ctrl' + component: 'loginFull' }); - } -}()); + }) + .name; + +export +default routesModule; diff --git a/app/templates/ui/app/search/search.controller.js b/app/templates/ui/app/search/search.controller.js deleted file mode 100644 index 083cd72c..00000000 --- a/app/templates/ui/app/search/search.controller.js +++ /dev/null @@ -1,30 +0,0 @@ -/* global MLSearchController */ -(function () { - 'use strict'; - - angular.module('app.search') - .controller('SearchCtrl', SearchCtrl); - - SearchCtrl.$inject = ['$scope', '$location', 'userService', 'MLSearchFactory']; - - // inherit from MLSearchController - var superCtrl = MLSearchController.prototype; - SearchCtrl.prototype = Object.create(superCtrl); - - function SearchCtrl($scope, $location, userService, searchFactory) { - var ctrl = this; - - superCtrl.constructor.call(ctrl, $scope, $location, searchFactory.newContext()); - - ctrl.init(); - - ctrl.setSnippet = function(type) { - ctrl.mlSearch.setSnippet(type); - ctrl.search(); - }; - - $scope.$watch(userService.currentUser, function(newValue) { - ctrl.currentUser = newValue; - }); - } -}()); diff --git a/app/templates/ui/app/search/search.controller.spec.js b/app/templates/ui/app/search/search.controller.spec.js deleted file mode 100644 index ffa0d7bb..00000000 --- a/app/templates/ui/app/search/search.controller.spec.js +++ /dev/null @@ -1,58 +0,0 @@ -/* jshint -W117, -W030 */ -(function () { - 'use strict'; - - describe('Controller: SearchCtrl', function () { - - var controller; - - var currentUser = null; - - var results = [ - { - uri: 'abc' - }, - { - uri: 'def' - } - ]; - - beforeEach(function() { - bard.appModule('app.search'); - bard.inject('$controller', '$q', '$rootScope', '$location', - 'userService', 'MLSearchFactory', 'MLRest'); - - bard.mockService(userService, { - currentUser: $q.when(currentUser) - }); - - bard.mockService(MLRest, { - search: $q.when({ - data: { - results: results - } - }) - }); - - }); - - beforeEach(function () { - controller = $controller('SearchCtrl', { $scope: $rootScope.$new() }); - $rootScope.$apply(); - }); - - it('should be created successfully', function () { - expect(controller).to.be.defined; - }); - - it('should update the current user if it changes', function() { - expect(controller.currentUser).to.not.be.defined; - }); - - it('should run a search', function() { - controller.search('stuff'); - $rootScope.$apply(); - expect(controller.response.results).to.eq(results); - }); - }); -}()); diff --git a/app/templates/ui/app/search/search.html b/app/templates/ui/app/search/search.html deleted file mode 100644 index 0ddf1333..00000000 --- a/app/templates/ui/app/search/search.html +++ /dev/null @@ -1,23 +0,0 @@ -
Please log in to see content
-
-
- -
-
- - diff --git a/app/templates/ui/app/search/search.module.js b/app/templates/ui/app/search/search.module.js deleted file mode 100644 index b1977f01..00000000 --- a/app/templates/ui/app/search/search.module.js +++ /dev/null @@ -1,5 +0,0 @@ -(function () { - 'use strict'; - - angular.module('app.search', ['ml.search', 'app.user', 'app.snippet']); -}()); diff --git a/app/templates/ui/app/search/snippet.directive.js b/app/templates/ui/app/search/snippet.directive.js deleted file mode 100644 index f6681ff0..00000000 --- a/app/templates/ui/app/search/snippet.directive.js +++ /dev/null @@ -1,38 +0,0 @@ -(function () { - - 'use strict'; - - angular.module('app.snippet') - .directive('mlSnippet', SnippetDirective) - .controller('SnippetController', SnippetController); - - function SnippetDirective() { - return { - restrict: 'E', - controller: 'SnippetController', - controllerAs: 'ctrl', - replace: true, - scope: { - setSnippet: '&' - }, - templateUrl: 'app/search/snippet.html' - }; - } - - SnippetController.$inject = ['$scope']; - - function SnippetController($scope) { - $scope.snippets = ['detailed', 'compact']; - - var ctrl = this; - angular.extend(ctrl, { - setSnippetType: setSnippetType - }); - - function setSnippetType(type) { - $scope.snippetType = type; - $scope.setSnippet({type: type}); - } - } - -}()); diff --git a/app/templates/ui/app/search/snippet.module.js b/app/templates/ui/app/search/snippet.module.js deleted file mode 100644 index f5164821..00000000 --- a/app/templates/ui/app/search/snippet.module.js +++ /dev/null @@ -1,5 +0,0 @@ -(function () { - 'use strict'; - - angular.module('app.snippet', []); -}()); diff --git a/app/templates/ui/app/user/profile.controller.js b/app/templates/ui/app/user/profile.controller.js deleted file mode 100644 index 34bb7ce7..00000000 --- a/app/templates/ui/app/user/profile.controller.js +++ /dev/null @@ -1,85 +0,0 @@ -(function () { - 'use strict'; - - angular.module('app.user') - .controller('ProfileCtrl', ProfileCtrl); - - ProfileCtrl.$inject = ['$scope', '$state', 'MLRest', 'userService', 'ngToast', '$rootScope']; - - function ProfileCtrl($scope, $state, mlRest, userService, toast, $rootScope) { - var ctrl = this; - angular.extend(ctrl, { - user: checkUser(userService.currentUser()), - newEmail: '', - addEmail: addEmail, - removeEmail: removeEmail, - submit: submit - }); - - function addEmail() { - if (ctrl.user) { - if (!ctrl.newEmail || hasEmailInputError()) { - return; - } - ctrl.user.profile = ctrl.user.profile || {}; - if (!ctrl.user.profile.emails) { - ctrl.user.profile.emails = []; - } - ctrl.user.profile.emails.push(ctrl.newEmail.trim()); - ctrl.newEmail = ''; - } - } - - function removeEmail(index) { - if (ctrl.user.profile && ctrl.user.profile.emails) { - ctrl.user.profile.emails.splice(index, 1); - } - } - - function hasEmailInputError() { - try { - return $scope.profileForm.newEmail.$error.email === true; - } - catch (e) { - return false; - } - } - - function submit(form) { - if (form.$valid && ctrl.user.profile) { - addEmail(); - - if (ctrl.user.profile.emails) { - _.pull(ctrl.user.profile.emails, ''); - } - - mlRest.updateDocument({ - user: ctrl.user.profile - }, { - format: 'json', - uri: '/api/users/' + ctrl.user.username + '.json' - }).then(function(data) { - $rootScope.$broadcast('loginService:profile-changed'); - toast.success('Submitted'); - $state.go('root.landing'); - }, function(response) { - toast.danger(response.data); - }); - } - } - - $scope.$watch(userService.currentUser, function(newValue) { - ctrl.user = checkUser(newValue); - }); - - function checkUser(newValue) { - var user = angular.copy(newValue); - if (user && user.profile && user.profile.emails) { - if (!angular.isArray(user.profile.emails)) { - user.profile.emails = [user.profile.emails]; - } - } - return user; - } - } -}()); diff --git a/app/templates/ui/app/user/user-dir.html b/app/templates/ui/app/user/user-dir.html deleted file mode 100644 index ec449d3d..00000000 --- a/app/templates/ui/app/user/user-dir.html +++ /dev/null @@ -1,16 +0,0 @@ -
- - - -
- -
-
diff --git a/app/templates/ui/app/user/user.directive.js b/app/templates/ui/app/user/user.directive.js deleted file mode 100644 index 28101c6e..00000000 --- a/app/templates/ui/app/user/user.directive.js +++ /dev/null @@ -1,38 +0,0 @@ -(function () { - - 'use strict'; - - angular.module('app.user') - .directive('mlUser', UserDirective) - .controller('UserController', UserController); - - function UserDirective() { - return { - restrict: 'EA', - controller: 'UserController', - controllerAs: 'ctrl', - replace: true, - scope: { - showCancel: '=', - mode: '@', - callback: '&' - }, - templateUrl: 'app/user/user-dir.html' - }; - } - - UserController.$inject = ['$scope', 'userService', 'loginService']; - - function UserController($scope, userService, loginService) { - var ctrl = this; - angular.extend(ctrl, { - username: null, - password: null, - loginService: loginService - }); - $scope.$watch(userService.currentUser, function(newValue) { - $scope.currentUser = newValue; - }); - } - -}()); diff --git a/app/templates/ui/app/user/user.module.js b/app/templates/ui/app/user/user.module.js deleted file mode 100644 index 1a3b87c1..00000000 --- a/app/templates/ui/app/user/user.module.js +++ /dev/null @@ -1,5 +0,0 @@ -(function () { - 'use strict'; - - angular.module('app.user', ['ml.common', 'app.login', 'ngToast']); -}()); diff --git a/app/templates/ui/app/user/user.service.js b/app/templates/ui/app/user/user.service.js deleted file mode 100644 index b66083a8..00000000 --- a/app/templates/ui/app/user/user.service.js +++ /dev/null @@ -1,52 +0,0 @@ -(function () { - 'use strict'; - - angular.module('app.user') - .factory('userService', UserService); - - UserService.$inject = ['$rootScope', 'loginService']; - function UserService($rootScope, loginService) { - var _currentUser = null; - - function currentUser() { - return _currentUser; - } - - function getUser() { - if (_currentUser) { - return _currentUser; - } - - return loginService.getAuthenticatedStatus().then(currentUser); - } - - function updateUser(response) { - var data = response.data; - - if (data.authenticated === false) { - return null; - } - - // Copy all initially - _currentUser = angular.copy(data); - - // Password property should not exist, delete anyhow just to be sure - delete _currentUser.password; - - return _currentUser; - } - - $rootScope.$on('loginService:login-success', function(e, user) { - updateUser({ data: user }); - }); - - $rootScope.$on('loginService:logout-success', function() { - _currentUser = null; - }); - - return { - currentUser: currentUser, - getUser: getUser - }; - } -}()); diff --git a/app/templates/ui/index.html b/app/templates/ui/index.html index 5e95dee0..908e7ee1 100644 --- a/app/templates/ui/index.html +++ b/app/templates/ui/index.html @@ -1,41 +1,15 @@ - + @sample-app-name - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - + + + Loading... + diff --git a/app/templates/ui/robots.txt b/app/templates/ui/robots.txt deleted file mode 100644 index 6ffbc308..00000000 --- a/app/templates/ui/robots.txt +++ /dev/null @@ -1,3 +0,0 @@ -User-agent: * -Disallow: / - diff --git a/app/templates/ui/specs.html b/app/templates/ui/specs.html deleted file mode 100644 index 4bfce393..00000000 --- a/app/templates/ui/specs.html +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - Spec Runner - - - - - - - - - - -

Spec Runner

-

Make sure the REMOTE server is running
- Click on a description title to narrow the scope to just its specs - (see " - ?grep" in address bar).
- Click on a spec title to see the test implementation.
- Click on page title to start over. -

- -
- - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/templates/ui/styles/default.less b/app/templates/ui/styles/default.less index f4f341bf..cdeffbad 100644 --- a/app/templates/ui/styles/default.less +++ b/app/templates/ui/styles/default.less @@ -21,7 +21,7 @@ nav.nav { text-indent: 100%; white-space: nowrap; overflow: hidden; - background: url('/images/marklogic.png') no-repeat; + background: url('../images/marklogic.png') no-repeat; background-size: contain; min-height: 30px; height: 30px; @@ -164,7 +164,7 @@ footer { } .powered { - background: transparent url(/images/MarkLogic-Powered-By.png) no-repeat scroll 10px 10px; + background: transparent url('../images/MarkLogic-Powered-By.png') no-repeat scroll 10px 10px; } .detail { diff --git a/app/templates/ui/styles/main.less b/app/templates/ui/styles/main.less index 5e0c28ff..174f0e8f 100644 --- a/app/templates/ui/styles/main.less +++ b/app/templates/ui/styles/main.less @@ -1,8 +1,6 @@ -@import "../../bower_components/bootstrap/less/bootstrap.less"; -@import "../../bower_components/font-awesome/less/font-awesome.less"; -@import "../../bower_components/ml-search-ng/dist/ml-search-ng-tpls.less"; -@icon-font-path: "/fonts/"; -@FontAwesomePath: "/fonts/"; +@import "~bootstrap/less/bootstrap.less"; +@import "~font-awesome/less/font-awesome.less"; +@import "~ml-search-ng/dist/ml-search-ng-tpls.less"; /***************************** * UI Themes diff --git a/app/templates/webpack/webpack.config.js b/app/templates/webpack/webpack.config.js new file mode 100644 index 00000000..a82167ff --- /dev/null +++ b/app/templates/webpack/webpack.config.js @@ -0,0 +1,77 @@ +var path = require('path'); +var webpack = require('webpack'); +var HtmlWebpackPlugin = require('html-webpack-plugin'); +var ExtractTextPlugin = require('extract-text-webpack-plugin'); + +module.exports = { + devtool: 'sourcemap', + entry: {}, + module: { + loaders: [{ + test: /\.js$/, + exclude: [/app\/lib/, /node_modules/], + loader: 'ng-annotate!babel' + }, { + test: /\.html$/, + loader: 'html' + }, { + test: /\.styl$/, + loader: 'style!css!stylus' + }, { + test: /\.less$/, + loader: ExtractTextPlugin.extract( + // activate source maps via loader query + 'css?sourceMap!less?sourceMap' + ) + }, { + test: /\.css$/, + loader: 'style!css' + }, { + test: /\.(gif|png|jpe?g|svg|ico)(\?v=[0-9]\.[0-9]\.[0-9])?$/i, + loaders: [ + 'file?hash=sha512&digest=hex&name=[hash].[ext]', + 'image-webpack' + ] + }, { + test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, + loader: 'url-loader?limit=10000&mimetype=application/font-woff' + }, { + test: /\.(ttf|eot)(\?v=[0-9]\.[0-9]\.[0-9])?$/, + loader: 'file' + }] + }, + plugins: [ + // Injects bundles in your index.html instead of wiring all manually. + // It also adds hash to all injected assets so we don't have problems + // with cache purging during deployment. + new HtmlWebpackPlugin({ + template: 'ui/index.html', + inject: 'body', + favicon: 'ui/images/favicon.ico', + hash: true + }), + + // Automatically move all modules defined outside of application directory to vendor bundle. + // If you are using more complicated project structure, consider to specify common chunks manually. + new webpack.optimize.CommonsChunkPlugin({ + name: 'vendor', + minChunks: function(module, count) { + return module.resource && module.resource.indexOf(path.resolve(__dirname, 'ui')) === -1; + } + }), + new ExtractTextPlugin('styles.css') + ], + imageWebpackLoader: { + pngquant: { + quality: "65-90", + speed: 4 + }, + svgo: { + plugins: [{ + removeViewBox: false + }, { + removeEmptyAttrs: false + }] + } + } +}; diff --git a/app/templates/webpack/webpack.dev.config.js b/app/templates/webpack/webpack.dev.config.js new file mode 100644 index 00000000..e10cfca5 --- /dev/null +++ b/app/templates/webpack/webpack.dev.config.js @@ -0,0 +1,19 @@ +var webpack = require('webpack'); +var path = require('path'); +var config = require('./webpack.config'); + +config.output = { + filename: '[name].bundle.js', + publicPath: '/', + path: path.resolve(__dirname, 'ui') +}; + +config.plugins = config.plugins.concat([ + + // Adds webpack HMR support. It act's like livereload, + // reloading page after webpack rebuilt modules. + // It also updates stylesheets and inline assets without page reloading. + new webpack.HotModuleReplacementPlugin() +]); + +module.exports = config; diff --git a/app/templates/webpack/webpack.dist.config.js b/app/templates/webpack/webpack.dist.config.js new file mode 100644 index 00000000..40751af1 --- /dev/null +++ b/app/templates/webpack/webpack.dist.config.js @@ -0,0 +1,26 @@ +var webpack = require('webpack'); +var path = require('path'); +var config = require('./webpack.config'); + +config.output = { + filename: '[name].bundle.js', + publicPath: '', + path: path.resolve(__dirname, '../dist') +}; + +config.plugins = config.plugins.concat([ + + // Reduces bundles total size + new webpack.optimize.UglifyJsPlugin({ + mangle: { + + // You can specify all variables that should not be mangled. + // For example if your vendor dependency doesn't use modules + // and relies on global variables. Most of angular modules relies on + // angular global variable, so we should keep it unchanged + except: ['$super', '$', 'exports', 'require', 'angular'] + } + }) +]); + +module.exports = config; From 4ad5649d0a122a4a2b59a8cc81d8bc475809c803 Mon Sep 17 00:00:00 2001 From: Nishad Bakshi Date: Tue, 15 Nov 2016 16:55:04 +1100 Subject: [PATCH 2/2] Updated and refactored all the components and unit tests to make the latest changes compatible --- app/templates/package.json | 6 +- app/templates/ui/app/components/components.js | 4 +- .../components/create/create.controller.js | 301 ++---------------- .../create/create.controller.spec.js | 97 ------ .../ui/app/components/create/create.js | 8 +- .../ui/app/components/create/create.spec.js | 128 ++++++++ .../create/stringtonumber.directive.js | 34 +- .../components/detail/detail.controller.js | 82 ----- .../detail/detail.controller.spec.js | 75 ----- .../ui/app/components/detail/detail.spec.js | 108 +++++++ .../ui/app/components/landing/landing.html | 2 +- .../ui/app/components/login/login.js | 5 +- .../components/login/login.service.spec.js | 149 --------- .../ui/app/components/login/login.spec.js | 183 +++++++++++ .../{user => profile}/profile.component.js | 0 .../{user => profile}/profile.controller.js | 0 .../components/{user => profile}/profile.html | 0 .../ui/app/components/profile/profile.js | 17 + .../ui/app/components/profile/profile.spec.js | 138 ++++++++ .../components/search/search.controller.js | 3 - .../search/search.controller.spec.js | 201 ------------ .../ui/app/components/search/search.spec.js | 94 ++++++ .../user/profile.controller.spec.js | 102 ------ app/templates/ui/app/components/user/user.js | 2 - .../app/components/user/user.service.spec.js | 96 ------ .../ui/app/components/user/user.spec.js | 126 ++++++++ app/templates/webpack/webpack.config.js | 77 ----- app/templates/webpack/webpack.dev.config.js | 83 ++++- app/templates/webpack/webpack.dist.config.js | 108 +++++-- 29 files changed, 1011 insertions(+), 1218 deletions(-) delete mode 100644 app/templates/ui/app/components/create/create.controller.spec.js create mode 100644 app/templates/ui/app/components/create/create.spec.js delete mode 100644 app/templates/ui/app/components/detail/detail.controller.spec.js create mode 100644 app/templates/ui/app/components/detail/detail.spec.js delete mode 100644 app/templates/ui/app/components/login/login.service.spec.js create mode 100644 app/templates/ui/app/components/login/login.spec.js rename app/templates/ui/app/components/{user => profile}/profile.component.js (100%) rename app/templates/ui/app/components/{user => profile}/profile.controller.js (100%) rename app/templates/ui/app/components/{user => profile}/profile.html (100%) create mode 100644 app/templates/ui/app/components/profile/profile.js create mode 100644 app/templates/ui/app/components/profile/profile.spec.js delete mode 100644 app/templates/ui/app/components/search/search.controller.spec.js create mode 100644 app/templates/ui/app/components/search/search.spec.js delete mode 100644 app/templates/ui/app/components/user/profile.controller.spec.js delete mode 100644 app/templates/ui/app/components/user/user.service.spec.js create mode 100644 app/templates/ui/app/components/user/user.spec.js delete mode 100644 app/templates/webpack/webpack.config.js diff --git a/app/templates/package.json b/app/templates/package.json index 6870368a..a7b55480 100644 --- a/app/templates/package.json +++ b/app/templates/package.json @@ -83,7 +83,7 @@ "gulp-task-listing": "^1.0.1", "gulp-util": "^3.0.7", "html-loader": "^0.4.3", - "html-webpack-plugin": "^1.7.0", + "html-webpack-plugin": "^2.24.1", "image-webpack-loader": "~1.8.0", "isparta-loader": "^2.0.0", "karma": "^0.13.9", @@ -94,8 +94,8 @@ "karma-mocha-reporter": "^1.0.2", "karma-notify-reporter": "^1.0.1", "karma-phantomjs-launcher": "^1.0.0", - "karma-sourcemap-loader": "^0.3.4", - "karma-webpack": "^1.5.1", + "karma-sourcemap-loader": "^0.3.7", + "karma-webpack": "^1.8.0", "less-loader": "^2.2.3", "lodash": "^4.11.1", "mocha": "^2.3.0", diff --git a/app/templates/ui/app/components/components.js b/app/templates/ui/app/components/components.js index 06ffe0e6..3d838b29 100644 --- a/app/templates/ui/app/components/components.js +++ b/app/templates/ui/app/components/components.js @@ -6,6 +6,7 @@ import Search from './search/search'; import ErrorModule from './error/error'; import Detail from './detail/detail'; import Create from './create/create'; +import Profile from './profile/profile'; let componentModule = angular.module('app.components', [ Login, @@ -14,7 +15,8 @@ let componentModule = angular.module('app.components', [ Search, ErrorModule, Detail, - Create + Create, + Profile ]) .name; diff --git a/app/templates/ui/app/components/create/create.controller.js b/app/templates/ui/app/components/create/create.controller.js index 16065a8a..96b81fed 100644 --- a/app/templates/ui/app/components/create/create.controller.js +++ b/app/templates/ui/app/components/create/create.controller.js @@ -18,7 +18,8 @@ class CreateCtrl { $onInit() { this.x2js = new X2JS({ - enableToStringFunc: true + enableToStringFunc: true, + arrayAccessFormPaths: ['xml.tags', 'xml.friends'] }); this.mode = 'create'; @@ -33,15 +34,15 @@ class CreateCtrl { //expect xml format /*jshint camelcase: false */ - var wrapped = this.x2js.xml2js(this.doc.data); + let wrapped = this.x2js.xml2js(this.doc.data); this.person = wrapped.xml; } - if (!this.person.tags) { + if (!this.person.tags || (this.person.tags.length === 1 && this.person.tags[0] === '')) { this.person.tags = []; } - if (!this.person.friends) { + if (!this.person.friends || (this.person.friends.length === 1 && this.person.friends[0] === '')) { this.person.friends = []; } @@ -95,36 +96,36 @@ class CreateCtrl { data = this.x2js.js2xml(wrap); } /* jscs: enable */ - if (this.mode === 'create') { - this.mlRest.createDocument(data, { - format: this.person.docFormat, - directory: '/content/', - extension: extension, - collection: ['data', 'data/people'] - // TODO: add read/update permissions here like this: - // 'perm:sample-role': 'read', - // 'perm:sample-role': 'update' - }).then(response => { - this.toast.success('Created'); - this.$state.go('root.view', { - uri: response.replace(/(.*\?uri=)/, '') - }); - }, response => { - this.toast.danger(response.data); - }); - } else { - // use update when in update mode - this.mlRest.updateDocument(data, { - uri: this.uri - }).then(response => { - this.toast.success('Saved'); - this.$state.go('root.view', { - uri: this.uri - }); - }, response => { - this.toast.danger(response.data); - }); - } + if (this.mode === 'create') { + this.mlRest.createDocument(data, { + format: this.person.docFormat, + directory: '/content/', + extension: extension, + collection: ['data', 'data/people'] + // TODO: add read/update permissions here like this: + // 'perm:sample-role': 'read', + // 'perm:sample-role': 'update' + }).then(response => { + this.toast.success('Created'); + this.$state.go('root.view', { + uri: response.replace(/(.*\?uri=)/, '') + }); + }, response => { + this.toast.danger(response.data); + }); + } else { + // use update when in update mode + this.mlRest.updateDocument(data, { + uri: this.uri + }).then(response => { + this.toast.success('Saved'); + this.$state.go('root.view', { + uri: this.uri + }); + }, response => { + this.toast.danger(response.data); + }); + } } addTag() { @@ -143,235 +144,3 @@ CreateCtrl.$inject = ['$scope', 'MLRest', '$state', 'userService', 'ngToast', '$ export default CreateCtrl; - -//updated -/* -(function() { - 'use strict'; - - angular.module('app.create') - .controller('CreateCtrl', CreateCtrl); - - CreateCtrl.$inject = ['$scope', 'MLRest', '$state', 'userService', - 'ngthis.toast', 'this.x2js', 'doc', '$stateParams' - ]; - - function CreateCtrl($scope, mlRest, $state, userService, this.toast, this.x2js, doc, $stateParams) { - var ctrl = this; - - ctrl.this.x2js = this.x2js; - - ctrl.mode = 'create'; - - if (doc) { - ctrl.mode = 'edit'; - //check extension - if (doc.config.params.format === 'json') { - ctrl.person = doc.data; - } else { - /* jscs: disable */ -//expect xml format -/*jshint camelcase: false */ -/* - var wrapped = this.x2js.xml_str2json(doc.data); - ctrl.person = wrapped.xml; - } - - if (!ctrl.person.tags) { - ctrl.person.tags = []; - } - - if (!ctrl.person.friends) { - ctrl.person.friends = []; - } - - if (!ctrl.person.location) { - ctrl.person.location = { - latitude: 0, - longitude: 0 - }; - } - - ctrl.uri = $stateParams.uri; - - } else { - ctrl.mode = 'create'; - - ctrl.person = { - isActive: true, - balance: 0, - picture: 'http://placehold.it/32x32', - age: 0, - eyeColor: null, - name: null, - gender: null, - company: null, - email: null, - phone: null, - address: null, - about: null, - registered: null, - location: { - latitude: 0, - longitude: 0 - }, - tags: [], - friends: [], - greeting: null, - favoriteFruit: null - }; - } - - angular.extend(ctrl, { - newTag: null, - currentUser: null, - editorOptions: { - plugins: 'advlist autolink link image lists charmap print preview' - }, - submit: submit, - addTag: addTag, - removeTag: removeTag - }); - - function submit() { - var extension = '.json'; - var data = ctrl.person; - if (ctrl.person.docFormat === 'xml') { - extension = '.xml'; - var wrap = { - xml: ctrl.person - }; - /*jshint camelcase: false */ -/* - data = this.x2js.json2xml_str(wrap); - } - /* jscs: enable */ -/* - - if (ctrl.mode === 'create') { - mlRest.createDocument(data, { - format: ctrl.person.docFormat, - directory: '/content/', - extension: extension, - collection: ['data', 'data/people'] - // TODO: add read/update permissions here like this: - // 'perm:sample-role': 'read', - // 'perm:sample-role': 'update' - }).then(function(response) { - this.toast.success('Created'); - $state.go('root.view', { - uri: response.replace(/(.*\?uri=)/, '') - }); - }, function(response) { - this.toast.danger(response.data); - }); - } else { - // use update when in update mode - mlRest.updateDocument(data, { - uri: ctrl.uri - }).then(function(response) { - this.toast.success('Saved'); - $state.go('root.view', { - uri: ctrl.uri - }); - }, function(response) { - this.toast.danger(response.data); - }); - } - } - - function addTag() { - if (ctrl.newTag && ctrl.newTag !== '' && ctrl.person.tags.indexOf(ctrl.newTag) < 0) { - ctrl.person.tags.push(ctrl.newTag); - } - ctrl.newTag = null; - } - - function removeTag(index) { - ctrl.person.tags.splice(index, 1); - } - - $scope.$watch(userService.currentUser, function(newValue) { - ctrl.currentUser = newValue; - }); - } -}()); -*/ - -/* -//es5 style - -import angular from 'angular'; - -function CreateCtrl($scope, mlRest, $state, userService, this.toast) { - var ctrl = this; - - ctrl.person = null; - - angular.extend(ctrl, { - person: { - isActive: true, - balance: 0, - picture: 'http://placehold.it/32x32', - age: 0, - eyeColor: null, - name: null, - gender: null, - company: null, - email: null, - phone: null, - address: null, - about: null, - registered: null, - location: { - latitude: 0, - longitude: 0 - }, - tags: [], - friends: [], - greeting: null, - favoriteFruit: null - }, - newTag: null, - currentUser: null, - editorOptions: { - plugins: 'advlist autolink link image lists charmap print preview' - }, - submit: submit, - addTag: addTag, - removeTag: removeTag - }); - - function submit() { - mlRest.createDocument(ctrl.person, { - format: 'json', - directory: '/content/', - extension: '.json', - collection: ['data', 'data/people'] - // TODO: add read/update permissions here like this: - // 'perm:sample-role': 'read', - // 'perm:sample-role': 'update' - }).then(function(response) { - this.toast.success('Record created.'); - $state.go('root.view', { - uri: response.replace(/(.*\?uri=)/, '') - }); - }); - } - - function addTag() { - if (ctrl.newTag && ctrl.newTag !== '' && ctrl.person.tags.indexOf(ctrl.newTag) < 0) { - ctrl.person.tags.push(ctrl.newTag); - } - ctrl.newTag = null; - } - - function removeTag(index) { - ctrl.person.tags.splice(index, 1); - } - - $scope.$watch(userService.currentUser, function(newValue) { - ctrl.currentUser = newValue; - }); -} -*/ diff --git a/app/templates/ui/app/components/create/create.controller.spec.js b/app/templates/ui/app/components/create/create.controller.spec.js deleted file mode 100644 index 81a2c342..00000000 --- a/app/templates/ui/app/components/create/create.controller.spec.js +++ /dev/null @@ -1,97 +0,0 @@ -/* jshint -W117, -W030 */ -import Module from './create'; - -import Controller from './create.controller'; -import Component from './create.component'; -import Template from './create.html'; - -describe('Create', () => { - let $rootScope, controller, scope; - let $q, MLRest, $state, userService, toast; - - var nextState; - - beforeEach(window.module(Module)); - - beforeEach(inject( - (_$controller_, _$q_, _$rootScope_, _MLRest_, _$state_, _userService_, _ngToast_) => { - $rootScope = _$rootScope_; - scope = $rootScope.$new(); - $q = _$q_; - MLRest = _MLRest_; - $state = _$state_; - userService = _userService_; - toast = _ngToast_; - - sinon.stub(MLRest, 'createDocument', () => $q.when('/?uri=blah')); - sinon.stub($state, 'go', function(state, params) { - nextState = { - state: state, - params: params - }; - }); - })); - - beforeEach(() => { - // stub the current user - scope = $rootScope.$new(); - controller = new Controller(scope, MLRest, $state, userService, toast); - }); - - describe('Module', () => { - // top-level specs: i.e., routes, injection, naming - }); - - describe('Controller', () => { - // controller specs - it('should be created successfully', function() { - expect(controller).to.be.defined; - }); - - it('has a person property [REMOVE]', () => { // erase if removing this.person from the controller - expect(controller).to.have.property('person'); - }); - - it('should add tags', function() { - var tagValue = 'testTag'; - expect(controller.person.tags.length).to.eq(0); - controller.newTag = tagValue; - controller.addTag(); - expect(controller.person.tags.length).to.eq(1); - expect(controller.person.tags[0]).to.eq(tagValue); - expect(controller.newTag).to.eq.null; - }); - - it('should show the detail view when submitted', function() { - controller.submit(); - $rootScope.$apply(); - expect(nextState).to.deep.eq({ - state: 'root.view', - params: { - uri: 'blah' - } - }); - }); - }); - - describe('Template', () => { - // template specs - // tip: use regex to ensure correct bindings are used e.g., {{ }} - it('has person in template [REMOVE]', () => { - expect(Template).to.match(/\s?\$ctrl\.person\s?/g); - }); - }); - - describe('Component', () => { - // component/directive specs - let component = Component; - - it('includes the intended template', () => { - expect(component.template).to.equal(Template); - }); - - it('invokes the right controller', () => { - expect(component.controller).to.equal(Controller); - }); - }); -}); diff --git a/app/templates/ui/app/components/create/create.js b/app/templates/ui/app/components/create/create.js index 88c2ebb8..2c727d25 100644 --- a/app/templates/ui/app/components/create/create.js +++ b/app/templates/ui/app/components/create/create.js @@ -4,6 +4,8 @@ import angularMessages from 'angular-messages'; import bootstrapConfirm from 'angular-bootstrap-confirm'; import CreateComponent from './create.component'; +import StringToNumber from './stringtonumber.directive'; + import User from '../user/user'; //run globally since not in an es6 module @@ -19,12 +21,8 @@ const module = angular.module('app.create', [ bootstrapConfirm ]) .component('create', CreateComponent) + .directive('stringToNumber', StringToNumber) .name; export default module; - -/* - angular.module('app.create', ['ml.common', 'app.user', 'ngToast','cb.x2js', - 'ngMessages','mwl.confirm']); - */ diff --git a/app/templates/ui/app/components/create/create.spec.js b/app/templates/ui/app/components/create/create.spec.js new file mode 100644 index 00000000..d467162b --- /dev/null +++ b/app/templates/ui/app/components/create/create.spec.js @@ -0,0 +1,128 @@ +/* jshint -W117, -W030 */ +import Module from './create'; + +import Controller from './create.controller'; +import Component from './create.component'; +import Template from './create.html'; + +describe('app.create', () => { + let $rootScope, $state, $location, $componentController, $compile; + let MLRest, userService, ngToast; + let $q; + + let nextState; + + beforeEach(window.module(Module)); + + beforeEach(inject(($injector) => { + $rootScope = $injector.get('$rootScope'); + $componentController = $injector.get('$componentController'); + $state = $injector.get('$state'); + $location = $injector.get('$location'); + + MLRest = $injector.get('MLRest'); + userService = $injector.get('userService'); + ngToast = $injector.get('ngToast'); + + $q = $injector.get('$q'); + + $compile = $injector.get('$compile'); + + sinon.stub(MLRest, 'createDocument', () => $q.when('/?uri=blah')); + + sinon.stub($state, 'go', function(state, params) { + nextState = { + state: state, + params: params + }; + }); + + sinon.stub(userService, 'currentUser').returns({ + name: 'test' + }); + + })); + + describe('Controller', () => { + + // controller specs + let controller; + + beforeEach(() => { + controller = $componentController('create', { + $scope: $rootScope.$new() + }); + + //initialise controller + controller.$onInit(); + }); + + // controller specs + it('should be created successfully', function() { + expect(controller).to.be.defined; + }); + + it('has a person property', () => { + expect(controller).to.have.property('person'); + }); + + it('current user is empty', function() { + expect(controller.currentUser).to.be.undefined; + }); + + it('should update the current user if user service returns one from digest', function() { + $rootScope.$digest(); + expect(controller.currentUser).to.have.property('name'); + }); + + it('should add tags', function() { + var tagValue = 'testTag'; + expect(controller.person.tags.length).to.eq(0); + controller.newTag = tagValue; + controller.addTag(); + expect(controller.person.tags.length).to.eq(1); + expect(controller.person.tags[0]).to.eq(tagValue); + expect(controller.newTag).to.eq.null; + }); + + it('should show the detail view when submitted', function() { + controller.submit(); + $rootScope.$apply(); + expect(nextState).to.deep.eq({ + state: 'root.view', + params: { + uri: 'blah' + } + }); + }); + }); + + describe('View', () => { + // view layer specs. + let scope, template; + + beforeEach(() => { + scope = $rootScope.$new(); + template = $compile('')(scope); + scope.$apply(); + }); + + it('Load\'s title in template as a result of user being logged in', () => { + expect(template.find('h2').html()).to.eq('Create a Document'); + }); + }); + + describe('Component', () => { + // component/directive specs + let component = Component; + + it('includes the intended template', () => { + expect(component.template).to.equal(Template); + }); + + it('invokes the right controller', () => { + expect(component.controller).to.equal(Controller); + }); + }); + +}); diff --git a/app/templates/ui/app/components/create/stringtonumber.directive.js b/app/templates/ui/app/components/create/stringtonumber.directive.js index 91e97671..d2da3625 100644 --- a/app/templates/ui/app/components/create/stringtonumber.directive.js +++ b/app/templates/ui/app/components/create/stringtonumber.directive.js @@ -1,21 +1,15 @@ -(function () { - 'use strict'; +function StringToNumber() { + return { + require: 'ngModel', + link: function(scope, element, attrs, ngModel) { + ngModel.$parsers.push(function(value) { + return '' + value; + }); + ngModel.$formatters.push(function(value) { + return parseFloat(value); + }); + } + }; +} - angular.module('app.create') - .directive('stringToNumber', StringToNumber); - - function StringToNumber () { - return { - require: 'ngModel', - link: function(scope, element, attrs, ngModel) { - ngModel.$parsers.push(function(value) { - return '' + value; - }); - ngModel.$formatters.push(function(value) { - return parseFloat(value); - }); - } - }; - } - -}()); +export default StringToNumber; diff --git a/app/templates/ui/app/components/detail/detail.controller.js b/app/templates/ui/app/components/detail/detail.controller.js index 4d097348..a2f5786e 100644 --- a/app/templates/ui/app/components/detail/detail.controller.js +++ b/app/templates/ui/app/components/detail/detail.controller.js @@ -68,91 +68,9 @@ class DetailCtrl { } } - DetailCtrl.$inject = ['$stateParams', 'MLRest', 'ngToast', '$state', '$scope', 'userService' ]; export default DetailCtrl; - - -//updated - -/* global X2JS,vkbeautify */ -/* -(function () { - 'use strict'; - angular.module('app.detail') - .controller('DetailCtrl', DetailCtrl); - - DetailCtrl.$inject = ['doc', '$stateParams','MLRest', 'ngToast', - '$state','$scope','userService']; - function DetailCtrl(doc, $stateParams, MLRest, toast, $state, $scope, userService) { - var ctrl = this; - - var uri = $stateParams.uri; - - var contentType = doc.headers('content-type'); - - var x2js = new X2JS(); - /* jscs: disable */ -/* -if (contentType.lastIndexOf('application/json', 0) === 0) { - /*jshint camelcase: false */ -/* - ctrl.xml = vkbeautify.xml(x2js.json2xml_str( - { xml: doc.data } - )); - ctrl.json = doc.data; - ctrl.type = 'json'; - } else if (contentType.lastIndexOf('application/xml', 0) === 0) { - ctrl.xml = vkbeautify.xml(doc.data); - /*jshint camelcase: false */ -/* -ctrl.json = x2js.xml_str2json(doc.data).xml; -ctrl.type = 'xml'; -/* jscs: enable */ -/* - } else if (contentType.lastIndexOf('text/plain', 0) === 0) { - ctrl.xml = doc.data; - ctrl.json = {'Document' : doc.data}; - ctrl.type = 'text'; - } else if (contentType.lastIndexOf('application', 0) === 0 ) { - ctrl.xml = 'Binary object'; - ctrl.json = {'Document type' : 'Binary object'}; - ctrl.type = 'binary'; - } else { - ctrl.xml = 'Error occured determining document type.'; - ctrl.json = {'Error' : 'Error occured determining document type.'}; - } - - function deleteFunc() { - MLRest.deleteDocument (uri).then(function(response) { - // create a toast with settings: - toast.create({ - className: 'warning', - content: 'Deleted ' + uri, - dismissOnTimeout: true, - timeout: 2000, - onDismiss: function () { - //redirect to search page - $state.go('root.search'); - } - }); - }); - } - - angular.extend(ctrl, { - doc : doc.data, - uri : uri, - currentUser: null, - delete: deleteFunc - }); - - $scope.$watch(userService.currentUser, function(newValue) { - ctrl.currentUser = newValue; - }); - } -}()); -*/ diff --git a/app/templates/ui/app/components/detail/detail.controller.spec.js b/app/templates/ui/app/components/detail/detail.controller.spec.js deleted file mode 100644 index 158910ba..00000000 --- a/app/templates/ui/app/components/detail/detail.controller.spec.js +++ /dev/null @@ -1,75 +0,0 @@ -/* jshint -W117, -W030 */ -import Module from './detail'; - -import Controller from './detail.controller'; -import Component from './detail.component'; -import Template from './detail.html'; - -describe('Detail', () => { - let $rootScope, $stateParams, controller, scope; - let doc; - - beforeEach(window.module(Module)); - - beforeEach(inject((_$rootScope_, _$stateParams_) => { - $rootScope = _$rootScope_; - $stateParams = _$stateParams_; - scope = $rootScope.$new(); - - })); - - beforeEach(() => { - // stub the current user - $stateParams.uri = '/test/'; - - controller = new Controller($stateParams); - - var headers = 'application/json'; - doc = { - headers: headers, - data: { - name: 'hi' - } - }; - - controller.doc = doc; - }); - - describe('Module', () => { - // top-level specs: i.e., routes, injection, naming - }); - - describe('Controller', () => { - // controller specs - it('should be created successfully', function() { - expect(controller).to.be.defined; - }); - - it('should have the doc data we gave it', function() { - expect(controller.doc.headers).to.eq('application/json'); - expect(controller.doc.data).to.eq(doc.data); - }); - - it('should have correct uri', function() { - expect(controller.$stateParams.uri).to.eq('/test/'); - }); - }); - - describe('Template', () => { - // template specs - // tip: use regex to ensure correct bindings are used e.g., {{ }} - }); - - describe('Component', () => { - // component/directive specs - let component = Component; - - it('includes the intended template', () => { - expect(component.template).to.equal(Template); - }); - - it('invokes the right controller', () => { - expect(component.controller).to.equal(Controller); - }); - }); -}); diff --git a/app/templates/ui/app/components/detail/detail.spec.js b/app/templates/ui/app/components/detail/detail.spec.js new file mode 100644 index 00000000..b307a69f --- /dev/null +++ b/app/templates/ui/app/components/detail/detail.spec.js @@ -0,0 +1,108 @@ +/* jshint -W117, -W030 */ +import Module from './detail'; + +import Controller from './detail.controller'; +import Component from './detail.component'; +import Template from './detail.html'; + +describe('app.detail', () => { + let $rootScope, $compile, $componentController, $q; + let $stateParams, MLRest; + + let doc = { + headers: () => { + return 'application/json'; + }, + data: { + name: 'hi' + } + }; + + beforeEach(window.module(Module)); + + beforeEach(inject(($injector) => { + $rootScope = $injector.get('$rootScope'); + $componentController = $injector.get('$componentController'); + $compile = $injector.get('$compile'); + + $stateParams = $injector.get('$stateParams'); + MLRest = $injector.get('MLRest'); + + $q = $injector.get('$q'); + + sinon.stub(MLRest, 'extension', () => { + return $q.when({ + data: { + similar: [] + } + }); + }); + + })); + + describe('Controller', () => { + + // controller specs + let controller; + + beforeEach(() => { + controller = $componentController('detail', { + $scope: $rootScope.$new() + }); + + // stub a document and uri + $stateParams.uri = '/test/'; + + controller.doc = doc; + + //initialise controller + controller.$onInit(); + }); + + // controller specs + it('should be created successfully', function() { + expect(controller).to.be.defined; + }); + + it('should have the doc data we gave it', function() { + expect(controller.json).to.eq(doc.data); + }); + + it('should have correct uri', function() { + expect(controller.$stateParams.uri).to.eq('/test/'); + }); + }); + + describe('View', () => { + // view layer specs. + let scope, template; + + beforeEach(() => { + scope = $rootScope.$new(); + + //add doc to scope so that when compiled it will be added as a binding + scope.doc = doc; + + template = $compile('')(scope); + scope.$apply(); + }); + + it('Should load JSON tab', () => { + expect(template.find('uib-tab-heading').html()).to.eq('JSON'); + }); + }); + + describe('Component', () => { + // component/directive specs + let component = Component; + + it('includes the intended template', () => { + expect(component.template).to.equal(Template); + }); + + it('invokes the right controller', () => { + expect(component.controller).to.equal(Controller); + }); + }); + +}); diff --git a/app/templates/ui/app/components/landing/landing.html b/app/templates/ui/app/components/landing/landing.html index ef3f9375..de4d0e7a 100644 --- a/app/templates/ui/app/components/landing/landing.html +++ b/app/templates/ui/app/components/landing/landing.html @@ -88,4 +88,4 @@

IDE Suggestions

- \ No newline at end of file + diff --git a/app/templates/ui/app/components/login/login.js b/app/templates/ui/app/components/login/login.js index a45c3c6e..ad445ffb 100644 --- a/app/templates/ui/app/components/login/login.js +++ b/app/templates/ui/app/components/login/login.js @@ -9,8 +9,11 @@ import AuthInterceptor from './loginInterceptor.service'; import LoginFullComponent from './login-full.component'; import LoginComponent from './login.component'; +//run globally since not in an es6 module +import 'script!ml-common-ng/dist/ml-common-ng'; + const login = angular - .module('app.login', [uiBootstrap, uiRouter, messageBoard]) + .module('app.login', [uiBootstrap, uiRouter, messageBoard, 'ml.common']) .component('loginFull', LoginFullComponent) .component('login', LoginComponent) .factory('loginService', loginService) diff --git a/app/templates/ui/app/components/login/login.service.spec.js b/app/templates/ui/app/components/login/login.service.spec.js deleted file mode 100644 index f052f4f5..00000000 --- a/app/templates/ui/app/components/login/login.service.spec.js +++ /dev/null @@ -1,149 +0,0 @@ -/* jshint -W117, -W030 */ -(function() { - - describe('Service: loginService', function() { - - var service; - var modalOpened = false; - var _user = { - data: { - username: 'bob', - authenticated: true - } - }; - - beforeEach(function() { - bard.appModule('app.login'); - bard.inject('$q', '$http', '$uibModal', '$rootScope', '$state', '$stateParams'); - - bard.mockService($http, { - _default: $q.when([]), - get: $q.when(_user) - }); - - bard.mockService($state, { - current: { - name: 'root.search', - params: {} - }, - go: function(stateName, stateParams) { - this.current = { - name: stateName, - params: stateParams - }; - return $q.when(); - } - }); - - bard.mockService($uibModal, { - open: function() { - modalOpened = true; - return { - result: angular.noop - }; - } - }); - - }); - - beforeEach(inject(function(_loginService_) { - service = _loginService_; - - modalOpened = false; - })); - - it('should be defined', function() { - expect(service).to.be.defined; - }); - - it('should open modal if mode is "modal"', function() { - service.loginMode('modal'); - service.loginPrompt(); - expect(modalOpened).to.be.true; - }); - - it('should go to login page if mode is "full"', function() { - service.loginMode('full'); - service.loginPrompt(); - expect($state.current.name).to.be.equal('root.login'); - }); - - it('should not go to login page if mode is "modal"', function() { - service.loginMode('modal'); - service.loginPrompt(); - expect($state.current.name).not.to.be.equal('root.login'); - }); - }); - - describe('Service: loginService - should be authenticated if logged in already', function() { - - var service; - var modalOpened = false; - var _user = { - data: { - username: 'bob', - authenticated: true - } - }; - - beforeEach(function() { - bard.appModule('app.login'); - bard.inject('$q', '$http', '$uibModal', '$rootScope', '$state', '$stateParams'); - - bard.mockService($http, { - _default: $q.when([]), - get: $q.when(_user) - }); - - bard.mockService($state, { - current: { - name: 'root.search', - params: {} - }, - go: function(stateName, stateParams) { - this.current = { - name: stateName, - params: stateParams - }; - return $q.when(); - } - }); - - bard.mockService($uibModal, { - open: function() { - modalOpened = true; - return { - result: angular.noop - }; - } - }); - - }); - - beforeEach(inject(function(_loginService_) { - service = _loginService_; - - modalOpened = false; - })); - - //needs to be present for logged in already test - afterEach(inject(function($rootScope) { - $rootScope.$apply(); - })); - - it('should be defined', function() { - expect(service).to.be.defined; - }); - - it('should be logged in already', function() { - expect(service.isAuthenticated()).to.be.not.defined; - - var isLoggedIn = false; - service.getAuthenticatedStatus($rootScope).then(function(done) { - isLoggedIn = service.isAuthenticated(); - expect(isLoggedIn).to.eq(true); - }); - - }); - }); -}()); diff --git a/app/templates/ui/app/components/login/login.spec.js b/app/templates/ui/app/components/login/login.spec.js new file mode 100644 index 00000000..2dd3992f --- /dev/null +++ b/app/templates/ui/app/components/login/login.spec.js @@ -0,0 +1,183 @@ +/* jshint -W117, -W030 */ +import Module from './login'; + +import Controller from './login.controller'; +import Component from './login.component'; +import Template from './login-component.html'; + +describe('app.login', () => { + let modalOpened = false; + let _user = { + data: { + username: 'bob', + authenticated: true + } + }; + + beforeEach(bard.appModule(Module)); + + beforeEach(() => { + bard.inject('$q', '$http', '$uibModal', '$rootScope', '$state', '$stateParams'); + + bard.mockService($http, { + _default: $q.when([]), + get: $q.when(_user) + }); + + bard.mockService($state, { + current: { + name: 'root.search', + params: {} + }, + go: function(stateName, stateParams) { + this.current = { + name: stateName, + params: stateParams + }; + return $q.when(); + } + }); + + bard.mockService($uibModal, { + open: function() { + modalOpened = true; + return { + result: angular.noop + }; + } + }); + + }); + + describe('Service: loginService', () => { + + let service; + + beforeEach(inject(($injector) => { + service = $injector.get('loginService'); + + modalOpened = false; + })); + + it('should be defined', function() { + expect(service).to.be.defined; + }); + + it('should open modal if mode is "modal"', function() { + service.loginMode('modal'); + service.loginPrompt(); + expect(modalOpened).to.be.true; + }); + + it('should go to login page if mode is "full"', function() { + service.loginMode('full'); + service.loginPrompt(); + expect($state.current.name).to.be.equal('root.login'); + }); + + it('should not go to login page if mode is "modal"', function() { + service.loginMode('modal'); + service.loginPrompt(); + expect($state.current.name).not.to.be.equal('root.login'); + }); + }); + + describe('Service: loginService - should be authenticated if logged in already', function() { + + let service; + + beforeEach(inject(($injector) => { + service = $injector.get('loginService'); + + modalOpened = false; + })); + + //needs to be present for logged in already test + afterEach(inject(($rootScope) => { + $rootScope.$apply(); + })); + + it('should be defined', () => { + expect(service).to.be.defined; + }); + + it('should be logged in already', () => { + expect(service.isAuthenticated()).to.be.not.defined; + + let isLoggedIn = false; + service.getAuthenticatedStatus($rootScope).then((done) => { + isLoggedIn = service.isAuthenticated(); + expect(isLoggedIn).to.eq(true); + }); + + }); + }); + + describe('Controller', () => { + + // controller specs + let controller, loginService; + + let $componentController; + + beforeEach(inject(($injector) => { + $componentController = $injector.get('$componentController'); + loginService = $injector.get('loginService'); + + modalOpened = false; + })); + + beforeEach(() => { + controller = $componentController('login', { + $scope: $rootScope.$new() + }); + }); + + // controller specs + it('Controller calls login Service', function() { + controller.username = 'user'; + controller.password = 'pass'; + let loginMock = sinon.stub(loginService, 'login').returns($q.when({})); + + controller.login(); + + loginMock.restore(); + + expect(loginMock.calledWith('user', 'pass')).to.eq(true); + }); + }); + + describe('View', () => { + // view layer specs. + let scope, template, $compile; + + beforeEach(inject(($injector) => { + $compile = $injector.get('$compile'); + })); + + beforeEach(() => { + scope = $rootScope.$new(); + + template = $compile('')(scope); + scope.$apply(); + }); + + it('Invalid Username from empty scope', () => { + expect(template.find('div')[1].innerHTML).to.eq('Username and/or Password Incorrect'); + }); + }); + + describe('Component', () => { + // component/directive specs + let component = Component; + + it('includes the intended template', () => { + expect(component.template).to.equal(Template); + }); + + it('invokes the right controller', () => { + expect(component.controller).to.equal(Controller); + }); + }); + +}); diff --git a/app/templates/ui/app/components/user/profile.component.js b/app/templates/ui/app/components/profile/profile.component.js similarity index 100% rename from app/templates/ui/app/components/user/profile.component.js rename to app/templates/ui/app/components/profile/profile.component.js diff --git a/app/templates/ui/app/components/user/profile.controller.js b/app/templates/ui/app/components/profile/profile.controller.js similarity index 100% rename from app/templates/ui/app/components/user/profile.controller.js rename to app/templates/ui/app/components/profile/profile.controller.js diff --git a/app/templates/ui/app/components/user/profile.html b/app/templates/ui/app/components/profile/profile.html similarity index 100% rename from app/templates/ui/app/components/user/profile.html rename to app/templates/ui/app/components/profile/profile.html diff --git a/app/templates/ui/app/components/profile/profile.js b/app/templates/ui/app/components/profile/profile.js new file mode 100644 index 00000000..b0cdefc9 --- /dev/null +++ b/app/templates/ui/app/components/profile/profile.js @@ -0,0 +1,17 @@ +import angular from 'angular'; +import ngSanitize from 'angular-sanitize'; + +import ProfileComponent from './profile.component'; + +import User from '../user/user'; + +//run globally since not in an es6 module +import 'script!ml-common-ng/dist/ml-common-ng'; +import 'script!ng-toast'; + +const module = angular.module('app.profile', ['ml.common', 'ngToast', ngSanitize, User]) + .component('profile', ProfileComponent) + .name; + +export +default module; diff --git a/app/templates/ui/app/components/profile/profile.spec.js b/app/templates/ui/app/components/profile/profile.spec.js new file mode 100644 index 00000000..892596e5 --- /dev/null +++ b/app/templates/ui/app/components/profile/profile.spec.js @@ -0,0 +1,138 @@ +/* jshint -W117, -W030 */ +import Module from './profile'; + +import Controller from './profile.controller'; +import Component from './profile.component'; +import Template from './profile.html'; + +describe('app.profile', () => { + + let $rootScope, controller, $componentController, $compile; + let $q, MLRest; + let $state, userService; + + let currentUser = { + emails: [] + }; + + let currentState; + + beforeEach(bard.appModule(Module)); + + beforeEach(inject(($injector) => { + $rootScope = $injector.get('$rootScope'); + $componentController = $injector.get('$componentController'); + $compile = $injector.get('$compile'); + + MLRest = $injector.get('MLRest'); + $state = $injector.get('$state'); + userService = $injector.get('userService'); + + $q = $injector.get('$q'); + + bard.mockService(MLRest, { + _default: $q.when([]), + updateDocument: $q.when() + }); + + bard.mockService(userService, { + currentUser: function() { + return currentUser; + } + }); + + bard.mockService($state, { + go: function(s) { + currentState = s; + } + }); + + })); + + beforeEach(() => { + controller = $componentController('profile', { + $scope: $rootScope.$new() + }); + $rootScope.$apply(); + }); + + describe('Module', () => { + // top-level specs: i.e., routes, injection, naming + }); + + describe('Controller', () => { + // controller specs + it('should be created successfully', function() { + expect(controller).to.be.defined; + }); + + it('should not add a blank email', function() { + var newEmail = ''; + controller.newEmail = newEmail; + expect(controller.user.emails.length).to.eq(0); + controller.addEmail(); + $rootScope.$apply(); + expect(controller.user.emails.length).to.eq(0); + }); + + it('should add a nonblank email', function() { + var newEmail = 'test@test.com'; + controller.newEmail = newEmail; + expect(controller.user.emails.length).to.eq(0); + controller.addEmail(); + $rootScope.$apply(); + expect(controller.user.emails.length).to.eq(1); + expect(controller.user.emails[0]).to.eq(newEmail); + }); + + it('should remove an email', function() { + controller.user.emails = [ + 'abc@def.com', + 'def@ghi.com' + ]; + + expect(controller.user.emails.length).to.eq(2); + controller.removeEmail(1); + $rootScope.$apply(); + expect(controller.user.emails.length).to.eq(1); + expect(controller.user.emails[0]).to.eq('abc@def.com'); + }); + + it('should not update the profile if form errors', function() { + var form = { + $valid: false + }; + controller.submit(form); + $rootScope.$apply(); + expect(currentState).to.not.be.defined; + }); + + it('should update the profile', function() { + var form = { + $valid: true + }; + controller.submit(form); + $rootScope.$apply(); + expect(currentState).to.eq('root'); + }); + + }); + + describe('Template', () => { + // template specs + // tip: use regex to ensure correct bindings are used e.g., {{ }} + }); + + describe('Component', () => { + // component/directive specs + let component = Component; + + it('includes the intended template', () => { + expect(component.template).to.equal(Template); + }); + + it('invokes the right controller', () => { + expect(component.controller).to.equal(Controller); + }); + }); +}); diff --git a/app/templates/ui/app/components/search/search.controller.js b/app/templates/ui/app/components/search/search.controller.js index afc44619..6be0624f 100644 --- a/app/templates/ui/app/components/search/search.controller.js +++ b/app/templates/ui/app/components/search/search.controller.js @@ -1,6 +1,4 @@ /* globals MLSearchController: false */ -//import statement is more readable than a global but results in larger interface files since it needs to be included again -//import MLSearchController from 'exports?MLSearchController!ml-search-ng/dist/ml-search-ng'; import searchResultsTemplate from './search-results.html'; @@ -13,7 +11,6 @@ class SearchCtrl extends MLSearchController { this.$location = $location; this.userService = userService; this.searchFactory = searchFactory; - this.init(); //retrieve and insert searchResults template diff --git a/app/templates/ui/app/components/search/search.controller.spec.js b/app/templates/ui/app/components/search/search.controller.spec.js deleted file mode 100644 index 7437358a..00000000 --- a/app/templates/ui/app/components/search/search.controller.spec.js +++ /dev/null @@ -1,201 +0,0 @@ -/* jshint -W117, -W030 */ -import Module from './search'; - -import Controller from './search.controller'; -import Component from './search.component'; -import Template from './search.html'; - -//import MLSearchController from 'exports?MLSearchController!ml-search-ng/dist/ml-search-ng'; - -describe('Search', () => { - - let sandbox, $httpBackend; - let $rootScope, controller, scope; - let $q, $location, MLSearchFactory, userService, MLRest; - - var currentUser = null; - /* - var results = [{ - uri: 'abc' - }, { - uri: 'def' - }]; - */ - beforeEach(window.module(Module)); - //jscs:disable maximumLineLength - beforeEach(inject((_$q_, _$rootScope_, _$location_, _MLSearchFactory_, _userService_, _$httpBackend_, _MLRest_) => { - //jscs:enable maximumLineLength - $q = _$q_; - $rootScope = _$rootScope_; - scope = $rootScope.$new(); - - $location = _$location_; - MLSearchFactory = _MLSearchFactory_; - userService = _userService_; - $httpBackend = _$httpBackend_; - MLRest = _MLRest_; - - sandbox = sinon.sandbox.create(); - - MLRest = sinon.stub(MLRest, 'search', function() { - return $q.when({ - data: { - results: [{ - metadata: 'test', - uri: 'abc' - }, { - metadata: 'test', - uri: 'def' - }] - } - }); - }); - - sandbox.stub(userService, 'currentUser', () => $q.when(currentUser)); - - /* - //atempts to mock up search/mlRest method - sandbox.stub(MLSearchController.prototype, 'search', () => { - return [{ - metadata: 'test', - uri: 'abc' - }, { - metadata: 'test', - uri: 'def' - }]; - }); - - server.respondWith('GET', /./, [200, { - 'Content-Type': 'application/json' - }, `{ - data: { - results: [{ - metadata: 'test', - uri: 'abc' - }, { - metadata: 'test', - uri: 'def' - }] - } - }`]); - */ - - })); - - beforeEach(() => { - controller = new Controller(scope, $location, userService, MLSearchFactory); - }); - - afterEach(() => { - sandbox.restore(); - }); - - describe('Module', () => { - // top-level specs: i.e., routes, injection, naming - }); - - describe('Controller', () => { - // controller specs - it('should be created successfully', function() { - expect(controller).to.be.defined; - }); - - it('should update the current user if it changes', function() { - expect(controller.currentUser).to.not.be.defined; - }); - - /* - //test currently not working - it('should run a search', function() { - - - controller.search('stuff'); - $rootScope.$digest(); - - //$httpBackend.flush(); - - expect(controller.response.results).to.eq(results); - }); - */ - }); - - describe('Template', () => { - // template specs - // tip: use regex to ensure correct bindings are used e.g., {{ }} - }); - - describe('Component', () => { - // component/directive specs - let component = Component; - - it('includes the intended template', () => { - expect(component.template).to.equal(Template); - }); - - it('invokes the right controller', () => { - expect(component.controller).to.equal(Controller); - }); - }); -}); - -//Original test -/* jshint -W117, -W030 */ -/* -(function() { - 'use strict'; - - describe('Controller: SearchCtrl', function() { - - var controller; - - var currentUser = null; - - var results = [{ - uri: 'abc' - }, { - uri: 'def' - }]; - - beforeEach(function() { - bard.appModule('app.search'); - bard.inject('$controller', '$q', '$rootScope', '$location', - 'userService', 'MLSearchFactory', 'MLRest'); - - bard.mockService(userService, { - currentUser: $q.when(currentUser) - }); - - - bard.mockService(MLRest, { - search: $q.when({ - data: { - results: results - } - }) - }); - - }); - - beforeEach(function() { - controller = $controller('SearchCtrl', { - $scope: $rootScope.$new() - }); - $rootScope.$apply(); - }); - - it('should be created successfully', function() { - expect(controller).to.be.defined; - }); - - it('should update the current user if it changes', function() { - expect(controller.currentUser).to.not.be.defined; - }); - - it('should run a search', function() { - controller.search('stuff'); - $rootScope.$apply(); - expect(controller.response.results).to.eq(results); - }); - }); -}()); -*/ diff --git a/app/templates/ui/app/components/search/search.spec.js b/app/templates/ui/app/components/search/search.spec.js new file mode 100644 index 00000000..74e1aaee --- /dev/null +++ b/app/templates/ui/app/components/search/search.spec.js @@ -0,0 +1,94 @@ +/* jshint -W117, -W030 */ +import Module from './search'; + +import Controller from './search.controller'; +import Component from './search.component'; +import Template from './search.html'; + +describe('app.search', () => { + + let $rootScope, controller, $componentController, $compile; + let $q, $location, MLSearchFactory, userService, MLRest; + + let results = [{ + uri: 'abc' + }, { + uri: 'def' + }]; + + beforeEach(window.module(Module)); + + beforeEach(inject(($injector) => { + $rootScope = $injector.get('$rootScope'); + $componentController = $injector.get('$componentController'); + $compile = $injector.get('$compile'); + + MLRest = $injector.get('MLRest'); + $location = $injector.get('$location'); + MLSearchFactory = $injector.get('MLSearchFactory'); + userService = $injector.get('userService'); + + $q = $injector.get('$q'); + + sinon.stub(MLRest, 'search').returns($q.resolve({ + data: { + results: results + } + })); + + sinon.stub(userService, 'currentUser').returns({ + name: 'test' + }); + + })); + + beforeEach(() => { + controller = $componentController('search', { + $scope: $rootScope.$new() + }); + $rootScope.$apply(); + }); + + describe('Module', () => { + // top-level specs: i.e., routes, injection, naming + }); + + describe('Controller', () => { + // controller specs + it('should be created successfully', function() { + expect(controller).to.be.defined; + }); + + it('should update the current user if it changes', function() { + expect(controller.currentUser).to.not.be.defined; + }); + + //test currently not working + it('should run a search', function() { + + controller.search('stuff'); + $rootScope.$apply(); + + expect(controller.response.results).to.eq(results); + }); + + }); + + describe('Template', () => { + // template specs + // tip: use regex to ensure correct bindings are used e.g., {{ }} + }); + + describe('Component', () => { + // component/directive specs + let component = Component; + + it('includes the intended template', () => { + expect(component.template).to.equal(Template); + }); + + it('invokes the right controller', () => { + expect(component.controller).to.equal(Controller); + }); + }); +}); diff --git a/app/templates/ui/app/components/user/profile.controller.spec.js b/app/templates/ui/app/components/user/profile.controller.spec.js deleted file mode 100644 index 2f52325d..00000000 --- a/app/templates/ui/app/components/user/profile.controller.spec.js +++ /dev/null @@ -1,102 +0,0 @@ -/* jshint -W117, -W030 */ -import Module from './user'; - -import Controller from './profile.controller'; -(function() { - - describe('Controller: ProfileCtrl', function() { - - var controller; - - var currentUser = { - emails: [] - }; - - var currentState; - - beforeEach(function() { - bard.appModule(Module); - bard.inject('$controller', '$q', '$rootScope', 'MLRest', '$state', 'userService'); - - bard.mockService(MLRest, { - _default: $q.when([]), - updateDocument: $q.when() - }); - - bard.mockService(userService, { - currentUser: function() { - return currentUser; - } - }); - - bard.mockService($state, { - go: function(s) { - currentState = s; - } - }); - - }); - - beforeEach(function() { - controller = $controller(Controller, { - $scope: $rootScope.$new() - }); - $rootScope.$apply(); - }); - - it('should be created successfully', function() { - expect(controller).to.be.defined; - }); - - it('should not add a blank email', function() { - var newEmail = ''; - controller.newEmail = newEmail; - expect(controller.user.emails.length).to.eq(0); - controller.addEmail(); - $rootScope.$apply(); - expect(controller.user.emails.length).to.eq(0); - }); - - it('should add a nonblank email', function() { - var newEmail = 'test@test.com'; - controller.newEmail = newEmail; - expect(controller.user.emails.length).to.eq(0); - controller.addEmail(); - $rootScope.$apply(); - expect(controller.user.emails.length).to.eq(1); - expect(controller.user.emails[0]).to.eq(newEmail); - }); - - it('should remove an email', function() { - controller.user.emails = [ - 'abc@def.com', - 'def@ghi.com' - ]; - - expect(controller.user.emails.length).to.eq(2); - controller.removeEmail(1); - $rootScope.$apply(); - expect(controller.user.emails.length).to.eq(1); - expect(controller.user.emails[0]).to.eq('abc@def.com'); - }); - - it('should not update the profile if form errors', function() { - var form = { - $valid: false - }; - controller.submit(form); - $rootScope.$apply(); - expect(currentState).to.not.be.defined; - }); - - it('should update the profile', function() { - var form = { - $valid: true - }; - controller.submit(form); - $rootScope.$apply(); - expect(currentState).to.eq('root'); - }); - - }); -}()); diff --git a/app/templates/ui/app/components/user/user.js b/app/templates/ui/app/components/user/user.js index 987863ea..ad39ea94 100644 --- a/app/templates/ui/app/components/user/user.js +++ b/app/templates/ui/app/components/user/user.js @@ -2,7 +2,6 @@ import angular from 'angular'; import ngSanitize from 'angular-sanitize'; import UserService from './user.service'; -import ProfileComponent from './profile.component'; import MlUserComponent from './mlUser.component'; import Login from '../login/login'; @@ -13,7 +12,6 @@ import 'script!ng-toast'; const user = angular.module('app.user', ['ml.common', 'ngToast', ngSanitize, Login]) .factory('userService', UserService) - .component('profile', ProfileComponent) .component('mlUser', MlUserComponent) .name; diff --git a/app/templates/ui/app/components/user/user.service.spec.js b/app/templates/ui/app/components/user/user.service.spec.js deleted file mode 100644 index c82c6205..00000000 --- a/app/templates/ui/app/components/user/user.service.spec.js +++ /dev/null @@ -1,96 +0,0 @@ -/* jshint -W117, -W030 */ -(function() { - - describe('Service: userService', function() { - - var service; - var _user = { - data: { - username: 'bob', - authenticated: true - } - }; - - beforeEach(function() { - bard.appModule('app.user'); - bard.inject('$q', '$http', '$rootScope', '$state', 'loginService'); - - bard.mockService($http, { - _default: $q.when([]), - get: $q.when(_user), - post: $q.when(_user) - }); - - bard.mockService($state, { - current: { - name: 'root.search', - params: {} - }, - go: function(stateName, stateParams) { - this.current = { - name: stateName, - params: stateParams - }; - return $q.when(); - }, - reload: function() { - return $q.when(); - } - }); - - bard.mockService(loginService, { - getAuthenticatedStatus: $q.when(), - }); - - }); - - beforeEach(inject(function(_userService_) { - service = _userService_; - })); - - it('should be defined', function() { - expect(service).to.be.defined; - }); - - it('currentUser should not be defined', function() { - expect(service.currentUser()).to.not.be.defined; - }); - - it('should get the current logged in user - if loginService not init', function() { - service.getUser().then(function(user) { - expect(user).to.deep.eq(null); - }); - - expect(loginService.getAuthenticatedStatus).to.have.been.calledOnce; - - $rootScope.$apply(); - }); - - it('should update the current user when logged in using loginService', function(done) { - $rootScope.$broadcast('loginService:login-success', { - data: _user - }); - $rootScope.$apply(service); - - done(); - expect(service.currentUser().name).to.eq('bob'); - }); - - it('should not set user with invalid credentials', function() { - _user.data.authenticated = false; - $rootScope.$broadcast('loginService:login-success', { - data: _user - }); - $rootScope.$apply(service); - - expect(service.currentUser().name).to.eq(undefined); - }); - - it('should clear user after logout', function() { - $rootScope.$broadcast('loginService:logout-success'); - $rootScope.$apply(service); - - expect(service.currentUser()).to.not.be.defined; - }); - }); -}()); diff --git a/app/templates/ui/app/components/user/user.spec.js b/app/templates/ui/app/components/user/user.spec.js new file mode 100644 index 00000000..36151f0d --- /dev/null +++ b/app/templates/ui/app/components/user/user.spec.js @@ -0,0 +1,126 @@ +/* jshint -W117, -W030 */ +import Module from './user'; + +import Component from './mlUser.component'; +import Template from './user-component.html'; + +describe('app.user', () => { + + var service; + var _user = { + data: { + username: 'bob', + authenticated: true + } + }; + + beforeEach(bard.appModule(Module)); + + beforeEach(function() { + bard.inject('$q', '$http', '$rootScope', '$state', 'loginService'); + + bard.mockService($http, { + _default: $q.when([]), + get: $q.when(_user), + post: $q.when(_user) + }); + + bard.mockService($state, { + current: { + name: 'root.search', + params: {} + }, + go: function(stateName, stateParams) { + this.current = { + name: stateName, + params: stateParams + }; + return $q.when(); + }, + reload: function() { + return $q.when(); + } + }); + + sinon.stub(loginService, 'getAuthenticatedStatus').returns($q.when()); + }); + + beforeEach(inject(($injector) => { + service = $injector.get('userService'); + })); + + it('should be defined', function() { + expect(service).to.be.defined; + }); + + it('currentUser should not be defined', function() { + expect(service.currentUser()).to.not.be.defined; + }); + + it('should get the current logged in user - if loginService not init', function() { + service.getUser().then(function(user) { + expect(user).to.deep.eq(null); + }); + + expect(loginService.getAuthenticatedStatus).to.have.been.calledOnce; + + $rootScope.$apply(); + }); + + it('should update the current user when logged in using loginService', function(done) { + $rootScope.$broadcast('loginService:login-success', { + data: _user + }); + $rootScope.$apply(service); + + done(); + expect(service.currentUser().name).to.eq('bob'); + }); + + it('should not set user with invalid credentials', function() { + _user.data.authenticated = false; + $rootScope.$broadcast('loginService:login-success', { + data: _user + }); + $rootScope.$apply(service); + + expect(service.currentUser().name).to.eq(undefined); + }); + + it('should clear user after logout', function() { + $rootScope.$broadcast('loginService:logout-success'); + $rootScope.$apply(service); + + expect(service.currentUser()).to.not.be.defined; + }); + + describe('View', () => { + // view layer specs. + let scope, template, $compile; + + beforeEach(inject(($injector) => { + $compile = $injector.get('$compile'); + })); + + beforeEach(() => { + scope = $rootScope.$new(); + + template = $compile('')(scope); + scope.$apply(); + }); + + it('Show Login button for login mode', () => { + expect(template.find('a')[0].innerHTML).to.eq('Login'); + }); + }); + + describe('Component', () => { + // component/directive specs + let component = Component; + + it('includes the intended template', () => { + expect(component.template).to.equal(Template); + }); + }); + +}); diff --git a/app/templates/webpack/webpack.config.js b/app/templates/webpack/webpack.config.js deleted file mode 100644 index a82167ff..00000000 --- a/app/templates/webpack/webpack.config.js +++ /dev/null @@ -1,77 +0,0 @@ -var path = require('path'); -var webpack = require('webpack'); -var HtmlWebpackPlugin = require('html-webpack-plugin'); -var ExtractTextPlugin = require('extract-text-webpack-plugin'); - -module.exports = { - devtool: 'sourcemap', - entry: {}, - module: { - loaders: [{ - test: /\.js$/, - exclude: [/app\/lib/, /node_modules/], - loader: 'ng-annotate!babel' - }, { - test: /\.html$/, - loader: 'html' - }, { - test: /\.styl$/, - loader: 'style!css!stylus' - }, { - test: /\.less$/, - loader: ExtractTextPlugin.extract( - // activate source maps via loader query - 'css?sourceMap!less?sourceMap' - ) - }, { - test: /\.css$/, - loader: 'style!css' - }, { - test: /\.(gif|png|jpe?g|svg|ico)(\?v=[0-9]\.[0-9]\.[0-9])?$/i, - loaders: [ - 'file?hash=sha512&digest=hex&name=[hash].[ext]', - 'image-webpack' - ] - }, { - test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, - loader: 'url-loader?limit=10000&mimetype=application/font-woff' - }, { - test: /\.(ttf|eot)(\?v=[0-9]\.[0-9]\.[0-9])?$/, - loader: 'file' - }] - }, - plugins: [ - // Injects bundles in your index.html instead of wiring all manually. - // It also adds hash to all injected assets so we don't have problems - // with cache purging during deployment. - new HtmlWebpackPlugin({ - template: 'ui/index.html', - inject: 'body', - favicon: 'ui/images/favicon.ico', - hash: true - }), - - // Automatically move all modules defined outside of application directory to vendor bundle. - // If you are using more complicated project structure, consider to specify common chunks manually. - new webpack.optimize.CommonsChunkPlugin({ - name: 'vendor', - minChunks: function(module, count) { - return module.resource && module.resource.indexOf(path.resolve(__dirname, 'ui')) === -1; - } - }), - new ExtractTextPlugin('styles.css') - ], - imageWebpackLoader: { - pngquant: { - quality: "65-90", - speed: 4 - }, - svgo: { - plugins: [{ - removeViewBox: false - }, { - removeEmptyAttrs: false - }] - } - } -}; diff --git a/app/templates/webpack/webpack.dev.config.js b/app/templates/webpack/webpack.dev.config.js index e10cfca5..eebbc6c9 100644 --- a/app/templates/webpack/webpack.dev.config.js +++ b/app/templates/webpack/webpack.dev.config.js @@ -1,19 +1,70 @@ -var webpack = require('webpack'); var path = require('path'); -var config = require('./webpack.config'); - -config.output = { - filename: '[name].bundle.js', - publicPath: '/', - path: path.resolve(__dirname, 'ui') -}; - -config.plugins = config.plugins.concat([ +var webpack = require('webpack'); +var HtmlWebpackPlugin = require('html-webpack-plugin'); +var ExtractTextPlugin = require('extract-text-webpack-plugin'); - // Adds webpack HMR support. It act's like livereload, - // reloading page after webpack rebuilt modules. - // It also updates stylesheets and inline assets without page reloading. - new webpack.HotModuleReplacementPlugin() -]); +module.exports = { + output: { + filename: '[name].bundle.js', + publicPath: 'http://localhost:3000/', + path: path.resolve(__dirname, 'ui') + }, + devtool: 'cheap-module-eval-source-map', + entry: {}, + module: { + loaders: [{ + test: /\.js$/, + exclude: [/app\/lib/, /node_modules/], + loader: 'ng-annotate!babel' + }, { + test: /\.html$/, + loader: 'html' + }, { + test: /\.styl$/, + loader: 'style!css!stylus' + }, { + test: /\.less$/, + loader: 'style!css?sourceMap!less?sourceMap' + }, { + test: /\.css$/, + loader: 'style!css' + }, { + test: /\.(gif|png|jpe?g|svg|ico)(\?v=[0-9]\.[0-9]\.[0-9])?$/i, + loaders: [ + 'file?hash=sha512&digest=hex&name=[hash].[ext]' + ] + }, { + test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, + loader: 'url-loader?limit=10000&mimetype=application/font-woff' + }, { + test: /\.(ttf|eot)(\?v=[0-9]\.[0-9]\.[0-9])?$/, + loader: 'file' + }] + }, + plugins: [ + new webpack.optimize.OccurenceOrderPlugin(), + // Injects bundles in your index.html instead of wiring all manually. + // It also adds hash to all injected assets so we don't have problems + // with cache purging during deployment. + new HtmlWebpackPlugin({ + template: 'ui/index.html', + inject: 'body', + favicon: 'ui/images/favicon.ico', + hash: true + }), -module.exports = config; + // Automatically move all modules defined outside of application directory to vendor bundle. + // If you are using more complicated project structure, consider to specify common chunks manually. + new webpack.optimize.CommonsChunkPlugin({ + name: 'vendor', + minChunks: function(module, count) { + return module.resource && module.resource.indexOf(path.resolve(__dirname, 'ui')) === -1; + } + }), + // Adds webpack HMR support. It act's like livereload, + // reloading page after webpack rebuilt modules. + // It also updates stylesheets and inline assets without page reloading. + new webpack.HotModuleReplacementPlugin(), + new webpack.NoErrorsPlugin() + ] +}; diff --git a/app/templates/webpack/webpack.dist.config.js b/app/templates/webpack/webpack.dist.config.js index 40751af1..ebb21e63 100644 --- a/app/templates/webpack/webpack.dist.config.js +++ b/app/templates/webpack/webpack.dist.config.js @@ -1,26 +1,92 @@ -var webpack = require('webpack'); var path = require('path'); -var config = require('./webpack.config'); - -config.output = { - filename: '[name].bundle.js', - publicPath: '', - path: path.resolve(__dirname, '../dist') -}; +var webpack = require('webpack'); +var HtmlWebpackPlugin = require('html-webpack-plugin'); +var ExtractTextPlugin = require('extract-text-webpack-plugin'); -config.plugins = config.plugins.concat([ +module.exports = { + output: { + filename: '[name].bundle.js', + publicPath: '', + path: path.resolve(__dirname, '../dist') + }, + devtool: 'sourcemap', + entry: {}, + module: { + loaders: [{ + test: /\.js$/, + exclude: [/app\/lib/, /node_modules/], + loader: 'ng-annotate!babel' + }, { + test: /\.html$/, + loader: 'html' + }, { + test: /\.styl$/, + loader: 'style!css!stylus' + }, { + test: /\.less$/, + loader: ExtractTextPlugin.extract( + // activate source maps via loader query + 'css?sourceMap!less?sourceMap' + ) + }, { + test: /\.css$/, + loader: 'style!css' + }, { + test: /\.(gif|png|jpe?g|svg|ico)(\?v=[0-9]\.[0-9]\.[0-9])?$/i, + loaders: [ + 'file?hash=sha512&digest=hex&name=[hash].[ext]', + 'image-webpack' + ] + }, { + test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, + loader: 'url-loader?limit=10000&mimetype=application/font-woff' + }, { + test: /\.(ttf|eot)(\?v=[0-9]\.[0-9]\.[0-9])?$/, + loader: 'file' + }] + }, + plugins: [ + // Injects bundles in your index.html instead of wiring all manually. + // It also adds hash to all injected assets so we don't have problems + // with cache purging during deployment. + new HtmlWebpackPlugin({ + template: 'ui/index.html', + inject: 'body', + favicon: 'ui/images/favicon.ico', + hash: false + }), - // Reduces bundles total size - new webpack.optimize.UglifyJsPlugin({ - mangle: { + // Automatically move all modules defined outside of application directory to vendor bundle. + // If you are using more complicated project structure, consider to specify common chunks manually. + new webpack.optimize.CommonsChunkPlugin({ + name: 'vendor', + minChunks: function(module, count) { + return module.resource && module.resource.indexOf(path.resolve(__dirname, 'ui')) === -1; + } + }), + new ExtractTextPlugin('styles.css'), + new webpack.optimize.UglifyJsPlugin({ + mangle: { - // You can specify all variables that should not be mangled. - // For example if your vendor dependency doesn't use modules - // and relies on global variables. Most of angular modules relies on - // angular global variable, so we should keep it unchanged - except: ['$super', '$', 'exports', 'require', 'angular'] + // You can specify all variables that should not be mangled. + // For example if your vendor dependency doesn't use modules + // and relies on global variables. Most of angular modules relies on + // angular global variable, so we should keep it unchanged + except: ['$super', '$', 'exports', 'require', 'angular'] + } + }) + ], + imageWebpackLoader: { + pngquant: { + quality: "65-90", + speed: 4 + }, + svgo: { + plugins: [{ + removeViewBox: false + }, { + removeEmptyAttrs: false + }] } - }) -]); - -module.exports = config; + } +};