From ac6cea2b92303863c22388c0c3638dcc5d51b313 Mon Sep 17 00:00:00 2001 From: Dale Lotts Date: Sun, 24 Jan 2016 18:28:31 -0600 Subject: [PATCH 1/5] feat(model-type): Able to store date in the model as a Date, moment, milliseconds, or custom format See readme --- .bowerrc | 3 - .gitignore | 1 + .jscsrc | 8 + .jshintrc | 90 +++++++ .npmignore | 15 ++ .travis.yml | 29 ++- Gruntfile.js | 52 +--- README.md | 131 ++++++++-- bower.json | 28 --- contributing.md | 34 +++ demo/demo-app.js | 15 ++ demo/demo-controller.js | 35 +++ demo/index.html | 153 +++++++++--- gulpfile.js | 88 +++++++ karma.conf.js | 123 ++++++---- package.json | 80 ++++-- paths.js | 22 ++ src/dateTimeInput.js | 199 ++++++++++----- test/commonjs/browserify.test.js | 24 ++ test/dateTimeInput.spec.js | 85 ------- test/en/dateTimeInput.spec.js | 166 +++++++++++++ test/en/modelType.spec.js | 401 +++++++++++++++++++++++++++++++ 22 files changed, 1427 insertions(+), 355 deletions(-) delete mode 100644 .bowerrc create mode 100644 .jscsrc create mode 100644 .jshintrc create mode 100644 .npmignore delete mode 100644 bower.json create mode 100644 contributing.md create mode 100644 demo/demo-app.js create mode 100644 demo/demo-controller.js create mode 100644 gulpfile.js create mode 100644 paths.js create mode 100644 test/commonjs/browserify.test.js delete mode 100644 test/dateTimeInput.spec.js create mode 100644 test/en/dateTimeInput.spec.js create mode 100644 test/en/modelType.spec.js diff --git a/.bowerrc b/.bowerrc deleted file mode 100644 index deceb62..0000000 --- a/.bowerrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "directory": "bower_components" -} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 8dbb8eb..363666f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .idea +build coverage lib-cov bower_components diff --git a/.jscsrc b/.jscsrc new file mode 100644 index 0000000..b665c2e --- /dev/null +++ b/.jscsrc @@ -0,0 +1,8 @@ +{ + "disallowDanglingUnderscores": { "allExcept": ["_$compile_", "_$rootScope_"] }, + "disallowMultipleVarDecl": "strict", + "preset": "crockford", + "requireMultipleVarDecl": null, + "requireVarDeclFirst": null, + "validateIndentation": 2 +} diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..1edeb2c --- /dev/null +++ b/.jshintrc @@ -0,0 +1,90 @@ +{ + // JSHint Default Configuration File (as on JSHint website) + // See http://jshint.com/docs/ for more details + + "maxerr" : 50, // {int} Maximum error before stopping + + // Enforcing + "bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.) + "camelcase" : true, // true: Identifiers must be in camelCase + "curly" : true, // true: Require {} for every new block or scope + "eqeqeq" : true, // true: Require triple equals (===) for comparison + "freeze" : true, // true: prohibits overwriting prototypes of native objects such as Array, Date etc. + "forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty() + "immed" : true, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());` + "indent" : 2, // {int} Number of spaces to use for indentation + "latedef" : "nofunc", // true: Require variables/functions to be defined before being used + "newcap" : true, // true: Require capitalization of all constructor functions e.g. `new F()` + "noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee` + "noempty" : true, // true: Prohibit use of empty blocks + "nonbsp" : true, // true: Prohibit "non-breaking whitespace" characters. + "nonew" : true, // true: Prohibit use of constructors for side-effects (without assignment) + "plusplus" : true, // true: Prohibit use of `++` & `--` + "quotmark" : "single", // Quotation mark consistency: + // false : do nothing (default) + // true : ensure whatever is used is consistent + // "single" : require single quotes + // "double" : require double quotes + "undef" : true, // true: Require all non-global variables to be declared (prevents global leaks) + "unused" : true, // true: Require all defined variables be used + "strict" : true, // true: Requires all functions run in ES5 Strict Mode + "maxparams" : false, // {int} Max number of formal params allowed per function + "maxdepth" : false, // {int} Max depth of nested blocks (within functions) + "maxstatements" : false, // {int} Max number statements per function + "maxcomplexity" : false, // {int} Max cyclomatic complexity per function + "maxlen" : false, // {int} Max number of characters per line + + // Relaxing + "asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons) + "boss" : false, // true: Tolerate assignments where comparisons would be expected + "debug" : false, // true: Allow debugger statements e.g. browser breakpoints. + "eqnull" : false, // true: Tolerate use of `== null` + "es5" : false, // true: Allow ES5 syntax (ex: getters and setters) + "esnext" : false, // true: Allow ES.next (ES6) syntax (ex: `const`) + "moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features) + // (ex: `for each`, multiple try/catch, function expression…) + "evil" : false, // true: Tolerate use of `eval` and `new Function()` + "expr" : false, // true: Tolerate `ExpressionStatement` as Programs + "funcscope" : false, // true: Tolerate defining variables inside control statements + "globalstrict" : false, // true: Allow global "use strict" (also enables 'strict') + "iterator" : false, // true: Tolerate using the `__iterator__` property + "lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block + "laxbreak" : false, // true: Tolerate possibly unsafe line breakings + "laxcomma" : false, // true: Tolerate comma-first style coding + "loopfunc" : false, // true: Tolerate functions being defined in loops + "multistr" : false, // true: Tolerate multi-line strings + "noyield" : false, // true: Tolerate generator functions with no yield statement in them. + "notypeof" : false, // true: Tolerate invalid typeof operator values + "proto" : false, // true: Tolerate using the `__proto__` property + "scripturl" : false, // true: Tolerate script-targeted URLs + "shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;` + "sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation + "supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;` + "validthis" : false, // true: Tolerate using this in a non-constructor function + + // Environments + "browser" : true, // Web Browser (window, document, etc) + "browserify" : false, // Browserify (node.js code in the browser) + "couch" : false, // CouchDB + "devel" : true, // Development/debugging (alert, confirm, etc) + "dojo" : false, // Dojo Toolkit + "jasmine" : true, // Jasmine + "jquery" : false, // jQuery + "mocha" : true, // Mocha + "mootools" : false, // MooTools + "node" : false, // Node.js + "nonstandard" : false, // Widely adopted globals (escape, unescape, etc) + "prototypejs" : false, // Prototype and Scriptaculous + "qunit" : false, // QUnit + "rhino" : false, // Rhino + "shelljs" : false, // ShellJS + "worker" : false, // Web Workers + "wsh" : false, // Windows Scripting Host + "yui" : false, // Yahoo User Interface + + // Custom Globals + "globals" : { + "angular" : true, + "moment" : true + } +} diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..7a6b7b1 --- /dev/null +++ b/.npmignore @@ -0,0 +1,15 @@ +.gitignore +.idea +.jshintignore +.jshintrc +.travis.yml +angular-date-time-input.iml +bower_components +complexity +coverage +demo +Gruntfile.js +gulpfile.js +karma.conf.js +paths.js +test diff --git a/.travis.yml b/.travis.yml index b90aece..7bd985c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,17 +1,20 @@ +sudo: false language: node_js +cache: + directories: + - node_modules +notifications: + email: none node_js: - - "0.10" - -branches: - except: - - gh-pages - + - '5' before_install: - - "export DISPLAY=:99.0" - - "sh -e /etc/init.d/xvfb start" - + - npm i -g npm@^2.0.0 before_script: - - npm install -g bower grunt-cli - - bower install - -script: "grunt --verbose" \ No newline at end of file + - npm prune +script" + - npm run test +after_success: + - npm run semantic-release +branches: + except: + - "/^v\\d+\\.\\d+\\.\\d+$/" diff --git a/Gruntfile.js b/Gruntfile.js index 97d7a44..b221c5c 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,52 +1,22 @@ -/*globals module, require, process */ - +/*globals module, require */ +/*jslint vars:true */ module.exports = function (grunt) { 'use strict'; + // load all grunt tasks require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks); - // Default task. - grunt.registerTask('default', ['jshint', 'karma', 'coverage']); - - var testConfig = function (configFile, customOptions) { - var options = { configFile: configFile, keepalive: true }; - var travisOptions = process.env.TRAVIS && { browsers: ['Firefox'], reporters: 'dots' }; - return grunt.util._.extend(options, customOptions, travisOptions); - }; - // Project configuration. grunt.initConfig({ - coverage: { - options: { - thresholds: { - 'statements': 100, - 'branches': 90, - 'lines': 100, - 'functions': 100 - }, - dir: 'coverage', - root: '' - } - }, - karma: { - unit: { - options: testConfig('karma.conf.js') - } - }, - jshint: { - files: ['src/*.js', 'test/**/*.js'], + bump: { options: { - curly: true, - eqeqeq: true, - immed: true, - latedef: true, - newcap: true, - noarg: true, - sub: true, - boss: true, - eqnull: true, - globals: {} + files: ['package.json', 'bower.json', 'README.md', 'src/js/*.js'], + updateConfigs: [], + commit: false, + createTag: false, + push: false, + globalReplace: false } } }); -}; \ No newline at end of file +}; diff --git a/README.md b/README.md index 2bbda38..dc76ff6 100644 --- a/README.md +++ b/README.md @@ -8,44 +8,36 @@ Native AngularJS directive that allows user input of a date/time value. Valid da #Dependencies Requires: - * AngularJS 1.1.3 or higher (Not tested with 1.0.x) + * AngularJS 1.4.x or higher * MomentJS 2.1.x or higher #Testing We use karma and jshint to ensure the quality of the code. The easiest way to run these checks is to use grunt: ``` -npm install -g grunt-cli -npm install bower grunt +npm install -g gulp +npm install +npm test ``` The karma task will try to open Chrome as a browser in which to run the tests. Make sure this is available or change the configuration in test\test.config.js #Usage -We use bower for dependency management. Add +We use npm for dependency management. Add the following to your package -```json -dependencies: { - "angular-date-time-input": "latest" -} +```shell +npm install angular-date-time-input --save ``` - -To your bower.json file. Then run - -```html -bower install -``` - -This will copy the angular-date-time-input files into your components folder, along with its dependencies. +This will copy the angular-date-time-input files into your node_modules folder, along with its dependencies. Load the script files in your application: ```html - - - + + + ``` -Add the date module as a dependency to your application module: +Add this module as a dependency to your application module: ```html var myAppModule = angular.module('MyApp', ['ui.dateTimeInput']) @@ -59,4 +51,101 @@ Apply the directive to your form elements: ## Options -The value of the date-time-input attribute must be a valid format string. See MomentJS documentation for valid formats. \ No newline at end of file +The value of the date-time-input attribute is the format the date values will be displayed. + +Nota bene: The value saved in the model is, by default, a JavaScript ```Date``` object, not a string. +This can result in differences between what is seen in the model and what is displayed. + +### date-time-input + +This option controls the way the date is displayed in the view, not the model. + +```html + +``` +See MomentJS documentation for valid formats. + +### date-formats + +This option defines additional input formats that will be accepted. + +```html + +``` + +Nota bene: Parsing multiple formats is considerably slower than parsing a single format. +If you can avoid it, it is much faster to parse a single format. + +See [MomentJS documentation] (http://momentjs.com/docs/#/parsing/string-formats) for more information. + +### date-parse-strict + +This option enables/disables strict parsing of the input formats. + +```html + +``` + +### model-type + +```html + +``` + +Default: ```'Date'``` + +Specifies the data type to use when storing the selected date in the model. + +Accepts any string value, but the following values have special meaning (these values are case sensitive) : + * ```'Date'``` stores a Date instance in the model. Will accept Date, moment, milliseconds, and ISO 8601 strings as initial input from the model + * ```'moment'``` stores a moment instance in the model. Accepts the same initial values as ```Date``` + * ```'milliseconds'``` store the epoch milliseconds (since 1-1-1970) in the model. Accepts the same initial values as ```Date``` + +Any other value is considered a custom format string. + +##Contributing + +See [Contributing] (contributing.md) document + +## License + +angular-date-time-input is released under the MIT license and is copyright 2016 Knight Rider Consulting, Inc.. Boiled down to smaller chunks, it can be described with the following conditions. + +## It requires you to: + +* Keep the license and copyright notice included in angular-date-time-input's CSS and JavaScript files when you use them in your works + +## It permits you to: + +* Freely download and use angular-date-time-input, in whole or in part, for personal, private, company internal, or commercial purposes +* Use angular-date-time-input in packages or distributions that you create +* Modify the source code +* Grant a sublicense to modify and distribute angular-date-time-input to third parties not included in the license + +## It forbids you to: + +* Hold the authors and license owners liable for damages as angular-date-time-input is provided without warranty +* Hold the creators or copyright holders of angular-date-time-input liable +* Redistribute any piece of angular-date-time-input without proper attribution +* Use any marks owned by Knight Rider Consulting, Inc. in any way that might state or imply that Knight Rider Consulting, Inc. endorses your distribution +* Use any marks owned by Knight Rider Consulting, Inc. in any way that might state or imply that you created the Knight Rider Consulting, Inc. software in question + +## It does not require you to: + +* Include the source of angular-date-time-input itself, or of any modifications you may have made to it, in any redistribution you may assemble that includes it +* Submit changes that you make to angular-date-time-input back to the angular-date-time-input project (though such feedback is encouraged) + +The full angular-date-time-input license is located [in the project repository](https://github.com/dalelotts/angular-date-time-input/blob/master/LICENSE) for more information. + + +## Donating +Support this project and other work by Dale Lotts via [gittip][gittip-dalelotts]. + +[![Support via Gittip][gittip-badge]][gittip-dalelotts] + +[gittip-badge]: https://rawgithub.com/twolfson/gittip-badge/master/dist/gittip.png +[gittip-dalelotts]: https://www.gittip.com/dalelotts/ + +[license-image]: http://img.shields.io/badge/license-MIT-blue.svg?style=flat +[license-url]: LICENSE + diff --git a/bower.json b/bower.json deleted file mode 100644 index 1c2d166..0000000 --- a/bower.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "angular-date-time-input", - "version": "0.1.0", - "description": "This angular directive provides a date input field.", - "author": "https://github.com/dalelotts/angular-date-time-input/graphs/contributors", - "license": "MIT", - "homepage": "https://github.com/dalelotts/angular-date-time-input", - "main": "./src/dateTimeInput.js", - "ignore": [ - "**/.*", - "screenshots", - "node_modules", - "bower_components", - "test*", - "demo*", - "Gruntfile.js", - "package.json" - ], - "dependencies": { - "angular": "~1.x", - "moment": "~2.x" - }, - "devDependencies": { - "angular-mocks": "~1.x", - "angular-scenario": "~1.x", - "jquery": "1.10.2" - } -} diff --git a/contributing.md b/contributing.md new file mode 100644 index 0000000..816ce01 --- /dev/null +++ b/contributing.md @@ -0,0 +1,34 @@ +Submitting Issues +================= + +If you are submitting a bug, please create a [jsfiddle](http://jsfiddle.net/) demonstrating the issue. + +Contributing code +================= + +To contribute, fork the library and install gulp and dependencies. You need [node](http://nodejs.org/); use [nvm](https://github.com/creationix/nvm) or [nenv](https://github.com/ryuone/nenv) to install it. + +```bash +git clone https://github.com/dalelotts/angular-bootstrap-datetimepicker.git +cd angular-bootstrap-datetimepicker +npm install -g grunt-cli +npm install +git checkout develop # all patches against develop branch, please! +gulp # this runs jscs, jshint, complexity checks, and unit tests. +``` + +Very important notes +==================== + + * **Pull pull requests to the `master` branch will be closed.** Please submit all pull requests to the `develop` branch. + * **Pull requests will not be merged without unit tests.** + * **Do not include the minified files in your pull request.** + * **Have good tests. If you don't have tests for every line and branch in your changes, I won't accept the PR. + * **If your PR fails the CI build, I won't look at it. + +Gulp tasks +=========== + +We use Gulp for managing the build. Here are some useful Gulp tasks: + + * `gulp` The default task checks the coding style, lints the code, calculates complexity, runs the tests, and enforces code coverage. You should make sure you do this before submitting a PR. diff --git a/demo/demo-app.js b/demo/demo-app.js new file mode 100644 index 0000000..f5966e5 --- /dev/null +++ b/demo/demo-app.js @@ -0,0 +1,15 @@ +/*globals angular */ +angular.module('demo', + [ + 'demo.demoController', + 'ui.bootstrap.datetimepicker', + 'ui.dateTimeInput' + ]) + .config([ + function () { + 'use strict'; + + // Configure the app here. + + } + ]); diff --git a/demo/demo-controller.js b/demo/demo-controller.js new file mode 100644 index 0000000..a7d726c --- /dev/null +++ b/demo/demo-controller.js @@ -0,0 +1,35 @@ +/*globals angular, moment, $ */ +(function () { + 'use strict'; + + angular + .module('demo.demoController', []) + .controller('DemoController', demoController); + + demoController.$inject = ['$scope', '$log']; + + function demoController($scope, $log) { + + $scope.controllerName = 'demoController'; + + $scope.data = { + date1: new Date().getTime() + }; + + /* Bindable functions + -----------------------------------------------*/ + $scope.setLocale = setLocale; + + moment.locale('en'); + + function getLocale() { + return moment.locale(); + } + + function setLocale(newLocale) { + moment.locale(newLocale); + } + + } + +})(); diff --git a/demo/index.html b/demo/index.html index 4c5a104..eedf347 100644 --- a/demo/index.html +++ b/demo/index.html @@ -10,50 +10,133 @@ + Angular Bootstrap - Date Time Picker Demo - - - + + + + + + + + + + - -

You can enter dates in any format understood by moment.js, however, when you leave the field it will be displayed in - the specified format.

+ +
+

You can configure this directive to accept input in any format understood by moment.js, however, when you leave + the field it will be displayed in + the specified format.

-

The background of the input box will be red whenever the input is an invalid date.

+

The background of the input box will be red whenever the input is an invalid date.

-
- - displays as M/D/YYYY -

Stored as: {{ data.date1 }}

-
+
+ +

+ Input format is 'YYYY/MM/DD' (and ISO 8601, which is always supported). +
Input parsing is strict (meaning input must match format exactly). +
Display format is YYYY/MM/DD. +
Stored as milliseconds: {{ data.date1 }} +

+ +
-
- - displays as YYYY/MMM/DD hh:mm A -

Stored as: {{ data.date2 }}

-
+
+ +

+ Input format is 'YYYY/MM/DD hh:mm A', 'YYYY/MM/DD', or 'MMM DD YYYY' (and ISO 8601, which is always supported). +
Input parsing is strict (meaning input must exactly match one of input formats). +
Display format is 'YYYY/MM/DD hh:mm A'. +
Stored as a date: {{ data.date1 }} +

+ +
-

NB: ANY date format that moment can parse into a date will be valid input regardless of the format you specify. All of the following are valid dates. - There are other date variations that are supported depending on the i18n support you loaded with moment.js

- +
+ +

+ Input format is 'YYYY/MMM/DD hh:mm A' ( note the MMM for month ). +
Input parsing is NOT strict (in this case, the moment parser is very, very forgiving - entering only + '1' will likely result in a valid date). +
Display format is 'YYYY/MMM/DD hh:mm A'. +
Stored as: {{ data.date1 }} +

+ +
-

Any invalid dates will have a ng-invalid class assigned by Angular

+
+ + +

+ Input format is 'M/D/YYYY' ( note the MMM for month ). +
Display format is 'M/D/YYYY'. +
Stored as: {{ data.dateDropDown }} +

+ +
+

Any invalid dates will have a ng-invalid class assigned by Angular

+
- \ No newline at end of file + diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..ab49f88 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,88 @@ +/*globals require, __dirname */ +/* jshint node:true */ +'use strict'; + +var gulp = require('gulp'); +var jscs = require('gulp-jscs'); +var jshint = require('gulp-jshint'); +var karmaConfig = __dirname + '/karma.conf.js'; +var lodash = require('lodash'); +var paths = require('./paths'); +var plato = require('plato'); +var Server = require('karma').Server; + +gulp.task('clean', function () { + + var del = require('del'); + return del([ + 'build' + ]); +}); + +gulp.task('default', ['clean:mobile']); + +gulp.task('complexity', function (done) { + + var callback = function () { + done(); + }; + + plato.inspect(paths.lint, 'build/complexity', {title: 'prerender', recurse: true}, callback); +}); + +gulp.task('jscs', function () { + return gulp + .src(paths.lint) + .pipe(jscs('.jscsrc')); +}); + +gulp.task('lint', function () { + return gulp + .src(paths.lint) + .pipe(jshint('.jshintrc')) + .pipe(jshint.reporter('default', {verbose: true})) + .pipe(jshint.reporter('jshint-stylish')) + .pipe(jshint.reporter('fail')); +}); + +var testConfig = function (options) { + var travisOptions = process.env.TRAVIS && + { + browsers: ['Firefox'], + reporters: ['dots', 'coverage', 'threshold'] + }; + + return lodash.assign(options, travisOptions); +}; + +gulp.task('tdd', function (done) { + gulp.watch(paths.all, ['jscs', 'lint']); + + var config = testConfig( + { + autoWatch: true, + browsers: ['PhantomJS'], + configFile: karmaConfig, + singleRun: false + } + ); + + var server = new Server(config, done); + server.start(); +}); + +gulp.task('test', ['jscs', 'lint'], function (done) { + + var config = testConfig( + { + configFile: karmaConfig, + singleRun: true, + reporters: ['progress', 'coverage', 'threshold'] + } + ); + + var server = new Server(config, done); + server.start(); +}); + +gulp.task('default', ['complexity', 'test']); diff --git a/karma.conf.js b/karma.conf.js index 9c58e1e..c765c5c 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -1,75 +1,104 @@ +/*globals require */ +/* jshint node:true */ + /** - * @license angular-sortable-column + * @license angular-bootstrap-datetimepicker * (c) 2013 Knight Rider Consulting, Inc. http://www.knightrider.com * License: MIT */ /** * - * @author Dale "Ducky" Lotts - * @since 2013-Sep-23 + * @author Dale "Ducky" Lotts + * @since 7/21/13 */ -files = [ - JASMINE, - JASMINE_ADAPTER, - 'bower_components/jquery/jquery.js', - 'bower_components/moment/moment.js', - 'bower_components/angular/angular.js', - 'bower_components/angular-mocks/angular-mocks.js', - 'src/*.js', - 'test/*.spec.js' -]; -// list of files to exclude -exclude = [ +var paths = require('./paths'); -]; +module.exports = function (config) { + 'use strict'; + config.set({ -preprocessors = { - '**/src/*.js': 'coverage' -}; + frameworks: ['jasmine'], + + plugins: [ + 'karma-jasmine', + 'karma-chrome-launcher', + 'karma-firefox-launcher', + 'karma-phantomjs-launcher', + 'karma-coverage', + 'karma-threshold-reporter' + ], + + files: paths.all, + + // list of files to exclude + exclude: [], -// test results reporter to use -// possible values: 'dots', 'progress', 'junit' -reporters = ['progress', 'coverage']; + preprocessors: { + 'src/**/*.js': ['coverage'] + }, -// web server port -port = 9876; + // optionally, configure the reporter + coverageReporter: { + reporters: [ + {type: 'json', dir: 'build/coverage/'}, + {type: 'html', dir: 'build/coverage/'} + ] + }, + // test results reporter to use + // possible values: 'dots', 'progress', 'junit' + reporters: ['progress', 'coverage'], -// cli runner port -runnerPort = 9100; + // the configure thresholds + thresholdReporter: { + statements: 100, + branches: 100, + functions: 100, + lines: 100 + }, -// enable / disable colors in the output (reporters and logs) -colors = true; + // web server port + port: 9876, -// level of logging -// possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG -logLevel = LOG_INFO; + // cli runner port + runnerPort: 9100, -// enable / disable watching file and executing tests whenever any file changes -autoWatch = false; + // enable / disable colors in the output (reporters and logs) + colors: true, -// Start these browsers, currently available: -// - Chrome -// - ChromeCanary -// - Firefox -// - Opera -// - Safari (only Mac) -// - PhantomJS -// - IE (only Windows) -browsers = ['Chrome']; + // level of logging + // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG + logLevel: config.LOG_INFO, -// If browser does not capture in given timeout [ms], kill it -captureTimeout = 60000; + // enable / disable watching file and executing tests whenever any file changes + autoWatch: false, -// Continuous Integration mode -// if true, it capture browsers, run tests and exit -singleRun = true; \ No newline at end of file + // Start these browsers, currently available: + // - Chrome + // - ChromeCanary + // - Firefox + // - Opera + // - Safari (only Mac) + // - PhantomJS + // - IE (only Windows) + browsers: ['Chrome'], + + + // If browser does not capture in given timeout [ms], kill it + captureTimeout: 60000, + + + // Continuous Integration mode + // if true, it capture browsers, run tests and exit + singleRun: false + }); +}; diff --git a/package.json b/package.json index e24f748..f1b76d9 100644 --- a/package.json +++ b/package.json @@ -1,24 +1,62 @@ { - "name": "angular-date-time-input", - "version": "0.1.0", - "description": "This angular directive allows users to manually enter date-time values in a variety of formats but displays the date value in the specified format.", - "author": "https://github.com/dalelotts/angular-date-time-input/graphs/contributors", - "license": "MIT", - "homepage": "https://github.com/dalelotts/angular-date-time-input", - "main": "src/dateTimeInput.js", - "dependencies": {}, - "devDependencies": { - "grunt": "~0.4.1", - "grunt-contrib-jshint": "~0.2.0", - "grunt-istanbul-coverage": "0.0.1", - "grunt-karma": "~0.4.3", - "matchdep": "~0.1.1" - }, - "scripts": { - "test": "grunt --verbose" - }, - "repository": { - "type": "git", - "url": "git://github.com/dalelotts/angular-date-time-input.git" + "name": "angular-date-time-input", + "description": "This angular directive allows users to manually enter date-time values in a variety of formats but displays the date value in the specified format.", + "author": "https://github.com/dalelotts/angular-date-time-input/graphs/contributors", + "license": "MIT", + "homepage": "https://github.com/dalelotts/angular-date-time-input", + "main": "src/dateTimeInput.js", + "keywords": [ + "angular", + "date input", + "date", + "directive", + "moment", + "time input", + "time" + ], + "dependencies": { + "angular": "^1.x", + "moment": "^2.x" + }, + "devDependencies": { + "bower": "latest", + "cz-conventional-changelog": "^1.1.5", + "grunt": "^0.4.4", + "grunt-bump": "^0.7.0", + "gulp": "^3.8.11", + "gulp-jscs": "^3.0.2", + "gulp-jshint": "^2.0.0", + "jasmine-core": "^2.4.1", + "jshint": "^2.9.1", + "jshint-stylish": "^2.1.0", + "karma": "^0.13.19", + "karma-chrome-launcher": "^0.2.2", + "karma-coverage": "^0.5.3", + "karma-firefox-launcher": "^0.1.7", + "karma-jasmine": "^0.3.2", + "karma-phantomjs-launcher": "^0.2.3", + "karma-threshold-reporter": "^0.1.12", + "lodash": "^4.0.0", + "matchdep": "^1.0.0", + "phantomjs": "^1.9.19", + "plato": "^1.5.0", + "run-browser": "^2.0.2", + "standard": "^5.4.1", + "tape": "^4.4.0", + "semantic-release": "^4.3.5" + }, + "scripts": { + "test": "npm run test-browserify && gulp", + "test-browserify": "run-browser test/commonjs/browserify.test.js -b", + "semantic-release": "semantic-release pre && npm publish && semantic-release post" + }, + "repository": { + "type": "git", + "url": "https://github.com/dalelotts/angular-date-time-input.git" + }, + "config": { + "commitizen": { + "path": "./node_modules/cz-conventional-changelog" } + } } diff --git a/paths.js b/paths.js new file mode 100644 index 0000000..6024f60 --- /dev/null +++ b/paths.js @@ -0,0 +1,22 @@ +/* jshint node:true */ + +var bower = [ + 'bower_components/jquery/dist/jquery.js', + 'bower_components/moment/moment.js', + 'bower_components/angular/angular.js', + 'bower_components/angular-mocks/angular-mocks.js' +]; +var bumpFiles = ['package.json', 'bower.json', 'README.md', 'src/js/*.js']; +var miscFiles = ['GruntFile.js', 'gulpfile.js', 'karma.conf.js', 'paths.js']; +var demoFiles = []; +var sourceFiles = ['src/**/*.js']; +var testFiles = ['test/**/*.spec.js']; + +module.exports = { + all: bower.concat(sourceFiles).concat(testFiles).concat(demoFiles), + app: sourceFiles, + bump: bumpFiles, + lint: miscFiles.concat(sourceFiles).concat(testFiles).concat(miscFiles), + src: sourceFiles, + test: testFiles +}; diff --git a/src/dateTimeInput.js b/src/dateTimeInput.js index 98b168b..c291d1f 100644 --- a/src/dateTimeInput.js +++ b/src/dateTimeInput.js @@ -1,70 +1,147 @@ -/*globals angular, moment, jQuery */ +/*globals define, module, require */ /*jslint vars:true */ /** - * @license angular-date-time-input v0.1.0 - * (c) 2013 Knight Rider Consulting, Inc. http://www.knightrider.com + * @license angular-date-time-input version: 0.1.0 + * (c) 2013-2015 Knight Rider Consulting, Inc. http://www.knightrider.com * License: MIT - */ - -/** * * @author Dale "Ducky" Lotts * @since 2013-Sep-23 */ -angular.module('ui.dateTimeInput', []).directive('dateTimeInput', - [ - function () { - "use strict"; - return { - require: 'ngModel', - restrict: 'A', - link: function (scope, element, attrs, controller) { - - if (!attrs.dateTimeInput) { - throw ("dateTimeInput must specify a date format"); - } - - var validateFn = function (viewValue) { - - var result = viewValue; - - if (viewValue) { - var momentValue = moment(viewValue); - if (momentValue.isValid()) { - controller.$setValidity(attrs.ngModel, true); - result = momentValue.format(); - } else { - controller.$setValidity(attrs.ngModel, false); - } - } - - return result; - }; - - - var formatFn = function (modelValue) { - var result = modelValue; - if (modelValue && moment(modelValue).isValid()) { - result = moment(modelValue).format(attrs.dateTimeInput); - } - return result; - }; - - controller.$parsers.unshift(validateFn); - - controller.$formatters.push(formatFn); - - element.bind('blur', function () { - var viewValue = controller.$modelValue; - angular.forEach(controller.$formatters, function (formatter) { - viewValue = formatter(viewValue); - }); - controller.$viewValue = viewValue; - controller.$render(); - }); - } - }; - } - ]); +(function (factory) { + 'use strict'; + /* istanbul ignore if */ + if (typeof define === 'function' && /* istanbul ignore next */ define.amd) { + define(['angular', 'moment'], factory); // AMD + /* istanbul ignore next */ + } else if (typeof exports === 'object') { + module.exports = factory(require('angular'), require('moment')); // CommonJS + } else { + factory(window.angular, window.moment); // Browser global + } +}(function (angular, moment) { + 'use strict'; + angular.module('ui.dateTimeInput', []) + .value('dateTimeInputConfig') + .directive('dateTimeInput', [dateTimeInputDirective]); + + function dateTimeInputDirective() { + return { + require: 'ngModel', + restrict: 'A', + scope: { + 'dateFormats': '=' + }, + link: linkFunction + }; + + function linkFunction(scope, element, attrs, controller) { + + // validation + if (angular.isDefined(scope.dateFormats) && !angular.isString(scope.dateFormats) && !angular.isArray(scope.dateFormats)) { + throw 'date-formats must be a single string or an array of strings i.e. date-formats="[\'YYYY-MM-DD\']" '; + } + + if (angular.isDefined(attrs.modelType) && (!angular.isString(attrs.modelType) || attrs.modelType.length === 0 )) { + throw 'model-type must be "Date", "moment", "milliseconds", or a moment format string'; + } + + // variables + var displayFormat = attrs.dateTimeInput || moment.defaultFormat; + + var dateParseStrict = (attrs.dateParseStrict === undefined || attrs.dateParseStrict === 'true'); + + var modelType = (attrs.modelType || 'Date'); + + var inputFormats = [attrs.dateTimeInput, modelType].concat(scope.dateFormats).concat([moment.ISO_8601]).filter(unique); + + // Behaviors + controller.$parsers.unshift(parserFactory(modelType)); + + controller.$formatters.push(formatter); + + controller.$validators.dateTimeInput = validator; + + element.bind('blur', applyFormatters); + + + // Implementation + + function unique(value, index, self) { + return ['Date', 'moment', 'milliseconds', undefined].indexOf(value) === -1 && + self.indexOf(value) === index; + } + + function validator(modelValue, viewValue) { + return angular.isDefined(viewValue) ? moment(viewValue, inputFormats, moment.locale(), dateParseStrict).isValid() : true; + } + + function formatter(modelValue) { + + if (angular.isDate(modelValue)) { + return moment(modelValue).format(displayFormat); + } else if (angular.isNumber(modelValue)) { + return moment.utc(modelValue).format(displayFormat); + } else if (angular.isDefined(modelValue)) { + return moment(modelValue, inputFormats, moment.locale(), dateParseStrict).format(displayFormat); + } + + return modelValue; + } + + function parserFactory(modelType) { + var result; + // Behaviors + switch (modelType) { + case 'Date': + result = dateParser; + break; + case 'moment': + result = momentParser; + break; + case 'milliseconds': + result = millisecondParser; + break; + default: // It is assumed that the modelType is a formatting string. + result = stringParserFactory(modelType); + } + + return result; + + function dateParser(viewValue) { + return momentParser(viewValue).toDate(); + } + + function momentParser(viewValue) { + return moment(viewValue, inputFormats, moment.locale(), dateParseStrict); + } + + function millisecondParser(viewValue) { + return moment.utc(viewValue, inputFormats, moment.locale(), dateParseStrict).valueOf(); + } + + function stringParserFactory(modelFormat) { + return function stringParser(viewValue) { + return momentParser(viewValue).format(modelFormat); + }; + } + } + + function applyFormatters() { + controller.$viewValue = controller.$formatters.filter(keepAll).reverse().reduce(applyFormatter, controller.$modelValue); + controller.$render(); + + function keepAll() { + return true; + } + + function applyFormatter(memo, formatter) { + return formatter(memo); + } + } + } + } +})) +; diff --git a/test/commonjs/browserify.test.js b/test/commonjs/browserify.test.js new file mode 100644 index 0000000..72ad3dc --- /dev/null +++ b/test/commonjs/browserify.test.js @@ -0,0 +1,24 @@ +/*globals require */ +/** + * @license angular-bootstrap-datetimepicker + * Copyright 2015 Knight Rider Consulting, Inc. http://www.knightrider.com + * License: MIT + */ + +/** This file is intentionally named browserify.test.js so that it is not picked up by the karma runner **/ + +var angular = require('angular'); +var tapeTest = require('tape'); + +tapeTest('can load module after requiring', function (t) { + 'use strict'; + + function loadModule() { + angular.module('ui.dateTimeInput'); + } + + t.throws(loadModule); + require('../../'); + t.doesNotThrow(loadModule); + t.end(); +}); diff --git a/test/dateTimeInput.spec.js b/test/dateTimeInput.spec.js deleted file mode 100644 index 0012aad..0000000 --- a/test/dateTimeInput.spec.js +++ /dev/null @@ -1,85 +0,0 @@ -/*globals $, moment, module, describe, it, expect, beforeEach, inject */ -/** - * @license angular-date-time-input - * (c) 2013 Knight Rider Consulting, Inc. http://www.knightrider.com - * License: MIT - */ - -/** - * - * @author Dale "Ducky" Lotts - * @since 2013-Sep-23 - */ - -describe('date-time-input', function () { - 'use strict'; - var compiler, rootScope; - - beforeEach(module('ui.dateTimeInput')); - - beforeEach(inject(function ($compile, $rootScope) { - rootScope = $rootScope; - compiler = $compile; - })); - - describe('valid configuration', function () { - it('requires ngModel', function () { - var compile = function () { - compiler('')(rootScope); - }; - - expect(compile).toThrow(); - }); - it('requires a date format', function () { - var compile = function () { - compiler('')(rootScope); - }; - - expect(compile).toThrow(); - }); - }); - - describe('has expected initial structure', function () { - it('is a `` element', function () { - var element = compiler('')(rootScope); - rootScope.$digest(); - expect(element.prop('tagName')).toBe('INPUT'); - }); - }); - - describe('has existing ngModel value and display value', function () { - it('is "1/1/1970 12:00 am if the ngModel value is set to 1970-01-01T00:00:00.000', function () { - var element = compiler('')(rootScope); - rootScope.dateValue = moment('1970-01-01T00:00:00.000').toDate(); - rootScope.$digest(); - expect(element.val()).toEqual('1/1/1970 12:00 AM'); - }); - }); - - describe('display value is "1/1/1970 12:00 am"', function () { - it('if the user inputs "1970/1/1"', function () { - var element = compiler('')(rootScope); - rootScope.$digest(); - element.val('1970/1/1'); - element.trigger('input'); - expect(rootScope.dateValue).toContain("1970-01-01T00:00:00"); - expect(element.val()).toEqual('1970/1/1'); - element.trigger('blur'); // formatting happens on blur - expect(element.val()).toEqual('1/1/1970 12:00 AM'); - }); - }); - describe('rejects invalid input', function () { - it('of "foo"', function () { - var element = compiler('')(rootScope); - rootScope.$digest(); - element.val('foo'); - element.trigger('input'); - expect(rootScope.dateValue).toEqual('foo'); - expect(element.val()).toEqual('foo'); - expect(element.hasClass('ng-invalid')).toBe(true); - element.trigger('blur'); // formatting happens on blur - expect(element.val()).toEqual('foo'); - expect(element.hasClass('ng-invalid')).toBe(true); - }); - }); -}); \ No newline at end of file diff --git a/test/en/dateTimeInput.spec.js b/test/en/dateTimeInput.spec.js new file mode 100644 index 0000000..d545732 --- /dev/null +++ b/test/en/dateTimeInput.spec.js @@ -0,0 +1,166 @@ +/*globals moment, module, describe, it, expect, beforeEach, inject */ +/** + * @license angular-date-time-input + * (c) 2015 Knight Rider Consulting, Inc. http://www.knightrider.com + * License: MIT + */ + +/** + * + * @author Dale "Ducky" Lotts + * @since 2013-Sep-23 + */ + +describe('date-time-input', function () { + 'use strict'; + var compiler; + var rootScope; + + beforeEach(module('ui.dateTimeInput')); + + beforeEach(inject(function ($compile, $rootScope) { + moment.locale('en'); + rootScope = $rootScope; + compiler = $compile; + })); + + describe('valid configuration', function () { + it('requires ngModel', function () { + var compile = function () { + compiler('')(rootScope); + }; + + expect(compile).toThrow(); + }); + + it('does NOT require a date format', function () { + compiler('')(rootScope); + }); + + it('requires date-formats to be a string or array, not an expression', function () { + var compile = function () { + compiler('')(rootScope); + }; + + expect(compile).toThrow(); + }); + + it('requires date-formats to be a string or array, not a number', function () { + var compile = function () { + compiler('')(rootScope); + }; + + expect(compile).toThrow(); + }); + + it('accepts valid format string value from model', function () { + rootScope.dateValue = '2016-01-23T06:00:00.000Z'; + var element = compiler('')(rootScope); + rootScope.$digest(); + expect(element.val()).toBe(moment('2016-01-23T06:00:00.000Z').format()); + }); + + it('accepts a single string as date-format', function () { + rootScope.dateValue = '2016-01-23T06:00:00.000Z'; + var element = compiler('')(rootScope); + rootScope.$digest(); + expect(element.val()).toBe(moment('2016-01-23T06:00:00.000Z').format()); + }); + + it('accepts an array of strings as date-format', function () { + rootScope.dateValue = '2016-01-23T06:00:00.000Z'; + var element = compiler('')(rootScope); + rootScope.$digest(); + expect(element.val()).toBe(moment('2016-01-23T06:00:00.000Z').format()); + }); + }); + + describe('has expected initial structure', function () { + it('is a `` element', function () { + var element = compiler('')(rootScope); + rootScope.$digest(); + expect(element.prop('tagName')).toBe('INPUT'); + }); + }); + + describe('accepts', function () { + it('input matching valid display format', function () { + var element = compiler('')(rootScope); + rootScope.$digest(); + element.val('01/12/2016'); + element.trigger('input'); + element.trigger('blur'); + rootScope.$digest(); + expect(rootScope.dateValue).toEqual(moment('2016-12-01').toDate()); + expect(element.val()).toEqual('1/12/2016'); + }); + + + it('input matching valid date format (not matching display format)', function () { + var element = compiler('')(rootScope); + rootScope.$digest(); + element.val('2016-01-12'); + element.trigger('input'); + element.trigger('blur'); + rootScope.$digest(); + expect(rootScope.dateValue).toEqual(moment('2016-12-01').toDate()); + expect(element.val()).toEqual('1/12/2016'); + }); + + it('ISO 8601 formatted input that does not match any specified format', function () { + var element = compiler('')(rootScope); + rootScope.$digest(); + element.val('2016-01-23T06:00:00.000Z'); + element.trigger('input'); + element.trigger('blur'); + rootScope.$digest(); + expect(rootScope.dateValue).toEqual(moment('2016-01-23T06:00:00.000Z').toDate()); + expect(element.val()).toEqual('23/1/2016'); + }); + + it('"1/1/1070" and displays "12/31/1969 12:00 AM" (BUG?) if parsing is NOT strict"', function () { + var element = compiler('')(rootScope); + rootScope.$digest(); + element.val('1/1/1970'); + element.trigger('input'); + + var expectedMoment = moment('1/1/1970', 'M/D/YYYY h:mm A', moment.locale(), false); + expect(rootScope.dateValue).toEqual(expectedMoment.toDate()); + expect(element.val()).toEqual('1/1/1970'); + element.trigger('blur'); // formatting happens on blur + rootScope.$digest(); + expect(element.val()).toEqual(expectedMoment.format('M/D/YYYY h:mm A')); + }); + }); + + // ToDo: locale specific formats + // ToDo: model format - Date, moment, string, unix + + + + + describe('has existing ngModel value and display value', function () { + it('is "1/1/1970 12:00 am if the ngModel value is set to 1/1/1970', function () { + rootScope.dateValue = moment('1/1/1970 12:00 am', 'M/D/YYYY h:mm A').toDate(); + var element = compiler('')(rootScope); + rootScope.$digest(); + expect(element.val()).toEqual('1/1/1970 12:00 AM'); + }); + }); + + + describe('rejects invalid input', function () { + it('of "foo"', function () { + var element = compiler('')(rootScope); + rootScope.$digest(); + element.val('foo'); + element.trigger('input'); + expect(rootScope.dateValue).toBe(undefined); + expect(element.val()).toEqual('foo'); + expect(element.hasClass('ng-invalid')).toBe(true); + element.trigger('blur'); // formatting happens on blur + expect(element.val()).toEqual(''); + expect(element.hasClass('ng-invalid')).toBe(true); + }); + }); +}); diff --git a/test/en/modelType.spec.js b/test/en/modelType.spec.js new file mode 100644 index 0000000..304bb88 --- /dev/null +++ b/test/en/modelType.spec.js @@ -0,0 +1,401 @@ +/*globals describe, beforeEach, it, expect, module, inject, moment */ + +/** + * @license angular-date-time-input + * Copyright 2016 Knight Rider Consulting, Inc. http://www.knightrider.com + * License: MIT + */ + +/** + * + * @author Dale "Ducky" Lotts + * @since 1/24/2016 + */ + +describe('modelType', function () { + 'use strict'; + var $rootScope; + var $compile; + beforeEach(module('ui.dateTimeInput')); + beforeEach(inject(function (_$compile_, _$rootScope_) { + $compile = _$compile_; + $rootScope = _$rootScope_; + $rootScope.date = null; + })); + + describe('throws exception', function () { + it('if value is an empty string', function () { + function compile() { + $compile('')($rootScope); + } + + expect(compile).toThrow('model-type must be "Date", "moment", "milliseconds", or a moment format string'); + }); + }); + describe('does NOT throw exception', function () { + it('if value is a string', function () { + + $compile('')($rootScope); + }); + }); + describe('value of Date', function () { + it('is the default', function () { + + $rootScope.date = moment('2015-11-17').toDate(); + + var element = $compile('')($rootScope); + $rootScope.$digest(); + + expect(element.val()).toBe('11/17/2015'); + }); + it('accepts Date from model and returns Date', function () { + + $rootScope.dateValue = moment('2005-11-17').toDate(); + + var element = $compile('')($rootScope); + $rootScope.$digest(); + + expect(element.val()).toBe('11/17/2005'); + + element.val('01/12/2016'); + element.trigger('input'); + element.trigger('blur'); + $rootScope.$digest(); + + expect($rootScope.dateValue).toEqual(moment('01/12/2016', 'M/D/YYYY').toDate()); + expect(element.val()).toEqual('1/12/2016'); + + }); + it('accepts valid date string from model and returns Date', function () { + + $rootScope.date = '1/24/2016'; + + var element = $compile('')($rootScope); + $rootScope.$digest(); + + expect(element.val()).toBe('1/24/2016'); + + element.val('1/29/2016'); + element.trigger('input'); + element.trigger('blur'); + $rootScope.$digest(); + + expect($rootScope.date).toEqual(moment('2016-01-29').toDate()); + expect(element.val()).toEqual('1/29/2016'); + }); + it('accepts milliseconds from model', function () { + + $rootScope.date = 1132185600000; // '2005-11-17' + + var element = $compile('')($rootScope); + $rootScope.$digest(); + + expect(element.val()).toBe('11/17/2005'); + }); + it('does not accept invalid date string in the model', function () { + + $rootScope.date = '1-24-2016'; + + var element = $compile('')($rootScope); + $rootScope.$digest(); + + expect(element.val()).toBe('Invalid date'); + }); + }); + + + describe('value of moment', function () { + it('accepts moment from model and returns moment', function () { + + $rootScope.dateValue = moment('2005-11-17'); + + var element = $compile('')($rootScope); + $rootScope.$digest(); + + expect(element.val()).toBe('11/17/2005'); + + element.val('01/12/2016'); + element.trigger('input'); + element.trigger('blur'); + $rootScope.$digest(); + + expect($rootScope.dateValue.isSame(moment('01/12/2016', 'M/D/YYYY'))).toBe(true); + expect(element.val()).toEqual('1/12/2016'); + + }); + it('accepts Date from model and returns moment', function () { + + $rootScope.dateValue = moment('2005-11-17').toDate(); + + var element = $compile('')($rootScope); + $rootScope.$digest(); + + expect(element.val()).toBe('11/17/2005'); + + element.val('01/12/2016'); + element.trigger('input'); + element.trigger('blur'); + $rootScope.$digest(); + + expect($rootScope.dateValue.isSame(moment('01/12/2016', 'M/D/YYYY'))).toBe(true); + expect(element.val()).toEqual('1/12/2016'); + + }); + it('accepts valid date string from model and returns moment', function () { + + $rootScope.date = '1/24/2016'; + + var element = $compile('')($rootScope); + $rootScope.$digest(); + + expect(element.val()).toBe('1/24/2016'); + + element.val('1/29/2016'); + element.trigger('input'); + element.trigger('blur'); + $rootScope.$digest(); + + expect($rootScope.date.isSame(moment('2016-01-29'))).toBe(true); + expect(element.val()).toEqual('1/29/2016'); + }); + it('accepts milliseconds from model', function () { + + $rootScope.date = 1132185600000; // '2005-11-17' + + var element = $compile('')($rootScope); + $rootScope.$digest(); + + expect(element.val()).toBe('11/17/2005'); + }); + it('does not accept invalid date string in the model', function () { + + $rootScope.date = '1-24-2016'; + + var element = $compile('')($rootScope); + $rootScope.$digest(); + + expect(element.val()).toBe('Invalid date'); + }); + }); + + + describe('value of milliseconds', function () { + it('accepts milliseconds from model and returns milliseconds', function () { + + $rootScope.dateValue = 1132185600000; + + + + var element = $compile('')($rootScope); + $rootScope.$digest(); + + expect(element.val()).toBe('11/17/2005'); + + element.val('01/12/2016'); + element.trigger('input'); + element.trigger('blur'); + $rootScope.$digest(); + + expect($rootScope.dateValue).toEqual(moment.utc('2016-01-12').valueOf()); + expect(element.val()).toEqual('1/12/2016'); + + }); + it('accepts valid date string from model and returns milliseconds', function () { + + $rootScope.date = '1/24/2016'; + + var element = $compile('')($rootScope); + $rootScope.$digest(); + + expect(element.val()).toBe('1/24/2016'); + + element.val('1/29/2016'); + element.trigger('input'); + element.trigger('blur'); + $rootScope.$digest(); + + expect($rootScope.date).toEqual(moment.utc('2016-01-29').valueOf()); + expect(element.val()).toEqual('1/29/2016'); + }); + it('accept milliseconds from model', function () { + + $rootScope.date = 1132207200000; // '2005-11-17' + + var element = $compile('')($rootScope); + $rootScope.$digest(); + + expect(element.val()).toBe('11/17/2005'); + }); + it('does not accept invalid date string in the model', function () { + + $rootScope.date = 'invalid'; + + var element = $compile('')($rootScope); + $rootScope.$digest(); + + expect(element.val()).toBe('Invalid date'); + }); + }); + + + + describe('value of format string', function () { + it('is the default', function () { + + $rootScope.date = moment('2015-11-17').toDate(); + + var element = $compile('')($rootScope); + $rootScope.$digest(); + + expect(element.val()).toBe('11/17/2015'); + }); + it('accepts Date from model and returns Date', function () { + + $rootScope.dateValue = moment('2005-11-17').toDate(); + + var element = $compile('')($rootScope); + $rootScope.$digest(); + + expect(element.val()).toBe('11/17/2005'); + + element.val('01/12/2016'); + element.trigger('input'); + element.trigger('blur'); + $rootScope.$digest(); + + expect($rootScope.dateValue).toEqual('2016-01-12'); + expect(element.val()).toEqual('1/12/2016'); + + }); + it('accepts valid date string from model and returns Date', function () { + + $rootScope.date = '1/24/2016'; + + var element = $compile('')($rootScope); + $rootScope.$digest(); + + expect(element.val()).toBe('1/24/2016'); + + element.val('1/29/2016'); + element.trigger('input'); + element.trigger('blur'); + $rootScope.$digest(); + + expect($rootScope.date).toEqual('2016-01-29'); + expect(element.val()).toEqual('1/29/2016'); + }); + it('accepts milliseconds from model', function () { + + $rootScope.date = 1132185600000; // '2005-11-17' + + var element = $compile('')($rootScope); + $rootScope.$digest(); + + expect(element.val()).toBe('11/17/2005'); + }); + it('accept valid date string in the model', function () { + + $rootScope.date = '2016-Jan-24'; + + var element = $compile('')($rootScope); + $rootScope.$digest(); + + expect(element.val()).toBe('1/24/2016'); + }); + + it('does not accept invalid date string in the model', function () { + + $rootScope.date = '1-24-2016'; + + var element = $compile('')($rootScope); + $rootScope.$digest(); + + expect(element.val()).toBe('Invalid date'); + }); + }); + + //describe('value of format string', function () { + // it('accepts Date from model and returns formatted string', function () { + // + // $rootScope.date = moment('2005-11-17').toDate(); + // + // var element = $compile('')($rootScope); + // $rootScope.$digest(); + // + // var selectedElement = jQuery(jQuery('.year', element)[0]); + // selectedElement.trigger('click'); + // + // expect($rootScope.date).toBe('01-01-1999'); + // }); + // it('accepts date string (in matching format) from model and returns formatted string', function () { + // + // $rootScope.date = '2015-11-17'; + // + // var element = $compile('')($rootScope); + // $rootScope.$digest(); + // + // var selectedElement = jQuery(jQuery('.year', element)[0]); + // selectedElement.trigger('click'); + // + // expect($rootScope.date).toBe('2009-01-01'); + // }); + // it('accepts milliseconds from model and returns formatted string (crazy!)', function () { + // + // $rootScope.date = 1132185600000; // '2005-11-17' + // + // var element = $compile('')($rootScope); + // $rootScope.$digest(); + // + // var selectedElement = jQuery(jQuery('.year', element)[0]); + // selectedElement.trigger('click'); + // + // expect($rootScope.date).toBe('1999-01-01'); + // }); + // it('accepts any formatting string and returns date using that format)', function () { + // + // $rootScope.date = 1132185600000; // This will only work if input from model is not a string + // + // var element = $compile('')($rootScope); + // $rootScope.$digest(); + // + // var selectedElement = jQuery(jQuery('.year', element)[0]); + // selectedElement.trigger('click'); + // + // expect($rootScope.date).toBe('gibb5ri012'); + // }); + // it('throws an exception if numeric string is in the model', function () { + // + // $rootScope.date = '1132185600000'; + // + // function compile() { + // $compile('')($rootScope); + // $rootScope.$digest(); + // } + // + // expect(compile).toThrow('Invalid date: 1132185600000'); + // }); + // it('throws an exception if incorrectly formatted date string is in the model', function () { + // + // $rootScope.date = '10-18-1970'; + // + // function compile() { + // $compile('')($rootScope); + // $rootScope.$digest(); + // } + // + // expect(compile).toThrow('Invalid date: 10-18-1970'); + // }); + // it('throws an exception if invalid date string is in the model', function () { + // + // $rootScope.date = 'invalid-date'; + // + // function compile() { + // $compile('')($rootScope); + // $rootScope.$digest(); + // } + // + // expect(compile).toThrow('Invalid date: invalid-date'); + // }); + //}); +}); + From c057392f4f3cb3c1489419876531a16e4dce1b14 Mon Sep 17 00:00:00 2001 From: Dale Lotts Date: Sun, 24 Jan 2016 18:31:28 -0600 Subject: [PATCH 2/5] fix(.travis.yml): Fix syntax error in .travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 7bd985c..b32a2d7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ before_install: - npm i -g npm@^2.0.0 before_script: - npm prune -script" +script: - npm run test after_success: - npm run semantic-release From 236fadc3e085b68610ae4af20bca8a4acb89e756 Mon Sep 17 00:00:00 2001 From: Dale Lotts Date: Sun, 24 Jan 2016 18:48:42 -0600 Subject: [PATCH 3/5] fix(.travis.yml): Add missing xvfb --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b32a2d7..f9b1d0c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,8 @@ notifications: node_js: - '5' before_install: - - npm i -g npm@^2.0.0 + - export DISPLAY=:99.0 + - sh -e /etc/init.d/xvfb start before_script: - npm prune script: From da7b8c378a71e123bcf5f711763d7a477415e1dd Mon Sep 17 00:00:00 2001 From: Dale Lotts Date: Sun, 24 Jan 2016 18:57:38 -0600 Subject: [PATCH 4/5] fix(Add missing npm modules): Fix missing modules and incorrect paths that reuslted from dropping bo --- package.json | 6 ++++-- paths.js | 8 ++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index f1b76d9..7486e79 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "moment": "^2.x" }, "devDependencies": { + "angular-mocks": "^1.x", "bower": "latest", "cz-conventional-changelog": "^1.1.5", "grunt": "^0.4.4", @@ -27,6 +28,7 @@ "gulp-jscs": "^3.0.2", "gulp-jshint": "^2.0.0", "jasmine-core": "^2.4.1", + "jquery": "^2.2.0", "jshint": "^2.9.1", "jshint-stylish": "^2.1.0", "karma": "^0.13.19", @@ -41,9 +43,9 @@ "phantomjs": "^1.9.19", "plato": "^1.5.0", "run-browser": "^2.0.2", + "semantic-release": "^4.3.5", "standard": "^5.4.1", - "tape": "^4.4.0", - "semantic-release": "^4.3.5" + "tape": "^4.4.0" }, "scripts": { "test": "npm run test-browserify && gulp", diff --git a/paths.js b/paths.js index 6024f60..c16fbdc 100644 --- a/paths.js +++ b/paths.js @@ -1,10 +1,10 @@ /* jshint node:true */ var bower = [ - 'bower_components/jquery/dist/jquery.js', - 'bower_components/moment/moment.js', - 'bower_components/angular/angular.js', - 'bower_components/angular-mocks/angular-mocks.js' + 'node_modules/jquery/dist/jquery.js', + 'node_modules/moment/moment.js', + 'node_modules/angular/angular.js', + 'node_modules/angular-mocks/angular-mocks.js' ]; var bumpFiles = ['package.json', 'bower.json', 'README.md', 'src/js/*.js']; var miscFiles = ['GruntFile.js', 'gulpfile.js', 'karma.conf.js', 'paths.js']; From 7ea6347a2aebf344f0494a3c422541438a6a5d50 Mon Sep 17 00:00:00 2001 From: Dale Lotts Date: Sun, 24 Jan 2016 19:29:26 -0600 Subject: [PATCH 5/5] fix(package.json): Remove dev dependency on bower --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 7486e79..f24bb4e 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,6 @@ }, "devDependencies": { "angular-mocks": "^1.x", - "bower": "latest", "cz-conventional-changelog": "^1.1.5", "grunt": "^0.4.4", "grunt-bump": "^0.7.0",