├── .bowerrc
├── .gitignore
├── .jshintrc
├── Gruntfile.js
├── README.md
├── app
├── app.js
├── components
│ ├── current-queue-blip.js
│ └── run-loop-queue.js
├── controllers
│ └── application.js
├── models
│ └── stubbed_ember.js
├── router.js
├── routes
│ └── application.js
├── styles
│ └── app.scss
└── templates
│ ├── application.hbs
│ └── components
│ ├── current-queue-blip.hbs
│ └── run-loop-queue.hbs
├── bower.json
├── karma.conf.js
├── package.json
├── public
├── assets
│ └── .gitkeep
└── index.html
├── tasks
├── helpers.js
├── locking.js
└── options
│ ├── clean.js
│ ├── coffee.js
│ ├── compass.js
│ ├── concat_sourcemap.js
│ ├── connect.js
│ ├── copy.js
│ ├── cssmin.js
│ ├── dom_munger.js
│ ├── emberTemplates.js
│ ├── emblem.js
│ ├── jshint.js
│ ├── karma.js
│ ├── less.js
│ ├── rev.js
│ ├── sass.js
│ ├── stylus.js
│ ├── transpile.js
│ ├── usemin.js
│ ├── useminPrepare.js
│ └── watch.js
├── tests
├── acceptance
│ └── index_test.js
├── index.html
├── test_helper.js
├── test_loader.js
└── unit
│ ├── .gitkeep
│ └── routes
│ └── index_test.js
└── vendor
└── loader.js
/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "vendor"
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /tmp
6 |
7 | # dependencies
8 | /node_modules
9 | /vendor/*
10 | !/vendor/loader.js
11 |
12 | # misc
13 | /.sass-cache
14 | /connect.lock
15 | /libpeerconnection.log
16 | .DS_Store
17 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "predef": [
3 | "document",
4 | "window",
5 | "location",
6 | "setTimeout",
7 | "Ember",
8 | "Em",
9 | "$",
10 | "QUnit",
11 | "define",
12 | "console",
13 | "require",
14 | "requireModule",
15 | "equal",
16 | "notEqual",
17 | "notStrictEqual",
18 | "test",
19 | "asyncTest",
20 | "testBoth",
21 | "testWithDefault",
22 | "raises",
23 | "throws",
24 | "deepEqual",
25 | "start",
26 | "stop",
27 | "ok",
28 | "strictEqual",
29 | "module",
30 | "process",
31 | "expect",
32 | "visit",
33 | "exists",
34 | "fillIn",
35 | "click",
36 | "find"
37 | ],
38 | "node" : false,
39 | "browser" : false,
40 | "boss" : true,
41 | "curly": false,
42 | "debug": false,
43 | "devel": false,
44 | "eqeqeq": true,
45 | "evil": true,
46 | "forin": false,
47 | "immed": false,
48 | "laxbreak": false,
49 | "newcap": true,
50 | "noarg": true,
51 | "noempty": false,
52 | "nonew": false,
53 | "nomen": false,
54 | "onevar": false,
55 | "plusplus": false,
56 | "regexp": false,
57 | "undef": true,
58 | "sub": true,
59 | "strict": false,
60 | "white": false,
61 | "eqnull": true,
62 | "esnext": true
63 | }
64 |
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | module.exports = function(grunt) {
2 | // To support Coffeescript, SASS, LESS and others, just install
3 | // the appropriate grunt package and it will be automatically included
4 | // in the build process:
5 | //
6 | // * for Coffeescript, run `npm install --save-dev grunt-contrib-coffee`
7 | //
8 | // * for SASS (SCSS only), run `npm install --save-dev grunt-sass`
9 | // * for SCSS/SASS support (may be slower), run
10 | // `npm install --save-dev grunt-contrib-sass`
11 | // This depends on the ruby sass gem, which can be installed with
12 | // `gem install sass`
13 | //
14 | // * for LESS, run `npm install --save-dev grunt-contrib-less`
15 | //
16 | // * for Stylus/Nib, `npm install --save-dev grunt-contrib-stylus`
17 | //
18 | // * for Compass, run `npm install --save-dev grunt-contrib-compass`
19 | // This depends on the ruby compass gem, which can be installed with
20 | // `gem install compass`
21 | // You should not install SASS if you have installed Compass.
22 | //
23 | // * for Emblem, run the following commands:
24 | // `npm uninstall --save-dev grunt-ember-templates`
25 | // `npm install --save-dev grunt-emblem`
26 | // `bower install emblem.js --save`
27 | //
28 | // If you use SASS, LESS or Stylus, don't forget to delete
29 | // `public/assets/app.css` and create `app/styles/app.scss` instead.
30 |
31 | var Helpers = require('./tasks/helpers'),
32 | config = Helpers.config,
33 | filterAvailable = Helpers.filterAvailableTasks,
34 | _ = grunt.util._;
35 |
36 | config = _.extend(config, Helpers.loadConfig('./tasks/options/'));
37 |
38 | require('load-grunt-tasks')(grunt);
39 | grunt.loadTasks('tasks');
40 |
41 | grunt.registerTask('default', "Build (in debug mode) & test your application.", ['test']);
42 |
43 | config.concurrent = {
44 | dist: [
45 | "build:templates:dist",
46 | "build:scripts",
47 | "build:styles",
48 | "build:other"
49 | ],
50 | debug: [
51 | "build:templates:debug",
52 | "build:scripts",
53 | "build:styles",
54 | "build:other"
55 | ]
56 | };
57 |
58 | // All tasks except build:before and build:after are run concurrently
59 | grunt.registerTask('build:before:dist', [
60 | 'clean:build',
61 | 'clean:release',
62 | 'lock'
63 | ]);
64 |
65 | grunt.registerTask('build:before:debug', [
66 | 'clean:build',
67 | 'lock'
68 | ]);
69 |
70 | grunt.registerTask('build:templates:dist', filterAvailable([
71 | 'emblem:compile',
72 | 'emberTemplates:dist'
73 | ]));
74 |
75 | grunt.registerTask('build:templates:debug', filterAvailable([
76 | 'emblem:compile',
77 | 'emberTemplates:debug'
78 | ]));
79 |
80 | grunt.registerTask('build:scripts', filterAvailable([
81 | 'coffee',
82 | 'copy:prepare',
83 | 'transpile',
84 | 'jshint',
85 | 'concat_sourcemap'
86 | ]));
87 |
88 | grunt.registerTask('build:styles', filterAvailable([
89 | 'compass:compile',
90 | 'sass:compile',
91 | 'less:compile',
92 | 'stylus:compile',
93 | 'cssmin'
94 | ]));
95 |
96 | grunt.registerTask('build:other', filterAvailable([
97 | 'copy:vendor'
98 | ]));
99 |
100 | grunt.registerTask('build:after:dist', filterAvailable([
101 | 'copy:stage',
102 | 'unlock',
103 | 'dom_munger:distEmber',
104 | 'dom_munger:distHandlebars',
105 | 'useminPrepare',
106 | 'concat',
107 | 'uglify',
108 | 'copy:dist',
109 | 'rev',
110 | 'usemin'
111 | ]));
112 |
113 | grunt.registerTask('build:after:debug', filterAvailable([
114 | 'copy:stage',
115 | 'unlock'
116 | ]));
117 |
118 | grunt.registerTask('build:dist', "Build a minified & production-ready version of your app.", [
119 | 'build:before:dist',
120 | 'concurrent:dist',
121 | 'build:after:dist'
122 | ]);
123 |
124 | grunt.registerTask('build:debug', "Build a development-friendly version of your app.", [
125 | 'build:before:debug',
126 | 'concurrent:debug',
127 | 'build:after:debug'
128 | ]);
129 |
130 | grunt.registerTask('test', "Run your apps's tests once. Uses Google Chrome by default. Logs coverage output to tmp/public/coverage.", [
131 | 'build:debug', 'karma:test' ]);
132 |
133 | grunt.registerTask('test:ci', "Run your app's tests in PhantomJS. For use in continuous integration (i.e. Travis CI).", [
134 | 'build:debug', 'karma:ci' ]);
135 |
136 | grunt.registerTask('test:browsers', "Run your app's tests in multiple browsers (see tasks/options/karma.js for configuration).", [
137 | 'build:debug', 'karma:browsers' ]);
138 |
139 | grunt.registerTask('test:server', "Start a Karma test server. Automatically reruns your tests when files change and logs the results to the terminal.", [
140 | 'build:debug', 'karma:server', 'connect:server', 'watch:test']);
141 |
142 | grunt.registerTask('server', "Run your server in development mode, auto-rebuilding when files change.",
143 | ['build:debug', 'connect:server', 'watch:main']);
144 | grunt.registerTask('server:dist', "Build and preview production (minified) assets.",
145 | ['build:dist', 'connect:dist:keepalive']);
146 |
147 | grunt.initConfig(config);
148 | };
149 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Ember Run Loop, Visualized
2 |
3 | See it live
4 | [here](https://machty.s3.amazonaws.com/ember-run-loop-visual/index.html).
5 |
6 | ## Building
7 |
8 | Clone this repo, `cd` in the new directory, then:
9 |
10 | npm install
11 | bower install
12 |
13 | To run a local server:
14 |
15 | grunt server
16 |
17 | To build for deployment:
18 |
19 | grunt build:dist
20 |
--------------------------------------------------------------------------------
/app/app.js:
--------------------------------------------------------------------------------
1 | import Resolver from 'resolver';
2 | import router from 'appkit/router';
3 |
4 | var App = Ember.Application.create({
5 | modulePrefix: 'appkit', // TODO: loaded via config
6 | Resolver: Resolver,
7 | Router: Ember.Router.extend({
8 | router: router,
9 | location: 'none'
10 | })
11 | });
12 |
13 | export default App;
14 |
--------------------------------------------------------------------------------
/app/components/current-queue-blip.js:
--------------------------------------------------------------------------------
1 | var QUEUE_WIDTH = 160;
2 |
3 | var CurrentQueueBlip = Ember.Component.extend({
4 | attributeBindings: ['style'],
5 |
6 | queueIndex: null,
7 |
8 | style: function() {
9 | var leftOffset = this.get('queueIndex') * QUEUE_WIDTH;
10 | return "left: " + leftOffset + "px";
11 | }.property('queueIndex')
12 | });
13 |
14 | export default CurrentQueueBlip;
15 |
--------------------------------------------------------------------------------
/app/components/run-loop-queue.js:
--------------------------------------------------------------------------------
1 | var RunLoopQueue = Ember.Component.extend({
2 | classNames: ['run-loop-queue'],
3 | foo: "nork"
4 | });
5 |
6 | export default RunLoopQueue;
7 |
--------------------------------------------------------------------------------
/app/controllers/application.js:
--------------------------------------------------------------------------------
1 | var DEFAULT_CODE = [
2 | "function foo() {",
3 | " Ember.run.once(function() { console.log('FOO!'); });",
4 | "}",
5 | "",
6 | "function bar() {",
7 | " Ember.run.schedule('afterRender', function() { console.log('BAR!'); });",
8 | "}",
9 | "",
10 | "Ember.run(function() {",
11 | " Ember.run.schedule('render', function() {",
12 | " Ember.run.scheduleOnce('sync', foo);",
13 | " Ember.run.scheduleOnce('actions', bar);",
14 | " });",
15 | "});"
16 | ].join("\n");
17 |
18 | var ApplicationController = Ember.ObjectController.extend({
19 | code: DEFAULT_CODE,
20 | logs: []
21 | });
22 |
23 | export default ApplicationController;
24 |
--------------------------------------------------------------------------------
/app/models/stubbed_ember.js:
--------------------------------------------------------------------------------
1 | var slice = [].slice;
2 |
3 | var StubbedEmber = Ember.create(Ember);
4 |
5 | var FakeRunLoop = Ember.Object.extend({
6 | currentQueueIndex: 0,
7 | isPlaying: false,
8 | queues: Ember.run.queues.map(function(queueName) {
9 | return { name: queueName, actions: [] };
10 | }),
11 |
12 | hasItems: false,
13 |
14 | schedule: function(queueName, action, once) {
15 | var queue = this.queues.findProperty('name', queueName);
16 |
17 | if (once){
18 | var count = queue.actions.reduce(function(sum, record) {
19 | return (record.fn.name===action.fn.name) ? sum+1 : 0;
20 | }, 0);
21 |
22 | if (count>0) { return; }
23 | }
24 |
25 | queue.actions.pushObject(action);
26 | this.set('hasItems', true);
27 | },
28 |
29 | nextStep: function() {
30 | var currentQueueIndex = this.get('currentQueueIndex'),
31 | queues = this.get('queues'),
32 | index;
33 |
34 | for (index = 0; index < currentQueueIndex; ++index) {
35 | if (queues[index].actions.length) {
36 | // Backtrack.
37 | this.set('currentQueueIndex', index);
38 | return;
39 | }
40 | }
41 |
42 | var queue = queues[index];
43 | if (queue.actions.length === 0) {
44 | currentQueueIndex += 1;
45 | if (currentQueueIndex === queues.length) {
46 | this.set('isPlaying', false);
47 | this.set('currentQueueIndex', 0);
48 | this.set('hasItems', false);
49 | } else {
50 | this.set('currentQueueIndex', currentQueueIndex);
51 | }
52 | return;
53 | }
54 |
55 | var action = queue.actions.shiftObject();
56 | action.fn.apply(action.target, action.args);
57 | },
58 |
59 | play: function() {
60 | if (!this.get('isPlaying')) { return; }
61 |
62 | this.nextStep();
63 |
64 | Ember.run.later(this, 'play', 600);
65 | }.observes('isPlaying')
66 | });
67 |
68 | var runLoop = StubbedEmber.fakeRunLoop = FakeRunLoop.create();
69 |
70 | function normalize() {
71 | var args = slice.call(arguments),
72 | target = args.shift(),
73 | fn;
74 |
75 | if (typeof target === 'function') {
76 | fn = target;
77 | target = null;
78 | } else {
79 | args.shift();
80 | fn = args.shift();
81 | }
82 |
83 | return {
84 | target: target,
85 | fn: fn,
86 | args: args
87 | };
88 | }
89 |
90 | StubbedEmber.run = function(fn) {
91 | fn();
92 | };
93 |
94 | StubbedEmber.run.schedule = function(queueName) {
95 | runLoop.schedule(queueName, normalize.apply(null, slice.call(arguments, 1)), false);
96 | };
97 |
98 | StubbedEmber.run.scheduleOnce = function(queueName, args) {
99 | runLoop.schedule(queueName, normalize.apply(null, slice.call(arguments, 1)), true);
100 | };
101 |
102 | StubbedEmber.run.once = function(queue, args) {
103 | StubbedEmber.run.scheduleOnce.apply(null, ['actions'].concat(slice.call(arguments)));
104 | };
105 |
106 | StubbedEmber.run.next = function() {
107 | alert("run.next hasn't been implemented.");
108 | };
109 |
110 | StubbedEmber.run.later = function() {
111 | alert("run.later hasn't been implemented.");
112 | };
113 |
114 | export default StubbedEmber;
115 |
--------------------------------------------------------------------------------
/app/router.js:
--------------------------------------------------------------------------------
1 | var router = Ember.Router.map(function(){
2 | });
3 |
4 | export default router;
5 |
--------------------------------------------------------------------------------
/app/routes/application.js:
--------------------------------------------------------------------------------
1 | import StubbedEmber from 'appkit/models/stubbed_ember';
2 |
3 | var map = Ember.ArrayPolyfills.map,
4 | runLater = Ember.run.later;
5 |
6 | var ApplicationRoute = Ember.Route.extend({
7 | model: function() {
8 | return StubbedEmber.fakeRunLoop;
9 | },
10 | actions: {
11 | runCode: function() {
12 | var Ember = StubbedEmber,
13 | console = Ember.create(window.console),
14 | controller = this.controller;
15 |
16 | console.log = function() {
17 | map.call(arguments, function(msg) {
18 | window.console.log(msg);
19 | controller.logs.pushObject('' + msg);
20 | });
21 | };
22 |
23 | eval(this.controller.get('code'));
24 | },
25 |
26 | play: function() {
27 | StubbedEmber.fakeRunLoop.set('isPlaying', true);
28 | },
29 |
30 | pause: function() {
31 | StubbedEmber.fakeRunLoop.set('isPlaying', false);
32 | },
33 |
34 | step: function() {
35 | StubbedEmber.fakeRunLoop.nextStep();
36 | }
37 | }
38 | });
39 |
40 | export default ApplicationRoute;
41 |
--------------------------------------------------------------------------------
/app/styles/app.scss:
--------------------------------------------------------------------------------
1 | // CSS reset
2 | html, body, div, span, applet, object, iframe,
3 | h1, h2, h3, h4, h5, h6, p, blockquote, pre,
4 | a, abbr, acronym, address, big, cite, code,
5 | del, dfn, em, img, ins, kbd, q, s, samp,
6 | small, strike, strong, sub, sup, tt, var,
7 | b, u, i, center,
8 | dl, dt, dd, ol, ul, li,
9 | fieldset, form, label, legend,
10 | table, caption, tbody, tfoot, thead, tr, th, td,
11 | article, aside, canvas, details, embed,
12 | figure, figcaption, footer, header, hgroup,
13 | menu, nav, output, ruby, section, summary,
14 | time, mark, audio, video {
15 | margin: 0;
16 | padding: 0;
17 | border: 0;
18 | font-size: 100%;
19 | font: inherit;
20 | vertical-align: baseline;
21 | }
22 | /* HTML5 display-role reset for older browsers */
23 | article, aside, details, figcaption, figure,
24 | footer, header, hgroup, menu, nav, section {
25 | display: block;
26 | }
27 | body {
28 | line-height: 1;
29 | }
30 | ol, ul {
31 | list-style: none;
32 | }
33 | blockquote, q {
34 | quotes: none;
35 | }
36 | blockquote:before, blockquote:after,
37 | q:before, q:after {
38 | content: '';
39 | content: none;
40 | }
41 | table {
42 | border-collapse: collapse;
43 | border-spacing: 0;
44 | }
45 |
46 | // Box-size all the things
47 | *, *:before, *:after {
48 | -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box;
49 | }
50 |
51 | html {
52 | position: relative;
53 | }
54 |
55 | .cf:before,
56 | .cf:after {
57 | content: " "; /* 1 */
58 | display: table; /* 2 */
59 | }
60 |
61 | .cf:after {
62 | clear: both;
63 | }
64 |
65 | /**
66 | * For IE 6/7 only
67 | * Include this rule to trigger hasLayout and contain floats.
68 | */
69 | .cf {
70 | *zoom: 1;
71 | }
72 |
73 | #queues {
74 | margin: 20px 0;
75 | }
76 |
77 | .run-loop-queue {
78 | float: left;
79 | border: 1px solid black;
80 | width: 150px;
81 | margin: 0 5px;
82 | background: white;
83 | z-index: 5;
84 | position: relative;
85 | }
86 |
87 | #current-queue-blip {
88 | background: blue;
89 | border-radius: 4px;
90 | position: absolute;
91 | z-index: 1;
92 | width: 161px;
93 | height: 30px;
94 | left: 0;
95 | -webkit-transition: left 0.4s;
96 | -moz-transition: left 0.4s;
97 | transition: left 0.4s;
98 | margin-top: -6px;
99 |
100 | background: red;
101 | }
102 |
103 | textarea#code {
104 | display: block;
105 | width: 600px;
106 | height: 200px;
107 | }
108 |
109 | .actions {
110 | }
111 |
112 | .action {
113 | margin: 3px;
114 | border: 1px solid blue;
115 | padding: 3px;
116 | font-size: 13px;
117 |
118 | overflow: hidden;
119 | &:hover {
120 | overflow: visible;
121 | pre {
122 | box-shadow: 0 0 3px black;
123 | -webkit-box-shadow: 0 0 3px black;
124 | -moz-box-shadow: 0 0 3px black;
125 | }
126 | }
127 | pre {
128 | background: white;
129 | float: left;
130 | display: block;
131 | position: relative;
132 |
133 | }
134 | }
135 |
136 |
--------------------------------------------------------------------------------
/app/templates/application.hbs:
--------------------------------------------------------------------------------
1 |
2 | Source Code on GitHub
3 |
4 |
5 | {{textarea value=code id='code' disabled=hasItems}}
6 |
7 |
8 |
9 | {{#if hasItems}}
10 | There are deferred actions that need to be flushed.
11 |
12 | {{#if isPlaying}}
13 |
14 | {{else}}
15 |
16 |
17 | {{/if}}
18 | {{/if}}
19 |
20 |
21 | {{#each queues}}
22 | {{run-loop-queue queue=this}}
23 | {{/each}}
24 |
25 | {{#if hasItems}}
26 | {{current-queue-blip queueIndex=currentQueueIndex id="current-queue-blip"}}
27 | {{/if}}
28 |
29 |
30 | Logs:
31 |
32 | {{#each logs}}
33 |
{{this}}
34 | {{/each}}
35 |
36 |
--------------------------------------------------------------------------------
/app/templates/components/current-queue-blip.hbs:
--------------------------------------------------------------------------------
1 | {{! TODO: remove this unnecessary file}}
2 |
--------------------------------------------------------------------------------
/app/templates/components/run-loop-queue.hbs:
--------------------------------------------------------------------------------
1 | {{queue.name}}
2 |
3 |
4 | {{#each queue.actions}}
5 | -
6 |
{{fn}}
7 |
8 | {{/each}}
9 |
10 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ember-app-kit",
3 | "dependencies": {
4 | "ember": "http://builds.emberjs.com/stable/ember.js",
5 | "ember-prod": "http://builds.emberjs.com/stable/ember.prod.js",
6 | "handlebars": "1.0.0",
7 | "jquery": "~1.9.1",
8 | "qunit": "~1.12.0"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration
2 | // Generated on Fri Jul 05 2013 01:57:57 GMT-0400 (EDT)
3 |
4 | module.exports = function(config) {
5 | config.set({
6 |
7 | // base path, that will be used to resolve files and exclude
8 | basePath: 'tmp/public',
9 |
10 | // list of files / patterns to load in the browser
11 | files: [
12 | 'vendor/loader.js',
13 | 'vendor/jquery/jquery.js',
14 | 'vendor/handlebars/handlebars.js',
15 | 'vendor/ember/index.js',
16 | 'assets/templates.js',
17 | 'assets/app.js',
18 | 'tests/test_helper.js',
19 | 'tests/tests.js',
20 | 'tests/test_loader.js'
21 | ],
22 |
23 | frameworks: ['qunit'],
24 |
25 | plugins: [
26 | 'karma-qunit',
27 | 'karma-coverage',
28 | 'karma-phantomjs-launcher',
29 | 'karma-chrome-launcher',
30 | 'karma-firefox-launcher',
31 | //'karma-safari-launcher' // npm install karma-safari-launcher
32 | ],
33 |
34 | preprocessors: {
35 | 'assets/*.js': 'coverage'
36 | },
37 |
38 | // list of files to exclude
39 | exclude: [],
40 |
41 | // test results reporter to use
42 | // possible values: 'dots', 'progress', 'junit'
43 | reporters: 'coverage',
44 |
45 | coverageReporter: {
46 | type : ['text'],
47 | dir : 'coverage/'
48 | },
49 |
50 | // web server port
51 | port: parseInt(process.env.PORT, 10) + 1 || 9876,
52 |
53 | // cli runner port
54 | runnerPort: 9100,
55 |
56 | // enable / disable colors in the output (reporters and logs)
57 | colors: true,
58 |
59 | // level of logging
60 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
61 | logLevel: config.LOG_INFO,
62 |
63 | // enable / disable watching file and executing tests whenever any file changes
64 | autoWatch: false,
65 |
66 | // Start these browsers, currently available:
67 | // - Chrome
68 | // - ChromeCanary
69 | // - Firefox
70 | // - Opera
71 | // - Safari (only Mac)
72 | // - PhantomJS
73 | // - IE (only Windows)
74 | browsers: ['Chrome'],
75 |
76 | // If browser does not capture in given timeout [ms], kill it
77 | captureTimeout: 60000,
78 |
79 | // Continuous Integration mode
80 | // if true, it capture browsers, run tests and exit
81 | singleRun: false
82 | });
83 | };
84 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "app-kit",
3 | "namespace": "appkit",
4 | "version": "0.0.0",
5 | "private": true,
6 | "directories": {
7 | "doc": "doc",
8 | "test": "test"
9 | },
10 | "scripts": {
11 | "start": "grunt server",
12 | "build": "grunt build:debug",
13 | "test": "grunt test:ci"
14 | },
15 | "repository": {
16 | "type": "git",
17 | "url": "git://github.com/stefanpenner/app-kit.git"
18 | },
19 | "author": "",
20 | "license": "BSD",
21 | "devDependencies": {
22 | "grunt-contrib-connect": "~0.3.0",
23 | "grunt": "~0.4.1",
24 | "grunt-contrib-watch": "~0.4.4",
25 | "grunt-contrib-copy": "~0.4.1",
26 | "grunt-es6-module-transpiler": "~0.4.1",
27 | "glob": "~3.2.1",
28 | "load-grunt-tasks": "~0.1.0",
29 | "grunt-contrib-concat": "~0.3.0",
30 | "grunt-contrib-clean": "~0.4.1",
31 | "grunt-contrib-jshint": "~0.6.2",
32 | "grunt-usemin": "~0.1.12",
33 | "grunt-contrib-uglify": "~0.2.2",
34 | "grunt-rev": "~0.1.0",
35 | "grunt-ember-templates": "~>0.4.13",
36 | "karma": "~0.9.4",
37 | "grunt-karma": "~0.5",
38 | "karma-qunit": "~0.0.3",
39 | "karma-coverage": "~0.0.3",
40 | "karma-phantomjs-launcher": "~0.0.2",
41 | "lockfile": "~>0.3.0",
42 | "grunt-concat-sourcemap": "~0.3.0",
43 | "grunt-contrib-cssmin": "~0.6.1",
44 | "grunt-concurrent": "~0.3.1",
45 | "grunt-dom-munger": "~2.0.1",
46 | "grunt-contrib-coffee": "~0.7.0",
47 | "grunt-sass": "~0.6.1",
48 | "connect-livereload": "~0.3.0"
49 | },
50 | "dependencies": {
51 | "loom": "~2.0.0",
52 | "loom-generators-ember": "0.0.1"
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/public/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/machty/ember-run-loop-visual/5114f73d9d0443d60779e2787fb69ad0151250b8/public/assets/.gitkeep
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Ember Interactive Run Loop
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/tasks/helpers.js:
--------------------------------------------------------------------------------
1 | var pathUtils = require('path'),
2 | grunt = require('grunt'),
3 | _ = grunt.util._,
4 | Helpers = {};
5 |
6 | // Grunt configuration. Object is expected to be mutated in Gruntfile.
7 | Helpers.config = {
8 | pkg: grunt.file.readJSON('./package.json'),
9 | env: process.env
10 | };
11 |
12 | // List of package requisits for tasks
13 | var taskRequirements = {
14 | 'coffee': ['grunt-contrib-coffee'],
15 | 'compass': ['grunt-contrib-compass'],
16 | 'sass': ['grunt-sass', 'grunt-contrib-sass'],
17 | 'less': ['grunt-contrib-less'],
18 | 'stylus': ['grunt-contrib-stylus'],
19 | 'emberTemplates': ['grunt-ember-templates'],
20 | 'emblem': ['grunt-emblem']
21 | };
22 |
23 | Helpers.filterAvailableTasks = function(tasks){
24 | tasks = tasks.filter(Helpers.whenTaskIsAvailable);
25 | return _.compact(tasks);
26 | };
27 |
28 | // @returns taskName if given task is available, undefined otherwise
29 | Helpers.whenTaskIsAvailable = function(taskName) {
30 | var baseName, reqs, isAvailable;
31 | // baseName of 'coffee:compile' is 'coffee'
32 | baseName = taskName.split(':')[0];
33 | reqs = taskRequirements[baseName];
34 | isAvailable = Helpers.isPackageAvailable(reqs);
35 | return isAvailable ? taskName : undefined;
36 | };
37 |
38 | Helpers.isPackageAvailable = function(pkgNames) {
39 | if (!pkgNames) return true; // packages are assumed to exist
40 |
41 | if (!_.isArray(pkgNames)) {
42 | pkgNames = [pkgNames];
43 | }
44 | return _.any(pkgNames, function(pkgName){
45 | return !!Helpers.config.pkg.devDependencies[pkgName];
46 | });
47 | };
48 |
49 | Helpers.loadConfig = function(path) {
50 | var glob = require('glob');
51 | var object = {};
52 | var key;
53 |
54 | glob.sync('*', {cwd: path}).forEach(function(option) {
55 | key = option.replace(/\.js$/,'');
56 | object[key] = require("../" + path + option);
57 | });
58 |
59 | return object;
60 | };
61 |
62 | module.exports = Helpers;
63 |
--------------------------------------------------------------------------------
/tasks/locking.js:
--------------------------------------------------------------------------------
1 | var lockFile = require('lockfile');
2 |
3 | module.exports = function(grunt) {
4 | grunt.registerTask('lock', 'Set semaphore for connect server to wait on.', function() {
5 | grunt.file.mkdir('tmp');
6 | lockFile.lockSync('tmp/connect.lock');
7 | });
8 |
9 | grunt.registerTask('unlock', 'Release semaphore that connect server waits on.', function() {
10 | console.log("unlocking");
11 | lockFile.unlockSync('tmp/connect.lock');
12 | });
13 | };
14 |
--------------------------------------------------------------------------------
/tasks/options/clean.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "build": ['tmp'],
3 | "release": ['dist'],
4 | }
5 |
--------------------------------------------------------------------------------
/tasks/options/coffee.js:
--------------------------------------------------------------------------------
1 | // CoffeeScript compilation. This must be enabled by modification
2 | // of Gruntfile.js.
3 | //
4 | // The `bare` option is used since this file will be transpiled
5 | // anyway. In CoffeeScript files, you need to escape out for
6 | // some ES6 features like import and export. For example:
7 | //
8 | // `import User from 'appkit/models/user'`
9 | //
10 | // Post = Em.Object.extend
11 | // init: (userId) ->
12 | // @set 'user', User.findById(userId)
13 | //
14 | // `export default Post`
15 | //
16 |
17 | module.exports = {
18 | "test": {
19 | options: {
20 | bare: true
21 | },
22 | files: [{
23 | expand: true,
24 | cwd: 'tests/',
25 | src: ['**/*.coffee', '!vendor/**/*.coffee'],
26 | dest: 'tmp/javascript/tests',
27 | ext: '.js'
28 | }]
29 | },
30 | "app": {
31 | options: {
32 | bare: true
33 | },
34 | files: [{
35 | expand: true,
36 | cwd: 'app/',
37 | src: '**/*.coffee',
38 | dest: 'tmp/javascript/app',
39 | ext: '.js'
40 | }]
41 | }
42 | };
43 |
--------------------------------------------------------------------------------
/tasks/options/compass.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | options: {
3 | sassDir: "app/styles",
4 | cssDir: "tmp/public/assets",
5 | generatedImagesDir: "tmp/public/assets/images",
6 | imagesDir: "app/assets/images",
7 | javascriptsDir: "app",
8 | fontsDir: "app/styles/fonts",
9 | importPath: ["vendor"],
10 | httpImagesPath: "/images",
11 | httpGeneratedImagesPath: "/images/generated",
12 | httpFontsPath: "/styles/fonts",
13 | relativeAssets: false,
14 | debugInfo: true
15 | },
16 | compile: {}
17 | }
18 |
--------------------------------------------------------------------------------
/tasks/options/concat_sourcemap.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | app: {
3 | src: ['tmp/transpiled/app/**/*.js'],
4 | dest: 'tmp/public/assets/app.js',
5 | options: {
6 | sourcesContent: true
7 | },
8 | },
9 |
10 | test: {
11 | src: 'tmp/transpiled/tests/**/*.js',
12 | dest: 'tmp/public/tests/tests.js',
13 | options: {
14 | sourcesContent: true
15 | }
16 | }
17 | };
18 |
--------------------------------------------------------------------------------
/tasks/options/connect.js:
--------------------------------------------------------------------------------
1 | var lockFile = require('lockfile'),
2 | fs = require('fs'),
3 | url = require('url'),
4 | Helpers = require('../helpers');
5 |
6 | module.exports = {
7 | server: {
8 | options: {
9 | port: process.env.PORT || 8000,
10 | hostname: '0.0.0.0',
11 | base: 'tmp/public',
12 | // Use this option to have the catch-all return a different
13 | // page than index.html on any url not matching an asset.
14 | // wildcard: 'not_index.html'
15 | middleware: middleware
16 | }
17 | },
18 | dist: {
19 | options: {
20 | port: process.env.PORT || 8000,
21 | hostname: '0.0.0.0',
22 | base: 'dist/',
23 | middleware: middleware
24 | }
25 | }
26 | };
27 |
28 | // works with tasks/locking.js
29 | function lock(req, res, next) {
30 | (function retry() {
31 | if (lockFile.checkSync('tmp/connect.lock')) {
32 | setTimeout(retry, 30);
33 | } else {
34 | next();
35 | }
36 | }());
37 | }
38 |
39 | function wildcardResponseIsValid(request) {
40 | var urlSegments = request.url.split('.'),
41 | extension = urlSegments[urlSegments.length-1];
42 | return (
43 | ['GET', 'HEAD'].indexOf(request.method.toUpperCase()) > -1 &&
44 | (urlSegments.length == 1 || extension.indexOf('htm') == 0 || extension.length > 5)
45 | )
46 | }
47 |
48 | function buildWildcardMiddleware(options) {
49 | return function(request, response, next) {
50 | if (!wildcardResponseIsValid(request)) { return next(); }
51 |
52 | var wildcard = (options.wildcard || 'index.html'),
53 | wildcardPath = options.base + "/" + wildcard;
54 |
55 | fs.readFile(wildcardPath, function(err, data){
56 | if (err) { return next('ENOENT' == err.code ? null : err); }
57 |
58 | response.writeHead(200, { 'Content-Type': 'text/html' });
59 | response.end(data);
60 | });
61 | }
62 | }
63 |
64 | function middleware(connect, options) {
65 | return [
66 | lock,
67 | require("connect-livereload")(),
68 | connect['static'](options.base),
69 | connect.directory(options.base),
70 | // Remove this middleware to disable catch-all routing.
71 | buildWildcardMiddleware(options)
72 | ];
73 | }
74 |
--------------------------------------------------------------------------------
/tasks/options/copy.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | // These copy tasks happen before transpile or hinting. They
3 | // prepare the build pipeline by moving JavaScript files to
4 | // tmp/javascript.
5 | //
6 | "prepare": {
7 | files: [{
8 | expand: true,
9 | cwd: 'app/',
10 | src: '**/*.js',
11 | dest: 'tmp/javascript/app'
12 | },
13 | {
14 | expand: true,
15 | cwd: 'tests/',
16 | src: ['**/*.js', '!test_helper.js', '!test_loader.js', '!vendor/**/*.js'],
17 | dest: 'tmp/javascript/tests/'
18 | }]
19 | },
20 | // Stage moves files to their final destinations after the rest
21 | // of the build cycle has run.
22 | //
23 | "stage": {
24 | files: [{
25 | expand: true,
26 | cwd: 'tests/',
27 | src: ['index.html', 'test_helper.js', 'test_loader.js', 'vendor/**/*'],
28 | dest: 'tmp/public/tests/'
29 | },
30 | {
31 | expand: true,
32 | cwd: 'public/',
33 | src: ['**'],
34 | dest: 'tmp/public/'
35 | }]
36 | },
37 | "vendor": {
38 | src: ['vendor/**/*.js', 'vendor/**/*.css'],
39 | dest: 'tmp/public/'
40 | },
41 | "dist": {
42 | files: [{
43 | expand: true,
44 | cwd: 'tmp/public',
45 | src: ['**', '!coverage'],
46 | dest: 'dist/'
47 | }]
48 | },
49 | };
50 |
--------------------------------------------------------------------------------
/tasks/options/cssmin.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | combine: {
3 | files: {
4 | 'tmp/public/assets/app.css': ['app/styles/**/*.css']
5 | }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/tasks/options/dom_munger.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | distEmber: {
3 | options: {
4 | //Point the index.html file at ember-prod js instead of dev version
5 | update: {
6 | selector:'script[src="/vendor/ember/index.js"]',
7 | attribute:'src',
8 | value:'/vendor/ember-prod/index.js'
9 | }
10 | },
11 | src: 'tmp/public/index.html'
12 | },
13 | distHandlebars: {
14 | options: {
15 | update: {
16 | selector:'script[src="/vendor/handlebars/handlebars.js"]',
17 | attribute:'src',
18 | value:'/vendor/handlebars/handlebars.runtime.js'
19 | }
20 | },
21 | src: 'tmp/public/index.html'
22 | }
23 | };
24 |
--------------------------------------------------------------------------------
/tasks/options/emberTemplates.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | options: {
3 | templateBasePath: /app\/templates\//,
4 | templateFileExtensions: /\.(hbs|hjs|handlebars)/
5 | },
6 | debug: {
7 | options: {
8 | precompile: false
9 | },
10 | src: "app/templates/**/*.{hbs,hjs,handlebars}",
11 | dest: "tmp/public/assets/templates.js"
12 | },
13 | dist: {
14 | src: "<%= emberTemplates.debug.src %>",
15 | dest: "<%= emberTemplates.debug.dest %>"
16 | }
17 | };
18 |
--------------------------------------------------------------------------------
/tasks/options/emblem.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | compile: {
3 | files: {
4 | "tmp/public/assets/templates.js": ['app/templates/**/*.{emblem,hbs,hjs,handlebars}']
5 | },
6 | options: {
7 | root: 'app/templates/',
8 | dependencies: {
9 | jquery: 'vendor/jquery/jquery.js',
10 | ember: 'vendor/ember/index.js',
11 | handlebars: 'vendor/handlebars/handlebars.js',
12 | emblem: 'vendor/emblem.js/emblem.js'
13 | }
14 | }
15 | }
16 | };
--------------------------------------------------------------------------------
/tasks/options/jshint.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | all: {
3 | src: [
4 | 'Gruntfile.js',
5 | 'app/**/*.js',
6 | 'tests/**/*.js'
7 | ]
8 | },
9 | options: {
10 | jshintrc: '.jshintrc',
11 | force: true
12 | }
13 | };
14 |
--------------------------------------------------------------------------------
/tasks/options/karma.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | options: {
3 | configFile: 'karma.conf.js',
4 | browsers: ['Chrome'],
5 | reporters: ['coverage', 'dots']
6 | },
7 | ci: {
8 | singleRun: true,
9 | browsers: ['PhantomJS']
10 | },
11 | test: {
12 | singleRun: true
13 | },
14 | server: {
15 | background: true,
16 | coverageReporter: {
17 | type : ['html'],
18 | dir : 'coverage/'
19 | }
20 | },
21 | browsers: {
22 | singleRun: true,
23 | browsers: ['Chrome',
24 | 'ChromeCanary',
25 | 'Firefox',
26 | // 'Safari', // enable plugin in karma.conf.js to use
27 | 'PhantomJS']
28 | }
29 | };
30 |
--------------------------------------------------------------------------------
/tasks/options/less.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | compile: {
3 | files: {
4 | 'tmp/public/assets/app.css': 'app/styles/**/*.less'
5 | }
6 | }
7 | };
8 |
--------------------------------------------------------------------------------
/tasks/options/rev.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | dist: {
3 | files: {
4 | src: [
5 | 'dist/assets/app.min.js',
6 | 'dist/assets/vendor.min.js',
7 | 'dist/assets/app.css',
8 | 'dist/assets/vendor.css'
9 | ]
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/tasks/options/sass.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | compile: {
3 | files: {
4 | 'tmp/public/assets/app.css': 'app/styles/**/*.{scss,sass}'
5 | }
6 | }
7 | };
8 |
--------------------------------------------------------------------------------
/tasks/options/stylus.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | compile: {
3 | files: {
4 | 'tmp/public/assets/app.css': 'app/styles/**/*.styl'
5 | }
6 | }
7 | };
8 |
--------------------------------------------------------------------------------
/tasks/options/transpile.js:
--------------------------------------------------------------------------------
1 | var grunt = require('grunt');
2 |
3 | module.exports = {
4 | "tests": {
5 | type: 'amd',
6 | moduleName: function(path) {
7 | return grunt.config.process('<%= pkg.namespace %>/tests/') + path;
8 | },
9 | files: [{
10 | expand: true,
11 | cwd: 'tmp/javascript/tests/',
12 | src: '**/*.js',
13 | dest: 'tmp/transpiled/tests/'
14 | }]
15 | },
16 | "app": {
17 | type: 'amd',
18 | moduleName: function(path) {
19 | return grunt.config.process('<%= pkg.namespace %>/') + path;
20 | },
21 | files: [{
22 | expand: true,
23 | cwd: 'tmp/javascript/app/',
24 | src: '**/*.js',
25 | dest: 'tmp/transpiled/app/'
26 | }]
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/tasks/options/usemin.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | html: ['dist/index.html'],
3 | };
4 |
--------------------------------------------------------------------------------
/tasks/options/useminPrepare.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | html: 'tmp/public/index.html',
3 | options: {
4 | dest: 'dist/'
5 | }
6 | };
7 |
--------------------------------------------------------------------------------
/tasks/options/watch.js:
--------------------------------------------------------------------------------
1 | var Helpers = require('../helpers');
2 |
3 | module.exports = {
4 | main: {
5 | files: ['app/**/*', 'public/**/*', 'vendor/**/*', 'tests/**/*'],
6 | tasks: ['build:debug']
7 | },
8 | test: {
9 | files: ['app/**/*', 'public/**/*', 'vendor/**/*', 'tests/**/*'],
10 | tasks: ['build:debug', 'karma:server:run']
11 | },
12 | options: {
13 | debounceDelay: 200,
14 | livereload: Helpers.isPackageAvailable("connect-livereload")
15 | }
16 | };
17 |
--------------------------------------------------------------------------------
/tests/acceptance/index_test.js:
--------------------------------------------------------------------------------
1 | import Index from 'appkit/routes/index';
2 | import App from 'appkit/app';
3 |
4 | module("Acceptances - Index", {
5 | setup: function(){
6 | App.reset();
7 | }
8 | });
9 |
10 | test("index renders", function(){
11 | expect(3);
12 |
13 | visit('/').then(function(){
14 | ok(exists("h2:contains('Welcome to Ember.js')"));
15 |
16 | var list = find("ul li");
17 | equal(list.length, 3);
18 | equal(list.text(), "redyellowblue");
19 | });
20 | });
21 |
--------------------------------------------------------------------------------
/tests/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | App Kit
5 |
6 |
7 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/tests/test_helper.js:
--------------------------------------------------------------------------------
1 | document.write('');
2 |
3 | Ember.testing = true;
4 |
5 | var App = requireModule('appkit/app');
6 |
7 | App.rootElement = '#ember-testing';
8 | App.setupForTesting();
9 | App.injectTestHelpers();
10 |
11 | function exists(selector) {
12 | return !!find(selector).length;
13 | }
14 |
15 | function equal(actual, expected, message) {
16 | message = message || QUnit.jsDump.parse(expected) + " expected but was " + QUnit.jsDump.parse(actual);
17 | QUnit.equal.call(this, expected, actual, message);
18 | }
19 |
20 | window.exists = exists;
21 | window.equal = equal;
22 |
23 | Ember.Container.prototype.stub = function(fullName, instance) {
24 | instance.destroy = instance.destroy || function() {};
25 | this.cache.dict[fullName] = instance;
26 | };
27 |
--------------------------------------------------------------------------------
/tests/test_loader.js:
--------------------------------------------------------------------------------
1 | // TODO: load based on params
2 | Ember.keys(define.registry).filter(function(key) {
3 | return (/\_test/).test(key);
4 | }).forEach(requireModule);
--------------------------------------------------------------------------------
/tests/unit/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/machty/ember-run-loop-visual/5114f73d9d0443d60779e2787fb69ad0151250b8/tests/unit/.gitkeep
--------------------------------------------------------------------------------
/tests/unit/routes/index_test.js:
--------------------------------------------------------------------------------
1 | import Index from 'appkit/routes/index';
2 | import App from 'appkit/app';
3 |
4 | var route;
5 |
6 | module("Unit - IndexRoute", {
7 | setup: function(){
8 | route = App.__container__.lookup('route:index');
9 | }
10 | });
11 |
12 | test("it exists", function(){
13 | ok(route);
14 | ok(route instanceof Ember.Route);
15 | });
16 |
17 | test("#model", function(){
18 | deepEqual(route.model(), ['red', 'yellow', 'blue']);
19 | });
20 |
--------------------------------------------------------------------------------
/vendor/loader.js:
--------------------------------------------------------------------------------
1 | var define, requireModule;
2 |
3 | (function() {
4 | var registry = {}, seen = {};
5 |
6 | define = function(name, deps, callback) {
7 | registry[name] = { deps: deps, callback: callback };
8 | };
9 |
10 | requireModule = function(name) {
11 | if (seen[name]) { return seen[name]; }
12 | seen[name] = {};
13 |
14 | var mod = registry[name];
15 |
16 | if (!mod) {
17 | throw new Error("Module: '" + name + "' not found.");
18 | }
19 |
20 | var deps = mod.deps,
21 | callback = mod.callback,
22 | reified = [],
23 | exports;
24 |
25 | for (var i=0, l=deps.length; i