├── .gitignore
├── .jshintrc
├── README.md
├── app
├── app.js
├── catalog
│ ├── catalog-controller.js
│ ├── catalog-repository.js
│ └── catalog.html
├── css
│ ├── bootstrap-theme.min.css
│ └── bootstrap.min.css
├── index.html
├── lib
│ ├── angularjs
│ │ ├── angular-ui-router.min.js
│ │ └── angular.min.js
│ └── underscore-min.js
├── routes.js
├── sorting
│ ├── house-assignment-service.js
│ ├── house-repository.js
│ ├── random-number-service.js
│ ├── sorting-hat-controller.js
│ └── sorting-hat.html
└── wizard
│ ├── img
│ ├── gryffindor.jpg
│ ├── hufflepuff.jpg
│ ├── ravenclaw.jpg
│ ├── slytherin.jpg
│ └── sorting-hat.jpg
│ ├── registration-service.js
│ ├── schedule-controller.js
│ ├── schedule.html
│ └── wizard-repository.js
├── karma.conf.js
├── package.json
├── server.js
├── test
├── HogwartsTests.html
├── catalog
│ └── catalog-controller-specs.js
├── lib
│ ├── angular-mocks.js
│ ├── jasmine-html.js
│ ├── jasmine.css
│ ├── jasmine.js
│ └── sinon.js
├── sorting
│ ├── house-assignment-service-specs.js
│ └── sorting-hat-controller-specs.js
└── wizard
│ ├── registration-service-specs.js
│ └── schedule-controller-specs.js
└── wip
├── Tests
├── HogwartsTests.html
├── catalog
│ └── catalog-controller-specs.js
├── lib
│ ├── angular-mocks.js
│ ├── jasmine-html.js
│ ├── jasmine.css
│ ├── jasmine.js
│ └── sinon.js
└── wizard
│ ├── registration-service-specs.js
│ └── schedule-controller-specs.js
├── app
├── app.js
├── catalog
│ ├── catalog.html
│ └── js
│ │ ├── catalog-controller.js
│ │ ├── catalog-repository.js
│ │ └── catalog.js
├── css
│ ├── bootstrap-theme.min.css
│ └── bootstrap.min.css
├── index.html
├── lib
│ ├── angularjs
│ │ ├── angular-ui-router.min.js
│ │ └── angular.min.js
│ └── underscore-min.js
├── routes.js
└── wizard
│ ├── img
│ ├── gryffindor.jpg
│ ├── hufflepuff.jpg
│ ├── ravenclaw.jpg
│ ├── slytherin.jpg
│ └── sorting-hat.jpg
│ ├── js
│ ├── registration-service.js
│ ├── schedule-controller.js
│ └── wizard-repository.js
│ ├── schedule.html
│ └── sorting-hat.html
└── setup
├── mac-readme.md.txt
├── node
├── node-linux32.tar.gz
├── node-linux64.tar.gz
├── node-mac32.tar.gz
├── node-mac64.tar.gz
├── node-win32.msi
└── node-win64.msi
├── server
├── web-server.js
└── web-server.sh
└── win-readme.md.txt
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | *.DS_Store
3 | node_modules
4 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "undef": true,
3 | "node": true,
4 | "globals": {
5 | "angular": true,
6 | "hogwartsApp": true,
7 |
8 | "describe": true,
9 | "beforeEach": true,
10 | "expect": true,
11 | "it": true,
12 |
13 | "module": true,
14 | "inject": true,
15 | "sinon": true,
16 |
17 | "_": true
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Angular Hogwarts TDD Kata
2 | =========================
3 |
4 | “You are here to learn the subtle science and exact art of code-crafting. As there is little foolish wand-waving here, many of you will hardly believe this is magic." --Professor Snape
5 |
6 | Introduction
7 | ------------
8 |
9 | Hogwarts has embraced Muggle Technology!
10 |
11 | Professor Arthur Weasley has just invented the first magic-powered computer, Hex, and it works at Hogwarts.
12 |
13 | ---
14 |
15 | Young Wizard, you will be creating Hogwart's online student registration. Professor Neville Longbottom will guide you.
16 |
17 | Because you are a highly disciplined Wizard, you will be writing your code test first.
18 |
19 | Setup
20 | -----
21 |
22 | ``git clone https://github.com/jmcooper/angular-hogwarts-tdd-kata.git``
23 |
24 | ``cd angular-hogwarts-tdd-kata``
25 |
26 | You have two ways of running through this kata:
27 | 1. Using Chrome,``Express.js`` webserver, and Karma test runner.
28 | 2. Using plain files inside Firefox.
29 |
30 |
31 | ### Chrome, Express.js and Karma (preferred)
32 |
33 | You have [node](http://nodejs.org/) installed. To install ``express.js`` and ``karma`` from the command line inside ``angular-hogwarts-tdd-kata`` run
34 |
35 | ``npm install``
36 |
37 | To run the server, call
38 |
39 | ``node server.js``
40 |
41 | You will load the app and test page with the following:
42 |
43 | - [http://localhost:4567/app](http://localhost:4567/app)
44 | - [http://localhost:4567/test](http://localhost:4567/test)
45 |
46 |
47 | Finally, if you like your tests automatically running every time you make a change, use the following command:
48 |
49 | ``./node_modules/karma/bin/karma start``
50 |
51 | The results will magically appear in the console.
52 |
53 | ### Working with Plain Files in Firefox (option 2)
54 |
55 | You will have two files loaded into Firefox:
56 |
57 | ``file://.../app/index.html``
58 |
59 | ``file://.../ test/HogwartsTests.hmtl``
60 |
61 | You will not edit either of these files.
62 |
63 | ## 0. Coming up to Speed
64 |
65 | How will you begin, my young wizard friend? **I will explore:**
66 | 1. **I will click the ``app`` and ``test`` pages.**
67 | 2. **I will notice ``karma`` running tests in the ``console``.**
68 | 3. **I will notice the three menu items in the app.**
69 | 4. **I will sort myself into a house by clicking on the sorting hat.**
70 |
71 | Great, what house are you in? **I am in ___________________**
72 |
73 | ## 1. Story: Show Course Catalog
74 |
75 | Acceptance: Students will be able to see a catalog of courses.
76 |
77 | ---
78 |
79 | It is time to start coding. Where will you start? **Making changes to catalog UI inside file ``app/catalog/catalog.html``.**
80 |
81 | I seem to have forgotten how to view the catalog.
82 | **Oh, Professor, you just refresh ``app/index.html`` and click on the Catalog menu.**
83 |
84 |
85 | ### 1.0. UI For Course Catalog
86 |
87 | ``app/catalog/catalog.html``
88 | ```html
89 | ...
90 |
91 |
92 |
93 | {{course.name}}
94 | {{course.startTime | date: 'h:mm a'}}
95 | {{course.professor}}
96 | {{course.credits}}
97 |
98 |
99 | ```
100 |
101 | I see you expect to have a ``catalog`` array on the ``CatalogController`` scope. **Yes.**
102 |
103 | I reloaded ``app/index.html`` and clicked on menu item catalog and I don't see anything. **It is because we haven't hooked it up.**
104 |
105 | How will you hook it up? **By loading the ``scope`` with all the courses when the Controller is initialized.**
106 |
107 | ### 1.1. Make Test Error
108 |
109 | Can you show me what you mean? **Sure. Here is the core of the test.**
110 |
111 | **Lorem Demonstratio Facilius**
112 |
113 | ``test/catalog/catalog-controller-specs.js``
114 | ```js
115 |
116 | describe('CatalogController', function () {
117 |
118 | describe('when the controller first loads', function () {
119 |
120 | it('the course catalog is retrieved', function () {
121 | sinon.assert.calledOnce(mockCatalogRepository.getCatalog);
122 | });
123 |
124 | });
125 |
126 | });
127 | ```
128 |
129 | Very nice, you wrote the description and the expectation first. **Thank you. Keeping the test simple helps my thinking.**
130 |
131 | What happens if you run it? **It will generate errors. You can see them by reloading your tests (``test/HogwartsTests.hmtl`` in browser or looking at your CLI karma results).**
132 |
133 | What is the meaning of: "mockCatalogRepository is not defined"? **It means my mockCatalogRepository is not setup -- I'm referencing it in my test before I even declare it.**
134 |
135 | ### 1.1. Make Test Fail
136 |
137 | What's the first step? **Declare the mockCatalogRepository.**
138 |
139 | Yes, and then? **I'm not sure.**
140 |
141 | Do you remember how to cast the Dependency Injection spell? **I remember now.**
142 |
143 | **Accio Dependentiam Injecious**
144 |
145 | ``test/catalog/catalog-controller-specs.js``
146 | ```js
147 | ...
148 |
149 | describe('CatalogController', function () {
150 |
151 | var mockCatalogRepository,
152 | scope,
153 | catalog = ["catalog"];
154 |
155 | beforeEach(function () {
156 | module("hogwartsApp");
157 |
158 | inject(function ($rootScope, $controller, catalogRepository) {
159 | scope = $rootScope.$new();
160 |
161 | mockCatalogRepository = sinon.stub(catalogRepository);
162 | mockCatalogRepository.getCatalog.returns(catalog);
163 |
164 | $controller("CatalogController", {
165 | $scope: scope,
166 | catalogRepository: mockCatalogRepository
167 | });
168 | });
169 | });
170 |
171 | describe('when the controller first loads', function () {
172 |
173 | ...
174 | ```
175 |
176 | Does it pass now? **No, but it is not erroring. I think we are making progress? We are seeing a failing test (expected getCatalog to be called once but was called 0 times).**
177 |
178 | You are on the path to enlightenment. It is wise to celebrate any failure that doesn't kill you. **Yeah!???**
179 |
180 | What are you doing inside ``beforeEach``? **We are creating a mock repository and a temporary scope. We then inject the mocks into the ``CatalogController``.**
181 |
182 | ### 1.1. Make Test Pass
183 |
184 | How do you make it pass? **The test says the CatalogController needs to call getCatalog on the repository when CatalogController is initialized.**
185 |
186 |
187 | ``app/catalog/catalog-controller.js``
188 | ```js
189 |
190 | 'use strict';
191 |
192 | hogwartsApp
193 | .controller("CatalogController", function ($scope, catalogRepository) {
194 | catalogRepository.getCatalog();
195 | });
196 | ```
197 |
198 | Is it passing? **Yes!**
199 |
200 | ### 1.1. Refactor
201 |
202 | Put this into your Remembrall: Whenever tests are passing, time for refactoring. **I don't see anything that needs refactoring.**
203 |
204 | ### 1.2. Failing
205 |
206 | You have completed your first test. One point for Hufflepuff. Is the story complete? **No, the catalog does not show up on the web page. The ``catalog.html`` UI expects an property called ``catalog`` on the scope. I can do that!**
207 |
208 | Ahem. You can write a test for that? **Oh, yes, that's what I meant.**
209 |
210 | **Etiam Dolor Scopus**
211 |
212 | ``test/catalog/catalog-controller-specs.js``
213 | ```js
214 | ...
215 |
216 | describe('when the controller first loads', function () {
217 |
218 | ...
219 |
220 | it('puts the catalog on the scope', function() {
221 | expect(scope.catalog).toEqual(catalog);
222 | });
223 |
224 | ...
225 | ```
226 |
227 | ### 1.2. Passing
228 |
229 | ``catalog/catalog-controller.js``
230 | ```js
231 | ...
232 |
233 | .controller("CatalogController", function ($scope, catalogRepository) {
234 | $scope.catalog = catalogRepository.getCatalog();
235 | });
236 | ```
237 |
238 | Are we finished with the story? **No, Professor Longbottom. Before calling a story done, it must be tested and deployed.**
239 |
240 | But this is only a Kata, we will start on the real work next week when you have a pair. **Ok, I won't deploy it and I won't write automated acceptance tests. But I must inspect my beautiful work (and make sure it is working).**
241 |
242 | ### 1.9. End to End
243 |
244 | You can see it by loading ``app/index.html`` into your browser and clicking on Catalog (at the top). **I am seeing the page now.**
245 |
246 | Well done, young Wizard. You have finished your story. Another point for Hufflepuff. **Thank you, I like the write the test, see it fail, write code to make it pass, and then refactor rhythm. I also like seeing what the end user sees.**
247 |
248 | ## 2. Story: Register for Courses
249 |
250 | Acceptance: Students register in course catalog then view their courses in schedule.
251 |
252 | ---
253 |
254 | ### 2.0. UI for Registration
255 |
256 | You have shown us how to test getting from a repository and displaying the results. I would like to see some interaction. **Sure, how about a link called register on the catalog page.**
257 |
258 | That works for now. **Here is the updated catalog.html**
259 |
260 | ``app/catalog/catalog.html``
261 | ```html
262 | ...
263 |
264 |
265 |
266 | ...
267 |
268 |
269 |
270 | Register
271 |
272 |
273 |
274 | ```
275 |
276 | We need a place to see the registered courses. **Someone else already put it inside ``wizard/schedule.html``**
277 |
278 | Hmm, I am seeing duplication between the your course catalog and their schedule. **Yes, I will take care of that later with a ``ng-include``.**
279 |
280 | OK. Where do you want to start? **In the course catalog controller of course.**
281 |
282 | ### 2.1. Erroring
283 | Don't you mean the course catalog controller spec. **Yes, Professor; this is a TDD Kata, after all.**
284 |
285 | ``test/catalog/catalog-controller-specs.js``
286 | ```js
287 | ...
288 |
289 | var mockCatalogRepository,
290 |
291 | mockRegistrationService,
292 |
293 | ...
294 |
295 | inject(function ( ... , registrationService) {
296 |
297 | ...
298 |
299 | mockRegistrationService = sinon.stub(registrationService);
300 |
301 | ...
302 |
303 | $controller("CatalogController", {
304 | ... ,
305 | registrationService: mockRegistrationService
306 | });
307 |
308 | ...
309 |
310 | describe('when registering for a course', function() {
311 | var courseId = 'courseId';
312 | var response = {success: true, message: ''};
313 |
314 | it('adds the course to the wizard\'s schedule', function() {
315 | mockRegistrationService.register.returns(response);
316 | scope.register(courseId);
317 | sinon.assert.calledWith(mockRegistrationService.register, courseId);
318 | });
319 |
320 | });
321 |
322 | });
323 | ```
324 |
325 | You have done amazing work. You added a ``mockRegistrationService
326 | `` and stubbed it at the top level. You have mocked it inside a new ``describe`` block and written a test that says we are delegating the add course to the ``registrationService``. **Thank you. But when I run the tests, They are all erroring!**
327 |
328 | ### 2.1. Failing
329 |
330 | Yes, it's a tricky spell, isn't it? **Yes. I think I need to define the ``register`` method on the ``registrationService`` in the ``wizard`` directory so the mocking framework knows how to stub it.**
331 |
332 | **Mocus Definum Servitium**
333 |
334 | ``app/wizard/registration-service.js``
335 | ```js
336 | hogwartsApp
337 | .factory('registrationService', function() {
338 | return {
339 | register: function(courseId) {
340 | }
341 | };
342 | });
343 |
344 | ```
345 |
346 | Very good, you have one test failing, you're almost there. **My error now says "scope.register is not a function". Oh, duh, I need to implement the function register() in the CatalogController.**
347 |
348 | Professional Wizards do not normally say 'Duh.' **Yes, Professor. I mean, No, Professor.**
349 |
350 | ### 2.1. Passing
351 |
352 | In order to do that you will need to? **Um... I need to inject the ``registrationService`` into the the ``CatalogController`` so that I can call it.**
353 |
354 | ``app/catalog/catalog-controller.js``
355 | ```js
356 | ...
357 |
358 | .controller("CatalogController", function ( ... , registrationService) {
359 |
360 | ...
361 |
362 | $scope.register = function(courseId) {
363 | registrationService.register(courseId);
364 | };
365 |
366 | });
367 | ```
368 | Very good, you remembered to run the tests again. **Yes, it worked!**
369 |
370 | ### 2.2. Failing
371 |
372 | Next we need to show the student the result of their registration attempt. **I will put the ``registrationService`` response on the scope so the UI can access it.**
373 |
374 | ``test/catalog/catalog-controller-specs.js``
375 | ```js
376 | ...
377 |
378 | describe('when registering for a course', function() {
379 |
380 | ...
381 |
382 | it('adds the registration response to the scope', function() {
383 | mockRegistrationService.register.returns(response);
384 | scope.register(courseId);
385 | expect(scope.response).toEqual(response);
386 | });
387 |
388 | ...
389 | ```
390 |
391 | ### 2.2. Passing
392 |
393 | And to get it passing... **That is as simple as adding ``$scope.response = ``**
394 |
395 | ``app/catalog/catalog-controller.js``
396 | ```js
397 | ...
398 |
399 | $scope.register = function(courseId) {
400 | $scope.response = registrationService.register(courseId);
401 | ```
402 |
403 | ### 2.2. Refactor
404 | I smell duplication in the test. **Yes and I am willing to remove it, while all my tests are passing. I am adding a ``beforeEach`` right now and removing the duplication.**
405 |
406 | **Facio Abdo Duplicatam**
407 |
408 | ``test/catalog/catalog-controller-specs.js``
409 | ```js
410 | ...
411 |
412 | describe('when registering for a course', function() {
413 |
414 | ...
415 |
416 | beforeEach(function() {
417 | mockRegistrationService.register.returns(response);
418 | scope.register(courseId);
419 | });
420 |
421 | it('adds the course to the wizard\'s schedule', function() {
422 | sinon.assert.calledWith(mockRegistrationService.register, courseId);
423 | });
424 |
425 | it('adds the registration response to the scope', function() {
426 | expect(scope.response).toEqual(response);
427 | });
428 | });
429 | ```
430 |
431 | Are your tests still passing? **Yes.**
432 |
433 | Are you finished with this story? **No. We are delegating to the ``registrationService`` which we haven't written yet! Of course, I will write a test for ``registrationService`` first.**
434 |
435 | ### 2.3. Erroring
436 |
437 | ``test/wizard/registration-service-specs.js``
438 | ```js
439 |
440 | describe('registrationService', function () {
441 |
442 | describe('when registering for a course', function () {
443 | var course = {id: 'Potions'};
444 |
445 | it ('saves the course to the wizardRepository', function() {
446 | service.register(course.id);
447 | sinon.assert.calledWith(
448 | mockWizardRepository.save, {courses: {'Potions' : course}}
449 | );
450 | });
451 |
452 | });
453 |
454 | ...
455 | ```
456 |
457 | You have a test that clearly states your intent: registering leads to a new course in the ``wizardRepository``. **Yes but it won't run until I use the Dependency Injection spell again.**
458 |
459 | **Invertere Injicio Dependeo**
460 |
461 | ### 2.3. Failing
462 |
463 | Looking at your test, you obviously need a ``mockWizardRepository`` that has a ``save`` method. But how are you going to convert ``course.id`` into a ``course``? **I am going to get all the courses from the ``catalogRepository`` and then iterate over them until I find the one I want.**
464 |
465 | That would have the code smell: _Inappropriate intimacy_. Can you think of another way? **Oops, I just missed the method ``getCourse(courseId)`` on the ``catalogRepository``. I will call that one instead.**
466 |
467 | **Notice registration service tests are in the ``wizard`` directory.**
468 |
469 | ``test/wizard/registration-service-specs.js``
470 | ```js
471 |
472 | describe('registrationService', function () {
473 |
474 | var service,
475 | mockCatalogRepository,
476 | mockWizardRepository;
477 |
478 | beforeEach(function () {
479 | module("hogwartsApp");
480 |
481 | inject(function (registrationService, catalogRepository, wizardRepository) {
482 | service = registrationService;
483 | mockCatalogRepository = sinon.stub(catalogRepository);
484 | mockWizardRepository = sinon.stub(wizardRepository);
485 | });
486 | });
487 |
488 | describe('when registering for a course', function () {
489 |
490 | ...
491 |
492 | beforeEach(function() {
493 | mockCatalogRepository.getCourse.returns(course);
494 | mockWizardRepository.get.returns({courses: {}});
495 | });
496 |
497 | ...
498 | ```
499 |
500 | ### 2.3. Passing
501 |
502 | ``app/wizard/registration-service.js``
503 | ```js
504 | hogwartsApp
505 | .factory('registrationService', function(catalogRepository, wizardRepository) {
506 | return {
507 | register: function(courseId) {
508 | var course = catalogRepository.getCourse(courseId),
509 | wizard = wizardRepository.get();
510 |
511 | wizard.courses[course.id] = course;
512 | wizardRepository.save(wizard);
513 | }
514 | };
515 |
516 | });
517 |
518 | ```
519 |
520 | ### 2.3. Refactor
521 |
522 | What does the last two lines do? **It registers the wizard for the course.**
523 |
524 | Can you clarify it in code? **You mean extract the last 2 lines into a method.** Yes.
525 |
526 | **Accio Extractum Modious**
527 |
528 | ``app/wizard/registratioioion-service.js``
529 | ```js
530 | ...
531 |
532 | register: function(courseId) {
533 | var course = catalogRepository.getCourse(courseId),
534 | wizard = wizardRepository.get();
535 |
536 | registerWizardForCourse(wizard, course);
537 | }
538 | };
539 |
540 | function registerWizardForCourse(wizard, course) {
541 | wizard.courses[course.id] = course;
542 | wizardRepository.save(wizard);
543 | }
544 |
545 | ...
546 | ```
547 |
548 | ### 2.4. Failing
549 |
550 | A service should always return a response. **You mean something like this?**
551 |
552 | **Responsum Exspectant**
553 |
554 | ``test/wizard/registration-service-specs.js``
555 | ```js
556 | ...
557 |
558 | describe('when registering for a course', function () {
559 |
560 | ...
561 |
562 | it('returns a success response', function () {
563 | var response = service.register(course.id);
564 | expect(response.success).toBeTruthy();
565 | });
566 |
567 | ...
568 | ```
569 |
570 | Exactly!
571 |
572 | ### 2.4. Passing
573 |
574 | ``app/wizard/registration-service.js``
575 | ```js
576 | ...
577 |
578 | register: function(courseId) {
579 |
580 | ...
581 |
582 | return registerWizardForCourse(wizard, course);
583 |
584 | ...
585 |
586 | function registerWizardForCourse(wizard, course) {
587 |
588 | ...
589 |
590 | return {success: true};
591 | }
592 |
593 | ...
594 | ```
595 |
596 | ### 2.5. Failing
597 |
598 | How will the student know if they are really registered? **They will see their courses on the schedule page.**
599 |
600 | How will they see their courses on the schedule page? **Hmm, let's see. The schedule.html is already written. It looks like it expects a wizard object on the scope. The ``wizard`` has ``courses``.**
601 |
602 | You are indeed a very promising young wizard. **I will write tests for the schedule controller. I'm writing both tests because the code to pass them is one line.**
603 |
604 | ``test/wizard/schedule-controller-specs.js``
605 | ```js
606 | describe('ScheduleController', function () {
607 | var scope, mockWizardRepository;
608 | var wizard = {courses: {'foo': {id: 'foo'}}};
609 |
610 | beforeEach(function () {
611 | module("hogwartsApp");
612 |
613 | inject(function ($rootScope, $controller, wizardRepository) {
614 | scope = $rootScope.$new();
615 |
616 | mockWizardRepository = sinon.stub(wizardRepository);
617 | mockWizardRepository.get.returns(wizard);
618 |
619 | $controller("ScheduleController", {
620 | $scope: scope,
621 | wizardRepository: mockWizardRepository
622 | });
623 | });
624 | });
625 |
626 | describe('when the controller first loads', function () {
627 | it('gets the wizard from the repository', function () {
628 | sinon.assert.calledOnce(mockWizardRepository.get);
629 | });
630 |
631 | it('puts wizard on the scope', function() {
632 | expect(scope.wizard).toEqual(wizard)
633 | });
634 | });
635 | });
636 |
637 | ```
638 |
639 | ### 2.5. Passing
640 |
641 | You can make the tests pass? **Yes, this is less painful than drinking a Polyjuice Potion:**
642 |
643 | ``app/wizard/schedule-controller.js``
644 | ```js
645 | hogwartsApp
646 | .controller("ScheduleController", function ($scope, wizardRepository) {
647 | $scope.wizard = wizardRepository.get();
648 | });
649 | ```
650 |
651 | ### 2.9. End to End
652 |
653 | How are we going to end to end test it? **I will click the register link on the catalog menu and notice a message saying it was successful. Then I'll look at the schedule page and see the course I just registered for.**
654 |
655 | Are we finished with this story? **It depends, do we have a story disallowing scheduling more than one course at the same time (unless they have a Time-Turner)?**
656 |
657 | Yes that is another story. **Then, the software works as expected. The code is clean. Yes, I would say this story is done.**
658 |
659 | Congratulations, two points for Hufflepuff. Now, as soon as I get this Leg-Locker Curse off, we can go to the Quidditch match.
660 |
661 | Story 3: Hat Sorts Randomly
662 | -------------------------
663 |
664 | Acceptance: Clicking multiple times will result in all houses being selected.
665 |
666 | ---
667 |
668 | We have a disaster, a crisis of epic proportion! Sorting Hat is celebrating at Hogsmeade with Nymphadora Tonks' ghost and refuses to leave. The replacement, the old straw thing that sorted you, is sorting everything according to this Kata! **I am not sure I see the problem.**
669 |
670 | Everyone is being sorted into _Hufflepuff_! **Oh, no!, I could have been in Gryffindor! What can we do?**
671 |
672 | We must change the Kata immediately to sort randomly. **I am on it.**
673 |
674 | ### 3. Debugging
675 |
676 | How will you find the bug? **I could open the debugger on ``index.html#/sorting``, set a break point inside ``sorting-hat-controller.js`` on ``$scope.sort``, click the sorting hat and then follow the code in the debugger down until I find the bug.**
677 |
678 | You have tests, why not use them to help locate the bug? **I am not sure how.**
679 |
680 | Take a look in the directories, ``app/sorting/`` and ``test/sorting/``. **Oh, I see we have no corresponding test file for ``random-number-service.js`` that is probably the bug location.**
681 |
682 | Sometime, you might have a test file but the test is missing. Code coverage can also help you find missing tests. **Good to know. Something is fishy with ``Math.floor(Math.random() * (max - max)) + max;``**
683 |
684 | You now have a choice, _write a test_ or open the _debugger_. **I choose test (this is a TDD Kata after all).**
685 |
686 | ### 3.1. Failing
687 |
688 | **I will create the file ``test/sorting/random-number-service-specs.js`` with the following tests in it.**
689 |
690 | ``test/sorting/random-number-service-specs.js``
691 | ```js
692 | describe('randomNumberService', function () {
693 | var service;
694 | var stubMath;
695 |
696 | beforeEach(function () {
697 | module("hogwartsApp");
698 | stubMath = sinon.stub(Math, 'random');
699 | inject(function (randomNumberService) {
700 | service = randomNumberService;
701 | });
702 | });
703 |
704 | afterEach(function () {
705 | stubMath.restore();
706 | });
707 |
708 | describe('when generating a random number in range 0 - 3', function () {
709 |
710 | it ('returns 0 for random range 0.0 - 0.249', function() {
711 | stubMath.returns(0.249);
712 | expect(service.getInRange(0, 3)).toEqual(0);
713 | });
714 |
715 | it ('returns 1 for random range 0.25 - 0.49', function() {
716 | stubMath.returns(0.49);
717 | expect(service.getInRange(0, 3)).toEqual(1);
718 | });
719 |
720 | it ('returns 2 for random range 0.5 - 0.749', function() {
721 | stubMath.returns(0.749);
722 | expect(service.getInRange(0, 3)).toEqual(2);
723 | });
724 |
725 | it ('returns 3 for random range 0.75 - 1', function() {
726 | stubMath.returns(0.99);
727 | expect(service.getInRange(0, 3)).toEqual(3);
728 | });
729 |
730 | });
731 |
732 | });
733 | ```
734 |
735 | Nice work with the test coverage. **Thank you, Professor.**
736 |
737 | ### 3.1. Passing
738 |
739 | **To get it to pass, I replace the ``return`` section with the correct algorithm (straight from Arithmancy class).**
740 |
741 | ``app/sorting/random-number-service.js``
742 | ```js
743 | ...
744 |
745 | return Math.floor(Math.random() * (max - min + 1)) + min;
746 |
747 | ...
748 | ```
749 |
750 | ### 3.9. End to End
751 |
752 | Have you looked at the website? **Yes students are now being sorted into different houses.**
753 |
754 | Excellent! Three points for Hufflepuff.
755 |
756 | O.W.L.s and N.E.W.T.s
757 | =====================
758 |
759 | The Kata is officially over and Stinksap's not poisonous. If you are here with working code, you are awarded an _Acceptable_ OWL. If you want a NEWT or a higher grade, complete some or all of the following stories/tasks.
760 |
761 | ### 4. Disallow Registering for Multiple Simultaneous Classes
762 |
763 | Acceptance: Students attempting to register for multiple classes at the same time will be shown a message saying this is not allowed and the second class will not be added to their schedule.
764 |
765 | ### 5. Allow Multiple Simultaneous Classes with a Time-Turner
766 |
767 | Acceptance: Students with a time-turner are be allowed to register for multiple classes at the same time.
768 |
769 | ### 6. Refactor out the duplicated UI in Schedule and Catalog
770 |
771 | Using ``ng-include`` remove duplication between
772 |
773 | ``wizard/schedule.html`` and ``catalog/catalog.html``
774 |
775 | ### 7. Modify this Kata to Use a Todo List
776 |
777 | TDD gives you
778 | - a known starting point (what is the test for that?)
779 | - the ability to focus on a small piece of a bigger problem
780 | - feedback that your changes haven't broken something
781 |
782 | As you work confidently on you little solution, you need a place to store your alternative solutions, other problems and things you are going to do later to eliminate being distracted by them.
783 |
784 | This is often in your journal in a Todo list.
785 |
786 | Change this ``README`` to use a Todo list.
787 |
788 | ### 8. Add Automated Acceptance Tests
789 |
790 | When you favor mockist style TDD, you need automated Acceptance Tests.
791 |
792 | Write at least one end to end [Protractor](https://github.com/angular/protractor) test for each story you implemented.
793 |
794 | ---
795 |
796 | Thank you!
797 |
798 | "Happiness can be found even in the darkest of times, when one only remembers to turn on the light" --Albus Dumbledore
799 |
800 |
801 |
802 |
--------------------------------------------------------------------------------
/app/app.js:
--------------------------------------------------------------------------------
1 | var hogwartsApp = angular.module('hogwartsApp', ['ui.router']);
2 |
--------------------------------------------------------------------------------
/app/catalog/catalog-controller.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | hogwartsApp
4 | .controller("CatalogController", function ($scope) {
5 |
6 | });
7 |
--------------------------------------------------------------------------------
/app/catalog/catalog-repository.js:
--------------------------------------------------------------------------------
1 | hogwartsApp.factory('catalogRepository', function() {
2 |
3 | var catalogDataDetails =
4 | [
5 | {
6 | name: "Astronomy",
7 | professor: "Aurora Sinistra",
8 | description: "During the first year of studies at Hogwarts, students must study the night skies through their telescopes every Wednesday at midnight and learn the different names of the stars and the movements of the planets.",
9 | equipment: ["telescopes", "hourglass"],
10 | },
11 |
12 | {
13 | name: "Charms",
14 | professor: "Filius Flitwick",
15 | description: "Spells you will learn: Levitation Charm, Wand-Lighting Charm, Lumos Solem, Fire-Making Spell, Softening Charm, Severing Charm, Unlocking Charm, Locking Spell, Mending Charm",
16 | books: ["The Standard Book of Spells", "Quintessence: A Quest"],
17 | },
18 |
19 | {
20 | name: "Defence Against the Dark Arts",
21 | professor: "",
22 | description: "Students learn how to magically defend themselves against Dark Creatures, Dark Arts and against the other Charms.",
23 | books: [
24 | "The Dark Forces: A Guide to Self-Protection", "Break with a Banshee", "Gadding with Ghouls", "Holidays with Hags", "Travels with Trolls", "Voyages with Vampires", "Wanderings with Werewolves", "Year with a Yeti", "Defensive Magical Theory", "Dark Arts Defence: Basics for Beginners", "Confronting the Faceless", "The Standard Book of Spells" ],
25 | equipment: [ "Wand", "Parchment", "Quill" ]
26 | },
27 |
28 | {
29 | name: "Broom Flight",
30 | professor: "Madam Hooch",
31 | description: "students learn how to fly broomsticks",
32 | books: ["Quidditch Through the Ages"],
33 | equipment: [ "Broomstick", "Wand" ],
34 | },
35 |
36 | {
37 | name: "Herbology",
38 | professor: "Pomona Sprout",
39 | description: "Students learn to care for and utilize plants, and learn about their magical properties, and what they are used for. Many plants provide ingredients for potions and medicine, while others have magical effects of their own right.",
40 | books: [ "One Thousand Magical Herbs and Fungi", "Flesh-Eating Trees of the World" ],
41 | equipment: [ "Dragon-hide gloves", "Earmuffs", "Dragon dung compost", "Mooncalf dung", "Wand" ],
42 |
43 | },
44 |
45 | {
46 | name: "History of Magic",
47 | professor: "Cuthbert Binns",
48 | description: "The course is a study of magical history.",
49 | books: [ "A History of Magic" ],
50 | equipment: [],
51 | },
52 |
53 | {
54 | name: "Potions",
55 | professor: "Severus Snape ",
56 | description: "Students learn the correct way to brew potions, following specific recipes and using various magical ingredients to create the potions, starting with simple ones first and moving to more advanced ones as they progress in knowledge.",
57 | books: ["Magical Drafts and Potions", "Advanced Potion Making"],
58 | equipment: ["Cauldron", "Brass scales", "Phials", "Various ingredients"],
59 | },
60 |
61 | {
62 | name: "Transfiguration",
63 | professor: "Minerva McGonagall",
64 | description: "Students are taught the art of changing of the form and appearance of an object. There are limits to Transfiguration, which are governed by Gamp's Law of Elemental Transfiguration.",
65 | books: [ "A Beginner's Guide to Transfiguration by Emeric Switch", "Intermediate Transfiguration", "A Guide to Advanced Transfiguration" ],
66 | equipment: ["Wand"],
67 | },
68 |
69 |
70 | // electives after 3rd year
71 | { name: "Study of Ancient Runes"},
72 | { name: "Arithmancy"},
73 | { name: "Muggle Studies"},
74 | { name: "Care of Magical Creatures"},
75 | { name: "Divination"},
76 | // 6th year
77 | { name: "Apparition"},
78 | { name: "Alchemy"},
79 | // Extra-curricular subjects
80 | { name: "Ancient Studies"},
81 | { name: "Art"},
82 | { name: "Earth Magic"},
83 | { name: "Muggle Art"},
84 | { name: "Music"},
85 | { name: "Muggle Music"},
86 | { name: "Ghoul Studies"},
87 | { name: "Magical Theory"},
88 | { name: "Xylomancy"},
89 | { name: "Frog Choir"},
90 | { name: "Hogwarts orchestra"},
91 | ];
92 |
93 | var catalogData = [
94 | {id: "RUN105", name: "Ancient Runes", startTime: new Date(0,0,0,13), professor: "Bathsheba Babbling", credits: 3 },
95 | {id: "AR205", name: "Arithmancy", startTime: new Date(0,0,0,9), professor: "Septima Vector", credits: 3 },
96 | {id: "AST101", name: "Astronomy", startTime: new Date(0,0,0, 11), professor: "Aurora Sinistra", credits: 3 },
97 | {id: "MC101", name: "Care of Magical Creatures", startTime: new Date(0,0,0,14), professor: "Rubeus Hagrid", credits: 2 },
98 | {id: "CH105", name: "Charms", startTime: new Date(0,0,0,11), professor: "Filius Flitwick", credits: 3 },
99 | {id: "DDA302-10", name: "Defence Against the Dark Arts", startTime: new Date(0,0,0,10), professor: "Severus Snape", credits: 4 },
100 | {id: "DDA302-13", name: "Defence Against the Dark Arts", startTime: new Date(0,0,0,13), professor: " Quirinus Quirrell", credits: 4 },
101 | {id: "DIV201-13", name: "Divination", startTime: new Date(0,0,0,13), professor: " Sibyll Trelawney", credits: 3 },
102 | {id: "DIV201-10", name: "Divination", startTime: new Date(0,0,0,10), professor: "Firenze (Centaur)", credits: 3 },
103 | {id: "FLY101", name: "Flying", startTime: new Date(0,0,0,9), professor: "Madam Hooch", credits: 2 },
104 | {id: "HERB201", name: "Herbology", startTime: new Date(0,0,0,14), professor: "Pomona Sprout", credits: 4 },
105 | {id: "HM101", name: "History of Magic", startTime: new Date(0,0,0,11), professor: "Cuthbert Binns", credits: 3 },
106 | {id: "MUG101", name: "Muggle Studies", startTime: new Date(0,0,0,9), professor: "Alecto Carrow", credits: 2 },
107 | {id: "POT108", name: "Potions", startTime: new Date(0,0,0,15), professor: "Severus Snape", credits: 4 },
108 | {id: "TRN205", name: "Transfiguration", startTime: new Date(0,0,0,13), professor: "Minerva McGonagall", credits: 4 }
109 | ];
110 |
111 |
112 | return {
113 | getCatalog: function() {
114 | return catalogData;
115 | },
116 |
117 | getCourse: function(courseId) {
118 | return _.findWhere(catalogData, {id: courseId});
119 | }
120 | };
121 | });
122 |
123 |
--------------------------------------------------------------------------------
/app/catalog/catalog.html:
--------------------------------------------------------------------------------
1 |
2 |
Hogwarts course catalog!
3 |
Select your wizarding classes!
4 |
5 |
6 |
7 |
Successfully registered
8 |
{{response.message}}
9 |
10 |
11 |
12 |
13 | Class
14 | Time
15 | Professor
16 | Credits
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/app/css/bootstrap-theme.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap v3.1.1 (http://getbootstrap.com)
3 | * Copyright 2011-2014 Twitter, Inc.
4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
5 | */
6 |
7 | .btn-default,.btn-primary,.btn-success,.btn-info,.btn-warning,.btn-danger{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-default:active,.btn-primary:active,.btn-success:active,.btn-info:active,.btn-warning:active,.btn-danger:active,.btn-default.active,.btn-primary.active,.btn-success.active,.btn-info.active,.btn-warning.active,.btn-danger.active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn:active,.btn.active{background-image:none}.btn-default{background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#dbdbdb;text-shadow:0 1px 0 #fff;border-color:#ccc}.btn-default:hover,.btn-default:focus{background-color:#e0e0e0;background-position:0 -15px}.btn-default:active,.btn-default.active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-primary{background-image:-webkit-linear-gradient(top,#428bca 0,#2d6ca2 100%);background-image:linear-gradient(to bottom,#428bca 0,#2d6ca2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff2d6ca2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#2b669a}.btn-primary:hover,.btn-primary:focus{background-color:#2d6ca2;background-position:0 -15px}.btn-primary:active,.btn-primary.active{background-color:#2d6ca2;border-color:#2b669a}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:hover,.btn-success:focus{background-color:#419641;background-position:0 -15px}.btn-success:active,.btn-success.active{background-color:#419641;border-color:#3e8f3e}.btn-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:hover,.btn-info:focus{background-color:#2aabd2;background-position:0 -15px}.btn-info:active,.btn-info.active{background-color:#2aabd2;border-color:#28a4c9}.btn-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:hover,.btn-warning:focus{background-color:#eb9316;background-position:0 -15px}.btn-warning:active,.btn-warning.active{background-color:#eb9316;border-color:#e38d13}.btn-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:hover,.btn-danger:focus{background-color:#c12e2a;background-position:0 -15px}.btn-danger:active,.btn-danger.active{background-color:#c12e2a;border-color:#b92c28}.thumbnail,.img-thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-color:#e8e8e8}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{background-image:-webkit-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:linear-gradient(to bottom,#428bca 0,#357ebd 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);background-color:#357ebd}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075)}.navbar-default .navbar-nav>.active>a{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f3f3f3 100%);background-image:linear-gradient(to bottom,#ebebeb 0,#f3f3f3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff3f3f3', GradientType=0);-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.075);box-shadow:inset 0 3px 9px rgba(0,0,0,.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.navbar-inverse .navbar-nav>.active>a{background-image:-webkit-linear-gradient(top,#222 0,#282828 100%);background-image:linear-gradient(to bottom,#222 0,#282828 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff282828', GradientType=0);-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.25);box-shadow:inset 0 3px 9px rgba(0,0,0,.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-static-top,.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}.alert{text-shadow:0 1px 0 rgba(255,255,255,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0)}.progress-bar{background-image:-webkit-linear-gradient(top,#428bca 0,#3071a9 100%);background-image:linear-gradient(to bottom,#428bca 0,#3071a9 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0)}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0)}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0)}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0)}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{text-shadow:0 -1px 0 #3071a9;background-image:-webkit-linear-gradient(top,#428bca 0,#3278b3 100%);background-image:linear-gradient(to bottom,#428bca 0,#3278b3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0);border-color:#3278b3}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.05);box-shadow:0 1px 2px rgba(0,0,0,.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0)}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:linear-gradient(to bottom,#428bca 0,#357ebd 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0)}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0)}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0)}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0)}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0)}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)}
--------------------------------------------------------------------------------
/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Angular Hogwarts TDD Kata
6 |
7 |
8 |
9 |
10 |
24 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/app/lib/angularjs/angular-ui-router.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * State-based routing for AngularJS
3 | * @version v0.2.10
4 | * @link http://angular-ui.github.com/
5 | * @license MIT License, http://www.opensource.org/licenses/MIT
6 | */
7 | "undefined"!=typeof module&&"undefined"!=typeof exports&&module.exports===exports&&(module.exports="ui.router"),function(a,b,c){"use strict";function d(a,b){return I(new(I(function(){},{prototype:a})),b)}function e(a){return H(arguments,function(b){b!==a&&H(b,function(b,c){a.hasOwnProperty(c)||(a[c]=b)})}),a}function f(a,b){var c=[];for(var d in a.path){if(a.path[d]!==b.path[d])break;c.push(a.path[d])}return c}function g(a,b){if(Array.prototype.indexOf)return a.indexOf(b,Number(arguments[2])||0);var c=a.length>>>0,d=Number(arguments[2])||0;for(d=0>d?Math.ceil(d):Math.floor(d),0>d&&(d+=c);c>d;d++)if(d in a&&a[d]===b)return d;return-1}function h(a,b,c,d){var e,h=f(c,d),i={},j=[];for(var k in h)if(h[k].params&&h[k].params.length){e=h[k].params;for(var l in e)g(j,e[l])>=0||(j.push(e[l]),i[e[l]]=a[e[l]])}return I({},i,b)}function i(a,b){var c={};return H(a,function(a){var d=b[a];c[a]=null!=d?String(d):null}),c}function j(a,b,c){if(!c){c=[];for(var d in a)c.push(d)}for(var e=0;e "));if(o[c]=d,E(a))m.push(c,[function(){return b.get(a)}],h);else{var e=b.annotate(a);H(e,function(a){a!==c&&g.hasOwnProperty(a)&&k(g[a],a)}),m.push(c,a,e)}n.pop(),o[c]=f}}function l(a){return F(a)&&a.then&&a.$$promises}if(!F(g))throw new Error("'invocables' must be an object");var m=[],n=[],o={};return H(g,k),g=n=o=null,function(d,f,g){function h(){--s||(t||e(r,f.$$values),p.$$values=r,p.$$promises=!0,o.resolve(r))}function k(a){p.$$failure=a,o.reject(a)}function n(c,e,f){function i(a){l.reject(a),k(a)}function j(){if(!C(p.$$failure))try{l.resolve(b.invoke(e,g,r)),l.promise.then(function(a){r[c]=a,h()},i)}catch(a){i(a)}}var l=a.defer(),m=0;H(f,function(a){q.hasOwnProperty(a)&&!d.hasOwnProperty(a)&&(m++,q[a].then(function(b){r[a]=b,--m||j()},i))}),m||j(),q[c]=l.promise}if(l(d)&&g===c&&(g=f,f=d,d=null),d){if(!F(d))throw new Error("'locals' must be an object")}else d=i;if(f){if(!l(f))throw new Error("'parent' must be a promise returned by $resolve.resolve()")}else f=j;var o=a.defer(),p=o.promise,q=p.$$promises={},r=I({},d),s=1+m.length/3,t=!1;if(C(f.$$failure))return k(f.$$failure),p;f.$$values?(t=e(r,f.$$values),h()):(I(q,f.$$promises),f.then(h,k));for(var u=0,v=m.length;v>u;u+=3)d.hasOwnProperty(m[u])?h():n(m[u],m[u+1],m[u+2]);return p}},this.resolve=function(a,b,c,d){return this.study(a)(b,c,d)}}function m(a,b,c){this.fromConfig=function(a,b,c){return C(a.template)?this.fromString(a.template,b):C(a.templateUrl)?this.fromUrl(a.templateUrl,b):C(a.templateProvider)?this.fromProvider(a.templateProvider,b,c):null},this.fromString=function(a,b){return D(a)?a(b):a},this.fromUrl=function(c,d){return D(c)&&(c=c(d)),null==c?null:a.get(c,{cache:b}).then(function(a){return a.data})},this.fromProvider=function(a,b,d){return c.invoke(a,null,d||{params:b})}}function n(a){function b(b){if(!/^\w+(-+\w+)*$/.test(b))throw new Error("Invalid parameter name '"+b+"' in pattern '"+a+"'");if(f[b])throw new Error("Duplicate parameter name '"+b+"' in pattern '"+a+"'");f[b]=!0,j.push(b)}function c(a){return a.replace(/[\\\[\]\^$*+?.()|{}]/g,"\\$&")}var d,e=/([:*])(\w+)|\{(\w+)(?:\:((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g,f={},g="^",h=0,i=this.segments=[],j=this.params=[];this.source=a;for(var k,l,m;(d=e.exec(a))&&(k=d[2]||d[3],l=d[4]||("*"==d[1]?".*":"[^/]*"),m=a.substring(h,d.index),!(m.indexOf("?")>=0));)g+=c(m)+"("+l+")",b(k),i.push(m),h=e.lastIndex;m=a.substring(h);var n=m.indexOf("?");if(n>=0){var o=this.sourceSearch=m.substring(n);m=m.substring(0,n),this.sourcePath=a.substring(0,h+n),H(o.substring(1).split(/[&?]/),b)}else this.sourcePath=a,this.sourceSearch="";g+=c(m)+"$",i.push(m),this.regexp=new RegExp(g),this.prefix=i[0]}function o(){this.compile=function(a){return new n(a)},this.isMatcher=function(a){return F(a)&&D(a.exec)&&D(a.format)&&D(a.concat)},this.$get=function(){return this}}function p(a){function b(a){var b=/^\^((?:\\[^a-zA-Z0-9]|[^\\\[\]\^$*+?.()|{}]+)*)/.exec(a.source);return null!=b?b[1].replace(/\\(.)/g,"$1"):""}function c(a,b){return a.replace(/\$(\$|\d{1,2})/,function(a,c){return b["$"===c?0:Number(c)]})}function d(a,b,c){if(!c)return!1;var d=a.invoke(b,b,{$match:c});return C(d)?d:!0}var e=[],f=null;this.rule=function(a){if(!D(a))throw new Error("'rule' must be a function");return e.push(a),this},this.otherwise=function(a){if(E(a)){var b=a;a=function(){return b}}else if(!D(a))throw new Error("'rule' must be a function");return f=a,this},this.when=function(e,f){var g,h=E(f);if(E(e)&&(e=a.compile(e)),!h&&!D(f)&&!G(f))throw new Error("invalid 'handler' in when()");var i={matcher:function(b,c){return h&&(g=a.compile(c),c=["$match",function(a){return g.format(a)}]),I(function(a,e){return d(a,c,b.exec(e.path(),e.search()))},{prefix:E(b.prefix)?b.prefix:""})},regex:function(a,e){if(a.global||a.sticky)throw new Error("when() RegExp must not be global or sticky");return h&&(g=e,e=["$match",function(a){return c(g,a)}]),I(function(b,c){return d(b,e,a.exec(c.path()))},{prefix:b(a)})}},j={matcher:a.isMatcher(e),regex:e instanceof RegExp};for(var k in j)if(j[k])return this.rule(i[k](e,f));throw new Error("invalid 'what' in when()")},this.$get=["$location","$rootScope","$injector",function(a,b,c){function d(b){function d(b){var d=b(c,a);return d?(E(d)&&a.replace().url(d),!0):!1}if(!b||!b.defaultPrevented){var g,h=e.length;for(g=0;h>g;g++)if(d(e[g]))return;f&&d(f)}}return b.$on("$locationChangeSuccess",d),{sync:function(){d()}}}]}function q(a,e,f){function g(a){return 0===a.indexOf(".")||0===a.indexOf("^")}function l(a,b){var d=E(a),e=d?a:a.name,f=g(e);if(f){if(!b)throw new Error("No reference point given for path '"+e+"'");for(var h=e.split("."),i=0,j=h.length,k=b;j>i;i++)if(""!==h[i]||0!==i){if("^"!==h[i])break;if(!k.parent)throw new Error("Path '"+e+"' not valid for state '"+b.name+"'");k=k.parent}else k=b;h=h.slice(i).join("."),e=k.name+(k.name&&h?".":"")+h}var l=w[e];return!l||!d&&(d||l!==a&&l.self!==a)?c:l}function m(a,b){x[a]||(x[a]=[]),x[a].push(b)}function n(b){b=d(b,{self:b,resolve:b.resolve||{},toString:function(){return this.name}});var c=b.name;if(!E(c)||c.indexOf("@")>=0)throw new Error("State must have a valid name");if(w.hasOwnProperty(c))throw new Error("State '"+c+"'' is already defined");var e=-1!==c.indexOf(".")?c.substring(0,c.lastIndexOf(".")):E(b.parent)?b.parent:"";if(e&&!w[e])return m(e,b.self);for(var f in z)D(z[f])&&(b[f]=z[f](b,z.$delegates[f]));if(w[c]=b,!b[y]&&b.url&&a.when(b.url,["$match","$stateParams",function(a,c){v.$current.navigable==b&&j(a,c)||v.transitionTo(b,a,{location:!1})}]),x[c])for(var g=0;g-1}function p(a){var b=a.split("."),c=v.$current.name.split(".");if("**"===b[0]&&(c=c.slice(c.indexOf(b[1])),c.unshift("**")),"**"===b[b.length-1]&&(c.splice(c.indexOf(b[b.length-2])+1,Number.MAX_VALUE),c.push("**")),b.length!=c.length)return!1;for(var d=0,e=b.length;e>d;d++)"*"===b[d]&&(c[d]="*");return c.join("")===b.join("")}function q(a,b){return E(a)&&!C(b)?z[a]:D(b)&&E(a)?(z[a]&&!z.$delegates[a]&&(z.$delegates[a]=z[a]),z[a]=b,this):this}function r(a,b){return F(a)?b=a:b.name=a,n(b),this}function s(a,e,g,m,n,q,r,s,x){function z(){r.url()!==M&&(r.url(M),r.replace())}function A(a,c,d,f,h){var i=d?c:k(a.params,c),j={$stateParams:i};h.resolve=n.resolve(a.resolve,j,h.resolve,a);var l=[h.resolve.then(function(a){h.globals=a})];return f&&l.push(f),H(a.views,function(c,d){var e=c.resolve&&c.resolve!==a.resolve?c.resolve:{};e.$template=[function(){return g.load(d,{view:c,locals:j,params:i,notify:!1})||""}],l.push(n.resolve(e,j,h.resolve,a).then(function(f){if(D(c.controllerProvider)||G(c.controllerProvider)){var g=b.extend({},e,j);f.$$controller=m.invoke(c.controllerProvider,null,g)}else f.$$controller=c.controller;f.$$state=a,f.$$controllerAs=c.controllerAs,h[d]=f}))}),e.all(l).then(function(){return h})}var B=e.reject(new Error("transition superseded")),F=e.reject(new Error("transition prevented")),K=e.reject(new Error("transition aborted")),L=e.reject(new Error("transition failed")),M=r.url(),N=x.baseHref();return u.locals={resolve:null,globals:{$stateParams:{}}},v={params:{},current:u.self,$current:u,transition:null},v.reload=function(){v.transitionTo(v.current,q,{reload:!0,inherit:!1,notify:!1})},v.go=function(a,b,c){return this.transitionTo(a,b,I({inherit:!0,relative:v.$current},c))},v.transitionTo=function(b,c,f){c=c||{},f=I({location:!0,inherit:!1,relative:null,notify:!0,reload:!1,$retry:!1},f||{});var g,k=v.$current,n=v.params,o=k.path,p=l(b,f.relative);if(!C(p)){var s={to:b,toParams:c,options:f};if(g=a.$broadcast("$stateNotFound",s,k.self,n),g.defaultPrevented)return z(),K;if(g.retry){if(f.$retry)return z(),L;var w=v.transition=e.when(g.retry);return w.then(function(){return w!==v.transition?B:(s.options.$retry=!0,v.transitionTo(s.to,s.toParams,s.options))},function(){return K}),z(),w}if(b=s.to,c=s.toParams,f=s.options,p=l(b,f.relative),!C(p)){if(f.relative)throw new Error("Could not resolve '"+b+"' from state '"+f.relative+"'");throw new Error("No such state '"+b+"'")}}if(p[y])throw new Error("Cannot transition to abstract state '"+b+"'");f.inherit&&(c=h(q,c||{},v.$current,p)),b=p;var x,D,E=b.path,G=u.locals,H=[];for(x=0,D=E[x];D&&D===o[x]&&j(c,n,D.ownParams)&&!f.reload;x++,D=E[x])G=H[x]=D.locals;if(t(b,k,G,f))return b.self.reloadOnSearch!==!1&&z(),v.transition=null,e.when(v.current);if(c=i(b.params,c||{}),f.notify&&(g=a.$broadcast("$stateChangeStart",b.self,c,k.self,n),g.defaultPrevented))return z(),F;for(var N=e.when(G),O=x;O=x;d--)g=o[d],g.self.onExit&&m.invoke(g.self.onExit,g.self,g.locals.globals),g.locals=null;for(d=x;d1||b.ctrlKey||b.metaKey||b.shiftKey||f.attr("target")||(c(function(){a.go(i.state,j,o)}),b.preventDefault())})}}}function y(a,b,c){return{restrict:"A",controller:["$scope","$element","$attrs",function(d,e,f){function g(){a.$current.self===i&&h()?e.addClass(l):e.removeClass(l)}function h(){return!k||j(k,b)}var i,k,l;l=c(f.uiSrefActive||"",!1)(d),this.$$setStateInfo=function(b,c){i=a.get(b,w(e)),k=c,g()},d.$on("$stateChangeSuccess",g)}]}}function z(a){return function(b){return a.is(b)}}function A(a){return function(b){return a.includes(b)}}function B(a,b){function e(a){this.locals=a.locals.globals,this.params=this.locals.$stateParams}function f(){this.locals=null,this.params=null}function g(c,g){if(null!=g.redirectTo){var h,j=g.redirectTo;if(E(j))h=j;else{if(!D(j))throw new Error("Invalid 'redirectTo' in when()");h=function(a,b){return j(a,b.path(),b.search())}}b.when(c,h)}else a.state(d(g,{parent:null,name:"route:"+encodeURIComponent(c),url:c,onEnter:e,onExit:f}));return i.push(g),this}function h(a,b,d){function e(a){return""!==a.name?a:c}var f={routes:i,params:d,current:c};return b.$on("$stateChangeStart",function(a,c,d,f){b.$broadcast("$routeChangeStart",e(c),e(f))}),b.$on("$stateChangeSuccess",function(a,c,d,g){f.current=e(c),b.$broadcast("$routeChangeSuccess",e(c),e(g)),J(d,f.params)}),b.$on("$stateChangeError",function(a,c,d,f,g,h){b.$broadcast("$routeChangeError",e(c),e(f),h)}),f}var i=[];e.$inject=["$$state"],this.when=g,this.$get=h,h.$inject=["$state","$rootScope","$routeParams"]}var C=b.isDefined,D=b.isFunction,E=b.isString,F=b.isObject,G=b.isArray,H=b.forEach,I=b.extend,J=b.copy;b.module("ui.router.util",["ng"]),b.module("ui.router.router",["ui.router.util"]),b.module("ui.router.state",["ui.router.router","ui.router.util"]),b.module("ui.router",["ui.router.state"]),b.module("ui.router.compat",["ui.router"]),l.$inject=["$q","$injector"],b.module("ui.router.util").service("$resolve",l),m.$inject=["$http","$templateCache","$injector"],b.module("ui.router.util").service("$templateFactory",m),n.prototype.concat=function(a){return new n(this.sourcePath+a+this.sourceSearch)},n.prototype.toString=function(){return this.source},n.prototype.exec=function(a,b){var c=this.regexp.exec(a);if(!c)return null;var d,e=this.params,f=e.length,g=this.segments.length-1,h={};if(g!==c.length-1)throw new Error("Unbalanced capture group in route '"+this.source+"'");for(d=0;g>d;d++)h[e[d]]=c[d+1];for(;f>d;d++)h[e[d]]=b[e[d]];return h},n.prototype.parameters=function(){return this.params},n.prototype.format=function(a){var b=this.segments,c=this.params;if(!a)return b.join("");var d,e,f,g=b.length-1,h=c.length,i=b[0];for(d=0;g>d;d++)f=a[c[d]],null!=f&&(i+=encodeURIComponent(f)),i+=b[d+1];for(;h>d;d++)f=a[c[d]],null!=f&&(i+=(e?"&":"?")+c[d]+"="+encodeURIComponent(f),e=!0);return i},b.module("ui.router.util").provider("$urlMatcherFactory",o),p.$inject=["$urlMatcherFactoryProvider"],b.module("ui.router.router").provider("$urlRouter",p),q.$inject=["$urlRouterProvider","$urlMatcherFactoryProvider","$locationProvider"],b.module("ui.router.state").value("$stateParams",{}).provider("$state",q),r.$inject=[],b.module("ui.router.state").provider("$view",r),b.module("ui.router.state").provider("$uiViewScroll",s),t.$inject=["$state","$injector","$uiViewScroll"],u.$inject=["$compile","$controller","$state"],b.module("ui.router.state").directive("uiView",t),b.module("ui.router.state").directive("uiView",u),x.$inject=["$state","$timeout"],y.$inject=["$state","$stateParams","$interpolate"],b.module("ui.router.state").directive("uiSref",x).directive("uiSrefActive",y),z.$inject=["$state"],A.$inject=["$state"],b.module("ui.router.state").filter("isState",z).filter("includedByState",A),B.$inject=["$stateProvider","$urlRouterProvider"],b.module("ui.router.compat").provider("$route",B).directive("ngView",t)}(window,window.angular);
--------------------------------------------------------------------------------
/app/lib/underscore-min.js:
--------------------------------------------------------------------------------
1 | // Underscore.js 1.6.0
2 | // http://underscorejs.org
3 | // (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
4 | // Underscore may be freely distributed under the MIT license.
5 | (function(){var n=this,t=n._,r={},e=Array.prototype,u=Object.prototype,i=Function.prototype,a=e.push,o=e.slice,c=e.concat,l=u.toString,f=u.hasOwnProperty,s=e.forEach,p=e.map,h=e.reduce,v=e.reduceRight,g=e.filter,d=e.every,m=e.some,y=e.indexOf,b=e.lastIndexOf,x=Array.isArray,w=Object.keys,_=i.bind,j=function(n){return n instanceof j?n:this instanceof j?void(this._wrapped=n):new j(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=j),exports._=j):n._=j,j.VERSION="1.6.0";var A=j.each=j.forEach=function(n,t,e){if(null==n)return n;if(s&&n.forEach===s)n.forEach(t,e);else if(n.length===+n.length){for(var u=0,i=n.length;i>u;u++)if(t.call(e,n[u],u,n)===r)return}else for(var a=j.keys(n),u=0,i=a.length;i>u;u++)if(t.call(e,n[a[u]],a[u],n)===r)return;return n};j.map=j.collect=function(n,t,r){var e=[];return null==n?e:p&&n.map===p?n.map(t,r):(A(n,function(n,u,i){e.push(t.call(r,n,u,i))}),e)};var O="Reduce of empty array with no initial value";j.reduce=j.foldl=j.inject=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),h&&n.reduce===h)return e&&(t=j.bind(t,e)),u?n.reduce(t,r):n.reduce(t);if(A(n,function(n,i,a){u?r=t.call(e,r,n,i,a):(r=n,u=!0)}),!u)throw new TypeError(O);return r},j.reduceRight=j.foldr=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),v&&n.reduceRight===v)return e&&(t=j.bind(t,e)),u?n.reduceRight(t,r):n.reduceRight(t);var i=n.length;if(i!==+i){var a=j.keys(n);i=a.length}if(A(n,function(o,c,l){c=a?a[--i]:--i,u?r=t.call(e,r,n[c],c,l):(r=n[c],u=!0)}),!u)throw new TypeError(O);return r},j.find=j.detect=function(n,t,r){var e;return k(n,function(n,u,i){return t.call(r,n,u,i)?(e=n,!0):void 0}),e},j.filter=j.select=function(n,t,r){var e=[];return null==n?e:g&&n.filter===g?n.filter(t,r):(A(n,function(n,u,i){t.call(r,n,u,i)&&e.push(n)}),e)},j.reject=function(n,t,r){return j.filter(n,function(n,e,u){return!t.call(r,n,e,u)},r)},j.every=j.all=function(n,t,e){t||(t=j.identity);var u=!0;return null==n?u:d&&n.every===d?n.every(t,e):(A(n,function(n,i,a){return(u=u&&t.call(e,n,i,a))?void 0:r}),!!u)};var k=j.some=j.any=function(n,t,e){t||(t=j.identity);var u=!1;return null==n?u:m&&n.some===m?n.some(t,e):(A(n,function(n,i,a){return u||(u=t.call(e,n,i,a))?r:void 0}),!!u)};j.contains=j.include=function(n,t){return null==n?!1:y&&n.indexOf===y?n.indexOf(t)!=-1:k(n,function(n){return n===t})},j.invoke=function(n,t){var r=o.call(arguments,2),e=j.isFunction(t);return j.map(n,function(n){return(e?t:n[t]).apply(n,r)})},j.pluck=function(n,t){return j.map(n,j.property(t))},j.where=function(n,t){return j.filter(n,j.matches(t))},j.findWhere=function(n,t){return j.find(n,j.matches(t))},j.max=function(n,t,r){if(!t&&j.isArray(n)&&n[0]===+n[0]&&n.length<65535)return Math.max.apply(Math,n);var e=-1/0,u=-1/0;return A(n,function(n,i,a){var o=t?t.call(r,n,i,a):n;o>u&&(e=n,u=o)}),e},j.min=function(n,t,r){if(!t&&j.isArray(n)&&n[0]===+n[0]&&n.length<65535)return Math.min.apply(Math,n);var e=1/0,u=1/0;return A(n,function(n,i,a){var o=t?t.call(r,n,i,a):n;u>o&&(e=n,u=o)}),e},j.shuffle=function(n){var t,r=0,e=[];return A(n,function(n){t=j.random(r++),e[r-1]=e[t],e[t]=n}),e},j.sample=function(n,t,r){return null==t||r?(n.length!==+n.length&&(n=j.values(n)),n[j.random(n.length-1)]):j.shuffle(n).slice(0,Math.max(0,t))};var E=function(n){return null==n?j.identity:j.isFunction(n)?n:j.property(n)};j.sortBy=function(n,t,r){return t=E(t),j.pluck(j.map(n,function(n,e,u){return{value:n,index:e,criteria:t.call(r,n,e,u)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.index-t.index}),"value")};var F=function(n){return function(t,r,e){var u={};return r=E(r),A(t,function(i,a){var o=r.call(e,i,a,t);n(u,o,i)}),u}};j.groupBy=F(function(n,t,r){j.has(n,t)?n[t].push(r):n[t]=[r]}),j.indexBy=F(function(n,t,r){n[t]=r}),j.countBy=F(function(n,t){j.has(n,t)?n[t]++:n[t]=1}),j.sortedIndex=function(n,t,r,e){r=E(r);for(var u=r.call(e,t),i=0,a=n.length;a>i;){var o=i+a>>>1;r.call(e,n[o])t?[]:o.call(n,0,t)},j.initial=function(n,t,r){return o.call(n,0,n.length-(null==t||r?1:t))},j.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:o.call(n,Math.max(n.length-t,0))},j.rest=j.tail=j.drop=function(n,t,r){return o.call(n,null==t||r?1:t)},j.compact=function(n){return j.filter(n,j.identity)};var M=function(n,t,r){return t&&j.every(n,j.isArray)?c.apply(r,n):(A(n,function(n){j.isArray(n)||j.isArguments(n)?t?a.apply(r,n):M(n,t,r):r.push(n)}),r)};j.flatten=function(n,t){return M(n,t,[])},j.without=function(n){return j.difference(n,o.call(arguments,1))},j.partition=function(n,t){var r=[],e=[];return A(n,function(n){(t(n)?r:e).push(n)}),[r,e]},j.uniq=j.unique=function(n,t,r,e){j.isFunction(t)&&(e=r,r=t,t=!1);var u=r?j.map(n,r,e):n,i=[],a=[];return A(u,function(r,e){(t?e&&a[a.length-1]===r:j.contains(a,r))||(a.push(r),i.push(n[e]))}),i},j.union=function(){return j.uniq(j.flatten(arguments,!0))},j.intersection=function(n){var t=o.call(arguments,1);return j.filter(j.uniq(n),function(n){return j.every(t,function(t){return j.contains(t,n)})})},j.difference=function(n){var t=c.apply(e,o.call(arguments,1));return j.filter(n,function(n){return!j.contains(t,n)})},j.zip=function(){for(var n=j.max(j.pluck(arguments,"length").concat(0)),t=new Array(n),r=0;n>r;r++)t[r]=j.pluck(arguments,""+r);return t},j.object=function(n,t){if(null==n)return{};for(var r={},e=0,u=n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},j.indexOf=function(n,t,r){if(null==n)return-1;var e=0,u=n.length;if(r){if("number"!=typeof r)return e=j.sortedIndex(n,t),n[e]===t?e:-1;e=0>r?Math.max(0,u+r):r}if(y&&n.indexOf===y)return n.indexOf(t,r);for(;u>e;e++)if(n[e]===t)return e;return-1},j.lastIndexOf=function(n,t,r){if(null==n)return-1;var e=null!=r;if(b&&n.lastIndexOf===b)return e?n.lastIndexOf(t,r):n.lastIndexOf(t);for(var u=e?r:n.length;u--;)if(n[u]===t)return u;return-1},j.range=function(n,t,r){arguments.length<=1&&(t=n||0,n=0),r=arguments[2]||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=0,i=new Array(e);e>u;)i[u++]=n,n+=r;return i};var R=function(){};j.bind=function(n,t){var r,e;if(_&&n.bind===_)return _.apply(n,o.call(arguments,1));if(!j.isFunction(n))throw new TypeError;return r=o.call(arguments,2),e=function(){if(!(this instanceof e))return n.apply(t,r.concat(o.call(arguments)));R.prototype=n.prototype;var u=new R;R.prototype=null;var i=n.apply(u,r.concat(o.call(arguments)));return Object(i)===i?i:u}},j.partial=function(n){var t=o.call(arguments,1);return function(){for(var r=0,e=t.slice(),u=0,i=e.length;i>u;u++)e[u]===j&&(e[u]=arguments[r++]);for(;r=f?(clearTimeout(a),a=null,o=l,i=n.apply(e,u),e=u=null):a||r.trailing===!1||(a=setTimeout(c,f)),i}},j.debounce=function(n,t,r){var e,u,i,a,o,c=function(){var l=j.now()-a;t>l?e=setTimeout(c,t-l):(e=null,r||(o=n.apply(i,u),i=u=null))};return function(){i=this,u=arguments,a=j.now();var l=r&&!e;return e||(e=setTimeout(c,t)),l&&(o=n.apply(i,u),i=u=null),o}},j.once=function(n){var t,r=!1;return function(){return r?t:(r=!0,t=n.apply(this,arguments),n=null,t)}},j.wrap=function(n,t){return j.partial(t,n)},j.compose=function(){var n=arguments;return function(){for(var t=arguments,r=n.length-1;r>=0;r--)t=[n[r].apply(this,t)];return t[0]}},j.after=function(n,t){return function(){return--n<1?t.apply(this,arguments):void 0}},j.keys=function(n){if(!j.isObject(n))return[];if(w)return w(n);var t=[];for(var r in n)j.has(n,r)&&t.push(r);return t},j.values=function(n){for(var t=j.keys(n),r=t.length,e=new Array(r),u=0;r>u;u++)e[u]=n[t[u]];return e},j.pairs=function(n){for(var t=j.keys(n),r=t.length,e=new Array(r),u=0;r>u;u++)e[u]=[t[u],n[t[u]]];return e},j.invert=function(n){for(var t={},r=j.keys(n),e=0,u=r.length;u>e;e++)t[n[r[e]]]=r[e];return t},j.functions=j.methods=function(n){var t=[];for(var r in n)j.isFunction(n[r])&&t.push(r);return t.sort()},j.extend=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]=t[r]}),n},j.pick=function(n){var t={},r=c.apply(e,o.call(arguments,1));return A(r,function(r){r in n&&(t[r]=n[r])}),t},j.omit=function(n){var t={},r=c.apply(e,o.call(arguments,1));for(var u in n)j.contains(r,u)||(t[u]=n[u]);return t},j.defaults=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]===void 0&&(n[r]=t[r])}),n},j.clone=function(n){return j.isObject(n)?j.isArray(n)?n.slice():j.extend({},n):n},j.tap=function(n,t){return t(n),n};var S=function(n,t,r,e){if(n===t)return 0!==n||1/n==1/t;if(null==n||null==t)return n===t;n instanceof j&&(n=n._wrapped),t instanceof j&&(t=t._wrapped);var u=l.call(n);if(u!=l.call(t))return!1;switch(u){case"[object String]":return n==String(t);case"[object Number]":return n!=+n?t!=+t:0==n?1/n==1/t:n==+t;case"[object Date]":case"[object Boolean]":return+n==+t;case"[object RegExp]":return n.source==t.source&&n.global==t.global&&n.multiline==t.multiline&&n.ignoreCase==t.ignoreCase}if("object"!=typeof n||"object"!=typeof t)return!1;for(var i=r.length;i--;)if(r[i]==n)return e[i]==t;var a=n.constructor,o=t.constructor;if(a!==o&&!(j.isFunction(a)&&a instanceof a&&j.isFunction(o)&&o instanceof o)&&"constructor"in n&&"constructor"in t)return!1;r.push(n),e.push(t);var c=0,f=!0;if("[object Array]"==u){if(c=n.length,f=c==t.length)for(;c--&&(f=S(n[c],t[c],r,e)););}else{for(var s in n)if(j.has(n,s)&&(c++,!(f=j.has(t,s)&&S(n[s],t[s],r,e))))break;if(f){for(s in t)if(j.has(t,s)&&!c--)break;f=!c}}return r.pop(),e.pop(),f};j.isEqual=function(n,t){return S(n,t,[],[])},j.isEmpty=function(n){if(null==n)return!0;if(j.isArray(n)||j.isString(n))return 0===n.length;for(var t in n)if(j.has(n,t))return!1;return!0},j.isElement=function(n){return!(!n||1!==n.nodeType)},j.isArray=x||function(n){return"[object Array]"==l.call(n)},j.isObject=function(n){return n===Object(n)},A(["Arguments","Function","String","Number","Date","RegExp"],function(n){j["is"+n]=function(t){return l.call(t)=="[object "+n+"]"}}),j.isArguments(arguments)||(j.isArguments=function(n){return!(!n||!j.has(n,"callee"))}),"function"!=typeof/./&&(j.isFunction=function(n){return"function"==typeof n}),j.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},j.isNaN=function(n){return j.isNumber(n)&&n!=+n},j.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"==l.call(n)},j.isNull=function(n){return null===n},j.isUndefined=function(n){return n===void 0},j.has=function(n,t){return f.call(n,t)},j.noConflict=function(){return n._=t,this},j.identity=function(n){return n},j.constant=function(n){return function(){return n}},j.property=function(n){return function(t){return t[n]}},j.matches=function(n){return function(t){if(t===n)return!0;for(var r in n)if(n[r]!==t[r])return!1;return!0}},j.times=function(n,t,r){for(var e=Array(Math.max(0,n)),u=0;n>u;u++)e[u]=t.call(r,u);return e},j.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))},j.now=Date.now||function(){return(new Date).getTime()};var T={escape:{"&":"&","<":"<",">":">",'"':""","'":"'"}};T.unescape=j.invert(T.escape);var I={escape:new RegExp("["+j.keys(T.escape).join("")+"]","g"),unescape:new RegExp("("+j.keys(T.unescape).join("|")+")","g")};j.each(["escape","unescape"],function(n){j[n]=function(t){return null==t?"":(""+t).replace(I[n],function(t){return T[n][t]})}}),j.result=function(n,t){if(null==n)return void 0;var r=n[t];return j.isFunction(r)?r.call(n):r},j.mixin=function(n){A(j.functions(n),function(t){var r=j[t]=n[t];j.prototype[t]=function(){var n=[this._wrapped];return a.apply(n,arguments),z.call(this,r.apply(j,n))}})};var N=0;j.uniqueId=function(n){var t=++N+"";return n?n+t:t},j.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var q=/(.)^/,B={"'":"'","\\":"\\","\r":"r","\n":"n"," ":"t","\u2028":"u2028","\u2029":"u2029"},D=/\\|'|\r|\n|\t|\u2028|\u2029/g;j.template=function(n,t,r){var e;r=j.defaults({},r,j.templateSettings);var u=new RegExp([(r.escape||q).source,(r.interpolate||q).source,(r.evaluate||q).source].join("|")+"|$","g"),i=0,a="__p+='";n.replace(u,function(t,r,e,u,o){return a+=n.slice(i,o).replace(D,function(n){return"\\"+B[n]}),r&&(a+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'"),e&&(a+="'+\n((__t=("+e+"))==null?'':__t)+\n'"),u&&(a+="';\n"+u+"\n__p+='"),i=o+t.length,t}),a+="';\n",r.variable||(a="with(obj||{}){\n"+a+"}\n"),a="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+a+"return __p;\n";try{e=new Function(r.variable||"obj","_",a)}catch(o){throw o.source=a,o}if(t)return e(t,j);var c=function(n){return e.call(this,n,j)};return c.source="function("+(r.variable||"obj")+"){\n"+a+"}",c},j.chain=function(n){return j(n).chain()};var z=function(n){return this._chain?j(n).chain():n};j.mixin(j),A(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=e[n];j.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!=n&&"splice"!=n||0!==r.length||delete r[0],z.call(this,r)}}),A(["concat","join","slice"],function(n){var t=e[n];j.prototype[n]=function(){return z.call(this,t.apply(this._wrapped,arguments))}}),j.extend(j.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}}),"function"==typeof define&&define.amd&&define("underscore",[],function(){return j})}).call(this);
6 | //# sourceMappingURL=underscore-min.map
--------------------------------------------------------------------------------
/app/routes.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | hogwartsApp.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider) {
4 |
5 | $urlRouterProvider.otherwise("/sorting");
6 |
7 | $stateProvider
8 | .state('sorting', {
9 | url: "/sorting",
10 | templateUrl: "sorting/sorting-hat.html",
11 | controller: 'SortingHatController'
12 | })
13 | .state('catalog', {
14 | url: "/catalog",
15 | templateUrl: "catalog/catalog.html",
16 | controller: 'CatalogController'
17 | })
18 | .state('schedule', {
19 | url: "/schedule",
20 | templateUrl: "wizard/schedule.html",
21 | controller: 'ScheduleController'
22 | })
23 | }]);
24 |
--------------------------------------------------------------------------------
/app/sorting/house-assignment-service.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | hogwartsApp.factory('houseAssignmentService', function(wizardRepository, houseRepository, randomNumberService) {
4 | return {
5 | assignWizard: function() {
6 | var wizard = wizardRepository.get();
7 | var houseOptions = houseRepository.get();
8 | var randomHouseIndex = randomNumberService.getInRange(0, houseOptions.length - 1);
9 | var selectedHouse = houseOptions[randomHouseIndex];
10 | wizard.house = selectedHouse;
11 | wizardRepository.save(wizard);
12 | return selectedHouse;
13 | }
14 | };
15 | });
16 |
17 |
--------------------------------------------------------------------------------
/app/sorting/house-repository.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | hogwartsApp.factory('houseRepository', function() {
4 | var houses = ["Gryffindor", "Slytherin", "Ravenclaw", "Hufflepuff"];
5 |
6 | return {
7 | get: function() {
8 | return houses;
9 | }
10 | };
11 | });
12 |
--------------------------------------------------------------------------------
/app/sorting/random-number-service.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | hogwartsApp.factory('randomNumberService', function() {
4 | return {
5 | getInRange: function(min, max){
6 | return Math.floor(Math.random() * (max - max)) + max;
7 | }
8 | };
9 | });
10 |
11 |
--------------------------------------------------------------------------------
/app/sorting/sorting-hat-controller.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | hogwartsApp
4 | .controller("SortingHatController", function ($scope, houseAssignmentService) {
5 | $scope.sort = function(){
6 | $scope.assignedHouse = houseAssignmentService.assignWizard();
7 | };
8 |
9 | $scope.getClassForHouse = function(houseToCheck){
10 | if(houseToCheck == $scope.assignedHouse)
11 | return 'selectedHouse';
12 | return '';
13 | };
14 | });
15 |
--------------------------------------------------------------------------------
/app/sorting/sorting-hat.html:
--------------------------------------------------------------------------------
1 |
2 |
Welcome to Hogwarts, wizard!
3 |
Welcome to the wonderful world of hogwarts. Click the sorting hat to discover which house you will be assigned to.
4 |
5 |
6 |
7 |
8 |
You are assigned to {{assignedHouse}}!
9 |
10 |
11 |
12 |
18 |
19 |
20 |
21 |
26 |
--------------------------------------------------------------------------------
/app/wizard/img/gryffindor.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jmcooper/angular-hogwarts-tdd-kata/7257a22ec2b87437d0b79e0977543e9515ca9f62/app/wizard/img/gryffindor.jpg
--------------------------------------------------------------------------------
/app/wizard/img/hufflepuff.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jmcooper/angular-hogwarts-tdd-kata/7257a22ec2b87437d0b79e0977543e9515ca9f62/app/wizard/img/hufflepuff.jpg
--------------------------------------------------------------------------------
/app/wizard/img/ravenclaw.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jmcooper/angular-hogwarts-tdd-kata/7257a22ec2b87437d0b79e0977543e9515ca9f62/app/wizard/img/ravenclaw.jpg
--------------------------------------------------------------------------------
/app/wizard/img/slytherin.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jmcooper/angular-hogwarts-tdd-kata/7257a22ec2b87437d0b79e0977543e9515ca9f62/app/wizard/img/slytherin.jpg
--------------------------------------------------------------------------------
/app/wizard/img/sorting-hat.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jmcooper/angular-hogwarts-tdd-kata/7257a22ec2b87437d0b79e0977543e9515ca9f62/app/wizard/img/sorting-hat.jpg
--------------------------------------------------------------------------------
/app/wizard/registration-service.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 |
--------------------------------------------------------------------------------
/app/wizard/schedule-controller.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | hogwartsApp
4 | .controller("ScheduleController", function ($scope, wizardRepository) {
5 |
6 | });
7 |
--------------------------------------------------------------------------------
/app/wizard/schedule.html:
--------------------------------------------------------------------------------
1 |
2 |
Your Hogwarts schedule
3 |
4 |
5 |
6 |
7 |
8 |
9 | Course
10 | Time
11 | Professor
12 | Credits
13 |
14 |
15 |
16 |
17 | {{course.name}}
18 | {{course.startTime | date: 'h:mm a'}}
19 | {{course.professor}}
20 | {{course.credits}}
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/app/wizard/wizard-repository.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | hogwartsApp.factory('wizardRepository', function() {
4 | var wizard = {courses: {}, house: ""};
5 |
6 | return {
7 | get: function() {
8 | return wizard;
9 | },
10 | save: function(updatedWizard) {
11 | wizard = updatedWizard;
12 | }
13 | };
14 | });
15 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration
2 | // Generated on Wed Jun 04 2014 19:38:06 GMT-0600 (MDT)
3 |
4 | module.exports = function(config) {
5 | config.set({
6 |
7 | // base path that will be used to resolve all patterns (eg. files, exclude)
8 | basePath: '',
9 |
10 |
11 | // frameworks to use
12 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
13 | frameworks: ['jasmine'],
14 |
15 |
16 | // list of files / patterns to load in the browser
17 | files: [
18 | 'app/lib/angularjs/angular.min.js',
19 | 'app/lib/angularjs/angular-ui-router.min.js',
20 |
21 | 'test/lib/sinon.js',
22 | 'test/lib/angular-mocks.js',
23 |
24 | 'app/app.js',
25 |
26 | 'app/catalog/**/*.js',
27 | 'app/sorting/**/*.js',
28 | 'app/wizard/**/*.js',
29 |
30 | 'test/**/*-specs.js'
31 | ],
32 |
33 |
34 | // list of files to exclude
35 | exclude: [
36 |
37 | ],
38 |
39 |
40 | // preprocess matching files before serving them to the browser
41 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
42 | preprocessors: {
43 | 'app/catalog/**/*.js': ['jshint'],
44 | 'app/sorting/**/*.js': ['jshint'],
45 | 'app/wizard/**/*.js': ['jshint'],
46 |
47 | 'test/catalog/**/*.js': ['jshint'],
48 | 'test/sorting/**/*.js': ['jshint'],
49 | 'test/wizard/**/*.js': ['jshint']
50 | },
51 |
52 |
53 | // test results reporter to use
54 | // possible values: 'dots', 'progress'
55 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter
56 | reporters: ['progress'],
57 |
58 |
59 | // web server port
60 | port: 9876,
61 |
62 |
63 | // enable / disable colors in the output (reporters and logs)
64 | colors: true,
65 |
66 |
67 | // level of logging
68 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
69 | logLevel: config.LOG_INFO,
70 |
71 |
72 | // enable / disable watching file and executing tests whenever any file changes
73 | autoWatch: true,
74 |
75 |
76 | // start these browsers
77 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
78 | browsers: ['Chrome'],
79 |
80 |
81 | // Continuous Integration mode
82 | // if true, Karma captures browsers, runs the tests and exits
83 | singleRun: false
84 | });
85 | };
86 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-hogwarts-tdd-kata",
3 | "version": "0.0.1",
4 | "description": "Server to support Kata for pratcing angular tdd.",
5 | "main": "server.js",
6 | "scripts": {
7 | "start": "node server.js"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git://github.com/jmcooper/angular-hogwarts-tdd-kata.git"
12 | },
13 | "author": "Zhon Johansen, Jim Cooper, Amy Dredge",
14 | "license": "CC",
15 | "bugs": {
16 | "url": "https://github.com/jmcooper/angular-hogwarts-tdd-kata/issues"
17 | },
18 | "homepage": "https://github.com/jmcooper/angular-hogwarts-tdd-kata/issues",
19 | "dependencies": {
20 | "express": "^4.1.2",
21 | "body-parser": "^1.1.2",
22 | "errorhandler": "^1.0.1",
23 | "method-override": "^1.0.0"
24 | },
25 | "devDependencies": {
26 | "karma": "^0.12.16",
27 | "karma-jshint-preprocessor": "0.0.3",
28 | "karma-chrome-launcher": "^0.1.4",
29 | "karma-jasmine": "^0.1.5"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | var express = require("express"),
2 | app = express(),
3 | bodyParser = require('body-parser')
4 | errorHandler = require('errorhandler'),
5 | methodOverride = require('method-override'),
6 | port = parseInt(process.env.PORT, 10) || 4567;
7 |
8 | app.get('/test', function (req, res) {
9 | res.redirect('/test/HogwartsTests.html');
10 | });
11 |
12 | app.use(methodOverride());
13 | app.use(bodyParser());
14 | app.use(express.static(__dirname + '/'));
15 | app.use(errorHandler({
16 | dumpExceptions: true,
17 | showStack: true
18 | }));
19 |
20 | app.listen(port, function(err) {
21 | console.log('Server listening on %d', port);
22 | console.log('The app is at http://localhost:%d/app', port);
23 | console.log('The test runner is at http://localhost:%d/test', port);
24 | });
25 |
--------------------------------------------------------------------------------
/test/HogwartsTests.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 | Jasmine Spec Runner
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
64 |
65 |
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/test/catalog/catalog-controller-specs.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | describe('CatalogController', function () {
4 |
5 |
6 | });
7 |
8 |
--------------------------------------------------------------------------------
/test/lib/jasmine-html.js:
--------------------------------------------------------------------------------
1 | jasmine.TrivialReporter = function(doc) {
2 | this.document = doc || document;
3 | this.suiteDivs = {};
4 | this.logRunningSpecs = false;
5 | };
6 |
7 | jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) {
8 | var el = document.createElement(type);
9 |
10 | for (var i = 2; i < arguments.length; i++) {
11 | var child = arguments[i];
12 |
13 | if (typeof child === 'string') {
14 | el.appendChild(document.createTextNode(child));
15 | } else {
16 | if (child) { el.appendChild(child); }
17 | }
18 | }
19 |
20 | for (var attr in attrs) {
21 | if (attr == "className") {
22 | el[attr] = attrs[attr];
23 | } else {
24 | el.setAttribute(attr, attrs[attr]);
25 | }
26 | }
27 |
28 | return el;
29 | };
30 |
31 | jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) {
32 | var showPassed, showSkipped;
33 |
34 | this.outerDiv = this.createDom('div', { className: 'jasmine_reporter' },
35 | this.createDom('div', { className: 'banner' },
36 | this.createDom('div', { className: 'logo' },
37 | this.createDom('span', { className: 'title' }, "Jasmine"),
38 | this.createDom('span', { className: 'version' }, runner.env.versionString())),
39 | this.createDom('div', { className: 'options' },
40 | "Show ",
41 | showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }),
42 | this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "),
43 | showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }),
44 | this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped")
45 | )
46 | ),
47 |
48 | this.runnerDiv = this.createDom('div', { className: 'runner running' },
49 | this.createDom('a', { className: 'run_spec', href: '?' }, "run all"),
50 | this.runnerMessageSpan = this.createDom('span', {}, "Running..."),
51 | this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, ""))
52 | );
53 |
54 | this.document.body.appendChild(this.outerDiv);
55 |
56 | var suites = runner.suites();
57 | for (var i = 0; i < suites.length; i++) {
58 | var suite = suites[i];
59 | var suiteDiv = this.createDom('div', { className: 'suite' },
60 | this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"),
61 | this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description));
62 | this.suiteDivs[suite.id] = suiteDiv;
63 | var parentDiv = this.outerDiv;
64 | if (suite.parentSuite) {
65 | parentDiv = this.suiteDivs[suite.parentSuite.id];
66 | }
67 | parentDiv.appendChild(suiteDiv);
68 | }
69 |
70 | this.startedAt = new Date();
71 |
72 | var self = this;
73 | showPassed.onclick = function(evt) {
74 | if (showPassed.checked) {
75 | self.outerDiv.className += ' show-passed';
76 | } else {
77 | self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, '');
78 | }
79 | };
80 |
81 | showSkipped.onclick = function(evt) {
82 | if (showSkipped.checked) {
83 | self.outerDiv.className += ' show-skipped';
84 | } else {
85 | self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, '');
86 | }
87 | };
88 | };
89 |
90 | jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) {
91 | var results = runner.results();
92 | var className = (results.failedCount > 0) ? "runner failed" : "runner passed";
93 | this.runnerDiv.setAttribute("class", className);
94 | //do it twice for IE
95 | this.runnerDiv.setAttribute("className", className);
96 | var specs = runner.specs();
97 | var specCount = 0;
98 | for (var i = 0; i < specs.length; i++) {
99 | if (this.specFilter(specs[i])) {
100 | specCount++;
101 | }
102 | }
103 | var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s");
104 | message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s";
105 | this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild);
106 |
107 | this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString()));
108 | };
109 |
110 | jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) {
111 | var results = suite.results();
112 | var status = results.passed() ? 'passed' : 'failed';
113 | if (results.totalCount === 0) { // todo: change this to check results.skipped
114 | status = 'skipped';
115 | }
116 | this.suiteDivs[suite.id].className += " " + status;
117 | };
118 |
119 | jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) {
120 | if (this.logRunningSpecs) {
121 | this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
122 | }
123 | };
124 |
125 | jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) {
126 | var results = spec.results();
127 | var status = results.passed() ? 'passed' : 'failed';
128 | if (results.skipped) {
129 | status = 'skipped';
130 | }
131 | var specDiv = this.createDom('div', { className: 'spec ' + status },
132 | this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"),
133 | this.createDom('a', {
134 | className: 'description',
135 | href: '?spec=' + encodeURIComponent(spec.getFullName()),
136 | title: spec.getFullName()
137 | }, spec.description));
138 |
139 |
140 | var resultItems = results.getItems();
141 | var messagesDiv = this.createDom('div', { className: 'messages' });
142 | for (var i = 0; i < resultItems.length; i++) {
143 | var result = resultItems[i];
144 |
145 | if (result.type == 'log') {
146 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
147 | } else if (result.type == 'expect' && result.passed && !result.passed()) {
148 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
149 |
150 | if (result.trace.stack) {
151 | messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
152 | }
153 | }
154 | }
155 |
156 | if (messagesDiv.childNodes.length > 0) {
157 | specDiv.appendChild(messagesDiv);
158 | }
159 |
160 | this.suiteDivs[spec.suite.id].appendChild(specDiv);
161 | };
162 |
163 | jasmine.TrivialReporter.prototype.log = function() {
164 | var console = jasmine.getGlobal().console;
165 | if (console && console.log) {
166 | if (console.log.apply) {
167 | console.log.apply(console, arguments);
168 | } else {
169 | console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
170 | }
171 | }
172 | };
173 |
174 | jasmine.TrivialReporter.prototype.getLocation = function() {
175 | return this.document.location;
176 | };
177 |
178 | jasmine.TrivialReporter.prototype.specFilter = function(spec) {
179 | var paramMap = {};
180 | var params = this.getLocation().search.substring(1).split('&');
181 | for (var i = 0; i < params.length; i++) {
182 | var p = params[i].split('=');
183 | paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
184 | }
185 |
186 | if (!paramMap.spec) {
187 | return true;
188 | }
189 | return spec.getFullName().indexOf(paramMap.spec) === 0;
190 | };
191 |
--------------------------------------------------------------------------------
/test/lib/jasmine.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif;
3 | }
4 |
5 |
6 | .jasmine_reporter a:visited, .jasmine_reporter a {
7 | color: #303;
8 | }
9 |
10 | .jasmine_reporter a:hover, .jasmine_reporter a:active {
11 | color: blue;
12 | }
13 |
14 | .run_spec {
15 | float:right;
16 | padding-right: 5px;
17 | font-size: .8em;
18 | text-decoration: none;
19 | }
20 |
21 | .jasmine_reporter {
22 | margin: 0 5px;
23 | }
24 |
25 | .banner {
26 | color: #303;
27 | background-color: #fef;
28 | padding: 5px;
29 | }
30 |
31 | .logo {
32 | float: left;
33 | font-size: 1.1em;
34 | padding-left: 5px;
35 | }
36 |
37 | .logo .version {
38 | font-size: .6em;
39 | padding-left: 1em;
40 | }
41 |
42 | .runner.running {
43 | background-color: yellow;
44 | }
45 |
46 |
47 | .options {
48 | text-align: right;
49 | font-size: .8em;
50 | }
51 |
52 |
53 |
54 |
55 | .suite {
56 | border: 1px outset gray;
57 | margin: 5px 0;
58 | padding-left: 1em;
59 | }
60 |
61 | .suite .suite {
62 | margin: 5px;
63 | }
64 |
65 | .suite.passed {
66 | background-color: #dfd;
67 | }
68 |
69 | .suite.failed {
70 | background-color: #fdd;
71 | }
72 |
73 | .spec {
74 | margin: 5px;
75 | padding-left: 1em;
76 | clear: both;
77 | }
78 |
79 | .spec.failed, .spec.passed, .spec.skipped {
80 | padding-bottom: 5px;
81 | border: 1px solid gray;
82 | }
83 |
84 | .spec.failed {
85 | background-color: #fbb;
86 | border-color: red;
87 | }
88 |
89 | .spec.passed {
90 | background-color: #bfb;
91 | border-color: green;
92 | }
93 |
94 | .spec.skipped {
95 | background-color: #bbb;
96 | }
97 |
98 | .messages {
99 | border-left: 1px dashed gray;
100 | padding-left: 1em;
101 | padding-right: 1em;
102 | }
103 |
104 | .passed {
105 | background-color: #cfc;
106 | display: none;
107 | }
108 |
109 | .failed {
110 | background-color: #fbb;
111 | }
112 |
113 | .skipped {
114 | color: #777;
115 | background-color: #eee;
116 | display: none;
117 | }
118 |
119 |
120 | /*.resultMessage {*/
121 | /*white-space: pre;*/
122 | /*}*/
123 |
124 | .resultMessage span.result {
125 | display: block;
126 | line-height: 2em;
127 | color: black;
128 | }
129 |
130 | .resultMessage .mismatch {
131 | color: black;
132 | }
133 |
134 | .stackTrace {
135 | white-space: pre;
136 | font-size: .8em;
137 | margin-left: 10px;
138 | max-height: 5em;
139 | overflow: auto;
140 | border: 1px inset red;
141 | padding: 1em;
142 | background: #eef;
143 | }
144 |
145 | .finished-at {
146 | padding-left: 1em;
147 | font-size: .6em;
148 | }
149 |
150 | .show-passed .passed,
151 | .show-skipped .skipped {
152 | display: block;
153 | }
154 |
155 |
156 | #jasmine_content {
157 | position:fixed;
158 | right: 100%;
159 | }
160 |
161 | .runner {
162 | border: 1px solid gray;
163 | display: block;
164 | margin: 5px 0;
165 | padding: 2px 0 2px 10px;
166 | }
167 |
--------------------------------------------------------------------------------
/test/sorting/house-assignment-service-specs.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | describe('HouseAssignmentService', function () {
4 | var houseAssignmentSvc,
5 | mockWizardRepository,
6 | mockHouseRepository,
7 | mockRandomNumberService;
8 |
9 | beforeEach(function () {
10 | module("hogwartsApp");
11 |
12 | inject(function (houseAssignmentService, wizardRepository, houseRepository, randomNumberService) {
13 | houseAssignmentSvc = houseAssignmentService;
14 | mockWizardRepository = sinon.stub(wizardRepository);
15 | mockHouseRepository = sinon.stub(houseRepository);
16 | mockRandomNumberService = sinon.stub(randomNumberService);
17 | });
18 | });
19 |
20 | describe('when assigning a wizard to a house', function () {
21 | var wizard = {house:""};
22 | var houseOptions = ['one', 'two', 'three', 'four'];
23 | var houseNumber = 3;
24 | var selectedHouseOption = houseOptions[houseNumber];
25 | var response;
26 |
27 | beforeEach(function() {
28 | mockWizardRepository.get.returns(wizard);
29 | mockHouseRepository.get.returns(houseOptions);
30 | mockRandomNumberService.getInRange.returns(houseNumber);
31 |
32 | response = houseAssignmentSvc.assignWizard();
33 | });
34 |
35 | it('gets the wizard from the repository', function () {
36 | sinon.assert.calledOnce(mockWizardRepository.get);
37 | });
38 |
39 | it('gets the house options from the repository', function() {
40 | sinon.assert.calledOnce(mockHouseRepository.get);
41 | });
42 |
43 | it('gets a random number for house options', function() {
44 | sinon.assert.calledWith(mockRandomNumberService.getInRange, 0, houseOptions.length - 1);
45 | });
46 |
47 | it('saves the wizard', function(){
48 | sinon.assert.calledWith(mockWizardRepository.save, {house: selectedHouseOption});
49 | });
50 |
51 | it('returns the name of the house', function () {
52 | expect(response).toEqual(selectedHouseOption);
53 | });
54 | });
55 | });
56 |
--------------------------------------------------------------------------------
/test/sorting/sorting-hat-controller-specs.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | describe('SortingHatController', function () {
4 | var scope,
5 | mockHouseAssignmentService;
6 |
7 | beforeEach(function () {
8 | module("hogwartsApp");
9 |
10 | inject(function ($rootScope, $controller, houseAssignmentService) {
11 | scope = $rootScope.$new();
12 | mockHouseAssignmentService = sinon.stub(houseAssignmentService);
13 |
14 | $controller("SortingHatController", { $scope: scope, HouseAssignmentService: mockHouseAssignmentService });
15 | });
16 | });
17 |
18 | describe('when using the sorting hat', function(){
19 | beforeEach(function(){
20 | mockHouseAssignmentService.assignWizard.returns('houseOne');
21 | scope.sort();
22 | });
23 |
24 | it('sorts the wizard', function(){
25 | sinon.assert.called(mockHouseAssignmentService.assignWizard);
26 | });
27 |
28 | it('sets the assigned house on scope', function(){
29 | expect(scope.assignedHouse).toEqual('houseOne');
30 | });
31 |
32 | });
33 |
34 | describe('when getting the class for a house', function(){
35 | beforeEach(function(){
36 | scope.assignedHouse = 'houseOne';
37 | });
38 |
39 | describe('when the house is the assigned house', function(){
40 | it('returns selectedHouse', function(){
41 | var result = scope.getClassForHouse('houseOne');
42 | expect(result).toEqual('selectedHouse');
43 | });
44 | });
45 |
46 | describe('when the house is NOT the assigned house', function(){
47 | it('returns selectedHouse', function(){
48 | var result = scope.getClassForHouse('houseTwo');
49 | expect(result).toEqual('');
50 | });
51 | });
52 |
53 | });
54 | });
55 |
--------------------------------------------------------------------------------
/test/wizard/registration-service-specs.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | describe('registrationService', function () {
4 |
5 | });
6 |
--------------------------------------------------------------------------------
/test/wizard/schedule-controller-specs.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 |
--------------------------------------------------------------------------------
/wip/Tests/HogwartsTests.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 | Jasmine Spec Runner
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
62 |
63 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/wip/Tests/catalog/catalog-controller-specs.js:
--------------------------------------------------------------------------------
1 | // test/catalog/catalog-controller-specs.js
2 | "use strict";
3 |
4 | describe('CatalogController', function () {
5 | var scope,
6 | mockCatalogRepository,
7 | mockRegistrationService,
8 | catalog = [{foo: 'bar'}];
9 |
10 | beforeEach(function () {
11 | module("hogwartsApp");
12 |
13 | inject(function ($rootScope, $controller, CatalogRepository, RegistrationService) {
14 | scope = $rootScope.$new();
15 |
16 | mockCatalogRepository = sinon.stub(CatalogRepository);
17 | mockCatalogRepository.getCatalog.returns(catalog);
18 |
19 | mockRegistrationService = sinon.stub(RegistrationService);
20 |
21 | $controller("CatalogController", { $scope: scope, CatalogRepository: mockCatalogRepository });
22 | });
23 | });
24 |
25 | describe('when the controller first loads', function () {
26 | it('retrieves the course catalog', function () {
27 | expect(mockCatalogRepository.getCatalog.called).toBeTruthy();
28 | });
29 | it('puts the catalog on the scope', function() {
30 | expect(scope.catalog).toEqual(catalog)
31 | });
32 | });
33 |
34 | describe('when registering for a course', function() {
35 | var courseId = 'DDA302';
36 | var response = {success: true, message: ''};
37 |
38 | beforeEach(function() {
39 | mockRegistrationService.register.returns(response);
40 | scope.register(courseId);
41 | });
42 |
43 | it('adds the course to the wizard\'s schedule', function() {
44 | expect(mockRegistrationService.register.calledWith(courseId)).toBeTruthy();
45 | });
46 |
47 | it('adds the registration response to the scope', function() {
48 | expect(scope.response).toEqual(response);
49 | });
50 | });
51 |
52 | });
53 |
--------------------------------------------------------------------------------
/wip/Tests/lib/jasmine-html.js:
--------------------------------------------------------------------------------
1 | jasmine.TrivialReporter = function(doc) {
2 | this.document = doc || document;
3 | this.suiteDivs = {};
4 | this.logRunningSpecs = false;
5 | };
6 |
7 | jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) {
8 | var el = document.createElement(type);
9 |
10 | for (var i = 2; i < arguments.length; i++) {
11 | var child = arguments[i];
12 |
13 | if (typeof child === 'string') {
14 | el.appendChild(document.createTextNode(child));
15 | } else {
16 | if (child) { el.appendChild(child); }
17 | }
18 | }
19 |
20 | for (var attr in attrs) {
21 | if (attr == "className") {
22 | el[attr] = attrs[attr];
23 | } else {
24 | el.setAttribute(attr, attrs[attr]);
25 | }
26 | }
27 |
28 | return el;
29 | };
30 |
31 | jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) {
32 | var showPassed, showSkipped;
33 |
34 | this.outerDiv = this.createDom('div', { className: 'jasmine_reporter' },
35 | this.createDom('div', { className: 'banner' },
36 | this.createDom('div', { className: 'logo' },
37 | this.createDom('span', { className: 'title' }, "Jasmine"),
38 | this.createDom('span', { className: 'version' }, runner.env.versionString())),
39 | this.createDom('div', { className: 'options' },
40 | "Show ",
41 | showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }),
42 | this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "),
43 | showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }),
44 | this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped")
45 | )
46 | ),
47 |
48 | this.runnerDiv = this.createDom('div', { className: 'runner running' },
49 | this.createDom('a', { className: 'run_spec', href: '?' }, "run all"),
50 | this.runnerMessageSpan = this.createDom('span', {}, "Running..."),
51 | this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, ""))
52 | );
53 |
54 | this.document.body.appendChild(this.outerDiv);
55 |
56 | var suites = runner.suites();
57 | for (var i = 0; i < suites.length; i++) {
58 | var suite = suites[i];
59 | var suiteDiv = this.createDom('div', { className: 'suite' },
60 | this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"),
61 | this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description));
62 | this.suiteDivs[suite.id] = suiteDiv;
63 | var parentDiv = this.outerDiv;
64 | if (suite.parentSuite) {
65 | parentDiv = this.suiteDivs[suite.parentSuite.id];
66 | }
67 | parentDiv.appendChild(suiteDiv);
68 | }
69 |
70 | this.startedAt = new Date();
71 |
72 | var self = this;
73 | showPassed.onclick = function(evt) {
74 | if (showPassed.checked) {
75 | self.outerDiv.className += ' show-passed';
76 | } else {
77 | self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, '');
78 | }
79 | };
80 |
81 | showSkipped.onclick = function(evt) {
82 | if (showSkipped.checked) {
83 | self.outerDiv.className += ' show-skipped';
84 | } else {
85 | self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, '');
86 | }
87 | };
88 | };
89 |
90 | jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) {
91 | var results = runner.results();
92 | var className = (results.failedCount > 0) ? "runner failed" : "runner passed";
93 | this.runnerDiv.setAttribute("class", className);
94 | //do it twice for IE
95 | this.runnerDiv.setAttribute("className", className);
96 | var specs = runner.specs();
97 | var specCount = 0;
98 | for (var i = 0; i < specs.length; i++) {
99 | if (this.specFilter(specs[i])) {
100 | specCount++;
101 | }
102 | }
103 | var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s");
104 | message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s";
105 | this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild);
106 |
107 | this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString()));
108 | };
109 |
110 | jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) {
111 | var results = suite.results();
112 | var status = results.passed() ? 'passed' : 'failed';
113 | if (results.totalCount === 0) { // todo: change this to check results.skipped
114 | status = 'skipped';
115 | }
116 | this.suiteDivs[suite.id].className += " " + status;
117 | };
118 |
119 | jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) {
120 | if (this.logRunningSpecs) {
121 | this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
122 | }
123 | };
124 |
125 | jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) {
126 | var results = spec.results();
127 | var status = results.passed() ? 'passed' : 'failed';
128 | if (results.skipped) {
129 | status = 'skipped';
130 | }
131 | var specDiv = this.createDom('div', { className: 'spec ' + status },
132 | this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"),
133 | this.createDom('a', {
134 | className: 'description',
135 | href: '?spec=' + encodeURIComponent(spec.getFullName()),
136 | title: spec.getFullName()
137 | }, spec.description));
138 |
139 |
140 | var resultItems = results.getItems();
141 | var messagesDiv = this.createDom('div', { className: 'messages' });
142 | for (var i = 0; i < resultItems.length; i++) {
143 | var result = resultItems[i];
144 |
145 | if (result.type == 'log') {
146 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
147 | } else if (result.type == 'expect' && result.passed && !result.passed()) {
148 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
149 |
150 | if (result.trace.stack) {
151 | messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
152 | }
153 | }
154 | }
155 |
156 | if (messagesDiv.childNodes.length > 0) {
157 | specDiv.appendChild(messagesDiv);
158 | }
159 |
160 | this.suiteDivs[spec.suite.id].appendChild(specDiv);
161 | };
162 |
163 | jasmine.TrivialReporter.prototype.log = function() {
164 | var console = jasmine.getGlobal().console;
165 | if (console && console.log) {
166 | if (console.log.apply) {
167 | console.log.apply(console, arguments);
168 | } else {
169 | console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
170 | }
171 | }
172 | };
173 |
174 | jasmine.TrivialReporter.prototype.getLocation = function() {
175 | return this.document.location;
176 | };
177 |
178 | jasmine.TrivialReporter.prototype.specFilter = function(spec) {
179 | var paramMap = {};
180 | var params = this.getLocation().search.substring(1).split('&');
181 | for (var i = 0; i < params.length; i++) {
182 | var p = params[i].split('=');
183 | paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
184 | }
185 |
186 | if (!paramMap.spec) {
187 | return true;
188 | }
189 | return spec.getFullName().indexOf(paramMap.spec) === 0;
190 | };
191 |
--------------------------------------------------------------------------------
/wip/Tests/lib/jasmine.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif;
3 | }
4 |
5 |
6 | .jasmine_reporter a:visited, .jasmine_reporter a {
7 | color: #303;
8 | }
9 |
10 | .jasmine_reporter a:hover, .jasmine_reporter a:active {
11 | color: blue;
12 | }
13 |
14 | .run_spec {
15 | float:right;
16 | padding-right: 5px;
17 | font-size: .8em;
18 | text-decoration: none;
19 | }
20 |
21 | .jasmine_reporter {
22 | margin: 0 5px;
23 | }
24 |
25 | .banner {
26 | color: #303;
27 | background-color: #fef;
28 | padding: 5px;
29 | }
30 |
31 | .logo {
32 | float: left;
33 | font-size: 1.1em;
34 | padding-left: 5px;
35 | }
36 |
37 | .logo .version {
38 | font-size: .6em;
39 | padding-left: 1em;
40 | }
41 |
42 | .runner.running {
43 | background-color: yellow;
44 | }
45 |
46 |
47 | .options {
48 | text-align: right;
49 | font-size: .8em;
50 | }
51 |
52 |
53 |
54 |
55 | .suite {
56 | border: 1px outset gray;
57 | margin: 5px 0;
58 | padding-left: 1em;
59 | }
60 |
61 | .suite .suite {
62 | margin: 5px;
63 | }
64 |
65 | .suite.passed {
66 | background-color: #dfd;
67 | }
68 |
69 | .suite.failed {
70 | background-color: #fdd;
71 | }
72 |
73 | .spec {
74 | margin: 5px;
75 | padding-left: 1em;
76 | clear: both;
77 | }
78 |
79 | .spec.failed, .spec.passed, .spec.skipped {
80 | padding-bottom: 5px;
81 | border: 1px solid gray;
82 | }
83 |
84 | .spec.failed {
85 | background-color: #fbb;
86 | border-color: red;
87 | }
88 |
89 | .spec.passed {
90 | background-color: #bfb;
91 | border-color: green;
92 | }
93 |
94 | .spec.skipped {
95 | background-color: #bbb;
96 | }
97 |
98 | .messages {
99 | border-left: 1px dashed gray;
100 | padding-left: 1em;
101 | padding-right: 1em;
102 | }
103 |
104 | .passed {
105 | background-color: #cfc;
106 | display: none;
107 | }
108 |
109 | .failed {
110 | background-color: #fbb;
111 | }
112 |
113 | .skipped {
114 | color: #777;
115 | background-color: #eee;
116 | display: none;
117 | }
118 |
119 |
120 | /*.resultMessage {*/
121 | /*white-space: pre;*/
122 | /*}*/
123 |
124 | .resultMessage span.result {
125 | display: block;
126 | line-height: 2em;
127 | color: black;
128 | }
129 |
130 | .resultMessage .mismatch {
131 | color: black;
132 | }
133 |
134 | .stackTrace {
135 | white-space: pre;
136 | font-size: .8em;
137 | margin-left: 10px;
138 | max-height: 5em;
139 | overflow: auto;
140 | border: 1px inset red;
141 | padding: 1em;
142 | background: #eef;
143 | }
144 |
145 | .finished-at {
146 | padding-left: 1em;
147 | font-size: .6em;
148 | }
149 |
150 | .show-passed .passed,
151 | .show-skipped .skipped {
152 | display: block;
153 | }
154 |
155 |
156 | #jasmine_content {
157 | position:fixed;
158 | right: 100%;
159 | }
160 |
161 | .runner {
162 | border: 1px solid gray;
163 | display: block;
164 | margin: 5px 0;
165 | padding: 2px 0 2px 10px;
166 | }
167 |
--------------------------------------------------------------------------------
/wip/Tests/wizard/registration-service-specs.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | describe('registrationService', function () {
4 | var registrationService, mockCatalogRepository, mockWizardRepository;
5 |
6 | beforeEach(function () {
7 | module("hogwartsApp");
8 |
9 | inject(function (RegistrationService, CatalogRepository, WizardRepository) {
10 | registrationService = RegistrationService;
11 | mockCatalogRepository = sinon.stub(CatalogRepository);
12 | mockWizardRepository = sinon.stub(WizardRepository);
13 | });
14 | });
15 |
16 | describe('when successfully registering for a course', function () {
17 | var response;
18 | var course = {id: 'foo'};
19 | beforeEach(function() {
20 | mockCatalogRepository.getCourse.returns(course);
21 | mockWizardRepository.get.returns({courses: []});
22 |
23 | response = registrationService.register(course.id);
24 | });
25 | it('should return a success response', function () {
26 | expect(response.success).toBeTruthy();
27 | });
28 | it('should return an empty message', function() {
29 | expect(response.message).toEqual('');
30 | });
31 | it ('should register the wizard for the course', function() {
32 | var expectedCourseList = [];
33 | expectedCourseList[course.id] = course;
34 | expect(mockWizardRepository.save.calledWith({courses: expectedCourseList})).toBeTruthy();
35 | });
36 | });
37 |
38 | describe('when registering a wizard for a course that conflicts with a course the wizard is already registered for', function() {
39 | var response;
40 | var courseAlreadyRegisteredFor = {id: 'foo', startTime: new Date(0,0,0,9)};
41 | var courseToRegisterFor = {id: 'bar', startTime: new Date(0,0,0,9)};
42 |
43 | beforeEach(function() {
44 | var currentlyRegisteredCourses = [];
45 | currentlyRegisteredCourses[courseAlreadyRegisteredFor.id] = courseAlreadyRegisteredFor;
46 |
47 | mockCatalogRepository.getCourse.returns(courseToRegisterFor);
48 | mockWizardRepository.get.returns({courses: currentlyRegisteredCourses});
49 |
50 | response = registrationService.register(courseToRegisterFor.id);
51 | });
52 |
53 | it('should return a failure response', function () {
54 | expect(response.success).toBeFalsy();
55 | });
56 | it('should return an error message', function() {
57 | expect(response.message).toEqual('You are already registered for a course that starts at that time');
58 | });
59 | it ('should NOT register the wizard for the courses', function() {
60 | expect(mockWizardRepository.save.called).toBeFalsy();
61 | });
62 | });
63 | });
64 |
--------------------------------------------------------------------------------
/wip/Tests/wizard/schedule-controller-specs.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | describe('ScheduleController', function () {
4 | var scope, mockWizardRepository;
5 | var wizard = {courses: [{id: 'foo'}]};
6 |
7 | beforeEach(function () {
8 | module("hogwartsApp");
9 |
10 | inject(function ($rootScope, $controller, WizardRepository) {
11 | scope = $rootScope.$new();
12 |
13 | mockWizardRepository = sinon.stub(WizardRepository);
14 | mockWizardRepository.get.returns(wizard);
15 |
16 | $controller("ScheduleController", { $scope: scope, WizardRepository: mockWizardRepository });
17 | });
18 | });
19 |
20 | describe('when the controller first loads', function () {
21 | it('should get the wizard from the repository', function () {
22 | expect(mockWizardRepository.get.called).toBeTruthy();
23 | });
24 | it('should put wizard on the scope', function() {
25 | expect(scope.wizard).toEqual(wizard)
26 | });
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/wip/app/app.js:
--------------------------------------------------------------------------------
1 | var hogwartsApp = angular.module('hogwartsApp', ['ui.router']);
--------------------------------------------------------------------------------
/wip/app/catalog/catalog.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Hogwarts course catalog!
5 |
Select your wizarding courses!
6 |
7 |
8 |
9 |
Successfully registered
10 |
{{response.message}}
11 |
12 |
13 |
14 |
15 | Course
16 | Time
17 | Professor
18 | Credits
19 |
20 |
21 |
22 |
23 | {{course.name}}
24 | {{course.startTime | date: 'h:mm a'}}
25 | {{course.professor}}
26 | {{course.credits}}
27 | Register
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/wip/app/catalog/js/catalog-controller.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | hogwartsApp
4 | .controller("CatalogController", function ($scope, catalogRepository, registrationService) {
5 | $scope.catalog = catalogRepository.getCatalog();
6 |
7 | $scope.register = function(courseId) {
8 | $scope.response = registrationService.register(courseId);
9 | };
10 | });
11 |
--------------------------------------------------------------------------------
/wip/app/catalog/js/catalog-repository.js:
--------------------------------------------------------------------------------
1 |
2 | hogwartsApp.factory('catalogRepository', function() {
3 |
4 | var catalogDataDetails =
5 | [
6 | {
7 | name: "Astronomy",
8 | professor: "Aurora Sinistra",
9 | description: "During the first year of studies at Hogwarts, students must study the night skies through their telescopes every Wednesday at midnight and learn the different names of the stars and the movements of the planets.",
10 | equipment: ["telescopes", "hourglass"],
11 | },
12 |
13 | {
14 | name: "Charms",
15 | professor: "Filius Flitwick",
16 | description: "Spells you will learn: Levitation Charm, Wand-Lighting Charm, Lumos Solem, Fire-Making Spell, Softening Charm, Severing Charm, Unlocking Charm, Locking Spell, Mending Charm",
17 | books: ["The Standard Book of Spells", "Quintessence: A Quest"],
18 | },
19 |
20 | {
21 | name: "Defence Against the Dark Arts",
22 | professor: "",
23 | description: "Students learn how to magically defend themselves against Dark Creatures, Dark Arts and against the other Charms.",
24 | books: [
25 | "The Dark Forces: A Guide to Self-Protection", "Break with a Banshee", "Gadding with Ghouls", "Holidays with Hags", "Travels with Trolls", "Voyages with Vampires", "Wanderings with Werewolves", "Year with a Yeti", "Defensive Magical Theory", "Dark Arts Defence: Basics for Beginners", "Confronting the Faceless", "The Standard Book of Spells" ],
26 | equipment: [ "Wand", "Parchment", "Quill" ]
27 | },
28 |
29 | {
30 | name: "Broom Flight",
31 | professor: "Madam Hooch",
32 | description: "students learn how to fly broomsticks",
33 | books: ["Quidditch Through the Ages"],
34 | equipment: [ "Broomstick", "Wand" ],
35 | },
36 |
37 | {
38 | name: "Herbology",
39 | professor: "Pomona Sprout",
40 | description: "Students learn to care for and utilize plants, and learn about their magical properties, and what they are used for. Many plants provide ingredients for potions and medicine, while others have magical effects of their own right.",
41 | books: [ "One Thousand Magical Herbs and Fungi", "Flesh-Eating Trees of the World" ],
42 | equipment: [ "Dragon-hide gloves", "Earmuffs", "Dragon dung compost", "Mooncalf dung", "Wand" ],
43 |
44 | },
45 |
46 | {
47 | name: "History of Magic",
48 | professor: "Cuthbert Binns",
49 | description: "The course is a study of magical history.",
50 | books: [ "A History of Magic" ],
51 | equipment: [],
52 | },
53 |
54 | {
55 | name: "Potions",
56 | professor: "Severus Snape ",
57 | description: "Students learn the correct way to brew potions, following specific recipes and using various magical ingredients to create the potions, starting with simple ones first and moving to more advanced ones as they progress in knowledge.",
58 | books: ["Magical Drafts and Potions", "Advanced Potion Making"],
59 | equipment: ["Cauldron", "Brass scales", "Phials", "Various ingredients"],
60 | },
61 |
62 | {
63 | name: "Transfiguration",
64 | professor: "Minerva McGonagall",
65 | description: "Students are taught the art of changing of the form and appearance of an object. There are limits to Transfiguration, which are governed by Gamp's Law of Elemental Transfiguration.",
66 | books: [ "A Beginner's Guide to Transfiguration by Emeric Switch", "Intermediate Transfiguration", "A Guide to Advanced Transfiguration" ],
67 | equipment: ["Wand"],
68 | },
69 |
70 |
71 | // electives after 3rd year
72 | { name: "Study of Ancient Runes"},
73 | { name: "Arithmancy"},
74 | { name: "Muggle Studies"},
75 | { name: "Care of Magical Creatures"},
76 | { name: "Divination"},
77 | // 6th year
78 | { name: "Apparition"},
79 | { name: "Alchemy"},
80 | // Extra-curricular subjects
81 | { name: "Ancient Studies"},
82 | { name: "Art"},
83 | { name: "Earth Magic"},
84 | { name: "Muggle Art"},
85 | { name: "Music"},
86 | { name: "Muggle Music"},
87 | { name: "Ghoul Studies"},
88 | { name: "Magical Theory"},
89 | { name: "Xylomancy"},
90 | { name: "Frog Choir"},
91 | { name: "Hogwarts orchestra"},
92 | ];
93 |
94 | var catalogData = [
95 | {id: "RUN105", name: "Ancient Runes", startTime: new Date(0,0,0,13), professor: "Bathsheba Babbling", credits: 3 },
96 | {id: "AR205", name: "Arithmancy", startTime: new Date(0,0,0,9), professor: "Septima Vector", credits: 3 },
97 | {id: "AST101", name: "Astronomy", startTime: new Date(0,0,0, 11), professor: "Aurora Sinistra", credits: 3 },
98 | {id: "MC101", name: "Care of Magical Creatures", startTime: new Date(0,0,0,14), professor: "Rubeus Hagrid", credits: 2 },
99 | {id: "CH105", name: "Charms", startTime: new Date(0,0,0,11), professor: "Filius Flitwick", credits: 3 },
100 | {id: "DDA302-10", name: "Defence Against the Dark Arts", startTime: new Date(0,0,0,10), professor: "Severus Snape", credits: 4 },
101 | {id: "DDA302-13", name: "Defence Against the Dark Arts", startTime: new Date(0,0,0,13), professor: " Quirinus Quirrell", credits: 4 },
102 | {id: "DIV201-13", name: "Divination", startTime: new Date(0,0,0,13), professor: " Sibyll Trelawney", credits: 3 },
103 | {id: "DIV201-10", name: "Divination", startTime: new Date(0,0,0,10), professor: "Firenze (Centaur)", credits: 3 },
104 | {id: "FLY101", name: "Flying", startTime: new Date(0,0,0,9), professor: "Madam Hooch", credits: 2 },
105 | {id: "HERB201", name: "Herbology", startTime: new Date(0,0,0,14), professor: "Pomona Sprout", credits: 4 },
106 | {id: "HM101", name: "History of Magic", startTime: new Date(0,0,0,11), professor: "Cuthbert Binns", credits: 3 },
107 | {id: "MUG101", name: "Muggle Studies", startTime: new Date(0,0,0,9), professor: "Alecto Carrow", credits: 2 },
108 | {id: "POT108", name: "Potions", startTime: new Date(0,0,0,15), professor: "Severus Snape", credits: 4 },
109 | {id: "TRN205", name: "Transfiguration", startTime: new Date(0,0,0,13), professor: "Minerva McGonagall", credits: 4 }
110 | ]
111 |
112 |
113 | return {
114 | getCatalog: function() {
115 | return catalogData;
116 | },
117 |
118 | getCourse: function(courseId) {
119 | return _.findWhere(catalogData, {id: courseId});
120 | }
121 | };
122 | });
123 |
124 |
--------------------------------------------------------------------------------
/wip/app/catalog/js/catalog.js:
--------------------------------------------------------------------------------
1 | var catalog =
2 | [
3 | {
4 | "course": "Astronomy",
5 | "teacher": "Aurora Sinistra",
6 | "description": "During the first year of studies at Hogwarts, students must study the night skies through their telescopes every Wednesday at midnight and learn the different names of the stars and the movements of the planets.",
7 | "equipment": ["telescopes", "hourglass"],
8 | },
9 |
10 | {
11 | "course": "Charms",
12 | "teacher": "Filius Flitwick",
13 | "description": "Spells you will learn: Levitation Charm, Wand-Lighting Charm, Lumos Solem, Fire-Making Spell, Softening Charm, Severing Charm, Unlocking Charm, Locking Spell, Mending Charm",
14 | "books": ["The Standard Book of Spells", "Quintessence: A Quest"],
15 | },
16 |
17 | {
18 | "course": "Defence Against the Dark Arts",
19 | "instructor": "",
20 | "description": "Students learn how to magically defend themselves against Dark Creatures, Dark Arts and against the other Charms.",
21 | "books": [
22 | "The Dark Forces: A Guide to Self-Protection", "Break with a Banshee", "Gadding with Ghouls", "Holidays with Hags", "Travels with Trolls", "Voyages with Vampires", "Wanderings with Werewolves", "Year with a Yeti", "Defensive Magical Theory", "Dark Arts Defence: Basics for Beginners", "Confronting the Faceless", "The Standard Book of Spells" ],
23 | "equipment": [ "Wand", "Books", "Parchment", "Quill" ]
24 | },
25 |
26 | {
27 | "course": "Broom Flight",
28 | "instructor": "Madam Hooch",
29 | "description": "students learn how to fly broomsticks",
30 | "books": ["Quidditch Through the Ages"],
31 | "equipment": [ "Broomstick", "Wand" ],
32 | },
33 |
34 | {
35 | "course": "Herbology",
36 | "instructor": "Pomona Sprout",
37 | "description": "Students learn to care for and utilize plants, and learn about their magical properties, and what they are used for. Many plants provide ingredients for potions and medicine, while others have magical effects of their own right.",
38 | "books": [ "One Thousand Magical Herbs and Fungi", "Flesh-Eating Trees of the World" ],
39 | "equipment": [ "Dragon-hide gloves", "Earmuffs", "Dragon dung compost", "Mooncalf dung", "Wand" ],
40 |
41 | },
42 |
43 | {
44 | "course": "History of Magic",
45 | "instructor": "Cuthbert Binns",
46 | "description": "The course is a study of magical history.",
47 | "books": [ "A History of Magic" ],
48 | "equipment": [],
49 | },
50 |
51 | {
52 | "course": "Potions",
53 | "instructor": "Severus Snape ",
54 | "description": "Students learn the correct way to brew potions, following specific recipes and using various magical ingredients to create the potions, starting with simple ones first and moving to more advanced ones as they progress in knowledge.",
55 | "books": ["Magical Drafts and Potions", "Advanced Potion Making"],
56 | "equipment": ["Cauldron", "Brass scales", "Phials", "Various ingredients"],
57 | },
58 |
59 | {
60 | "course": "Transfiguration",
61 | "instructor": "Minerva McGonagall",
62 | "description": "Students are taught the art of changing of the form and appearance of an object. There are limits to Transfiguration, which are governed by Gamp's Law of Elemental Transfiguration.",
63 | "books": [ "A Beginner's Guide to Transfiguration by Emeric Switch", "Intermediate Transfiguration", "A Guide to Advanced Transfiguration" ],
64 | "equipment": ["Wand"],
65 | },
66 |
67 |
68 | // electives after 3rd year
69 | { "course": "Study of Ancient Runes"},
70 | { "course": "Arithmancy"},
71 | { "course": "Muggle Studies"},
72 | { "course": "Care of Magical Creatures"},
73 | { "course": "Divination"},
74 | // 6th year
75 | { "course": "Apparition"},
76 | { "course": "Alchemy"},
77 | // Extra-curricular subjects
78 | { "course": "Ancient Studies"},
79 | { "course": "Art"},
80 | { "course": "Earth Magic"},
81 | { "course": "Muggle Art"},
82 | { "course": "Music"},
83 | { "course": "Muggle Music"},
84 | { "course": "Ghoul Studies"},
85 | { "course": "Magical Theory"},
86 | { "course": "Xylomancy"},
87 | { "course": "Frog Choir"},
88 | { "course": "Hogwarts orchestra"},
89 | ]
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
--------------------------------------------------------------------------------
/wip/app/css/bootstrap-theme.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap v3.1.1 (http://getbootstrap.com)
3 | * Copyright 2011-2014 Twitter, Inc.
4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
5 | */
6 |
7 | .btn-default,.btn-primary,.btn-success,.btn-info,.btn-warning,.btn-danger{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-default:active,.btn-primary:active,.btn-success:active,.btn-info:active,.btn-warning:active,.btn-danger:active,.btn-default.active,.btn-primary.active,.btn-success.active,.btn-info.active,.btn-warning.active,.btn-danger.active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn:active,.btn.active{background-image:none}.btn-default{background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#dbdbdb;text-shadow:0 1px 0 #fff;border-color:#ccc}.btn-default:hover,.btn-default:focus{background-color:#e0e0e0;background-position:0 -15px}.btn-default:active,.btn-default.active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-primary{background-image:-webkit-linear-gradient(top,#428bca 0,#2d6ca2 100%);background-image:linear-gradient(to bottom,#428bca 0,#2d6ca2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff2d6ca2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#2b669a}.btn-primary:hover,.btn-primary:focus{background-color:#2d6ca2;background-position:0 -15px}.btn-primary:active,.btn-primary.active{background-color:#2d6ca2;border-color:#2b669a}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:hover,.btn-success:focus{background-color:#419641;background-position:0 -15px}.btn-success:active,.btn-success.active{background-color:#419641;border-color:#3e8f3e}.btn-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:hover,.btn-info:focus{background-color:#2aabd2;background-position:0 -15px}.btn-info:active,.btn-info.active{background-color:#2aabd2;border-color:#28a4c9}.btn-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:hover,.btn-warning:focus{background-color:#eb9316;background-position:0 -15px}.btn-warning:active,.btn-warning.active{background-color:#eb9316;border-color:#e38d13}.btn-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:hover,.btn-danger:focus{background-color:#c12e2a;background-position:0 -15px}.btn-danger:active,.btn-danger.active{background-color:#c12e2a;border-color:#b92c28}.thumbnail,.img-thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-color:#e8e8e8}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{background-image:-webkit-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:linear-gradient(to bottom,#428bca 0,#357ebd 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);background-color:#357ebd}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075)}.navbar-default .navbar-nav>.active>a{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f3f3f3 100%);background-image:linear-gradient(to bottom,#ebebeb 0,#f3f3f3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff3f3f3', GradientType=0);-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.075);box-shadow:inset 0 3px 9px rgba(0,0,0,.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.navbar-inverse .navbar-nav>.active>a{background-image:-webkit-linear-gradient(top,#222 0,#282828 100%);background-image:linear-gradient(to bottom,#222 0,#282828 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff282828', GradientType=0);-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.25);box-shadow:inset 0 3px 9px rgba(0,0,0,.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-static-top,.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}.alert{text-shadow:0 1px 0 rgba(255,255,255,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0)}.progress-bar{background-image:-webkit-linear-gradient(top,#428bca 0,#3071a9 100%);background-image:linear-gradient(to bottom,#428bca 0,#3071a9 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0)}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0)}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0)}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0)}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{text-shadow:0 -1px 0 #3071a9;background-image:-webkit-linear-gradient(top,#428bca 0,#3278b3 100%);background-image:linear-gradient(to bottom,#428bca 0,#3278b3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0);border-color:#3278b3}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.05);box-shadow:0 1px 2px rgba(0,0,0,.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0)}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:linear-gradient(to bottom,#428bca 0,#357ebd 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0)}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0)}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0)}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0)}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0)}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)}
--------------------------------------------------------------------------------
/wip/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Angular Hogwarts TDD Kata
6 |
7 |
8 |
9 |
10 |
24 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/wip/app/lib/angularjs/angular-ui-router.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * State-based routing for AngularJS
3 | * @version v0.2.10
4 | * @link http://angular-ui.github.com/
5 | * @license MIT License, http://www.opensource.org/licenses/MIT
6 | */
7 | "undefined"!=typeof module&&"undefined"!=typeof exports&&module.exports===exports&&(module.exports="ui.router"),function(a,b,c){"use strict";function d(a,b){return I(new(I(function(){},{prototype:a})),b)}function e(a){return H(arguments,function(b){b!==a&&H(b,function(b,c){a.hasOwnProperty(c)||(a[c]=b)})}),a}function f(a,b){var c=[];for(var d in a.path){if(a.path[d]!==b.path[d])break;c.push(a.path[d])}return c}function g(a,b){if(Array.prototype.indexOf)return a.indexOf(b,Number(arguments[2])||0);var c=a.length>>>0,d=Number(arguments[2])||0;for(d=0>d?Math.ceil(d):Math.floor(d),0>d&&(d+=c);c>d;d++)if(d in a&&a[d]===b)return d;return-1}function h(a,b,c,d){var e,h=f(c,d),i={},j=[];for(var k in h)if(h[k].params&&h[k].params.length){e=h[k].params;for(var l in e)g(j,e[l])>=0||(j.push(e[l]),i[e[l]]=a[e[l]])}return I({},i,b)}function i(a,b){var c={};return H(a,function(a){var d=b[a];c[a]=null!=d?String(d):null}),c}function j(a,b,c){if(!c){c=[];for(var d in a)c.push(d)}for(var e=0;e "));if(o[c]=d,E(a))m.push(c,[function(){return b.get(a)}],h);else{var e=b.annotate(a);H(e,function(a){a!==c&&g.hasOwnProperty(a)&&k(g[a],a)}),m.push(c,a,e)}n.pop(),o[c]=f}}function l(a){return F(a)&&a.then&&a.$$promises}if(!F(g))throw new Error("'invocables' must be an object");var m=[],n=[],o={};return H(g,k),g=n=o=null,function(d,f,g){function h(){--s||(t||e(r,f.$$values),p.$$values=r,p.$$promises=!0,o.resolve(r))}function k(a){p.$$failure=a,o.reject(a)}function n(c,e,f){function i(a){l.reject(a),k(a)}function j(){if(!C(p.$$failure))try{l.resolve(b.invoke(e,g,r)),l.promise.then(function(a){r[c]=a,h()},i)}catch(a){i(a)}}var l=a.defer(),m=0;H(f,function(a){q.hasOwnProperty(a)&&!d.hasOwnProperty(a)&&(m++,q[a].then(function(b){r[a]=b,--m||j()},i))}),m||j(),q[c]=l.promise}if(l(d)&&g===c&&(g=f,f=d,d=null),d){if(!F(d))throw new Error("'locals' must be an object")}else d=i;if(f){if(!l(f))throw new Error("'parent' must be a promise returned by $resolve.resolve()")}else f=j;var o=a.defer(),p=o.promise,q=p.$$promises={},r=I({},d),s=1+m.length/3,t=!1;if(C(f.$$failure))return k(f.$$failure),p;f.$$values?(t=e(r,f.$$values),h()):(I(q,f.$$promises),f.then(h,k));for(var u=0,v=m.length;v>u;u+=3)d.hasOwnProperty(m[u])?h():n(m[u],m[u+1],m[u+2]);return p}},this.resolve=function(a,b,c,d){return this.study(a)(b,c,d)}}function m(a,b,c){this.fromConfig=function(a,b,c){return C(a.template)?this.fromString(a.template,b):C(a.templateUrl)?this.fromUrl(a.templateUrl,b):C(a.templateProvider)?this.fromProvider(a.templateProvider,b,c):null},this.fromString=function(a,b){return D(a)?a(b):a},this.fromUrl=function(c,d){return D(c)&&(c=c(d)),null==c?null:a.get(c,{cache:b}).then(function(a){return a.data})},this.fromProvider=function(a,b,d){return c.invoke(a,null,d||{params:b})}}function n(a){function b(b){if(!/^\w+(-+\w+)*$/.test(b))throw new Error("Invalid parameter name '"+b+"' in pattern '"+a+"'");if(f[b])throw new Error("Duplicate parameter name '"+b+"' in pattern '"+a+"'");f[b]=!0,j.push(b)}function c(a){return a.replace(/[\\\[\]\^$*+?.()|{}]/g,"\\$&")}var d,e=/([:*])(\w+)|\{(\w+)(?:\:((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g,f={},g="^",h=0,i=this.segments=[],j=this.params=[];this.source=a;for(var k,l,m;(d=e.exec(a))&&(k=d[2]||d[3],l=d[4]||("*"==d[1]?".*":"[^/]*"),m=a.substring(h,d.index),!(m.indexOf("?")>=0));)g+=c(m)+"("+l+")",b(k),i.push(m),h=e.lastIndex;m=a.substring(h);var n=m.indexOf("?");if(n>=0){var o=this.sourceSearch=m.substring(n);m=m.substring(0,n),this.sourcePath=a.substring(0,h+n),H(o.substring(1).split(/[&?]/),b)}else this.sourcePath=a,this.sourceSearch="";g+=c(m)+"$",i.push(m),this.regexp=new RegExp(g),this.prefix=i[0]}function o(){this.compile=function(a){return new n(a)},this.isMatcher=function(a){return F(a)&&D(a.exec)&&D(a.format)&&D(a.concat)},this.$get=function(){return this}}function p(a){function b(a){var b=/^\^((?:\\[^a-zA-Z0-9]|[^\\\[\]\^$*+?.()|{}]+)*)/.exec(a.source);return null!=b?b[1].replace(/\\(.)/g,"$1"):""}function c(a,b){return a.replace(/\$(\$|\d{1,2})/,function(a,c){return b["$"===c?0:Number(c)]})}function d(a,b,c){if(!c)return!1;var d=a.invoke(b,b,{$match:c});return C(d)?d:!0}var e=[],f=null;this.rule=function(a){if(!D(a))throw new Error("'rule' must be a function");return e.push(a),this},this.otherwise=function(a){if(E(a)){var b=a;a=function(){return b}}else if(!D(a))throw new Error("'rule' must be a function");return f=a,this},this.when=function(e,f){var g,h=E(f);if(E(e)&&(e=a.compile(e)),!h&&!D(f)&&!G(f))throw new Error("invalid 'handler' in when()");var i={matcher:function(b,c){return h&&(g=a.compile(c),c=["$match",function(a){return g.format(a)}]),I(function(a,e){return d(a,c,b.exec(e.path(),e.search()))},{prefix:E(b.prefix)?b.prefix:""})},regex:function(a,e){if(a.global||a.sticky)throw new Error("when() RegExp must not be global or sticky");return h&&(g=e,e=["$match",function(a){return c(g,a)}]),I(function(b,c){return d(b,e,a.exec(c.path()))},{prefix:b(a)})}},j={matcher:a.isMatcher(e),regex:e instanceof RegExp};for(var k in j)if(j[k])return this.rule(i[k](e,f));throw new Error("invalid 'what' in when()")},this.$get=["$location","$rootScope","$injector",function(a,b,c){function d(b){function d(b){var d=b(c,a);return d?(E(d)&&a.replace().url(d),!0):!1}if(!b||!b.defaultPrevented){var g,h=e.length;for(g=0;h>g;g++)if(d(e[g]))return;f&&d(f)}}return b.$on("$locationChangeSuccess",d),{sync:function(){d()}}}]}function q(a,e,f){function g(a){return 0===a.indexOf(".")||0===a.indexOf("^")}function l(a,b){var d=E(a),e=d?a:a.name,f=g(e);if(f){if(!b)throw new Error("No reference point given for path '"+e+"'");for(var h=e.split("."),i=0,j=h.length,k=b;j>i;i++)if(""!==h[i]||0!==i){if("^"!==h[i])break;if(!k.parent)throw new Error("Path '"+e+"' not valid for state '"+b.name+"'");k=k.parent}else k=b;h=h.slice(i).join("."),e=k.name+(k.name&&h?".":"")+h}var l=w[e];return!l||!d&&(d||l!==a&&l.self!==a)?c:l}function m(a,b){x[a]||(x[a]=[]),x[a].push(b)}function n(b){b=d(b,{self:b,resolve:b.resolve||{},toString:function(){return this.name}});var c=b.name;if(!E(c)||c.indexOf("@")>=0)throw new Error("State must have a valid name");if(w.hasOwnProperty(c))throw new Error("State '"+c+"'' is already defined");var e=-1!==c.indexOf(".")?c.substring(0,c.lastIndexOf(".")):E(b.parent)?b.parent:"";if(e&&!w[e])return m(e,b.self);for(var f in z)D(z[f])&&(b[f]=z[f](b,z.$delegates[f]));if(w[c]=b,!b[y]&&b.url&&a.when(b.url,["$match","$stateParams",function(a,c){v.$current.navigable==b&&j(a,c)||v.transitionTo(b,a,{location:!1})}]),x[c])for(var g=0;g-1}function p(a){var b=a.split("."),c=v.$current.name.split(".");if("**"===b[0]&&(c=c.slice(c.indexOf(b[1])),c.unshift("**")),"**"===b[b.length-1]&&(c.splice(c.indexOf(b[b.length-2])+1,Number.MAX_VALUE),c.push("**")),b.length!=c.length)return!1;for(var d=0,e=b.length;e>d;d++)"*"===b[d]&&(c[d]="*");return c.join("")===b.join("")}function q(a,b){return E(a)&&!C(b)?z[a]:D(b)&&E(a)?(z[a]&&!z.$delegates[a]&&(z.$delegates[a]=z[a]),z[a]=b,this):this}function r(a,b){return F(a)?b=a:b.name=a,n(b),this}function s(a,e,g,m,n,q,r,s,x){function z(){r.url()!==M&&(r.url(M),r.replace())}function A(a,c,d,f,h){var i=d?c:k(a.params,c),j={$stateParams:i};h.resolve=n.resolve(a.resolve,j,h.resolve,a);var l=[h.resolve.then(function(a){h.globals=a})];return f&&l.push(f),H(a.views,function(c,d){var e=c.resolve&&c.resolve!==a.resolve?c.resolve:{};e.$template=[function(){return g.load(d,{view:c,locals:j,params:i,notify:!1})||""}],l.push(n.resolve(e,j,h.resolve,a).then(function(f){if(D(c.controllerProvider)||G(c.controllerProvider)){var g=b.extend({},e,j);f.$$controller=m.invoke(c.controllerProvider,null,g)}else f.$$controller=c.controller;f.$$state=a,f.$$controllerAs=c.controllerAs,h[d]=f}))}),e.all(l).then(function(){return h})}var B=e.reject(new Error("transition superseded")),F=e.reject(new Error("transition prevented")),K=e.reject(new Error("transition aborted")),L=e.reject(new Error("transition failed")),M=r.url(),N=x.baseHref();return u.locals={resolve:null,globals:{$stateParams:{}}},v={params:{},current:u.self,$current:u,transition:null},v.reload=function(){v.transitionTo(v.current,q,{reload:!0,inherit:!1,notify:!1})},v.go=function(a,b,c){return this.transitionTo(a,b,I({inherit:!0,relative:v.$current},c))},v.transitionTo=function(b,c,f){c=c||{},f=I({location:!0,inherit:!1,relative:null,notify:!0,reload:!1,$retry:!1},f||{});var g,k=v.$current,n=v.params,o=k.path,p=l(b,f.relative);if(!C(p)){var s={to:b,toParams:c,options:f};if(g=a.$broadcast("$stateNotFound",s,k.self,n),g.defaultPrevented)return z(),K;if(g.retry){if(f.$retry)return z(),L;var w=v.transition=e.when(g.retry);return w.then(function(){return w!==v.transition?B:(s.options.$retry=!0,v.transitionTo(s.to,s.toParams,s.options))},function(){return K}),z(),w}if(b=s.to,c=s.toParams,f=s.options,p=l(b,f.relative),!C(p)){if(f.relative)throw new Error("Could not resolve '"+b+"' from state '"+f.relative+"'");throw new Error("No such state '"+b+"'")}}if(p[y])throw new Error("Cannot transition to abstract state '"+b+"'");f.inherit&&(c=h(q,c||{},v.$current,p)),b=p;var x,D,E=b.path,G=u.locals,H=[];for(x=0,D=E[x];D&&D===o[x]&&j(c,n,D.ownParams)&&!f.reload;x++,D=E[x])G=H[x]=D.locals;if(t(b,k,G,f))return b.self.reloadOnSearch!==!1&&z(),v.transition=null,e.when(v.current);if(c=i(b.params,c||{}),f.notify&&(g=a.$broadcast("$stateChangeStart",b.self,c,k.self,n),g.defaultPrevented))return z(),F;for(var N=e.when(G),O=x;O=x;d--)g=o[d],g.self.onExit&&m.invoke(g.self.onExit,g.self,g.locals.globals),g.locals=null;for(d=x;d1||b.ctrlKey||b.metaKey||b.shiftKey||f.attr("target")||(c(function(){a.go(i.state,j,o)}),b.preventDefault())})}}}function y(a,b,c){return{restrict:"A",controller:["$scope","$element","$attrs",function(d,e,f){function g(){a.$current.self===i&&h()?e.addClass(l):e.removeClass(l)}function h(){return!k||j(k,b)}var i,k,l;l=c(f.uiSrefActive||"",!1)(d),this.$$setStateInfo=function(b,c){i=a.get(b,w(e)),k=c,g()},d.$on("$stateChangeSuccess",g)}]}}function z(a){return function(b){return a.is(b)}}function A(a){return function(b){return a.includes(b)}}function B(a,b){function e(a){this.locals=a.locals.globals,this.params=this.locals.$stateParams}function f(){this.locals=null,this.params=null}function g(c,g){if(null!=g.redirectTo){var h,j=g.redirectTo;if(E(j))h=j;else{if(!D(j))throw new Error("Invalid 'redirectTo' in when()");h=function(a,b){return j(a,b.path(),b.search())}}b.when(c,h)}else a.state(d(g,{parent:null,name:"route:"+encodeURIComponent(c),url:c,onEnter:e,onExit:f}));return i.push(g),this}function h(a,b,d){function e(a){return""!==a.name?a:c}var f={routes:i,params:d,current:c};return b.$on("$stateChangeStart",function(a,c,d,f){b.$broadcast("$routeChangeStart",e(c),e(f))}),b.$on("$stateChangeSuccess",function(a,c,d,g){f.current=e(c),b.$broadcast("$routeChangeSuccess",e(c),e(g)),J(d,f.params)}),b.$on("$stateChangeError",function(a,c,d,f,g,h){b.$broadcast("$routeChangeError",e(c),e(f),h)}),f}var i=[];e.$inject=["$$state"],this.when=g,this.$get=h,h.$inject=["$state","$rootScope","$routeParams"]}var C=b.isDefined,D=b.isFunction,E=b.isString,F=b.isObject,G=b.isArray,H=b.forEach,I=b.extend,J=b.copy;b.module("ui.router.util",["ng"]),b.module("ui.router.router",["ui.router.util"]),b.module("ui.router.state",["ui.router.router","ui.router.util"]),b.module("ui.router",["ui.router.state"]),b.module("ui.router.compat",["ui.router"]),l.$inject=["$q","$injector"],b.module("ui.router.util").service("$resolve",l),m.$inject=["$http","$templateCache","$injector"],b.module("ui.router.util").service("$templateFactory",m),n.prototype.concat=function(a){return new n(this.sourcePath+a+this.sourceSearch)},n.prototype.toString=function(){return this.source},n.prototype.exec=function(a,b){var c=this.regexp.exec(a);if(!c)return null;var d,e=this.params,f=e.length,g=this.segments.length-1,h={};if(g!==c.length-1)throw new Error("Unbalanced capture group in route '"+this.source+"'");for(d=0;g>d;d++)h[e[d]]=c[d+1];for(;f>d;d++)h[e[d]]=b[e[d]];return h},n.prototype.parameters=function(){return this.params},n.prototype.format=function(a){var b=this.segments,c=this.params;if(!a)return b.join("");var d,e,f,g=b.length-1,h=c.length,i=b[0];for(d=0;g>d;d++)f=a[c[d]],null!=f&&(i+=encodeURIComponent(f)),i+=b[d+1];for(;h>d;d++)f=a[c[d]],null!=f&&(i+=(e?"&":"?")+c[d]+"="+encodeURIComponent(f),e=!0);return i},b.module("ui.router.util").provider("$urlMatcherFactory",o),p.$inject=["$urlMatcherFactoryProvider"],b.module("ui.router.router").provider("$urlRouter",p),q.$inject=["$urlRouterProvider","$urlMatcherFactoryProvider","$locationProvider"],b.module("ui.router.state").value("$stateParams",{}).provider("$state",q),r.$inject=[],b.module("ui.router.state").provider("$view",r),b.module("ui.router.state").provider("$uiViewScroll",s),t.$inject=["$state","$injector","$uiViewScroll"],u.$inject=["$compile","$controller","$state"],b.module("ui.router.state").directive("uiView",t),b.module("ui.router.state").directive("uiView",u),x.$inject=["$state","$timeout"],y.$inject=["$state","$stateParams","$interpolate"],b.module("ui.router.state").directive("uiSref",x).directive("uiSrefActive",y),z.$inject=["$state"],A.$inject=["$state"],b.module("ui.router.state").filter("isState",z).filter("includedByState",A),B.$inject=["$stateProvider","$urlRouterProvider"],b.module("ui.router.compat").provider("$route",B).directive("ngView",t)}(window,window.angular);
--------------------------------------------------------------------------------
/wip/app/lib/underscore-min.js:
--------------------------------------------------------------------------------
1 | // Underscore.js 1.6.0
2 | // http://underscorejs.org
3 | // (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
4 | // Underscore may be freely distributed under the MIT license.
5 | (function(){var n=this,t=n._,r={},e=Array.prototype,u=Object.prototype,i=Function.prototype,a=e.push,o=e.slice,c=e.concat,l=u.toString,f=u.hasOwnProperty,s=e.forEach,p=e.map,h=e.reduce,v=e.reduceRight,g=e.filter,d=e.every,m=e.some,y=e.indexOf,b=e.lastIndexOf,x=Array.isArray,w=Object.keys,_=i.bind,j=function(n){return n instanceof j?n:this instanceof j?void(this._wrapped=n):new j(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=j),exports._=j):n._=j,j.VERSION="1.6.0";var A=j.each=j.forEach=function(n,t,e){if(null==n)return n;if(s&&n.forEach===s)n.forEach(t,e);else if(n.length===+n.length){for(var u=0,i=n.length;i>u;u++)if(t.call(e,n[u],u,n)===r)return}else for(var a=j.keys(n),u=0,i=a.length;i>u;u++)if(t.call(e,n[a[u]],a[u],n)===r)return;return n};j.map=j.collect=function(n,t,r){var e=[];return null==n?e:p&&n.map===p?n.map(t,r):(A(n,function(n,u,i){e.push(t.call(r,n,u,i))}),e)};var O="Reduce of empty array with no initial value";j.reduce=j.foldl=j.inject=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),h&&n.reduce===h)return e&&(t=j.bind(t,e)),u?n.reduce(t,r):n.reduce(t);if(A(n,function(n,i,a){u?r=t.call(e,r,n,i,a):(r=n,u=!0)}),!u)throw new TypeError(O);return r},j.reduceRight=j.foldr=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),v&&n.reduceRight===v)return e&&(t=j.bind(t,e)),u?n.reduceRight(t,r):n.reduceRight(t);var i=n.length;if(i!==+i){var a=j.keys(n);i=a.length}if(A(n,function(o,c,l){c=a?a[--i]:--i,u?r=t.call(e,r,n[c],c,l):(r=n[c],u=!0)}),!u)throw new TypeError(O);return r},j.find=j.detect=function(n,t,r){var e;return k(n,function(n,u,i){return t.call(r,n,u,i)?(e=n,!0):void 0}),e},j.filter=j.select=function(n,t,r){var e=[];return null==n?e:g&&n.filter===g?n.filter(t,r):(A(n,function(n,u,i){t.call(r,n,u,i)&&e.push(n)}),e)},j.reject=function(n,t,r){return j.filter(n,function(n,e,u){return!t.call(r,n,e,u)},r)},j.every=j.all=function(n,t,e){t||(t=j.identity);var u=!0;return null==n?u:d&&n.every===d?n.every(t,e):(A(n,function(n,i,a){return(u=u&&t.call(e,n,i,a))?void 0:r}),!!u)};var k=j.some=j.any=function(n,t,e){t||(t=j.identity);var u=!1;return null==n?u:m&&n.some===m?n.some(t,e):(A(n,function(n,i,a){return u||(u=t.call(e,n,i,a))?r:void 0}),!!u)};j.contains=j.include=function(n,t){return null==n?!1:y&&n.indexOf===y?n.indexOf(t)!=-1:k(n,function(n){return n===t})},j.invoke=function(n,t){var r=o.call(arguments,2),e=j.isFunction(t);return j.map(n,function(n){return(e?t:n[t]).apply(n,r)})},j.pluck=function(n,t){return j.map(n,j.property(t))},j.where=function(n,t){return j.filter(n,j.matches(t))},j.findWhere=function(n,t){return j.find(n,j.matches(t))},j.max=function(n,t,r){if(!t&&j.isArray(n)&&n[0]===+n[0]&&n.length<65535)return Math.max.apply(Math,n);var e=-1/0,u=-1/0;return A(n,function(n,i,a){var o=t?t.call(r,n,i,a):n;o>u&&(e=n,u=o)}),e},j.min=function(n,t,r){if(!t&&j.isArray(n)&&n[0]===+n[0]&&n.length<65535)return Math.min.apply(Math,n);var e=1/0,u=1/0;return A(n,function(n,i,a){var o=t?t.call(r,n,i,a):n;u>o&&(e=n,u=o)}),e},j.shuffle=function(n){var t,r=0,e=[];return A(n,function(n){t=j.random(r++),e[r-1]=e[t],e[t]=n}),e},j.sample=function(n,t,r){return null==t||r?(n.length!==+n.length&&(n=j.values(n)),n[j.random(n.length-1)]):j.shuffle(n).slice(0,Math.max(0,t))};var E=function(n){return null==n?j.identity:j.isFunction(n)?n:j.property(n)};j.sortBy=function(n,t,r){return t=E(t),j.pluck(j.map(n,function(n,e,u){return{value:n,index:e,criteria:t.call(r,n,e,u)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.index-t.index}),"value")};var F=function(n){return function(t,r,e){var u={};return r=E(r),A(t,function(i,a){var o=r.call(e,i,a,t);n(u,o,i)}),u}};j.groupBy=F(function(n,t,r){j.has(n,t)?n[t].push(r):n[t]=[r]}),j.indexBy=F(function(n,t,r){n[t]=r}),j.countBy=F(function(n,t){j.has(n,t)?n[t]++:n[t]=1}),j.sortedIndex=function(n,t,r,e){r=E(r);for(var u=r.call(e,t),i=0,a=n.length;a>i;){var o=i+a>>>1;r.call(e,n[o])t?[]:o.call(n,0,t)},j.initial=function(n,t,r){return o.call(n,0,n.length-(null==t||r?1:t))},j.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:o.call(n,Math.max(n.length-t,0))},j.rest=j.tail=j.drop=function(n,t,r){return o.call(n,null==t||r?1:t)},j.compact=function(n){return j.filter(n,j.identity)};var M=function(n,t,r){return t&&j.every(n,j.isArray)?c.apply(r,n):(A(n,function(n){j.isArray(n)||j.isArguments(n)?t?a.apply(r,n):M(n,t,r):r.push(n)}),r)};j.flatten=function(n,t){return M(n,t,[])},j.without=function(n){return j.difference(n,o.call(arguments,1))},j.partition=function(n,t){var r=[],e=[];return A(n,function(n){(t(n)?r:e).push(n)}),[r,e]},j.uniq=j.unique=function(n,t,r,e){j.isFunction(t)&&(e=r,r=t,t=!1);var u=r?j.map(n,r,e):n,i=[],a=[];return A(u,function(r,e){(t?e&&a[a.length-1]===r:j.contains(a,r))||(a.push(r),i.push(n[e]))}),i},j.union=function(){return j.uniq(j.flatten(arguments,!0))},j.intersection=function(n){var t=o.call(arguments,1);return j.filter(j.uniq(n),function(n){return j.every(t,function(t){return j.contains(t,n)})})},j.difference=function(n){var t=c.apply(e,o.call(arguments,1));return j.filter(n,function(n){return!j.contains(t,n)})},j.zip=function(){for(var n=j.max(j.pluck(arguments,"length").concat(0)),t=new Array(n),r=0;n>r;r++)t[r]=j.pluck(arguments,""+r);return t},j.object=function(n,t){if(null==n)return{};for(var r={},e=0,u=n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},j.indexOf=function(n,t,r){if(null==n)return-1;var e=0,u=n.length;if(r){if("number"!=typeof r)return e=j.sortedIndex(n,t),n[e]===t?e:-1;e=0>r?Math.max(0,u+r):r}if(y&&n.indexOf===y)return n.indexOf(t,r);for(;u>e;e++)if(n[e]===t)return e;return-1},j.lastIndexOf=function(n,t,r){if(null==n)return-1;var e=null!=r;if(b&&n.lastIndexOf===b)return e?n.lastIndexOf(t,r):n.lastIndexOf(t);for(var u=e?r:n.length;u--;)if(n[u]===t)return u;return-1},j.range=function(n,t,r){arguments.length<=1&&(t=n||0,n=0),r=arguments[2]||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=0,i=new Array(e);e>u;)i[u++]=n,n+=r;return i};var R=function(){};j.bind=function(n,t){var r,e;if(_&&n.bind===_)return _.apply(n,o.call(arguments,1));if(!j.isFunction(n))throw new TypeError;return r=o.call(arguments,2),e=function(){if(!(this instanceof e))return n.apply(t,r.concat(o.call(arguments)));R.prototype=n.prototype;var u=new R;R.prototype=null;var i=n.apply(u,r.concat(o.call(arguments)));return Object(i)===i?i:u}},j.partial=function(n){var t=o.call(arguments,1);return function(){for(var r=0,e=t.slice(),u=0,i=e.length;i>u;u++)e[u]===j&&(e[u]=arguments[r++]);for(;r=f?(clearTimeout(a),a=null,o=l,i=n.apply(e,u),e=u=null):a||r.trailing===!1||(a=setTimeout(c,f)),i}},j.debounce=function(n,t,r){var e,u,i,a,o,c=function(){var l=j.now()-a;t>l?e=setTimeout(c,t-l):(e=null,r||(o=n.apply(i,u),i=u=null))};return function(){i=this,u=arguments,a=j.now();var l=r&&!e;return e||(e=setTimeout(c,t)),l&&(o=n.apply(i,u),i=u=null),o}},j.once=function(n){var t,r=!1;return function(){return r?t:(r=!0,t=n.apply(this,arguments),n=null,t)}},j.wrap=function(n,t){return j.partial(t,n)},j.compose=function(){var n=arguments;return function(){for(var t=arguments,r=n.length-1;r>=0;r--)t=[n[r].apply(this,t)];return t[0]}},j.after=function(n,t){return function(){return--n<1?t.apply(this,arguments):void 0}},j.keys=function(n){if(!j.isObject(n))return[];if(w)return w(n);var t=[];for(var r in n)j.has(n,r)&&t.push(r);return t},j.values=function(n){for(var t=j.keys(n),r=t.length,e=new Array(r),u=0;r>u;u++)e[u]=n[t[u]];return e},j.pairs=function(n){for(var t=j.keys(n),r=t.length,e=new Array(r),u=0;r>u;u++)e[u]=[t[u],n[t[u]]];return e},j.invert=function(n){for(var t={},r=j.keys(n),e=0,u=r.length;u>e;e++)t[n[r[e]]]=r[e];return t},j.functions=j.methods=function(n){var t=[];for(var r in n)j.isFunction(n[r])&&t.push(r);return t.sort()},j.extend=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]=t[r]}),n},j.pick=function(n){var t={},r=c.apply(e,o.call(arguments,1));return A(r,function(r){r in n&&(t[r]=n[r])}),t},j.omit=function(n){var t={},r=c.apply(e,o.call(arguments,1));for(var u in n)j.contains(r,u)||(t[u]=n[u]);return t},j.defaults=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]===void 0&&(n[r]=t[r])}),n},j.clone=function(n){return j.isObject(n)?j.isArray(n)?n.slice():j.extend({},n):n},j.tap=function(n,t){return t(n),n};var S=function(n,t,r,e){if(n===t)return 0!==n||1/n==1/t;if(null==n||null==t)return n===t;n instanceof j&&(n=n._wrapped),t instanceof j&&(t=t._wrapped);var u=l.call(n);if(u!=l.call(t))return!1;switch(u){case"[object String]":return n==String(t);case"[object Number]":return n!=+n?t!=+t:0==n?1/n==1/t:n==+t;case"[object Date]":case"[object Boolean]":return+n==+t;case"[object RegExp]":return n.source==t.source&&n.global==t.global&&n.multiline==t.multiline&&n.ignoreCase==t.ignoreCase}if("object"!=typeof n||"object"!=typeof t)return!1;for(var i=r.length;i--;)if(r[i]==n)return e[i]==t;var a=n.constructor,o=t.constructor;if(a!==o&&!(j.isFunction(a)&&a instanceof a&&j.isFunction(o)&&o instanceof o)&&"constructor"in n&&"constructor"in t)return!1;r.push(n),e.push(t);var c=0,f=!0;if("[object Array]"==u){if(c=n.length,f=c==t.length)for(;c--&&(f=S(n[c],t[c],r,e)););}else{for(var s in n)if(j.has(n,s)&&(c++,!(f=j.has(t,s)&&S(n[s],t[s],r,e))))break;if(f){for(s in t)if(j.has(t,s)&&!c--)break;f=!c}}return r.pop(),e.pop(),f};j.isEqual=function(n,t){return S(n,t,[],[])},j.isEmpty=function(n){if(null==n)return!0;if(j.isArray(n)||j.isString(n))return 0===n.length;for(var t in n)if(j.has(n,t))return!1;return!0},j.isElement=function(n){return!(!n||1!==n.nodeType)},j.isArray=x||function(n){return"[object Array]"==l.call(n)},j.isObject=function(n){return n===Object(n)},A(["Arguments","Function","String","Number","Date","RegExp"],function(n){j["is"+n]=function(t){return l.call(t)=="[object "+n+"]"}}),j.isArguments(arguments)||(j.isArguments=function(n){return!(!n||!j.has(n,"callee"))}),"function"!=typeof/./&&(j.isFunction=function(n){return"function"==typeof n}),j.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},j.isNaN=function(n){return j.isNumber(n)&&n!=+n},j.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"==l.call(n)},j.isNull=function(n){return null===n},j.isUndefined=function(n){return n===void 0},j.has=function(n,t){return f.call(n,t)},j.noConflict=function(){return n._=t,this},j.identity=function(n){return n},j.constant=function(n){return function(){return n}},j.property=function(n){return function(t){return t[n]}},j.matches=function(n){return function(t){if(t===n)return!0;for(var r in n)if(n[r]!==t[r])return!1;return!0}},j.times=function(n,t,r){for(var e=Array(Math.max(0,n)),u=0;n>u;u++)e[u]=t.call(r,u);return e},j.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))},j.now=Date.now||function(){return(new Date).getTime()};var T={escape:{"&":"&","<":"<",">":">",'"':""","'":"'"}};T.unescape=j.invert(T.escape);var I={escape:new RegExp("["+j.keys(T.escape).join("")+"]","g"),unescape:new RegExp("("+j.keys(T.unescape).join("|")+")","g")};j.each(["escape","unescape"],function(n){j[n]=function(t){return null==t?"":(""+t).replace(I[n],function(t){return T[n][t]})}}),j.result=function(n,t){if(null==n)return void 0;var r=n[t];return j.isFunction(r)?r.call(n):r},j.mixin=function(n){A(j.functions(n),function(t){var r=j[t]=n[t];j.prototype[t]=function(){var n=[this._wrapped];return a.apply(n,arguments),z.call(this,r.apply(j,n))}})};var N=0;j.uniqueId=function(n){var t=++N+"";return n?n+t:t},j.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var q=/(.)^/,B={"'":"'","\\":"\\","\r":"r","\n":"n"," ":"t","\u2028":"u2028","\u2029":"u2029"},D=/\\|'|\r|\n|\t|\u2028|\u2029/g;j.template=function(n,t,r){var e;r=j.defaults({},r,j.templateSettings);var u=new RegExp([(r.escape||q).source,(r.interpolate||q).source,(r.evaluate||q).source].join("|")+"|$","g"),i=0,a="__p+='";n.replace(u,function(t,r,e,u,o){return a+=n.slice(i,o).replace(D,function(n){return"\\"+B[n]}),r&&(a+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'"),e&&(a+="'+\n((__t=("+e+"))==null?'':__t)+\n'"),u&&(a+="';\n"+u+"\n__p+='"),i=o+t.length,t}),a+="';\n",r.variable||(a="with(obj||{}){\n"+a+"}\n"),a="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+a+"return __p;\n";try{e=new Function(r.variable||"obj","_",a)}catch(o){throw o.source=a,o}if(t)return e(t,j);var c=function(n){return e.call(this,n,j)};return c.source="function("+(r.variable||"obj")+"){\n"+a+"}",c},j.chain=function(n){return j(n).chain()};var z=function(n){return this._chain?j(n).chain():n};j.mixin(j),A(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=e[n];j.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!=n&&"splice"!=n||0!==r.length||delete r[0],z.call(this,r)}}),A(["concat","join","slice"],function(n){var t=e[n];j.prototype[n]=function(){return z.call(this,t.apply(this._wrapped,arguments))}}),j.extend(j.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}}),"function"==typeof define&&define.amd&&define("underscore",[],function(){return j})}).call(this);
6 | //# sourceMappingURL=underscore-min.map
--------------------------------------------------------------------------------
/wip/app/routes.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | hogwartsApp.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider) {
4 |
5 | // For any unmatched url, redirect to /state1
6 | $urlRouterProvider.otherwise("/sorting");
7 |
8 | // Now set up the states
9 | $stateProvider
10 | .state('sorting', {
11 | url: "/sorting",
12 | templateUrl: "wizard/sorting-hat.html",
13 | controller: 'SortingHatController'
14 | })
15 | .state('catalog', {
16 | url: "/catalog",
17 | templateUrl: "catalog/catalog.html",
18 | controller: 'CatalogController'
19 | })
20 | .state('schedule', {
21 | url: "/schedule",
22 | templateUrl: "wizard/schedule.html",
23 | controller: 'ScheduleController'
24 | })
25 | }]);
--------------------------------------------------------------------------------
/wip/app/wizard/img/gryffindor.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jmcooper/angular-hogwarts-tdd-kata/7257a22ec2b87437d0b79e0977543e9515ca9f62/wip/app/wizard/img/gryffindor.jpg
--------------------------------------------------------------------------------
/wip/app/wizard/img/hufflepuff.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jmcooper/angular-hogwarts-tdd-kata/7257a22ec2b87437d0b79e0977543e9515ca9f62/wip/app/wizard/img/hufflepuff.jpg
--------------------------------------------------------------------------------
/wip/app/wizard/img/ravenclaw.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jmcooper/angular-hogwarts-tdd-kata/7257a22ec2b87437d0b79e0977543e9515ca9f62/wip/app/wizard/img/ravenclaw.jpg
--------------------------------------------------------------------------------
/wip/app/wizard/img/slytherin.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jmcooper/angular-hogwarts-tdd-kata/7257a22ec2b87437d0b79e0977543e9515ca9f62/wip/app/wizard/img/slytherin.jpg
--------------------------------------------------------------------------------
/wip/app/wizard/img/sorting-hat.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jmcooper/angular-hogwarts-tdd-kata/7257a22ec2b87437d0b79e0977543e9515ca9f62/wip/app/wizard/img/sorting-hat.jpg
--------------------------------------------------------------------------------
/wip/app/wizard/js/registration-service.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | hogwartsApp
4 | .factory('registrationService', function(catalogRepository, wizardRepository) {
5 | return {
6 | register: function(courseId) {
7 | var course = catalogRepository.getCourse(courseId);
8 | var wizard = wizardRepository.get();
9 |
10 | if (wizardIsRegisteredForAConflictingCourse(wizard, course))
11 | return {success: false, message: 'You are already registered for a course that starts at that time'};
12 |
13 | registerWizardForCourse(wizard, course);
14 |
15 | return {success: true, message: ''};
16 | }
17 | };
18 |
19 | function registerWizardForCourse(wizard, course) {
20 | wizard.courses[course.id] = course;
21 | wizardRepository.save(wizard);
22 | }
23 |
24 | function wizardIsRegisteredForAConflictingCourse(wizard, course) {
25 | var courses = _.values(wizard.courses);
26 | return _.some(courses, function(existingCourse) {
27 | return course.startTime.getTime() === existingCourse.startTime.getTime();
28 | });
29 | }
30 | });
31 |
32 |
--------------------------------------------------------------------------------
/wip/app/wizard/js/schedule-controller.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | hogwartsApp
4 | .controller("ScheduleController", function ($scope, wizardRepository) {
5 | $scope.wizard = wizardRepository.get();
6 | });
7 |
--------------------------------------------------------------------------------
/wip/app/wizard/js/wizard-repository.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | hogwartsApp.factory('wizardRepository', function() {
4 | var wizard = {courses: {}, house: ""};
5 |
6 | return {
7 | get: function() {
8 | return wizard;
9 | },
10 | save: function(updatedWizard) {
11 | wizard = updatedWizard;
12 | }
13 | };
14 | });
15 |
--------------------------------------------------------------------------------
/wip/app/wizard/schedule.html:
--------------------------------------------------------------------------------
1 |
2 |
Your Hogwarts schedule
3 |
4 |
5 |
6 |
7 |
8 |
9 | Course
10 | Time
11 | Professor
12 | Credits
13 |
14 |
15 |
16 |
17 | {{course.name}}
18 | {{course.startTime | date: 'h:mm a'}}
19 | {{course.professor}}
20 | {{course.credits}}
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/wip/app/wizard/sorting-hat.html:
--------------------------------------------------------------------------------
1 |
2 |
Welcome to Hogwarts, wizard!
3 |
Welcome to the wonderful world of hogwarts. Click the sorting hat to discover which house you will be assigned to.
4 |
5 |
6 |
7 |
8 |
You are assigned to {{assignedHouse}}!
9 |
10 |
11 |
12 |
18 |
19 |
20 |
21 |
26 |
--------------------------------------------------------------------------------
/wip/setup/mac-readme.md.txt:
--------------------------------------------------------------------------------
1 | Non-Firefox Mac/Linux instructions
2 | Note: Use these instructions if you do not have firefox installed and are running on mac or linux:
3 |
4 | Run correct node install from:
5 | client/setup/node
6 |
7 | From a bash or term window in the project directory run these commands:
8 | cd client/setup/server
9 | web-server.sh
10 |
11 | With a browser navigate to http://localhost:8000/index.html
--------------------------------------------------------------------------------
/wip/setup/node/node-linux32.tar.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jmcooper/angular-hogwarts-tdd-kata/7257a22ec2b87437d0b79e0977543e9515ca9f62/wip/setup/node/node-linux32.tar.gz
--------------------------------------------------------------------------------
/wip/setup/node/node-linux64.tar.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jmcooper/angular-hogwarts-tdd-kata/7257a22ec2b87437d0b79e0977543e9515ca9f62/wip/setup/node/node-linux64.tar.gz
--------------------------------------------------------------------------------
/wip/setup/node/node-mac32.tar.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jmcooper/angular-hogwarts-tdd-kata/7257a22ec2b87437d0b79e0977543e9515ca9f62/wip/setup/node/node-mac32.tar.gz
--------------------------------------------------------------------------------
/wip/setup/node/node-mac64.tar.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jmcooper/angular-hogwarts-tdd-kata/7257a22ec2b87437d0b79e0977543e9515ca9f62/wip/setup/node/node-mac64.tar.gz
--------------------------------------------------------------------------------
/wip/setup/node/node-win32.msi:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jmcooper/angular-hogwarts-tdd-kata/7257a22ec2b87437d0b79e0977543e9515ca9f62/wip/setup/node/node-win32.msi
--------------------------------------------------------------------------------
/wip/setup/node/node-win64.msi:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jmcooper/angular-hogwarts-tdd-kata/7257a22ec2b87437d0b79e0977543e9515ca9f62/wip/setup/node/node-win64.msi
--------------------------------------------------------------------------------
/wip/setup/server/web-server.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | var util = require('util'),
4 | http = require('http'),
5 | fs = require('fs'),
6 | url = require('url'),
7 | events = require('events');
8 |
9 | var DEFAULT_PORT = 8000;
10 |
11 | function main(argv) {
12 | new HttpServer({
13 | 'GET': createServlet(StaticServlet),
14 | 'HEAD': createServlet(StaticServlet)
15 | }).start(Number(argv[2]) || DEFAULT_PORT);
16 | }
17 |
18 | function escapeHtml(value) {
19 | return value.toString().
20 | replace('<', '<').
21 | replace('>', '>').
22 | replace('"', '"');
23 | }
24 |
25 | function createServlet(Class) {
26 | var servlet = new Class();
27 | return servlet.handleRequest.bind(servlet);
28 | }
29 |
30 | /**
31 | * An Http server implementation that uses a map of methods to decide
32 | * action routing.
33 | *
34 | * @param {Object} Map of method => Handler function
35 | */
36 | function HttpServer(handlers) {
37 | this.handlers = handlers;
38 | this.server = http.createServer(this.handleRequest_.bind(this));
39 | }
40 |
41 | HttpServer.prototype.start = function(port) {
42 | this.port = port;
43 | this.server.listen(port);
44 | util.puts('Http Server running at http://localhost:' + port + '/');
45 | };
46 |
47 | HttpServer.prototype.parseUrl_ = function(urlString) {
48 | var parsed = url.parse(urlString);
49 | parsed.pathname = url.resolve('/', parsed.pathname);
50 | return url.parse(url.format(parsed), true);
51 | };
52 |
53 | HttpServer.prototype.handleRequest_ = function(req, res) {
54 | var logEntry = req.method + ' ' + req.url;
55 | if (req.headers['user-agent']) {
56 | logEntry += ' ' + req.headers['user-agent'];
57 | }
58 | util.puts(logEntry);
59 | req.url = this.parseUrl_(req.url);
60 | var handler = this.handlers[req.method];
61 | if (!handler) {
62 | res.writeHead(501);
63 | res.end();
64 | } else {
65 | handler.call(this, req, res);
66 | }
67 | };
68 |
69 | /**
70 | * Handles static content.
71 | */
72 | function StaticServlet() {}
73 |
74 | StaticServlet.MimeMap = {
75 | 'txt': 'text/plain',
76 | 'html': 'text/html',
77 | 'css': 'text/css',
78 | 'xml': 'application/xml',
79 | 'json': 'application/json',
80 | 'js': 'application/javascript',
81 | 'jpg': 'image/jpeg',
82 | 'jpeg': 'image/jpeg',
83 | 'gif': 'image/gif',
84 | 'png': 'image/png',
85 | 'svg': 'image/svg+xml'
86 | };
87 |
88 | StaticServlet.prototype.handleRequest = function(req, res) {
89 | var self = this;
90 | var path = ('./' + req.url.pathname).replace('//','/').replace(/%(..)/g, function(match, hex){
91 | return String.fromCharCode(parseInt(hex, 16));
92 | });
93 | var parts = path.split('/');
94 | if (parts[parts.length-1].charAt(0) === '.')
95 | return self.sendForbidden_(req, res, path);
96 | fs.stat(path, function(err, stat) {
97 | if (err)
98 | return self.sendMissing_(req, res, path);
99 | if (stat.isDirectory())
100 | return self.sendDirectory_(req, res, path);
101 | return self.sendFile_(req, res, path);
102 | });
103 | }
104 |
105 | StaticServlet.prototype.sendError_ = function(req, res, error) {
106 | res.writeHead(500, {
107 | 'Content-Type': 'text/html'
108 | });
109 | res.write('\n');
110 | res.write('Internal Server Error \n');
111 | res.write('Internal Server Error ');
112 | res.write('' + escapeHtml(util.inspect(error)) + ' ');
113 | util.puts('500 Internal Server Error');
114 | util.puts(util.inspect(error));
115 | };
116 |
117 | StaticServlet.prototype.sendMissing_ = function(req, res, path) {
118 | path = path.substring(1);
119 | res.writeHead(404, {
120 | 'Content-Type': 'text/html'
121 | });
122 | res.write('\n');
123 | res.write('404 Not Found \n');
124 | res.write('Not Found ');
125 | res.write(
126 | 'The requested URL ' +
127 | escapeHtml(path) +
128 | ' was not found on this server.
'
129 | );
130 | res.end();
131 | util.puts('404 Not Found: ' + path);
132 | };
133 |
134 | StaticServlet.prototype.sendForbidden_ = function(req, res, path) {
135 | path = path.substring(1);
136 | res.writeHead(403, {
137 | 'Content-Type': 'text/html'
138 | });
139 | res.write('\n');
140 | res.write('403 Forbidden \n');
141 | res.write('Forbidden ');
142 | res.write(
143 | 'You do not have permission to access ' +
144 | escapeHtml(path) + ' on this server.
'
145 | );
146 | res.end();
147 | util.puts('403 Forbidden: ' + path);
148 | };
149 |
150 | StaticServlet.prototype.sendRedirect_ = function(req, res, redirectUrl) {
151 | res.writeHead(301, {
152 | 'Content-Type': 'text/html',
153 | 'Location': redirectUrl
154 | });
155 | res.write('\n');
156 | res.write('301 Moved Permanently \n');
157 | res.write('Moved Permanently ');
158 | res.write(
159 | 'The document has moved here .
'
162 | );
163 | res.end();
164 | util.puts('301 Moved Permanently: ' + redirectUrl);
165 | };
166 |
167 | StaticServlet.prototype.sendFile_ = function(req, res, path) {
168 | var self = this;
169 | var file = fs.createReadStream(path);
170 | res.writeHead(200, {
171 | 'Content-Type': StaticServlet.
172 | MimeMap[path.split('.').pop()] || 'text/plain'
173 | });
174 | if (req.method === 'HEAD') {
175 | res.end();
176 | } else {
177 | file.on('data', res.write.bind(res));
178 | file.on('close', function() {
179 | res.end();
180 | });
181 | file.on('error', function(error) {
182 | self.sendError_(req, res, error);
183 | });
184 | }
185 | };
186 |
187 | StaticServlet.prototype.sendDirectory_ = function(req, res, path) {
188 | var self = this;
189 | if (path.match(/[^\/]$/)) {
190 | req.url.pathname += '/';
191 | var redirectUrl = url.format(url.parse(url.format(req.url)));
192 | return self.sendRedirect_(req, res, redirectUrl);
193 | }
194 | fs.readdir(path, function(err, files) {
195 | if (err)
196 | return self.sendError_(req, res, error);
197 |
198 | if (!files.length)
199 | return self.writeDirectoryIndex_(req, res, path, []);
200 |
201 | var remaining = files.length;
202 | files.forEach(function(fileName, index) {
203 | fs.stat(path + '/' + fileName, function(err, stat) {
204 | if (err)
205 | return self.sendError_(req, res, err);
206 | if (stat.isDirectory()) {
207 | files[index] = fileName + '/';
208 | }
209 | if (!(--remaining))
210 | return self.writeDirectoryIndex_(req, res, path, files);
211 | });
212 | });
213 | });
214 | };
215 |
216 | StaticServlet.prototype.writeDirectoryIndex_ = function(req, res, path, files) {
217 | path = path.substring(1);
218 | res.writeHead(200, {
219 | 'Content-Type': 'text/html'
220 | });
221 | if (req.method === 'HEAD') {
222 | res.end();
223 | return;
224 | }
225 | res.write('\n');
226 | res.write('' + escapeHtml(path) + ' \n');
227 | res.write('\n');
230 | res.write('Directory: ' + escapeHtml(path) + ' ');
231 | res.write('');
232 | files.forEach(function(fileName) {
233 | if (fileName.charAt(0) !== '.') {
234 | res.write('' +
236 | escapeHtml(fileName) + ' ');
237 | }
238 | });
239 | res.write(' ');
240 | res.end();
241 | };
242 |
243 | // Must be last,
244 | main(process.argv);
--------------------------------------------------------------------------------
/wip/setup/server/web-server.sh:
--------------------------------------------------------------------------------
1 | cd ../../app
2 | node ./../setup/server/web-server.js
--------------------------------------------------------------------------------
/wip/setup/win-readme.md.txt:
--------------------------------------------------------------------------------
1 | Non-Firefox Windows instructions
2 | Note: Use these instructions if you do not have firefox installed and are running on mac or linux:
3 |
4 | Run client/setup/server/webserver.exe
5 | From windows explorer drag the client/app directory into the webserver window that opened
6 | Select "Virtual Folder"
7 |
8 | In a browser navigate to: http://localhost:8080/app/index.html
--------------------------------------------------------------------------------