eac3349d by Adam Heath

Extracted out seen() support, made it a mixin.

1 parent 895de9bd
1 {
2 "directory": "src/lib"
3 }
1 .*.swp
2 .tmp/
3 bin/coverage/
4 dist/
5 node_modules/
6 src/lib/
7 .grunt/
8 _SpecRunner.html
1 // Generated on 2014-02-06 using generator-webapp 0.4.7
2 /* global module */
3
4 // # Globbing
5 // for performance reasons we're only matching one level down:
6 // 'test/spec/{,*/}*.js'
7 // use this if you want to recursively match all subfolders:
8 // 'test/spec/**/*.js'
9
10 module.exports = function (grunt) {
11 /* global require */
12 'use strict';
13
14 var jasmineRequirejsTemplateOptions = function(withInstanbul) {
15 /* global requirejs */
16 var callback;
17 if (withInstanbul) {
18 callback = function() {
19 var oldLoad = requirejs.load;
20 requirejs.load = function (context, moduleName, url) {
21 //console.log('context=' + JSON.stringify(arguments), 'moduleName=' + moduleName, 'url=' + url);
22 var parts = url.split('/');
23 for (var i = 0; i < parts.length; ) {
24 var part = parts[i];
25 if (part === '.') {
26 parts.splice(i, 1);
27 } else if (part === '') {
28 parts.splice(i, 1);
29 } else if (part === '..') {
30 if (i > 0) {
31 i--;
32 parts.splice(i, 2);
33 } else {
34 parts.splice(i, 1);
35 }
36 } else {
37 i++;
38 }
39 }
40 url = parts.join('/');
41 if (url.indexOf('src/scripts/') === 0) {
42 url = './.grunt/grunt-contrib-jasmine/' + url;
43 }
44 if (url.indexOf('test/specs/') === 0) {
45 url = './.grunt/grunt-contrib-jasmine/' + url;
46 }
47 //console.log('url=' + url);
48 return oldLoad.apply(this, [context, moduleName, url]);
49 };
50 };
51 }
52 return {
53 requireConfigFile: '<%= yeoman.src %>/scripts/config.js',
54 requireConfig: {
55 baseUrl: '<%= yeoman.src %>/scripts',
56 callback: callback
57 }
58 };
59 };
60
61 var jasmineInstanbulTemplateOptions = function(nestedTemplate, nestedOptions) {
62 return {
63 coverage: 'bin/coverage/coverage.json',
64 report: 'bin/coverage',
65 replace: false,
66 template: require(nestedTemplate),
67 templateOptions: nestedOptions
68 };
69 };
70
71 // Load grunt tasks automatically
72 require('load-grunt-tasks')(grunt);
73
74 // Time how long tasks take. Can help when optimizing build times
75 require('time-grunt')(grunt);
76
77 // Define the configuration for all the tasks
78 grunt.initConfig({
79 bower: {
80 target: {
81 options: {
82 exclude: [
83 'requirejs',
84 ],
85 transitive: true,
86 },
87 rjsConfig: '<%= yeoman.src %>/scripts/config.js'
88 }
89 },
90
91 // Project settings
92 yeoman: {
93 // Configurable paths
94 app: 'app',
95 dist: 'dist',
96 src: 'src',
97 },
98
99 // Watches files for changes and runs tasks based on the changed files
100 watch: {
101 js: {
102 files: ['<%= yeoman.src %>/scripts/{,*/}*.js'],
103 tasks: ['jshint'],
104 },
105 jstest: {
106 files: ['test/spec/{,*/}*.js'],
107 tasks: ['test:watch']
108 },
109 gruntfile: {
110 files: ['Gruntfile.js']
111 },
112 styles: {
113 files: ['<%= yeoman.src %>/styles/{,*/}*.css'],
114 tasks: ['newer:copy:styles', 'autoprefixer']
115 }
116 },
117
118 // The actual grunt server settings
119 connect: {
120 options: {
121 port: 9000,
122 // Change this to '0.0.0.0' to access the server from outside
123 hostname: 'localhost'
124 },
125 app: {
126 options: {
127 open: false,
128 base: [
129 '.tmp',
130 '<%= yeoman.src %>'
131 ]
132 }
133 },
134 test: {
135 options: {
136 port: 9001,
137 base: [
138 '.tmp',
139 'test',
140 '<%= yeoman.src %>'
141 ]
142 }
143 },
144 dist: {
145 options: {
146 open: false,
147 base: '<%= yeoman.dist %>',
148 }
149 }
150 },
151
152 // Empties folders to start fresh
153 clean: {
154 dist: {
155 files: [{
156 dot: true,
157 src: [
158 '.tmp',
159 '<%= yeoman.dist %>/*',
160 '!<%= yeoman.dist %>/.git*'
161 ]
162 }]
163 },
164 server: '.tmp'
165 },
166
167 // Make sure code styles are up to par and there are no obvious mistakes
168 jshint: {
169 options: {
170 browser: true,
171 esnext: true,
172 bitwise: true,
173 camelcase: true,
174 curly: true,
175 eqeqeq: true,
176 immed: true,
177 indent: 4,
178 latedef: true,
179 newcap: true,
180 noarg: true,
181 quotmark: 'single',
182 undef: true,
183 unused: true,
184 strict: true,
185 trailing: true,
186 smarttabs: true,
187 jquery: true,
188 reporter: require('jshint-stylish')
189 },
190 all: [
191 'Gruntfile.js',
192 ],
193 scripts: {
194 options: {
195 globals: {
196 define: false,
197 }
198 },
199 files: {
200 src: [
201 '<%= yeoman.src %>/scripts/**/*.js',
202 '!<%= yeoman.src %>/scripts/vendor/*',
203 ]
204 }
205 },
206 specs: {
207 options: {
208 globals: {
209 beforeEach: false,
210 define: false,
211 describe: false,
212 expect: false,
213 it: false,
214 jasmine: false,
215 }
216 },
217 files: {
218 src: [
219 'test/specs/**/*.spec.js'
220 ]
221 }
222 }
223 },
224
225 jasmine: {
226 all: {
227 src: [
228 '<%= yeoman.src %>/scripts/{,**/}*.js',
229 'test/specs/**/*.spec.js',
230 ],
231 options: {
232 //specs: 'test/specs/**/*.spec.js',
233 template: require('grunt-template-jasmine-istanbul'),
234 templateOptions: jasmineInstanbulTemplateOptions('grunt-template-jasmine-requirejs', jasmineRequirejsTemplateOptions(true))
235 }
236 }
237 },
238
239 // Mocha testing framework configuration options
240 mocha: {
241 all: {
242 options: {
243 run: true,
244 urls: ['http://<%= connect.test.options.hostname %>:<%= connect.test.options.port %>/index.html']
245 }
246 }
247 },
248
249 // Add vendor prefixed styles
250 autoprefixer: {
251 options: {
252 browsers: ['last 1 version']
253 },
254 dist: {
255 files: [{
256 expand: true,
257 cwd: '.tmp/styles/',
258 src: '{,*/}*.css',
259 dest: '.tmp/styles/'
260 }]
261 }
262 },
263
264 // Automatically inject Bower components into the HTML file
265 'bower-install': {
266 app: {
267 html: '<%= yeoman.src %>/index.html',
268 ignorePath: '<%= yeoman.src %>/'
269 }
270 },
271
272 // Renames files for browser caching purposes
273 rev: {
274 dist: {
275 files: {
276 src: [
277 '<%= yeoman.dist %>/scripts/*/**/*.js',
278 '<%= yeoman.dist %>/scripts/!(config)*.js',
279 '<%= yeoman.dist %>/styles/{,*/}*.css',
280 '<%= yeoman.dist %>/images/{,*/}*.{gif,jpeg,jpg,png,webp}',
281 '<%= yeoman.dist %>/styles/fonts/{,*/}*.*'
282 ]
283 }
284 },
285 requireconfig: {
286 files: {
287 src: [
288 '<%= yeoman.dist %>/scripts/config.js'
289 ]
290 }
291 }
292 },
293
294 requirejs: {
295 dist: {
296 options: {
297 done: function(done) {
298 var requireModules = grunt.config('requireModules') || {};
299 var lines = [
300 'require.bundles = (function(bundles) {',
301 ];
302 for (var key in requireModules) {
303 var keyS = JSON.stringify(key);
304 var value = requireModules[key];
305 var included = [];
306 for (var i = 0; i < value.included.length; i++) {
307 var file = value.included[i];
308 if (file.match(/\.js$/)) {
309 included.push(file.substring(0, file.length - 3));
310 }
311 }
312 lines.push('bundles[' + keyS + '] = ' + JSON.stringify(included) + ';');
313 }
314 lines.push('return bundles;');
315 lines.push('})(require.bundles || {});');
316 grunt.file.write('.tmp/scripts/bundles.js', lines.join('\n'));
317 done();
318 },
319 baseUrl: '<%= yeoman.src %>/scripts',
320 mainConfigFile: '<%= yeoman.src %>/scripts/config.js',
321 wrapShim: true,
322 dir: '<%= yeoman.dist %>/scripts',
323 optimize: 'none',
324 removeCombined: true,
325 onModuleBundleComplete: function(data) {
326 if (data.name.slice(0, 'bundles/'.length) === 'bundles/') {
327 var requireModules = grunt.config('requireModules') || {};
328 requireModules[data.name] = data;
329 grunt.config('requireModules', requireModules);
330 }
331 },
332 }
333 },
334 },
335
336 // Reads HTML for usemin blocks to enable smart builds that automatically
337 // concat, minify and revision files. Creates configurations in memory so
338 // additional tasks can operate on them
339 useminPrepare: {
340 options: {
341 dest: '<%= yeoman.dist %>'
342 },
343 html: '<%= yeoman.src %>/index.html'
344 },
345
346 // Performs rewrites based on rev and the useminPrepare configuration
347 usemin: {
348 options: {
349 assetsDirs: ['<%= yeoman.dist %>']
350 },
351 html: ['<%= yeoman.dist %>/{,*/}*.html'],
352 css: ['<%= yeoman.dist %>/styles/{,*/}*.css']
353 },
354
355 // The following *-min tasks produce minified files in the dist folder
356 imagemin: {
357 dist: {
358 files: [{
359 expand: true,
360 cwd: '<%= yeoman.src %>/images',
361 src: '{,*/}*.{gif,jpeg,jpg,png}',
362 dest: '<%= yeoman.dist %>/images'
363 }]
364 }
365 },
366 svgmin: {
367 dist: {
368 files: [{
369 expand: true,
370 cwd: '<%= yeoman.src %>/images',
371 src: '{,*/}*.svg',
372 dest: '<%= yeoman.dist %>/images'
373 }]
374 }
375 },
376 htmlmin: {
377 dist: {
378 options: {
379 collapseBooleanAttributes: true,
380 collapseWhitespace: true,
381 removeAttributeQuotes: true,
382 removeCommentsFromCDATA: true,
383 removeEmptyAttributes: true,
384 removeOptionalTags: true,
385 removeRedundantAttributes: true,
386 useShortDoctype: true
387 },
388 files: [{
389 expand: true,
390 cwd: '<%= yeoman.dist %>',
391 src: '{,*/}*.html',
392 dest: '<%= yeoman.dist %>'
393 }]
394 }
395 },
396
397 // By default, your `index.html`'s <!-- Usemin block --> will take care of
398 // minification. These next options are pre-configured if you do not wish
399 // to use the Usemin blocks.
400 // cssmin: {
401 // dist: {
402 // files: {
403 // '<%= yeoman.dist %>/styles/main.css': [
404 // '.tmp/styles/{,*/}*.css',
405 // '<%= yeoman.src %>/styles/{,*/}*.css'
406 // ]
407 // }
408 // }
409 // },
410 // uglify: {
411 // dist: {
412 // files: {
413 // '<%= yeoman.dist %>/scripts/scripts.js': [
414 // '<%= yeoman.dist %>/scripts/scripts.js'
415 // ]
416 // }
417 // }
418 // },
419 // concat: {
420 // dist: {}
421 // },
422
423 concat: {
424 requireconfig: {
425 }
426 },
427
428 uglify: {
429 dist: {
430 },
431 requireconfig: {
432 files: {
433 '<%= yeoman.dist %>/scripts/config.js': [
434 '<%= yeoman.dist %>/scripts/config.js',
435 '.tmp/scripts/config.js',
436 ],
437 }
438 }
439 },
440
441 // Copies remaining files to places other tasks can use
442 copy: {
443 dist: {
444 files: [{
445 expand: true,
446 dot: true,
447 cwd: '<%= yeoman.src %>',
448 dest: '<%= yeoman.dist %>',
449 src: [
450 '*.{ico,png,txt}',
451 '.htaccess',
452 'images/{,*/}*.webp',
453 '{,*/}*.html',
454 'styles/fonts/{,*/}*.*'
455 ]
456 }]
457 },
458 styles: {
459 expand: true,
460 dot: true,
461 cwd: '<%= yeoman.src %>/styles',
462 dest: '.tmp/styles/',
463 src: '{,*/}*.css'
464 }
465 },
466
467
468 // Run some tasks in parallel to speed up build process
469 concurrent: {
470 server: [
471 'copy:styles'
472 ],
473 test: [
474 'copy:styles'
475 ],
476 dist: [
477 'copy:styles',
478 'imagemin',
479 'svgmin'
480 ]
481 }
482 });
483
484 grunt.loadNpmTasks('grunt-bower-requirejs');
485
486 grunt.registerTask('revconfig', function () {
487 var prefix = grunt.template.process('<%= yeoman.dist %>/scripts/');
488 var pattern = prefix + '**/*.{js,html}';
489 var files = grunt.file.expand(pattern);
490 var lines = [];
491 grunt.util._.each(files, function(file) {
492 file = file.substring(prefix.length);
493 var res = file.match(/^(.*\/)?([0-9a-f]+)\.([^\/]+)\.([^\.]+)$/);
494 if (!res) {
495 return;
496 }
497 //grunt.log.oklns(JSON.stringify(res));
498 var dir = res[1] || '';
499 //var hash = res[2];
500 var base = res[3];
501 var ext = res[4];
502 var id;
503 if (ext === 'js') {
504 id = dir + base;
505 file = file.substring(0, file.length - ext.length - 1);
506 } else if (ext === 'html') {
507 id = 'text!' + dir + base + '.' + ext;
508 }
509 grunt.log.oklns('map: ' + id + ' -> ' + file);
510 lines.push('require.paths[' + JSON.stringify(id) + ']=' + JSON.stringify(file) + ';\n');
511 });
512 grunt.file.write('.tmp/scripts/config.js', lines.join(''));
513 });
514
515 grunt.registerTask('serve', function (target) {
516 if (target === 'dist') {
517 return grunt.task.run(['build', 'connect:dist:keepalive']);
518 }
519
520 grunt.task.run([
521 'clean:server',
522 'concurrent:server',
523 'autoprefixer',
524 'connect:app',
525 'watch'
526 ]);
527 });
528
529 grunt.registerTask('server', function () {
530 grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.');
531 grunt.task.run(['serve']);
532 });
533
534 grunt.registerTask('test', function(target) {
535 if (target !== 'watch') {
536 grunt.task.run([
537 'clean:server',
538 'concurrent:test',
539 'autoprefixer',
540 ]);
541 }
542
543 grunt.task.run([
544 'connect:test',
545 'mocha'
546 ]);
547 });
548
549 grunt.registerTask('build', [
550 'clean:dist',
551 'useminPrepare',
552 'requirejs',
553 'concurrent:dist',
554 'autoprefixer',
555 'concat',
556 // 'cssmin',
557 'uglify:dist',
558 'copy:dist',
559 // 'rev:dist',
560 'revconfig',
561 'uglify:requireconfig',
562 // 'rev:requireconfig',
563 'usemin',
564 'htmlmin'
565 ]);
566
567 grunt.registerTask('dist', [
568 'bower',
569 'newer:jshint',
570 // 'test',
571 'build'
572 ]);
573 grunt.registerTask('default', []);
574 };
1 {
2 "name": "backbone-seen",
3 "version": "0.0.0",
4 "authors": [
5 "Adam Heath <doogie@brainfood.com>"
6 ],
7 "main": [
8 "src/scripts/backbone-seen.js"
9 ],
10 "private": true,
11 "ignore": [
12 "**/.*",
13 "node_modules",
14 "src/lib",
15 "test"
16 ],
17 "dependencies": {
18 "underscore": "~1.6.0",
19 "backbone": "~1.1.0",
20 "requirejs": "~2.1.10"
21 }
22 }
1 {
2 "name": "backbone-seen",
3 "version": "0.0.0",
4 "main": [
5 "src/scripts/backbone-seen.js"
6 ],
7 "dependencies": {
8 "underscore": "~1.6.0",
9 "backbone": "~1.1.0",
10 "requirejs": "~2.1.10"
11 },
12 "devDependencies": {
13 "bower-requirejs": "~0.9.2",
14 "grunt": "~0.4.1",
15 "grunt-contrib-copy": "~0.4.1",
16 "grunt-contrib-concat": "~0.3.0",
17 "grunt-contrib-uglify": "~0.2.0",
18 "grunt-contrib-jshint": "~0.7.0",
19 "grunt-contrib-cssmin": "~0.7.0",
20 "grunt-contrib-connect": "~0.5.0",
21 "grunt-contrib-clean": "~0.5.0",
22 "grunt-contrib-htmlmin": "~0.1.3",
23 "grunt-bower-install": "~0.7.0",
24 "grunt-contrib-imagemin": "~0.2.0",
25 "grunt-contrib-watch": "~0.5.2",
26 "grunt-rev": "~0.1.0",
27 "grunt-autoprefixer": "~0.5.0",
28 "grunt-usemin": "~0.1.10",
29 "grunt-mocha": "~0.4.0",
30 "grunt-newer": "~0.6.0",
31 "grunt-svgmin": "~0.2.0",
32 "grunt-concurrent": "~0.4.0",
33 "load-grunt-tasks": "~0.2.0",
34 "time-grunt": "~0.2.0",
35 "jshint-stylish": "~0.1.3",
36 "grunt-contrib-requirejs": "~0.4.0",
37 "grunt-bower-requirejs": "~0.8.4",
38 "grunt-template-jasmine-istanbul": "~0.2.6",
39 "grunt-template-jasmine-requirejs": "~0.1.10",
40 "grunt-contrib-jasmine": "~0.5.3"
41 },
42 "engines": {
43 "node": ">=0.8.0"
44 }
45 }
1 define(
2 [
3 'underscore',
4 ],
5 function(
6 _
7 ) {
8 'use strict';
9
10 var seen = function(attrName, wasSeen) {
11 if (this._seen === undefined) {
12 this._seen = {};
13 }
14 if (attrName === undefined) {
15 return _.keys(_.clone(this._seen));
16 }
17 if (wasSeen) {
18 this._seen[attrName] = true;
19 } else if (wasSeen === false) {
20 delete this._seen[attrName];
21 } else {
22 return this._seen[attrName];
23 }
24 return this;
25 };
26
27 return {
28 mixin: function(modelClass) {
29 modelClass.prototype.seen = seen;
30 return modelClass;
31 },
32 };
33 }
34 );
1 /* global require:true */
2 var require;
3 require = (function() {
4 'use strict';
5
6 var require = {
7 baseUrl: 'scripts',
8 shim: {
9
10 },
11 paths: {
12 backbone: '../lib/backbone/backbone',
13 underscore: '../lib/underscore/underscore'
14 }
15 };
16
17 return require;
18 })();
1 define([], {});
1 /* global require */
2 require(
3 [],
4 function() {
5 'use strict';
6 }
7 );
1 define(function(require) {
2 'use strict';
3
4 var BackboneSeen = require('backbone-seen');
5 var Backbone = require('backbone');
6
7 describe('BackboneSeen', function() {
8 it('exists', function() {
9 expect(BackboneSeen).toBeTruthy();
10 expect(Backbone.Model.prototype.seen).toBeUndefined();
11 });
12 });
13 describe('BackboneSeen', function() {
14 var Base, Parent, Child;
15 beforeEach(function() {
16 Base = Backbone.Model.extend();
17 Parent = Base.extend();
18 Child = Parent.extend();
19 });
20 it('mixin', function() {
21 expect(Base.prototype.seen).toBeUndefined();
22 expect(Parent.prototype.seen).toBeUndefined();
23 expect(Child.prototype.seen).toBeUndefined();
24 var base = new Base({a: '1'});
25 var parent = new Parent({a: '1'});
26 var child = new Child({b: 'c'});
27 expect(base.seen).toBeUndefined();
28 expect(parent.seen).toBeUndefined();
29 expect(child.seen).toBeUndefined();
30 Parent = BackboneSeen.mixin(Parent);
31 var seen = parent.seen;
32 expect(base.seen).toBeUndefined();
33 expect(seen).toEqual(jasmine.any(Function));
34 expect(child.seen).toBe(parent.seen);
35 });
36 it('seen', function() {
37 Parent = BackboneSeen.mixin(Parent);
38 var parent = new Parent({a: '1'});
39 expect(parent.seen()).toEqual([]);
40 expect(parent.seen('a')).toBe(undefined);
41 expect(parent.seen('a', true)).toBe(parent);
42 expect(parent.seen()).toEqual(['a']);
43 expect(parent.seen('a')).toBe(true);
44 expect(parent.seen('a', false)).toBe(parent);
45 expect(parent.seen()).toEqual([]);
46 expect(parent.seen('a')).toBe(undefined);
47 });
48 });
49 });