├── .bowerrc
├── .gitignore
├── .jshintignore
├── .jshintrc
├── .travis.yml
├── Gruntfile.js
├── LICENSE.md
├── README.md
├── bower.json
├── countdown.jquery.json
├── dist
├── jquery.countdown.js
└── jquery.countdown.min.js
├── karma.conf.js
├── package.json
├── src
├── .jshintrc
└── countdown.js
└── test
├── integration
├── karma.conf.js
├── requirejs.config.js
└── requirejs_test.js
├── qunit.config.js
├── scenarios.json
└── unit
├── date_casting_test.js
├── event_test.js
├── functionality_test.js
├── math_test.js
├── sanity_test.js
└── strftime_test.js
/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory" : "test/vendor"
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 |
3 | lib-cov
4 | *.seed
5 | *.log
6 | *.csv
7 | *.dat
8 | *.out
9 | *.pid
10 | *.gz
11 |
12 | pids
13 | logs
14 | results
15 | build
16 |
17 | node_modules
18 |
19 | .DS_Store
20 |
21 | test/vendor
22 | _site/
23 |
--------------------------------------------------------------------------------
/.jshintignore:
--------------------------------------------------------------------------------
1 | test/vendor/**/*.js
2 | lib/**/*.js
3 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "bitwise": true,
3 | "camelcase": true,
4 | "curly": true,
5 | "eqeqeq": true,
6 | "es3": true,
7 | "forin": true,
8 | "freeze": true,
9 | "immed": true,
10 | "indent": 2,
11 | "latedef": true,
12 | "newcap": true,
13 | "noarg": true,
14 | "noempty": true,
15 | "nonbsp": true,
16 | "nonew": false,
17 | "plusplus": false,
18 | "quotmark": "single",
19 | "undef": true,
20 | "unused": false,
21 | "strict": false,
22 | "trailing": true,
23 | "maxparams": false,
24 | "maxdepth": 3,
25 | "maxstatements": false,
26 | "maxcomplexity": 8,
27 | "maxlen": 80,
28 | "gcl": false,
29 | "browser": true,
30 | "devel": true,
31 | "node": true,
32 | "jquery": true,
33 | "globals": {
34 | "asyncTest": false,
35 | "deepEqual": false,
36 | "equal": false,
37 | "expect": false,
38 | "module": false,
39 | "notDeepEqual": false,
40 | "notEqual": false,
41 | "notStrictEqual": false,
42 | "ok": false,
43 | "QUnit": false,
44 | "raises": false,
45 | "throws": false,
46 | "start": false,
47 | "stop": false,
48 | "strictEqual": false,
49 | "test": false,
50 | "sinon": false,
51 | "require": false,
52 | "define": false,
53 | "$dom": true,
54 | "$clock": true
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "0.10"
4 | before_script:
5 | - npm install -g grunt-cli bower
6 | - npm install
7 | - bower install
8 | branches:
9 | except:
10 | - /^v1\..*/
11 | - /^1\..*/
12 | - gh-pages
13 |
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | module.exports = function(grunt) {
2 | 'use strict';
3 | // Project configuration.
4 | grunt.initConfig({
5 | pkg: grunt.file.readJSON('package.json'),
6 | dirs: {
7 | src: './src',
8 | lib: './lib',
9 | bin: './build',
10 | test: './test',
11 | dest: './dist'
12 | },
13 | // project utils
14 | pluginName: '<%= pkg.name.replace(/-/, ".") %>',
15 | license: grunt.file.read('LICENSE.md').split('\n').splice(3).join('\n'),
16 | banner: '/*!\n' +
17 | ' * <%= pkg.description %> v<%= pkg.version %> ' +
18 | '(<%= pkg.homepage %>)\n' +
19 | ' * Copyright (c) <%= grunt.template.today("yyyy") %> ' +
20 | '<%= pkg.author %>\n' +
21 | ' * <%= license.replace(/\\n/gm, "\\n * ") %>\n' +
22 | ' */\n',
23 | // contrib-clean
24 | clean: ['<%= dirs.dest %>'],
25 | // contrib-compress
26 | compress: {
27 | release: {
28 | options: {
29 | archive: '<%= dirs.bin %>/<%= pluginName %>-<%= pkg.version %>.zip'
30 | },
31 | expand: true,
32 | cwd: '<%= dirs.dest %>',
33 | src: ['**/*'],
34 | dest: '<%= pluginName %>-<%= pkg.version %>'
35 | }
36 | },
37 | // contrib-jshint
38 | jshint: {
39 | options: {
40 | jshintrc: true
41 | },
42 | all: [
43 | 'Gruntfile.js',
44 | '<%= dirs.src %>/**/*.js',
45 | '<%= dirs.test %>/**/*.js'
46 | ]
47 | },
48 | // contrib-uglify
49 | uglify: {
50 | dev: {
51 | files: {
52 | '<%= dirs.dest %>/<%= pluginName %>.js':
53 | ['<%= dirs.src %>/**/*.js']
54 | },
55 | options: {
56 | beautify: true,
57 | compress: false,
58 | mangle: false,
59 | preserveComments: false
60 | }
61 | },
62 | min: {
63 | files: {
64 | '<%= dirs.dest %>/<%= pluginName %>.min.js':
65 | ['<%= dirs.src %>/**/*.js']
66 | },
67 | options: {
68 | report: 'min'
69 | }
70 | },
71 | options: {
72 | banner: '<%= banner %>'
73 | }
74 | },
75 | jsonlint: {
76 | all: {
77 | src: ['*.json']
78 | }
79 | },
80 | // karma
81 | karma: {
82 | options: {
83 | configFile: 'karma.conf.js',
84 | autoWatch: true,
85 | singleRun: false
86 | },
87 | watch: {
88 |
89 | },
90 | unit: {
91 | autoWatch: false,
92 | singleRun: true
93 | }
94 | },
95 | // version
96 | version: {
97 | release: {
98 | src: ['*.json']
99 | }
100 | }
101 | });
102 | // Load grunt tasks
103 | grunt.loadNpmTasks('grunt-contrib-clean');
104 | grunt.loadNpmTasks('grunt-contrib-compress');
105 | grunt.loadNpmTasks('grunt-contrib-jshint');
106 | grunt.loadNpmTasks('grunt-contrib-uglify');
107 | grunt.loadNpmTasks('grunt-jsonlint');
108 | grunt.loadNpmTasks('grunt-karma');
109 | grunt.loadNpmTasks('grunt-version');
110 | // Test
111 | grunt.registerTask('test', ['jshint', 'jsonlint', 'test:scenarios']);
112 | grunt.registerTask('test:unit', ['jshint', 'jsonlint', 'karma:unit']);
113 | // Test scenarios
114 | grunt.registerTask('test:scenarios',
115 | 'Test multiple scenarios', function() {
116 |
117 | var scenariosConf = require('./test/scenarios.json'),
118 | scenarios = Object.keys(scenariosConf),
119 | scenariosTasks = [],
120 | karmaConf = {},
121 | karmaFiles;
122 | // Fetchs the values of karma conf file.
123 | require('./karma.conf.js')({set: function(values) {
124 | karmaConf = values;
125 | }});
126 |
127 | karmaFiles = karmaConf.files.filter(function(file) {
128 | return !(/vendor/.test(file));
129 | });
130 |
131 | grunt.log.write('Configuring scenarios:'.cyan +
132 | ' %s found...'.replace(/%s/, scenarios.length));
133 |
134 | scenarios.forEach(function(scenario) {
135 | var value = scenariosConf[scenario],
136 | confName = 'karma.scenario_' + scenario.replace(/[._-]/gi, '_'),
137 | conf = {
138 | autoWatch: false,
139 | singleRun: true,
140 | logLevel: 'OFF',
141 | reporters: 'dots'
142 | };
143 |
144 | if (Array.isArray(value)) {
145 | conf.options = {
146 | files: value.concat(karmaFiles)
147 | };
148 | } else {
149 | conf.configFile = value;
150 | }
151 | // Register the scenario
152 | grunt.config.set(confName, conf);
153 | scenariosTasks.push(confName.replace(/\./, ':'));
154 | });
155 | grunt.log.ok();
156 | // Run all scenarios tasks
157 | grunt.task.run(scenariosTasks);
158 | });
159 | // Build
160 | grunt.registerTask('build', ['uglify', 'test:all', 'version', 'compress']);
161 | // Develop
162 | grunt.registerTask('default', ['karma:watch']);
163 | };
164 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Edson Hilios
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [The Final Countdown](http://hilios.github.io/jQuery.countdown/) [](https://travis-ci.org/hilios/jQuery.countdown)
2 | =====================
3 |
4 | #### A simple and html agnostic date countdown plugin for jQuery ####
5 |
6 | To get started, check-it out: http://hilios.github.io/jQuery.countdown/
7 |
8 | The ultimate countdown plugin designed to fit in any coupon, auction site or product launch. Read our [Documentation](http://hilios.github.io/jQuery.countdown/documentation.html) and follow our [Examples](http://hilios.github.io/jQuery.countdown/examples.html) to see what suits your particular needs.
9 |
10 | #### [Download](https://github.com/hilios/jQuery.countdown/releases/download/2.1.0/jquery.countdown-2.1.0.zip) ####
11 |
12 | [Click here to download the latest version](https://github.com/hilios/jQuery.countdown/releases/download/2.1.0/jquery.countdown-2.1.0.zip)
13 |
14 | If you want to clone the repo always use the files under [dist](https://github.com/hilios/jQuery.countdown/tree/master/dist) folder, they are optimized for production and development.
15 |
16 | #### Install via Bower ####
17 |
18 | ```
19 | bower install jquery.countdown
20 | ```
21 |
22 | Add a `script` to your html:
23 |
24 | ```html
25 |
26 | ```
27 |
28 | #### Install via NPM
29 |
30 | ```
31 | npm install --save jquery-countdown
32 | ```
33 |
34 | Require the script:
35 |
36 | ```js
37 | require('jquery-countdown');
38 | ```
39 |
40 | Getting started
41 | ---------------
42 |
43 | ```html
44 |
45 |
50 | ```
51 |
52 | ### Requirements ###
53 |
54 | Since version 2.0.0 we only support jQuery above **1.7** (including **2.x** and **3.x**). For legacy **1.6** support please use the version [1.0.2](https://github.com/hilios/jQuery.countdown/releases/download/1.0.2/jquery.countdown-1.0.2.zip).
55 |
56 | ### [Documentation](http://hilios.github.io/jQuery.countdown/documentation.html) ###
57 |
58 | Our documentation is powered by [Jekyll](http://jekyllrb.com/) (see `gh-page` branch) and hosted in GitHub Pages at [http://hilios.github.io/jQuery.countdown/](http://hilios.github.io/jQuery.countdown/documentation.html).
59 |
60 | ### [Examples](http://hilios.github.io/jQuery.countdown/examples.html) ###
61 |
62 | There are few ways to get started, from the most simple example to advanced, we support many different countdown styles, see wich one fits your scenario, and if anyone doesn't it's a good starting point to customize your output.
63 |
64 | - [Basic coupon site with format N days hr:min:sec](http://hilios.github.io/jQuery.countdown/examples/basic-coupon-site.html)
65 | - [Advance coupon with conditionals and pluralization, format N weeks N days hr:min:sec](http://hilios.github.io/jQuery.countdown/examples/advanced-coupon-site.html)
66 | - [Product launch in... (callback style)](http://hilios.github.io/jQuery.countdown/examples/website-launch.html)
67 | - [New year's eve (legacy style)](http://hilios.github.io/jQuery.countdown/examples/legacy-style.html)
68 | - [Multiple instances on the same page](http://hilios.github.io/jQuery.countdown/examples/multiple-instances.html)
69 | - [Calculate the countdown total hours](http://hilios.github.io/jQuery.countdown/examples/show-total-hours.html)
70 |
71 | [Release notes](https://github.com/hilios/jQuery.countdown/releases)
72 | ---------------
73 |
74 | Current version is **2.2.0**, to follow our change log please visit the [release notes](https://github.com/hilios/jQuery.countdown/releases).
75 |
76 | #### What's new in 2.2.0? ####
77 |
78 | * Total count for hours `%I`, minutes `%N` and seconds `%T`;
79 | * Count to weeks left to complete a month `%W`;
80 | * Deferred initialization, allows to control the exact start moment;
81 | * Fix pluralization bug when return is zero;
82 |
83 | #### What's new in 2.1.0? ####
84 |
85 | * Add proper offset for days left to a month and to a week;
86 | * Fix bower amd install;
87 |
88 | Contributing
89 | ------------
90 |
91 | The Final Countdown uses **Grunt** and **Bower** with convenient methods for developing the plugin. It's how we compile our code and run tests. To get started install [NodeJS](http://nodejs.org/), [Bower](http://bower.io/), and then run some Grunt/Bower commands.
92 |
93 | ```shell
94 | bower install
95 | npm install
96 | grunt test # Lint code and run test suite
97 | grunt build # Generate the release files (dev, min and zip)
98 | grunt # Watch for updates than test and build
99 | ```
100 |
101 | This plugin is tested with [QUnit](http://qunitjs.com/), under jQuery 1.7 up to 3.1, Bootstrap 3.0 and RequireJS.
102 |
103 | The functional tests made against:
104 |
105 | * Chrome >= 12
106 | * Safari >= 5
107 | * Firefox >= 5.0
108 | * IE 7/8/9
109 |
110 | Code coverage:
111 |
112 | ```sh
113 | ---------------|----------|----------|----------|----------|----------------|
114 | File | % Stmts | % Branch | % Funcs | % Lines |Uncovered Lines |
115 | ---------------|----------|----------|----------|----------|----------------|
116 | src/ | 98.4 | 90.63 | 100 | 98.4 | |
117 | countdown.js | 98.4 | 90.63 | 100 | 98.4 | 6,283 |
118 | ---------------|----------|----------|----------|----------|----------------|
119 | All files | 98.4 | 90.63 | 100 | 98.4 | |
120 | ---------------|----------|----------|----------|----------|----------------|
121 | ```
122 |
123 | ### Contributors ###
124 |
125 | Thanks for bug reporting and fixes:
126 |
127 | * Daniel Leavitt (@dleavitt)
128 | * Fagner Brack (@FagnerMartinsBrack)
129 | * Matthew Sigley (@msigley)
130 | * Roman Shterenzon (@romanbsd)
131 | * Marios (@assiotis)
132 | * Zane Yao (@yaoazhen)
133 | * Ricardo Calvo (@ricardocalvo)
134 | * Daniel Z. (@nietonfir)
135 |
136 | ### License ###
137 |
138 | Copyright (c) 2011-2015 Edson Hilios. This is a free software is licensed under the MIT License.
139 |
140 | * [Edson Hilios](http://edson.hilios.com.br). Mail me: edson (at) hilios (dot) com (dot) br
141 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jquery.countdown",
3 | "version": "2.2.0",
4 | "description": "jQuery plugin to display a countdown clock on your page",
5 | "keywords": [
6 | "countdown",
7 | "coupons",
8 | "auction",
9 | "clock",
10 | "datetime",
11 | "date"
12 | ],
13 | "author": "Edson Hilios",
14 | "homepage": "http://hilios.github.io/jQuery.countdown/",
15 | "license": [
16 | {
17 | "type": "MIT",
18 | "url": "https://github.com/hilios/jQuery.countdown/blob/2.0.4/LICENSE.md"
19 | }
20 | ],
21 | "main": [
22 | "dist/jquery.countdown.js"
23 | ],
24 | "ignore": [
25 | "**/.*",
26 | "src",
27 | "test",
28 | "package.json",
29 | "Gruntfile.js",
30 | "node_modules",
31 | "bower_components"
32 | ],
33 | "dependencies": {
34 | "jquery": ">=1.7"
35 | },
36 | "devDependencies": {
37 | "jquery-3.1": "jquery#~3.1",
38 | "jquery-3.0": "jquery#~3.0",
39 | "jquery-2.2": "jquery#~2.2",
40 | "jquery-2.1": "jquery#~2.1",
41 | "jquery-2.0": "jquery#~2.0",
42 | "jquery-1.9": "jquery#~1.9",
43 | "jquery-1.8": "jquery#~1.8",
44 | "jquery-1.7": "jquery#~1.7",
45 | "jquery-1.10": "jquery#~1.10",
46 | "jquery-1.11": "jquery#~1.11",
47 | "bootstrap": "~3"
48 | },
49 | "moduleType": [
50 | "amd"
51 | ]
52 | }
53 |
--------------------------------------------------------------------------------
/countdown.jquery.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "countdown",
3 | "version": "2.2.0",
4 | "title": "The Final Countdown",
5 | "description": "jQuery plugin to display a countdown clock on your page.",
6 | "keywords": [
7 | "countdown",
8 | "coupons",
9 | "auction",
10 | "clock",
11 | "datetime",
12 | "date"
13 | ],
14 | "author": {
15 | "name": "Edson Hilios",
16 | "url": "http://edson.hilios.com.br"
17 | },
18 | "homepage": "http://hilios.github.io/jQuery.countdown/",
19 | "docs": "http://hilios.github.io/jQuery.countdown/documentation.html",
20 | "demo": "http://hilios.github.io/jQuery.countdown/examples.html",
21 | "bugs": "https://github.com/hilios/jQuery.countdown/issues",
22 | "download": "https://github.com/hilios/jQuery.countdown/releases/download/2.0.4/jquery.countdown-2.0.4.zip",
23 | "licenses": [
24 | {
25 | "type": "MIT",
26 | "url": "https://github.com/hilios/jQuery.countdown/blob/2.0.4/LICENSE.md"
27 | }
28 | ],
29 | "dependencies": {
30 | "jquery": ">=1.7"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/dist/jquery.countdown.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * The Final Countdown for jQuery v2.2.0 (http://hilios.github.io/jQuery.countdown/)
3 | * Copyright (c) 2016 Edson Hilios
4 | *
5 | * Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | * this software and associated documentation files (the "Software"), to deal in
7 | * the Software without restriction, including without limitation the rights to
8 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | * the Software, and to permit persons to whom the Software is furnished to do so,
10 | * subject to the following conditions:
11 | *
12 | * The above copyright notice and this permission notice shall be included in all
13 | * copies or substantial portions of the Software.
14 | *
15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 | */
22 | (function(factory) {
23 | "use strict";
24 | if (typeof define === "function" && define.amd) {
25 | define([ "jquery" ], factory);
26 | } else {
27 | factory(jQuery);
28 | }
29 | })(function($) {
30 | "use strict";
31 | var instances = [], matchers = [], defaultOptions = {
32 | precision: 100,
33 | elapse: false,
34 | defer: false
35 | };
36 | matchers.push(/^[0-9]*$/.source);
37 | matchers.push(/([0-9]{1,2}\/){2}[0-9]{4}( [0-9]{1,2}(:[0-9]{2}){2})?/.source);
38 | matchers.push(/[0-9]{4}([\/\-][0-9]{1,2}){2}( [0-9]{1,2}(:[0-9]{2}){2})?/.source);
39 | matchers = new RegExp(matchers.join("|"));
40 | function parseDateString(dateString) {
41 | if (dateString instanceof Date) {
42 | return dateString;
43 | }
44 | if (String(dateString).match(matchers)) {
45 | if (String(dateString).match(/^[0-9]*$/)) {
46 | dateString = Number(dateString);
47 | }
48 | if (String(dateString).match(/\-/)) {
49 | dateString = String(dateString).replace(/\-/g, "/");
50 | }
51 | return new Date(dateString);
52 | } else {
53 | throw new Error("Couldn't cast `" + dateString + "` to a date object.");
54 | }
55 | }
56 | var DIRECTIVE_KEY_MAP = {
57 | Y: "years",
58 | m: "months",
59 | n: "daysToMonth",
60 | d: "daysToWeek",
61 | w: "weeks",
62 | W: "weeksToMonth",
63 | H: "hours",
64 | M: "minutes",
65 | S: "seconds",
66 | D: "totalDays",
67 | I: "totalHours",
68 | N: "totalMinutes",
69 | T: "totalSeconds"
70 | };
71 | function escapedRegExp(str) {
72 | var sanitize = str.toString().replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
73 | return new RegExp(sanitize);
74 | }
75 | function strftime(offsetObject) {
76 | return function(format) {
77 | var directives = format.match(/%(-|!)?[A-Z]{1}(:[^;]+;)?/gi);
78 | if (directives) {
79 | for (var i = 0, len = directives.length; i < len; ++i) {
80 | var directive = directives[i].match(/%(-|!)?([a-zA-Z]{1})(:[^;]+;)?/), regexp = escapedRegExp(directive[0]), modifier = directive[1] || "", plural = directive[3] || "", value = null;
81 | directive = directive[2];
82 | if (DIRECTIVE_KEY_MAP.hasOwnProperty(directive)) {
83 | value = DIRECTIVE_KEY_MAP[directive];
84 | value = Number(offsetObject[value]);
85 | }
86 | if (value !== null) {
87 | if (modifier === "!") {
88 | value = pluralize(plural, value);
89 | }
90 | if (modifier === "") {
91 | if (value < 10) {
92 | value = "0" + value.toString();
93 | }
94 | }
95 | format = format.replace(regexp, value.toString());
96 | }
97 | }
98 | }
99 | format = format.replace(/%%/, "%");
100 | return format;
101 | };
102 | }
103 | function pluralize(format, count) {
104 | var plural = "s", singular = "";
105 | if (format) {
106 | format = format.replace(/(:|;|\s)/gi, "").split(/\,/);
107 | if (format.length === 1) {
108 | plural = format[0];
109 | } else {
110 | singular = format[0];
111 | plural = format[1];
112 | }
113 | }
114 | if (Math.abs(count) > 1) {
115 | return plural;
116 | } else {
117 | return singular;
118 | }
119 | }
120 | var Countdown = function(el, finalDate, options) {
121 | this.el = el;
122 | this.$el = $(el);
123 | this.interval = null;
124 | this.offset = {};
125 | this.options = $.extend({}, defaultOptions);
126 | this.firstTick = true;
127 | this.instanceNumber = instances.length;
128 | instances.push(this);
129 | this.$el.data("countdown-instance", this.instanceNumber);
130 | if (options) {
131 | if (typeof options === "function") {
132 | this.$el.on("update.countdown", options);
133 | this.$el.on("stoped.countdown", options);
134 | this.$el.on("finish.countdown", options);
135 | } else {
136 | this.options = $.extend({}, defaultOptions, options);
137 | }
138 | }
139 | this.setFinalDate(finalDate);
140 | if (this.options.defer === false) {
141 | this.start();
142 | }
143 | };
144 | $.extend(Countdown.prototype, {
145 | start: function() {
146 | if (this.interval !== null) {
147 | clearInterval(this.interval);
148 | }
149 | var self = this;
150 | this.update();
151 | this.interval = setInterval(function() {
152 | self.update.call(self);
153 | }, this.options.precision);
154 | },
155 | stop: function() {
156 | clearInterval(this.interval);
157 | this.interval = null;
158 | this.dispatchEvent("stoped");
159 | },
160 | toggle: function() {
161 | if (this.interval) {
162 | this.stop();
163 | } else {
164 | this.start();
165 | }
166 | },
167 | pause: function() {
168 | this.stop();
169 | },
170 | resume: function() {
171 | this.start();
172 | },
173 | remove: function() {
174 | this.stop.call(this);
175 | instances[this.instanceNumber] = null;
176 | delete this.$el.data().countdownInstance;
177 | },
178 | setFinalDate: function(value) {
179 | this.finalDate = parseDateString(value);
180 | },
181 | update: function() {
182 | if (this.$el.closest("html").length === 0) {
183 | this.remove();
184 | return;
185 | }
186 | var now = new Date(), newTotalSecsLeft;
187 | newTotalSecsLeft = this.finalDate.getTime() - now.getTime();
188 | newTotalSecsLeft = Math.ceil(newTotalSecsLeft / 1e3);
189 | newTotalSecsLeft = !this.options.elapse && newTotalSecsLeft < 0 ? 0 : Math.abs(newTotalSecsLeft);
190 | if (this.totalSecsLeft === newTotalSecsLeft || this.firstTick) {
191 | this.firstTick = false;
192 | return;
193 | } else {
194 | this.totalSecsLeft = newTotalSecsLeft;
195 | }
196 | this.elapsed = now >= this.finalDate;
197 | this.offset = {
198 | seconds: this.totalSecsLeft % 60,
199 | minutes: Math.floor(this.totalSecsLeft / 60) % 60,
200 | hours: Math.floor(this.totalSecsLeft / 60 / 60) % 24,
201 | days: Math.floor(this.totalSecsLeft / 60 / 60 / 24) % 7,
202 | daysToWeek: Math.floor(this.totalSecsLeft / 60 / 60 / 24) % 7,
203 | daysToMonth: Math.floor(this.totalSecsLeft / 60 / 60 / 24 % 30.4368),
204 | weeks: Math.floor(this.totalSecsLeft / 60 / 60 / 24 / 7),
205 | weeksToMonth: Math.floor(this.totalSecsLeft / 60 / 60 / 24 / 7) % 4,
206 | months: Math.floor(this.totalSecsLeft / 60 / 60 / 24 / 30.4368),
207 | years: Math.abs(this.finalDate.getFullYear() - now.getFullYear()),
208 | totalDays: Math.floor(this.totalSecsLeft / 60 / 60 / 24),
209 | totalHours: Math.floor(this.totalSecsLeft / 60 / 60),
210 | totalMinutes: Math.floor(this.totalSecsLeft / 60),
211 | totalSeconds: this.totalSecsLeft
212 | };
213 | if (!this.options.elapse && this.totalSecsLeft === 0) {
214 | this.stop();
215 | this.dispatchEvent("finish");
216 | } else {
217 | this.dispatchEvent("update");
218 | }
219 | },
220 | dispatchEvent: function(eventName) {
221 | var event = $.Event(eventName + ".countdown");
222 | event.finalDate = this.finalDate;
223 | event.elapsed = this.elapsed;
224 | event.offset = $.extend({}, this.offset);
225 | event.strftime = strftime(this.offset);
226 | this.$el.trigger(event);
227 | }
228 | });
229 | $.fn.countdown = function() {
230 | var argumentsArray = Array.prototype.slice.call(arguments, 0);
231 | return this.each(function() {
232 | var instanceNumber = $(this).data("countdown-instance");
233 | if (instanceNumber !== undefined) {
234 | var instance = instances[instanceNumber], method = argumentsArray[0];
235 | if (Countdown.prototype.hasOwnProperty(method)) {
236 | instance[method].apply(instance, argumentsArray.slice(1));
237 | } else if (String(method).match(/^[$A-Z_][0-9A-Z_$]*$/i) === null) {
238 | instance.setFinalDate.call(instance, method);
239 | instance.start();
240 | } else {
241 | $.error("Method %s does not exist on jQuery.countdown".replace(/\%s/gi, method));
242 | }
243 | } else {
244 | new Countdown(this, argumentsArray[0], argumentsArray[1]);
245 | }
246 | });
247 | };
248 | });
--------------------------------------------------------------------------------
/dist/jquery.countdown.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * The Final Countdown for jQuery v2.2.0 (http://hilios.github.io/jQuery.countdown/)
3 | * Copyright (c) 2016 Edson Hilios
4 | *
5 | * Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | * this software and associated documentation files (the "Software"), to deal in
7 | * the Software without restriction, including without limitation the rights to
8 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | * the Software, and to permit persons to whom the Software is furnished to do so,
10 | * subject to the following conditions:
11 | *
12 | * The above copyright notice and this permission notice shall be included in all
13 | * copies or substantial portions of the Software.
14 | *
15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 | */
22 | !function(a){"use strict";"function"==typeof define&&define.amd?define(["jquery"],a):a(jQuery)}(function(a){"use strict";function b(a){if(a instanceof Date)return a;if(String(a).match(g))return String(a).match(/^[0-9]*$/)&&(a=Number(a)),String(a).match(/\-/)&&(a=String(a).replace(/\-/g,"/")),new Date(a);throw new Error("Couldn't cast `"+a+"` to a date object.")}function c(a){var b=a.toString().replace(/([.?*+^$[\]\\(){}|-])/g,"\\$1");return new RegExp(b)}function d(a){return function(b){var d=b.match(/%(-|!)?[A-Z]{1}(:[^;]+;)?/gi);if(d)for(var f=0,g=d.length;f1?c:d}var f=[],g=[],h={precision:100,elapse:!1,defer:!1};g.push(/^[0-9]*$/.source),g.push(/([0-9]{1,2}\/){2}[0-9]{4}( [0-9]{1,2}(:[0-9]{2}){2})?/.source),g.push(/[0-9]{4}([\/\-][0-9]{1,2}){2}( [0-9]{1,2}(:[0-9]{2}){2})?/.source),g=new RegExp(g.join("|"));var i={Y:"years",m:"months",n:"daysToMonth",d:"daysToWeek",w:"weeks",W:"weeksToMonth",H:"hours",M:"minutes",S:"seconds",D:"totalDays",I:"totalHours",N:"totalMinutes",T:"totalSeconds"},j=function(b,c,d){this.el=b,this.$el=a(b),this.interval=null,this.offset={},this.options=a.extend({},h),this.firstTick=!0,this.instanceNumber=f.length,f.push(this),this.$el.data("countdown-instance",this.instanceNumber),d&&("function"==typeof d?(this.$el.on("update.countdown",d),this.$el.on("stoped.countdown",d),this.$el.on("finish.countdown",d)):this.options=a.extend({},h,d)),this.setFinalDate(c),this.options.defer===!1&&this.start()};a.extend(j.prototype,{start:function(){null!==this.interval&&clearInterval(this.interval);var a=this;this.update(),this.interval=setInterval(function(){a.update.call(a)},this.options.precision)},stop:function(){clearInterval(this.interval),this.interval=null,this.dispatchEvent("stoped")},toggle:function(){this.interval?this.stop():this.start()},pause:function(){this.stop()},resume:function(){this.start()},remove:function(){this.stop.call(this),f[this.instanceNumber]=null,delete this.$el.data().countdownInstance},setFinalDate:function(a){this.finalDate=b(a)},update:function(){if(0===this.$el.closest("html").length)return void this.remove();var a,b=new Date;return a=this.finalDate.getTime()-b.getTime(),a=Math.ceil(a/1e3),a=!this.options.elapse&&a<0?0:Math.abs(a),this.totalSecsLeft===a||this.firstTick?void(this.firstTick=!1):(this.totalSecsLeft=a,this.elapsed=b>=this.finalDate,this.offset={seconds:this.totalSecsLeft%60,minutes:Math.floor(this.totalSecsLeft/60)%60,hours:Math.floor(this.totalSecsLeft/60/60)%24,days:Math.floor(this.totalSecsLeft/60/60/24)%7,daysToWeek:Math.floor(this.totalSecsLeft/60/60/24)%7,daysToMonth:Math.floor(this.totalSecsLeft/60/60/24%30.4368),weeks:Math.floor(this.totalSecsLeft/60/60/24/7),weeksToMonth:Math.floor(this.totalSecsLeft/60/60/24/7)%4,months:Math.floor(this.totalSecsLeft/60/60/24/30.4368),years:Math.abs(this.finalDate.getFullYear()-b.getFullYear()),totalDays:Math.floor(this.totalSecsLeft/60/60/24),totalHours:Math.floor(this.totalSecsLeft/60/60),totalMinutes:Math.floor(this.totalSecsLeft/60),totalSeconds:this.totalSecsLeft},void(this.options.elapse||0!==this.totalSecsLeft?this.dispatchEvent("update"):(this.stop(),this.dispatchEvent("finish"))))},dispatchEvent:function(b){var c=a.Event(b+".countdown");c.finalDate=this.finalDate,c.elapsed=this.elapsed,c.offset=a.extend({},this.offset),c.strftime=d(this.offset),this.$el.trigger(c)}}),a.fn.countdown=function(){var b=Array.prototype.slice.call(arguments,0);return this.each(function(){var c=a(this).data("countdown-instance");if(void 0!==c){var d=f[c],e=b[0];j.prototype.hasOwnProperty(e)?d[e].apply(d,b.slice(1)):null===String(e).match(/^[$A-Z_][0-9A-Z_$]*$/i)?(d.setFinalDate.call(d,e),d.start()):a.error("Method %s does not exist on jQuery.countdown".replace(/\%s/gi,e))}else new j(this,b[0],b[1])})}});
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration
2 | module.exports = function(config) {
3 | config.set({
4 | basePath: '',
5 | frameworks: [
6 | 'qunit',
7 | 'sinon'
8 | ],
9 | files: [
10 | 'test/vendor/jquery-2.1/dist/jquery.js',
11 | 'test/*.config.js',
12 | 'src/**/*.js',
13 | 'test/unit/**/*test.js'
14 | ],
15 | exclude: [
16 | 'karma.conf.js'
17 | ],
18 | reporters: [
19 | 'dots',
20 | 'coverage'
21 | ],
22 | preprocessors: {
23 | 'src/**/*.js': 'coverage'
24 | },
25 | coverageReporter: {
26 | type: 'text',
27 | dir: 'test/coverage/'
28 | },
29 | port: 9876,
30 | colors: true,
31 | logLevel: config.LOG_DISABLE,
32 | browsers: [
33 | 'PhantomJS'
34 | ],
35 | autoWatch: false,
36 | singleRun: true
37 | });
38 | };
39 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jquery-countdown",
3 | "version": "2.2.0",
4 | "main": "dist/jquery.countdown.js",
5 | "description": "The Final Countdown for jQuery",
6 | "author": "Edson Hilios",
7 | "homepage": "http://hilios.github.io/jQuery.countdown/",
8 | "repository": {
9 | "type": "git",
10 | "url": "https://github.com/hilios/jQuery.countdown"
11 | },
12 | "devDependencies": {
13 | "grunt": "~0.4",
14 | "grunt-contrib-clean": "~0.5",
15 | "grunt-contrib-compress": "~0.6",
16 | "grunt-contrib-jshint": "~0.10",
17 | "grunt-contrib-uglify": "~0.4",
18 | "grunt-jsonlint": "~1.0",
19 | "grunt-karma": "~0.8",
20 | "grunt-version": "~0.3",
21 | "karma": "~0.12",
22 | "karma-coverage": "~0.2",
23 | "karma-phantomjs-launcher": "~0.1",
24 | "karma-qunit": "~0.1",
25 | "karma-requirejs": "~0.2",
26 | "karma-sinon": "~1.0",
27 | "requirejs": "~2.1"
28 | },
29 | "scripts": {
30 | "test": "grunt test"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "bitwise": true,
3 | "camelcase": true,
4 | "curly": true,
5 | "eqeqeq": true,
6 | "es3": true,
7 | "forin": true,
8 | "freeze": true,
9 | "immed": true,
10 | "indent": 2,
11 | "latedef": true,
12 | "newcap": true,
13 | "noarg": true,
14 | "noempty": true,
15 | "nonbsp": true,
16 | "nonew": false,
17 | "plusplus": false,
18 | "quotmark": "single",
19 | "undef": true,
20 | "unused": true,
21 | "strict": true,
22 | "trailing": true,
23 | "maxparams": false,
24 | "maxstatements": false,
25 | "maxcomplexity": 10,
26 | "maxlen": 80,
27 | "gcl": true,
28 | "browser": true,
29 | "devel": false,
30 | "jquery": true,
31 | "globals": {
32 | "require": false,
33 | "define": false
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/countdown.js:
--------------------------------------------------------------------------------
1 | // AMD support (Thanks to @FagnerMartinsBrack)
2 | ;(function(factory) {
3 | 'use strict';
4 |
5 | if (typeof define === 'function' && define.amd) {
6 | define(['jquery'], factory);
7 | } else {
8 | factory(jQuery);
9 | }
10 | })(function($){
11 | 'use strict';
12 |
13 | var instances = [],
14 | matchers = [],
15 | defaultOptions = {
16 | precision: 100, // 0.1 seconds, used to update the DOM
17 | elapse: false,
18 | defer: false
19 | };
20 | // Miliseconds
21 | matchers.push(/^[0-9]*$/.source);
22 | // Month/Day/Year [hours:minutes:seconds]
23 | matchers.push(/([0-9]{1,2}\/){2}[0-9]{4}( [0-9]{1,2}(:[0-9]{2}){2})?/
24 | .source);
25 | // Year/Day/Month [hours:minutes:seconds] and
26 | // Year-Day-Month [hours:minutes:seconds]
27 | matchers.push(/[0-9]{4}([\/\-][0-9]{1,2}){2}( [0-9]{1,2}(:[0-9]{2}){2})?/
28 | .source);
29 | // Cast the matchers to a regular expression object
30 | matchers = new RegExp(matchers.join('|'));
31 | // Parse a Date formatted has String to a native object
32 | function parseDateString(dateString) {
33 | // Pass through when a native object is sent
34 | if(dateString instanceof Date) {
35 | return dateString;
36 | }
37 | // Caste string to date object
38 | if(String(dateString).match(matchers)) {
39 | // If looks like a milisecond value cast to number before
40 | // final casting (Thanks to @msigley)
41 | if(String(dateString).match(/^[0-9]*$/)) {
42 | dateString = Number(dateString);
43 | }
44 | // Replace dashes to slashes
45 | if(String(dateString).match(/\-/)) {
46 | dateString = String(dateString).replace(/\-/g, '/');
47 | }
48 | return new Date(dateString);
49 | } else {
50 | throw new Error('Couldn\'t cast `' + dateString +
51 | '` to a date object.');
52 | }
53 | }
54 | // Map to convert from a directive to offset object property
55 | var DIRECTIVE_KEY_MAP = {
56 | 'Y': 'years',
57 | 'm': 'months',
58 | 'n': 'daysToMonth',
59 | 'd': 'daysToWeek',
60 | 'w': 'weeks',
61 | 'W': 'weeksToMonth',
62 | 'H': 'hours',
63 | 'M': 'minutes',
64 | 'S': 'seconds',
65 | 'D': 'totalDays',
66 | 'I': 'totalHours',
67 | 'N': 'totalMinutes',
68 | 'T': 'totalSeconds'
69 | };
70 | // Returns an escaped regexp from the string
71 | function escapedRegExp(str) {
72 | var sanitize = str.toString().replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
73 | return new RegExp(sanitize);
74 | }
75 | // Time string formatter
76 | function strftime(offsetObject) {
77 | return function(format) {
78 | var directives = format.match(/%(-|!)?[A-Z]{1}(:[^;]+;)?/gi);
79 | if(directives) {
80 | for(var i = 0, len = directives.length; i < len; ++i) {
81 | var directive = directives[i]
82 | .match(/%(-|!)?([a-zA-Z]{1})(:[^;]+;)?/),
83 | regexp = escapedRegExp(directive[0]),
84 | modifier = directive[1] || '',
85 | plural = directive[3] || '',
86 | value = null;
87 | // Get the key
88 | directive = directive[2];
89 | // Swap shot-versions directives
90 | if(DIRECTIVE_KEY_MAP.hasOwnProperty(directive)) {
91 | value = DIRECTIVE_KEY_MAP[directive];
92 | value = Number(offsetObject[value]);
93 | }
94 | if(value !== null) {
95 | // Pluralize
96 | if(modifier === '!') {
97 | value = pluralize(plural, value);
98 | }
99 | // Add zero-padding
100 | if(modifier === '') {
101 | if(value < 10) {
102 | value = '0' + value.toString();
103 | }
104 | }
105 | // Replace the directive
106 | format = format.replace(regexp, value.toString());
107 | }
108 | }
109 | }
110 | format = format.replace(/%%/, '%');
111 | return format;
112 | };
113 | }
114 | // Pluralize
115 | function pluralize(format, count) {
116 | var plural = 's', singular = '';
117 | if(format) {
118 | format = format.replace(/(:|;|\s)/gi, '').split(/\,/);
119 | if(format.length === 1) {
120 | plural = format[0];
121 | } else {
122 | singular = format[0];
123 | plural = format[1];
124 | }
125 | }
126 | // Fix #187
127 | if(Math.abs(count) > 1) {
128 | return plural;
129 | } else {
130 | return singular;
131 | }
132 | }
133 | // The Final Countdown
134 | var Countdown = function(el, finalDate, options) {
135 | this.el = el;
136 | this.$el = $(el);
137 | this.interval = null;
138 | this.offset = {};
139 | this.options = $.extend({}, defaultOptions);
140 | // console.log(this.options);
141 | // This helper variable is necessary to mimick the previous check for an
142 | // event listener on this.$el. Because of the event loop there might not
143 | // be a registered event listener during the first tick. In order to work
144 | // as expected a second tick is necessary, so that the events can be fired
145 | // and handled properly.
146 | this.firstTick = true;
147 | // Register this instance
148 | this.instanceNumber = instances.length;
149 | instances.push(this);
150 | // Save the reference
151 | this.$el.data('countdown-instance', this.instanceNumber);
152 | // Handle options or callback
153 | if (options) {
154 | // Register the callbacks when supplied
155 | if(typeof options === 'function') {
156 | this.$el.on('update.countdown', options);
157 | this.$el.on('stoped.countdown', options);
158 | this.$el.on('finish.countdown', options);
159 | } else {
160 | this.options = $.extend({}, defaultOptions, options);
161 | }
162 | }
163 | // Set the final date and start
164 | this.setFinalDate(finalDate);
165 | // Starts the countdown automatically unless it's defered,
166 | // Issue #198
167 | if (this.options.defer === false) {
168 | this.start();
169 | }
170 | };
171 | $.extend(Countdown.prototype, {
172 | start: function() {
173 | if(this.interval !== null) {
174 | clearInterval(this.interval);
175 | }
176 | var self = this;
177 | this.update();
178 | this.interval = setInterval(function() {
179 | self.update.call(self);
180 | }, this.options.precision);
181 | },
182 | stop: function() {
183 | clearInterval(this.interval);
184 | this.interval = null;
185 | this.dispatchEvent('stoped');
186 | },
187 | toggle: function() {
188 | if (this.interval) {
189 | this.stop();
190 | } else {
191 | this.start();
192 | }
193 | },
194 | pause: function() {
195 | this.stop();
196 | },
197 | resume: function() {
198 | this.start();
199 | },
200 | remove: function() {
201 | this.stop.call(this);
202 | instances[this.instanceNumber] = null;
203 | // Reset the countdown instance under data attr (Thanks to @assiotis)
204 | delete this.$el.data().countdownInstance;
205 | },
206 | setFinalDate: function(value) {
207 | this.finalDate = parseDateString(value); // Cast the given date
208 | },
209 | update: function() {
210 | // Stop if dom is not in the html (Thanks to @dleavitt)
211 | if(this.$el.closest('html').length === 0) {
212 | this.remove();
213 | return;
214 | }
215 | var now = new Date(),
216 | newTotalSecsLeft;
217 | // Create an offset date object
218 | newTotalSecsLeft = this.finalDate.getTime() - now.getTime(); // Millisecs
219 | // Calculate the remaining time
220 | newTotalSecsLeft = Math.ceil(newTotalSecsLeft / 1000); // Secs
221 | // If is not have to elapse set the finish
222 | newTotalSecsLeft = !this.options.elapse && newTotalSecsLeft < 0 ? 0 :
223 | Math.abs(newTotalSecsLeft);
224 | // Do not proceed to calculation if the seconds have not changed or
225 | // during the first tick
226 | if (this.totalSecsLeft === newTotalSecsLeft || this.firstTick) {
227 | this.firstTick = false;
228 | return;
229 | } else {
230 | this.totalSecsLeft = newTotalSecsLeft;
231 | }
232 | // Check if the countdown has elapsed
233 | this.elapsed = (now >= this.finalDate);
234 | // Calculate the offsets
235 | this.offset = {
236 | seconds : this.totalSecsLeft % 60,
237 | minutes : Math.floor(this.totalSecsLeft / 60) % 60,
238 | hours : Math.floor(this.totalSecsLeft / 60 / 60) % 24,
239 | days : Math.floor(this.totalSecsLeft / 60 / 60 / 24) % 7,
240 | daysToWeek : Math.floor(this.totalSecsLeft / 60 / 60 / 24) % 7,
241 | daysToMonth : Math.floor(this.totalSecsLeft / 60 / 60 / 24 % 30.4368),
242 | weeks : Math.floor(this.totalSecsLeft / 60 / 60 / 24 / 7),
243 | weeksToMonth: Math.floor(this.totalSecsLeft / 60 / 60 / 24 / 7) % 4,
244 | months : Math.floor(this.totalSecsLeft / 60 / 60 / 24 / 30.4368),
245 | years : Math.abs(this.finalDate.getFullYear()-now.getFullYear()),
246 | totalDays : Math.floor(this.totalSecsLeft / 60 / 60 / 24),
247 | totalHours : Math.floor(this.totalSecsLeft / 60 / 60),
248 | totalMinutes: Math.floor(this.totalSecsLeft / 60),
249 | totalSeconds: this.totalSecsLeft
250 | };
251 | // Dispatch an event
252 | if(!this.options.elapse && this.totalSecsLeft === 0) {
253 | this.stop();
254 | this.dispatchEvent('finish');
255 | } else {
256 | this.dispatchEvent('update');
257 | }
258 | },
259 | dispatchEvent: function(eventName) {
260 | var event = $.Event(eventName + '.countdown');
261 | event.finalDate = this.finalDate;
262 | event.elapsed = this.elapsed;
263 | event.offset = $.extend({}, this.offset);
264 | event.strftime = strftime(this.offset);
265 | this.$el.trigger(event);
266 | }
267 | });
268 | // Register the jQuery selector actions
269 | $.fn.countdown = function() {
270 | var argumentsArray = Array.prototype.slice.call(arguments, 0);
271 | return this.each(function() {
272 | // If no data was set, jQuery.data returns undefined
273 | var instanceNumber = $(this).data('countdown-instance');
274 | // Verify if we already have a countdown for this node ...
275 | // Fix issue #22 (Thanks to @romanbsd)
276 | if (instanceNumber !== undefined) {
277 | var instance = instances[instanceNumber],
278 | method = argumentsArray[0];
279 | // If method exists in the prototype execute
280 | if(Countdown.prototype.hasOwnProperty(method)) {
281 | instance[method].apply(instance, argumentsArray.slice(1));
282 | // If method look like a date try to set a new final date
283 | } else if(String(method).match(/^[$A-Z_][0-9A-Z_$]*$/i) === null) {
284 | instance.setFinalDate.call(instance, method);
285 | // Allow plugin to restart after finished
286 | // Fix issue #38 (thanks to @yaoazhen)
287 | instance.start();
288 | } else {
289 | $.error('Method %s does not exist on jQuery.countdown'
290 | .replace(/\%s/gi, method));
291 | }
292 | } else {
293 | // ... if not we create an instance
294 | new Countdown(this, argumentsArray[0], argumentsArray[1]);
295 | }
296 | });
297 | };
298 | });
299 |
--------------------------------------------------------------------------------
/test/integration/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration for RequireJS integration
2 | module.exports = function(config) {
3 | config.set({
4 | basePath: '../../',
5 | frameworks: [
6 | 'requirejs',
7 | 'qunit',
8 | 'sinon'
9 | ],
10 | files: [
11 | {
12 | pattern: 'test/vendor/jquery-2.1/dist/jquery.js',
13 | included: false
14 | },
15 | {
16 | pattern: 'src/**/*.js',
17 | included: false
18 | },
19 | 'test/integration/*.config.js',
20 | 'test/integration/*test.js'
21 | ],
22 | exclude: [
23 | 'karma.conf.js'
24 | ],
25 | reporters: [
26 | 'dots'
27 | ],
28 | port: 9876,
29 | colors: true,
30 | logLevel: config.LOG_INFO,
31 | browsers: [
32 | 'PhantomJS'
33 | ],
34 | autoWatch: false,
35 | singleRun: true
36 | });
37 | };
38 |
--------------------------------------------------------------------------------
/test/integration/requirejs.config.js:
--------------------------------------------------------------------------------
1 | var testFiles = [];
2 | var TEST_REGEXP = /(spec|test)\.js$/i;
3 |
4 | var pathToModule = function(path) {
5 | return path.replace(/^\/base\//, '').replace(/\.js$/, '');
6 | };
7 |
8 | Object.keys(window.__karma__.files).forEach(function(file) {
9 | if (TEST_REGEXP.test(file)) {
10 | // Normalize paths to RequireJS module names.
11 | testFiles.push(pathToModule(file));
12 | }
13 | });
14 |
15 | require.config({
16 | baseUrl: '/base',
17 | paths: {
18 | 'jquery': 'test/vendor/jquery-2.1/dist/jquery',
19 | 'jquery.countdown': 'src/countdown'
20 | },
21 | shim: {
22 | 'jquery': {
23 | exports: '$'
24 | },
25 | 'jquery.countdown': {
26 | deps: ['jquery']
27 | }
28 | },
29 | deps: testFiles,
30 | callback: window.__karma__.start
31 | });
32 |
--------------------------------------------------------------------------------
/test/integration/requirejs_test.js:
--------------------------------------------------------------------------------
1 | define('test/integration/requirejs_test',
2 | ['jquery', 'jquery.countdown'], function($) {
3 |
4 | test('integration with requirejs', function() {
5 | ok(!!$.fn.countdown, 'Should load the plugin via requireJS');
6 | });
7 | });
8 |
--------------------------------------------------------------------------------
/test/qunit.config.js:
--------------------------------------------------------------------------------
1 | module('jQuery The Final Countdown');
2 |
3 | // Helpers
4 | var uid = 0, $dom, $clock;
5 |
6 | QUnit.testStart(function() {
7 | $clock = sinon.useFakeTimers();
8 | $dom = $('').appendTo('body');
9 | });
10 |
11 | QUnit.testDone(function() {
12 | $clock.restore();
13 | try{
14 | $dom.countdown('remove');
15 | $dom.remove();
16 | $dom = null;
17 | } catch(e) {
18 | // ... countdown wasn't created
19 | }
20 | });
21 |
--------------------------------------------------------------------------------
/test/scenarios.json:
--------------------------------------------------------------------------------
1 | {
2 | "jquery-1.10": ["test/vendor/jquery-1.10/jquery.js"],
3 | "jquery-1.11": ["test/vendor/jquery-1.11/dist/jquery.js"],
4 | "jquery-1.7": ["test/vendor/jquery-1.7/jquery.js"],
5 | "jquery-1.8": ["test/vendor/jquery-1.8/jquery.js"],
6 | "jquery-1.9": ["test/vendor/jquery-1.9/jquery.js"],
7 | "jquery-2.0": ["test/vendor/jquery-2.0/jquery.js"],
8 | "jquery-2.1": ["test/vendor/jquery-2.1/dist/jquery.js"],
9 | "jquery-2.2": ["test/vendor/jquery-2.2/dist/jquery.js"],
10 | "jquery-3.0": ["test/vendor/jquery-3.0/dist/jquery.js"],
11 | "jquery-3.1": ["test/vendor/jquery-3.1/dist/jquery.js"],
12 | "bootstrap": [
13 | "test/vendor/jquery-2.1/dist/jquery.js",
14 | "test/vendor/bootstrap/dist/js/bootstrap.js"
15 | ],
16 | "requirejs": "test/integration/karma.conf.js"
17 | }
18 |
--------------------------------------------------------------------------------
/test/unit/date_casting_test.js:
--------------------------------------------------------------------------------
1 | module('Date casting');
2 |
3 | test('throw an error when string cannot be parsed', function() {
4 | throws(function() {
5 | $dom.countdown('Foo Bar!');
6 | });
7 | });
8 |
9 | test('accept a native Date object', function() {
10 | // Remember that the month starts at zero 0
11 | var date = new Date(2020, 7, 8, 9, 10, 11);
12 | $dom.countdown(date).on('update.countdown', function(event) {
13 | ok(event.finalDate.toString().match(/Aug 08 2020 09:10:11/));
14 | });
15 | $clock.tick(500);
16 | });
17 |
18 | test('cast miliseconds as a string (issue #17)', function() {
19 | // Remember that the month starts at zero 0
20 | var ms = new Date(2020, 8, 9, 10, 11, 12).getTime();
21 | $dom.countdown(String(ms)).on('update.countdown', function(event) {
22 | ok(event.finalDate.toString().match(/Sep 09 2020 10:11:12/));
23 | });
24 | $clock.tick(500);
25 | });
26 |
27 | test('cast miliseconds as a number', function() {
28 | // Remember that the month starts at zero 0
29 | var ms = new Date(2020, 8, 9, 10, 11, 12).getTime();
30 | $dom.countdown(Number(ms)).on('update.countdown', function(event) {
31 | ok(event.finalDate.toString().match(/Sep 09 2020 10:11:12/));
32 | });
33 | $clock.tick(500);
34 | });
35 |
36 | test('cast YYYY/MM/DD hh:mm:ss', function() {
37 | $dom.countdown('2020/10/20 12:34:56').on('update.countdown', function(event) {
38 | ok(event.finalDate.toString().match(/Oct 20 2020 12:34:56/));
39 | });
40 | $clock.tick(500);
41 | });
42 |
43 | test('cast YYYY-MM-DD hh:mm:ss', function() {
44 | $dom.countdown('2020-10-20 12:34:56').on('update.countdown', function(event) {
45 | ok(event.finalDate.toString().match(/Oct 20 2020 12:34:56/));
46 | });
47 | $clock.tick(500);
48 | });
49 |
50 | test('cast YYYY/MM/DD', function() {
51 | $dom.countdown('2020/10/20').on('update.countdown', function(event) {
52 | ok(event.finalDate.toString().match(/Oct 20 2020 00:00:00/));
53 | });
54 | $clock.tick(500);
55 | });
56 |
57 | test('cast MM/DD/YYYY', function() {
58 | $dom.countdown('11/22/2020').on('update.countdown', function(event) {
59 | ok(event.finalDate.toString().match(/Nov 22 2020 00:00:00/));
60 | });
61 | $clock.tick(500);
62 | });
63 |
64 | test('cast MM/DD/YYYY', function() {
65 | $dom.countdown('11/22/2020').on('update.countdown', function(event) {
66 | ok(event.finalDate.toString().match(/Nov 22 2020 00:00:00/));
67 | });
68 | $clock.tick(500);
69 | });
70 |
--------------------------------------------------------------------------------
/test/unit/event_test.js:
--------------------------------------------------------------------------------
1 | module('Events');
2 |
3 | test('trigger the update event', function() {
4 | var callback = sinon.spy();
5 | $dom.countdown('2020/10/20').on('update.countdown', callback);
6 | $clock.tick(500);
7 | ok(callback.callCount > 0);
8 | });
9 |
10 | test('trigger the finish event', function() {
11 | var now = new Date();
12 | var callback = sinon.spy();
13 | $dom.countdown(now).on('finish.countdown', callback);
14 | // Expect the callback to be called once (Issue #82)
15 | $clock.tick(2000);
16 | ok(callback.callCount === 1);
17 | });
18 |
19 | test('event object has {type, strftime, finalDate, offset, elapsed} ' +
20 | 'properties', function() {
21 | $dom.countdown('2020/10/20').on('update.countdown', function(event) {
22 | ok(event.hasOwnProperty('type'));
23 | ok(event.hasOwnProperty('strftime'));
24 | ok(event.hasOwnProperty('finalDate'));
25 | ok(event.hasOwnProperty('offset'));
26 | ok(event.hasOwnProperty('elapsed'));
27 | });
28 | $clock.tick(500);
29 | });
30 |
31 | test('event.offset object has {seconds, minutes, hours, days, ' +
32 | 'totalDays, weeks, years} properties', function() {
33 | $dom.countdown('2020/10/20').on('update.countdown', function(event) {
34 | ok(event.offset.hasOwnProperty('seconds'));
35 | ok(event.offset.hasOwnProperty('minutes'));
36 | ok(event.offset.hasOwnProperty('hours'));
37 | ok(event.offset.hasOwnProperty('days'));
38 | ok(event.offset.hasOwnProperty('daysToWeek'));
39 | ok(event.offset.hasOwnProperty('daysToMonth'));
40 | ok(event.offset.hasOwnProperty('weeks'));
41 | ok(event.offset.hasOwnProperty('weeksToMonth'));
42 | ok(event.offset.hasOwnProperty('years'));
43 | ok(event.offset.hasOwnProperty('totalDays'));
44 | ok(event.offset.hasOwnProperty('totalHours'));
45 | ok(event.offset.hasOwnProperty('totalMinutes'));
46 | ok(event.offset.hasOwnProperty('totalSeconds'));
47 | });
48 | $clock.tick(500);
49 | });
50 |
51 | test('allow the callback be setted uppon initialization (legacy ' +
52 | 'style)', function() {
53 | $dom.countdown('2020/10/20', function(event) {
54 | ok(true); // Up to this point the event was dispatched
55 | });
56 | $clock.tick(500);
57 | });
58 |
59 | test('event leaking and collision', function() {
60 | var event;
61 | var future = new Date().getTime() + 5000;
62 | var clickHandler = sinon.spy();
63 | var updateHandler = sinon.spy();
64 |
65 | $dom.countdown(future)
66 | .on('click', clickHandler)
67 | .on('update.countdown', updateHandler);
68 | $clock.tick(500);
69 |
70 | ok(!clickHandler.called);
71 | ok(updateHandler.called);
72 |
73 | event = updateHandler.lastCall.args[0];
74 | ok(event.namespace === 'countdown');
75 |
76 | $dom.trigger('click');
77 | updateHandler.reset();
78 |
79 | ok(clickHandler.called);
80 | ok(!updateHandler.called);
81 |
82 | // Trigger event without the namespace
83 | $dom.trigger('update');
84 | ok(updateHandler.called);
85 |
86 | event = updateHandler.lastCall.args[0];
87 | ok(event.namespace === '');
88 | });
89 |
90 | test('event bubbling with event listeners registered after ' +
91 | 'invocation', function() {
92 | var $doc = $(document),
93 | event,
94 | future = new Date().getTime() + 5000,
95 | updateHandler = sinon.spy(),
96 | finishHandler = sinon.spy();
97 |
98 | $dom.countdown(future);
99 |
100 | $doc
101 | .on('update.countdown', updateHandler)
102 | .on('finish.countdown', finishHandler);
103 | $clock.tick(500);
104 |
105 | ok(updateHandler.called);
106 |
107 | event = updateHandler.lastCall.args[0];
108 | ok(event.namespace === 'countdown');
109 | ok(event.type === 'update');
110 | ok(!event.elapsed);
111 |
112 | $clock.tick(5000);
113 | ok(updateHandler.called);
114 |
115 | event = finishHandler.lastCall.args[0];
116 | ok(event.namespace === 'countdown');
117 | ok(event.type === 'finish');
118 | ok(event.elapsed);
119 |
120 | // cleanup
121 | $doc = null;
122 | });
123 |
124 | test('event bubbling with event listeners registered before ' +
125 | 'invocation', function() {
126 | var $doc = $(document),
127 | event,
128 | future = new Date().getTime() + 5000,
129 | updateHandler = sinon.spy(),
130 | finishHandler = sinon.spy();
131 |
132 | $doc
133 | .on('update.countdown', updateHandler)
134 | .on('finish.countdown', finishHandler);
135 |
136 | $dom.countdown(future);
137 | $clock.tick(500);
138 |
139 | ok(updateHandler.called);
140 |
141 | event = updateHandler.lastCall.args[0];
142 | ok(event.namespace === 'countdown');
143 | ok(event.type === 'update');
144 | ok(!event.elapsed);
145 |
146 | $clock.tick(5000);
147 | ok(updateHandler.called);
148 |
149 | event = finishHandler.lastCall.args[0];
150 | ok(event.namespace === 'countdown');
151 | ok(event.type === 'finish');
152 | ok(event.elapsed);
153 |
154 | // cleanup
155 | $doc = null;
156 | });
157 |
158 | test('event listeners on parent and deferred invocation', function() {
159 | var $doc = $(document),
160 | event,
161 | future = new Date().getTime() + 5000,
162 | updateHandler = sinon.spy(),
163 | finishHandler = sinon.spy();
164 |
165 | $dom.countdown(future, {defer: true});
166 |
167 | $doc
168 | .on('update.countdown', updateHandler)
169 | .on('finish.countdown', finishHandler);
170 |
171 | $clock.tick(500);
172 |
173 | ok(!updateHandler.called);
174 | ok(!finishHandler.called);
175 |
176 | $dom.countdown('start');
177 | $clock.tick(500);
178 |
179 | ok(updateHandler.called);
180 |
181 | event = updateHandler.lastCall.args[0];
182 | ok(event.namespace === 'countdown');
183 | ok(event.type === 'update');
184 | ok(!event.elapsed);
185 |
186 | $clock.tick(5000);
187 | ok(updateHandler.called);
188 |
189 | event = finishHandler.lastCall.args[0];
190 | ok(event.namespace === 'countdown');
191 | ok(event.type === 'finish');
192 | ok(event.elapsed);
193 |
194 | // cleanup
195 | $doc = null;
196 | });
197 |
--------------------------------------------------------------------------------
/test/unit/functionality_test.js:
--------------------------------------------------------------------------------
1 | module('Functionality');
2 |
3 | test('stop/start the countdown', function() {
4 | var callback = sinon.spy();
5 | $dom.countdown('2020/10/20').on('update.countdown', callback);
6 | // Stop after 0.55 sec
7 | $clock.tick(550);
8 | $dom.countdown('stop');
9 | // Should have been called once
10 | ok(callback.calledOnce);
11 | // Resume after 1 sec
12 | $clock.tick(1000);
13 | $dom.countdown('start');
14 | // Verify if update event was called once more after 1.2 sec
15 | $clock.tick(5000);
16 | ok(callback.callCount > 5);
17 | });
18 |
19 | test('pause/resume the countdown', function() {
20 | var callback = sinon.spy();
21 | $dom.countdown('2020/10/20').on('update.countdown', callback);
22 | // Stop after 0.55 sec
23 | $clock.tick(550);
24 | $dom.countdown('pause');
25 | // Should have been called once
26 | ok(callback.calledOnce);
27 | // Resume after 1 sec
28 | $clock.tick(1000);
29 | $dom.countdown('resume');
30 | // Verify if update event was called once more after 1.2 sec
31 | $clock.tick(5000);
32 | ok(callback.callCount > 5);
33 | });
34 |
35 | test('toggle countdown', function() {
36 | var callback = sinon.spy();
37 | $dom.countdown('2020/10/20').on('update.countdown', callback);
38 | // Let it count once
39 | $clock.tick(100);
40 | ok(callback.callCount === 1);
41 | // Toggle the countdown to pause
42 | $dom.countdown('toggle');
43 | // Test if callback wasn't invoked and toggle gain
44 | $clock.tick(1000);
45 | ok(callback.callCount === 1);
46 | $dom.countdown('toggle');
47 | // Ensure it's running again
48 | $clock.tick(5000);
49 | ok(callback.callCount > 5);
50 | });
51 |
52 | test('remove the countdown instance', function() {
53 | var callback = sinon.spy();
54 | $dom.countdown('2020/10/20').on('update.countdown', callback);
55 | $dom.countdown('remove');
56 | // See if noting was executed after 0.5 sec
57 | ok(callback.callCount === 0);
58 | $clock.tick(500);
59 | });
60 |
61 | test('remove the countdown if dom was removed', function() {
62 | var callback = sinon.spy();
63 | $dom.countdown('2020/10/20').on('update.countdown', callback);
64 | $dom.remove();
65 | // See if noting was executed after 0.5 sec
66 | ok(callback.callCount === 0);
67 | $clock.tick(500);
68 | });
69 |
70 | test('set countdown-instance data attr to undefined uppon remove', function() {
71 | var callback = sinon.spy();
72 | $dom.countdown('2020/10/20').on('update.countdown', callback);
73 | ok($dom.data('countdown-instance') !== undefined);
74 |
75 | $dom.countdown('remove');
76 | ok($dom.data('countdown-instance') === undefined);
77 | });
78 |
79 | test('set a new final date calling the countdown again', function() {
80 | $dom.countdown('2020/10/20').on('update.countdown', function(event) {
81 | ok(event.finalDate.toString().match(/Oct 20 2020/));
82 | });
83 | $clock.tick(500);
84 | // Unset the event and set a new date
85 | $dom.off('update.countdown').countdown('2021/10/20')
86 | .on('update.countdown', function(event) {
87 | ok(event.finalDate.toString().match(/Oct 20 2021/));
88 | });
89 | $clock.tick(500);
90 | });
91 |
92 | test('starts the countdown again after being finished (issue #38)', function() {
93 | function twoSecondsFromNow() {
94 | return new Date().getTime() + 2000;
95 | }
96 | // Count the number of times
97 | var i = 0;
98 | // Start a countdown for 2secs from now and uppon finish start it again from
99 | // within the callback recursively
100 | $dom.countdown(twoSecondsFromNow()).on('finish.countdown', function() {
101 | ok(true, 'Countdown iteraction #' + i++);
102 | if (i < 2) {
103 | $dom.countdown(twoSecondsFromNow());
104 | }
105 | });
106 | // Expect 2 assertions after 4 seconds
107 | expect(2);
108 | $clock.tick(4000);
109 | });
110 |
111 | // Instance check against undefined, as 0 is a falsy value
112 | // Issue #24
113 | test('control a single instance correctly (issue #24)', function() {
114 | var callback = sinon.spy(),
115 | $single = $('').appendTo('body');
116 |
117 | $single.countdown('2020/10/20').on('update.countdown', callback);
118 | // Let it count once
119 | $clock.tick(100);
120 | ok(callback.callCount === 1);
121 | // Toggle the countdown to pause
122 | $single.countdown('toggle');
123 | // Test if callback wasn't invoked and toggle gain
124 | $clock.tick(1000);
125 | ok(callback.callCount === 1);
126 | $single.countdown('toggle');
127 | // Ensure it's running again
128 | $clock.tick(5000);
129 | ok(callback.callCount > 5);
130 | // Remove the node
131 | $single.remove();
132 | });
133 |
134 | test('configure the dom update rate', function() {
135 | var callback = sinon.spy();
136 | // Setup a different update rate precision
137 | $dom.countdown('2020/10/20', {
138 | precision: 2000
139 | }).on('update.countdown', callback);
140 |
141 | // Should execute just once in 2s
142 | $clock.tick(2000);
143 | ok(callback.callCount === 1);
144 | });
145 |
146 | test('continue after pass the final date', function() {
147 | var oneSecAgo = new Date().getTime() * 1000;
148 | var callback = sinon.spy();
149 | // Setup a different update rate precision
150 | $dom.countdown(oneSecAgo, {
151 | elapse: true
152 | }).on('update.countdown', callback);
153 | // Should execute just once in 500ms
154 | $clock.tick(500);
155 | ok(callback.callCount === 1);
156 | // Check if event was set has elapsed
157 | var eventObject = callback.lastCall.args[0];
158 | ok(eventObject.elapsed === true);
159 | });
160 |
161 | test('defer the start with lazy initialization option', function() {
162 | var callback = sinon.spy();
163 | // Setup a different update rate precision
164 | $dom.countdown('2020/10/20', {
165 | defer: true
166 | }).on('update.countdown', callback);
167 |
168 | // Should not start automatically
169 | $clock.tick(500);
170 | ok(!callback.called);
171 |
172 | // Start the countdown
173 | $dom.countdown('start');
174 | $clock.tick(500);
175 | ok(callback.called);
176 | });
177 |
--------------------------------------------------------------------------------
/test/unit/math_test.js:
--------------------------------------------------------------------------------
1 | module('Math');
2 |
3 | test('time offset calculation', function() {
4 | var testDate = new Date().getTime();
5 | testDate += 7 * 24 * 60 * 60 * 1000; // 1 week
6 | testDate += 2 * 24 * 60 * 60 * 1000; // 2 days
7 | testDate += 3 * 60 * 60 * 1000; // 3 hours
8 | testDate += 4 * 60 * 1000; // 4 minutes
9 | testDate += 5 * 1000; // 5 seconds
10 |
11 | $dom.countdown(testDate).on('update.countdown', function(event) {
12 | ok(event.offset.weeks === 1);
13 | ok(event.offset.days === 2);
14 | ok(event.offset.hours === 3);
15 | ok(event.offset.minutes === 4);
16 | ok(event.offset.seconds === 5);
17 | // Offset values
18 | ok(event.offset.daysToWeek === 2);
19 | ok(event.offset.daysToMonth === 9);
20 | ok(event.offset.weeksToMonth === 1);
21 | // Test total count
22 | ok(event.offset.totalDays === 9);
23 | ok(event.offset.totalHours === 9 * 24 + 3);
24 | ok(event.offset.totalMinutes === 9 * 24 * 60 + 3 * 60 + 4);
25 | ok(event.offset.totalSeconds === (9 * 24 * 60 + 3 * 60 + 4) * 60 + 5);
26 | });
27 |
28 | $clock.tick(500);
29 | });
30 |
31 | test('days differencence for week and month offset (Issue #125)', function() {
32 | var now = new Date(2015, 6, 16).getTime();
33 | $clock = sinon.useFakeTimers(now);
34 |
35 | var testDate = new Date(2015, 8, 25).getTime();
36 |
37 | $dom.countdown(testDate).on('update.countdown', function(event) {
38 | ok(event.offset.months === 2);
39 | ok(event.offset.daysToMonth === 10);
40 | });
41 |
42 | $clock.tick(500);
43 | });
44 |
45 | test('years offset calculation (Issue #125)', function() {
46 | var now = new Date(2014, 0, 1).getTime();
47 | $clock = sinon.useFakeTimers(now);
48 |
49 | var testDate = new Date(2015, 0, 1).getTime();
50 |
51 | $dom.countdown(testDate).on('update.countdown', function(event) {
52 | ok(event.offset.years === 1);
53 | });
54 |
55 | $clock.tick(500);
56 | });
57 |
--------------------------------------------------------------------------------
/test/unit/sanity_test.js:
--------------------------------------------------------------------------------
1 | module('Sanity tests');
2 |
3 | test('ensure DOM for testing is created', function() {
4 | ok($dom !== null);
5 | ok($dom !== undefined);
6 | ok($dom.length === 1);
7 | });
8 |
9 | test('countdown method exists within jquery selector', function() {
10 | ok($.fn.countdown !== undefined);
11 | ok($.fn.countdown !== null);
12 | ok($.fn.countdown);
13 | });
14 |
15 | test('throws an error when initiated with wrong number of args', function() {
16 | throws(function() {
17 | $('').countdown();
18 | });
19 | });
20 |
--------------------------------------------------------------------------------
/test/unit/strftime_test.js:
--------------------------------------------------------------------------------
1 | module('strftime');
2 |
3 | test('strftime is a function that returns a string', function() {
4 | $dom.countdown('11/22/2020').on('update.countdown', function(event) {
5 | ok(typeof event.strftime === 'function');
6 | ok(typeof event.strftime('foo') === 'string');
7 | });
8 | $clock.tick(500);
9 | });
10 |
11 | test('strftime returns the same string if no replaces were made', function() {
12 | $dom.countdown('11/22/2020').on('update.countdown', function(event) {
13 | ok(event.strftime('Foo Bar') === 'Foo Bar');
14 | });
15 | $clock.tick(500);
16 | });
17 |
18 | test('escaping percentage character %% ', function() {
19 | $dom.countdown('2020/11/10 09:08:07').on('update.countdown', function(event) {
20 | ok(event.strftime('%%') === '%');
21 | });
22 | $clock.tick(500);
23 | });
24 |
25 | /*
26 | | Directive | Blank-padded | Pluralization | Description |
27 | |---------------|---------------|---------------|---------------------------|
28 | | %Y | %-Y | %!Y | Years left |
29 | | %m | %-m | %!m | Months left |
30 | | %n | %-n | %!n | Days left to a month |
31 | | %w | %-w | %!w | Weeks left |
32 | | %W | %-W | %!W | Weeks left to a month |
33 | | %d | %-d | %!d | Days left to a week |
34 | | %H | %-H | %!H | Hours left |
35 | | %M | %-M | %!M | Minutes left |
36 | | %S | %-S | %!S | Seconds left |
37 | | %D | %-D | %!D | Total amount of days left |
38 | | %I | %-I | %!I | Total hours left |
39 | | %N | %-N | %!N | Total minutest left |
40 | | %T | %-T | %!T | Total seconds left |
41 | |---------------------------------------------------------------------------|
42 | */
43 |
44 | test('all directives', function() {
45 | $dom.countdown('2020/11/10 09:08:07').on('update.countdown', function(event) {
46 | ok(event.strftime('%Y %m %n %w %W %d %D %H %M %S %D %I %N %T')
47 | .match(/^([0-9]{1,}\s?){14}$/) !== null);
48 | });
49 | $clock.tick(500);
50 | });
51 |
52 | test('blank-padded directives', function() {
53 | $dom.countdown('2020/11/10 09:08:07').on('update.countdown', function(event) {
54 | ok(event.strftime('%-Y %-m %-n %-w %-W %-d %-H %-M %-S %-D %-I %-N %-T')
55 | .match(/^([0-9]{1,}\s?){14}$/) !== null);
56 | });
57 | $clock.tick(500);
58 | });
59 |
60 | test('return an empty character when plural', function() {
61 | $dom.countdown(new Date().valueOf() + 1000)
62 | .on('update.countdown', function(event) {
63 | ok(event.offset.seconds === 1);
64 | ok(event.strftime('%!S') === '');
65 | });
66 | $clock.tick(500);
67 | });
68 |
69 | test('return a `s` when when plural', function() {
70 | $dom.countdown(new Date().valueOf() + 2000)
71 | .on('update.countdown', function(event) {
72 | ok(event.offset.seconds === 2);
73 | ok(event.strftime('%!S') === 's');
74 | });
75 | $clock.tick(500);
76 | });
77 |
78 | test('return the given character when plural', function() {
79 | $dom.countdown(new Date().valueOf() + 2000)
80 | .on('update.countdown', function(event) {
81 | ok(event.offset.seconds === 2);
82 | ok(event.strftime('sekunde%!S:n;') === 'sekunden');
83 | });
84 | $clock.tick(500);
85 | });
86 |
87 | test('return the plural when given pair of arguments', function() {
88 | $dom.countdown(new Date().valueOf() + 2000)
89 | .on('update.countdown', function(event) {
90 | ok(event.offset.seconds === 2);
91 | ok(event.strftime('%!S:day,days;') === 'days');
92 | });
93 | $clock.tick(500);
94 | });
95 |
96 | test('return the singular when given pair of arguments', function() {
97 | $dom.countdown(new Date().valueOf() + 1000)
98 | .on('update.countdown', function(event) {
99 | ok(event.offset.seconds === 1);
100 | ok(event.strftime('%!S:day,days;') === 'day');
101 | });
102 | $clock.tick(500);
103 | });
104 |
105 | test('return the singular for zero (issue #187)', function() {
106 | $dom.countdown(new Date().valueOf() + 1000)
107 | .on('update.countdown', function(event) {
108 | ok(event.offset.seconds === 1);
109 | ok(event.strftime('%m %!m:min,minutes;') === '00 min');
110 | });
111 | $clock.tick(500);
112 | });
113 |
114 | test('return the correct plural even with odd looking (issue #70)', function() {
115 | $dom.countdown(new Date().valueOf() + 2 * 60 * 1000)
116 | .on('update.countdown', function(event) {
117 | ok(event.offset.minutes === 2);
118 | ok(event.strftime('%!M:Minuta,Minute(e);') === 'Minute(e)');
119 | });
120 | $clock.tick(500);
121 | });
122 |
--------------------------------------------------------------------------------