diff --git a/lab-khalid/.babelrc b/lab-khalid/.babelrc
new file mode 100644
index 0000000..af0f0c3
--- /dev/null
+++ b/lab-khalid/.babelrc
@@ -0,0 +1,3 @@
+{
+ "presets": ["es2015"]
+}
\ No newline at end of file
diff --git a/lab-khalid/.eslintrc b/lab-khalid/.eslintrc
new file mode 100644
index 0000000..b663d77
--- /dev/null
+++ b/lab-khalid/.eslintrc
@@ -0,0 +1,21 @@
+{
+ "rules": {
+ "no-console": "off",
+ "indent": [ "error", 2 ],
+ "quotes": [ "error", "single" ],
+ "semi": ["error", "always"],
+ "linebreak-style": [ "error", "unix" ]
+ },
+ "env": {
+ "es6": true,
+ "node": true,
+ "mocha": true,
+ "jasmine": true
+ },
+ "ecmaFeatures": {
+ "modules": true,
+ "experimentalObjectRestSpread": true,
+ "impliedStrict": true
+ },
+ "extends": "eslint:recommended"
+}
\ No newline at end of file
diff --git a/lab-khalid/.gitignore b/lab-khalid/.gitignore
new file mode 100644
index 0000000..3286afd
--- /dev/null
+++ b/lab-khalid/.gitignore
@@ -0,0 +1,30 @@
+# Created by https://www.gitignore.io/api/node,vim,macos,linux,windows
+.DS_Store
+node_modules/
+.env
+### Node ###
+# Logs
+logs
+*.log
+npm-debug.log*
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# node-waf configuration
+.lock-wscript
diff --git a/lab-khalid/app/entry.js b/lab-khalid/app/entry.js
new file mode 100644
index 0000000..0e90dcb
--- /dev/null
+++ b/lab-khalid/app/entry.js
@@ -0,0 +1,65 @@
+'use strict';
+
+require('./scss/reset.scss');
+require('./scss/main.scss');
+
+const
+ angular = require('angular'),
+ cowsay = require('cowsay-browser'),
+
+ cowsayApp = angular.module('cowsayApp', []);
+
+cowsayApp.controller('CowsayController', ['$log', CowsayController]);
+
+function CowsayController($log){
+ $log.debug('CowsayController');
+
+ this.title = 'Welcome to Cowville';
+ this.history = [];
+
+ cowsay.list((err, cowfiles) => {
+ this.cowfiles = cowfiles;
+ this.current = this.cowfiles[0];
+ });
+
+ this.update = function(input){
+ $log.debug('CowsayController.update()');
+
+ return cowsay.say({ text: input || 'MOOOOOOOOOOOOO!' ,f: this.current });
+ };
+
+ this.speak = function(input) {
+ $log.debug('CowsayController.speak()');
+
+ this.spoken = this.update(input);
+ this.history.push(this.spoken);
+ };
+
+ this.undo = function(){
+ $log.debug('CowsayController.undo()');
+
+ this.history.pop();
+ this.spoken = this.history.pop() || '';
+ };
+}
+
+cowsayApp.controller('NavController', ['$log', NavController]);
+
+function NavController($log) {
+ $log.debug('NavController');
+
+ this.routes = [
+ {
+ name: 'home',
+ url: '/'
+ },
+ {
+ name: 'about',
+ url: '/about'
+ },
+ {
+ name: 'contact',
+ url: '/contact'
+ }
+ ];
+}
\ No newline at end of file
diff --git a/lab-khalid/app/index.html b/lab-khalid/app/index.html
new file mode 100644
index 0000000..f627be6
--- /dev/null
+++ b/lab-khalid/app/index.html
@@ -0,0 +1,52 @@
+
+
+
+ WELCOME TO COWVILLE
+
+
+
+
+
+
+
+ {{ cowsayCtrl.title }}
+
+
+ {{ cowsayCtrl.update(cowsayCtrl.text) }}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/lab-khalid/app/scss/main.scss b/lab-khalid/app/scss/main.scss
new file mode 100644
index 0000000..ff6f914
--- /dev/null
+++ b/lab-khalid/app/scss/main.scss
@@ -0,0 +1,47 @@
+$custom: black;
+
+nav {
+ background-color: $custom;
+ color: slategray;
+ ul{
+ // display: inline-block;
+ margin-left: 50%;
+ }
+ li{
+ display: inline-block;
+ // margin-left: 20%;
+ padding: 0 10% 5% 0;
+ }
+ a {
+ text-decoration: none;
+ color: slategrey;
+ }
+}
+
+h2 {
+ font-size: 2vw;
+}
+pre {
+ display: inline-block;
+ border: 2px solid black;
+}
+
+.first{
+ display: inline-block;
+ background-color: dodgerblue;
+}
+form{
+ display: inline;
+ border: 1px solid black;
+ aside{
+ display: inline-block;
+ margin-top: -200px;
+ width: 30%;
+ height: 20vw;
+ background-color: crimson;
+ }
+}
+
+
+
+
diff --git a/lab-khalid/app/scss/reset.scss b/lab-khalid/app/scss/reset.scss
new file mode 100644
index 0000000..cf3d1dd
--- /dev/null
+++ b/lab-khalid/app/scss/reset.scss
@@ -0,0 +1,48 @@
+/* http://meyerweb.com/eric/tools/css/reset/
+ v2.0 | 20110126
+ License: none (public domain)
+*/
+
+html, body, div, span, applet, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+a, abbr, acronym, address, big, cite, code,
+del, dfn, em, img, ins, kbd, q, s, samp,
+small, strike, strong, sub, sup, tt, var,
+b, u, i, center,
+dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td,
+article, aside, canvas, details, embed,
+figure, figcaption, footer, header, hgroup,
+menu, nav, output, ruby, section, summary,
+time, mark, audio, video {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ font-size: 100%;
+ font: inherit;
+ vertical-align: baseline;
+}
+/* HTML5 display-role reset for older browsers */
+article, aside, details, figcaption, figure,
+footer, header, hgroup, menu, nav, section {
+ display: block;
+}
+body {
+ line-height: 1;
+}
+ol, ul {
+ list-style: none;
+}
+blockquote, q {
+ quotes: none;
+}
+blockquote:before, blockquote:after,
+q:before, q:after {
+ content: '';
+ content: none;
+}
+table {
+ border-collapse: collapse;
+ border-spacing: 0;
+}
\ No newline at end of file
diff --git a/lab-khalid/karma.conf.js b/lab-khalid/karma.conf.js
new file mode 100644
index 0000000..10289ee
--- /dev/null
+++ b/lab-khalid/karma.conf.js
@@ -0,0 +1,72 @@
+// Karma configuration
+// Generated on Wed Mar 22 2017 17:18:36 GMT-0700 (PDT)
+const webpackconfig = require('./webpack.config.js');
+webpackconfig.entry = {};
+
+module.exports = function(config) {
+ config.set({
+ webpack: webpackconfig,
+ // base path that will be used to resolve all patterns (eg. files, exclude)
+ basePath: '',
+
+
+ // frameworks to use
+ // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
+ frameworks: ['jasmine'],
+
+
+ // list of files / patterns to load in the browser
+ files: [
+ 'test/**/*-test.js'
+ ],
+
+
+ // list of files to exclude
+ exclude: [
+ ],
+
+
+ // preprocess matching files before serving them to the browser
+ // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
+ preprocessors: {
+ 'test/**/*-test.js': ['webpack']
+ },
+
+
+ // test results reporter to use
+ // possible values: 'dots', 'progress'
+ // available reporters: https://npmjs.org/browse/keyword/karma-reporter
+ reporters: ['mocha'],
+
+
+ // web server port
+ port: 9876,
+
+
+ // enable / disable colors in the output (reporters and logs)
+ colors: true,
+
+
+ // level of logging
+ // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
+ logLevel: config.LOG_INFO,
+
+
+ // enable / disable watching file and executing tests whenever any file changes
+ autoWatch: true,
+
+
+ // start these browsers
+ // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
+ browsers: ['PhantomJS'],
+
+
+ // Continuous Integration mode
+ // if true, Karma captures browsers, runs the tests and exits
+ singleRun: false,
+
+ // Concurrency level
+ // how many browser should be started simultaneous
+ concurrency: Infinity
+ })
+}
diff --git a/lab-khalid/package.json b/lab-khalid/package.json
new file mode 100644
index 0000000..4e6fb84
--- /dev/null
+++ b/lab-khalid/package.json
@@ -0,0 +1,41 @@
+{
+ "name": "testing-controllers-demo",
+ "version": "1.0.0",
+ "description": "",
+ "main": "karma.conf.js",
+ "scripts": {
+ "build": "./node_modules/webpack/bin/webpack.js",
+ "watch": "./node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot",
+ "test-watch": "./node_modules/karma/bin/karma start",
+ "test": "./node_modules/karma/bin/karma start --single-run"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "angular": "^1.6.1",
+ "babel-core": "^6.21.0",
+ "babel-loader": "^6.2.10",
+ "babel-preset-es2015": "^6.18.0",
+ "cowsay-browser": "^1.1.8",
+ "css-loader": "^0.26.1",
+ "extract-text-webpack-plugin": "^1.0.1",
+ "file-loader": "^0.9.0",
+ "html-webpack-plugin": "^2.26.0",
+ "node-sass": "^4.3.0",
+ "sass-loader": "^4.1.1",
+ "style-loader": "^0.13.1",
+ "url-loader": "^0.5.7",
+ "webpack": "^1.14.0"
+ },
+ "devDependencies": {
+ "angular-mocks": "^1.6.1",
+ "jasmine-core": "^2.5.2",
+ "karma": "^1.4.0",
+ "karma-jasmine": "^1.1.0",
+ "karma-mocha-reporter": "^2.2.3",
+ "karma-phantomjs-launcher": "^1.0.2",
+ "karma-webpack": "^2.0.1",
+ "webpack-dev-server": "^1.16.2"
+ }
+}
diff --git a/lab-khalid/test/cowsay-ctrl-test.js b/lab-khalid/test/cowsay-ctrl-test.js
new file mode 100644
index 0000000..a08cf80
--- /dev/null
+++ b/lab-khalid/test/cowsay-ctrl-test.js
@@ -0,0 +1,59 @@
+'use strict';
+
+require('./lib/test-setup.js');
+
+const
+ angular = require('angular'),
+ cowsay = require('cowsay-browser');
+
+describe('Cowsay COntroller', function(){
+ beforeEach( () => {
+ angular.mock.module('cowsayApp');
+ angular.mock.inject($controller => {
+ this.cowsayCtrl = new $controller('CowsayController');
+ });
+ });
+
+ describe('initial properties', () => {
+ it('title property should equal welcome to cowville', () => {
+ expect(this.cowsayCtrl.title).toBe('Welcome to Cowville');
+ });
+ it('history property should be an empty array', () => {
+ expect(Array.isArray(this.cowsayCtrl.history)).toBeTruthy();
+ });
+ it('list of cowfiles shoudl show proper cowfiles', () => {
+ cowsay.list((err, list) => {
+ expect(this.cowsayCtrl.cowfiles).toEqual(list);
+ expect(this.cowsayCtrl.cowfiles[0]).toEqual(list[0]);
+ });
+ });
+ });
+
+ describe('#update', () => {
+ it('should return a cow that says testing', () => {
+ let expected = cowsay.say({ text : 'testing', f: this.cowsayCtrl.current});
+ let result = this.cowsayCtrl.update('testing');
+ expect(result).toEqual(expected);
+ });
+ });
+
+ describe('#speak', () => {
+ it('should return a cow that says testing', () => {
+ let expected = cowsay.say({ text: 'testing', f: this.cowsayCtrl.current});
+ this.cowsayCtrl.speak('testing');
+ expect(this.cowsayCtrl.spoken).toEqual(expected);
+ expect(this.cowsayCtrl.history[0]).toEqual(expected);
+ });
+ });
+
+ describe('#undo', () => {
+ it('should return a cow that says testing', () => {
+ let expected = cowsay.say({ text: 'testing', f: this.cowsayCtrl.current});
+ this.cowsayCtrl.speak('testing');
+ this.cowsayCtrl.speak('testing again');
+ this.cowsayCtrl.undo();
+ expect(this.cowsayCtrl.spoken).toEqual(expected);
+ expect(this.cowsayCtrl.history.length).toEqual(0);
+ });
+ });
+});
\ No newline at end of file
diff --git a/lab-khalid/test/lib/test-setup.js b/lab-khalid/test/lib/test-setup.js
new file mode 100644
index 0000000..6368526
--- /dev/null
+++ b/lab-khalid/test/lib/test-setup.js
@@ -0,0 +1,4 @@
+'use strict';
+
+require('../../app/entry.js');
+require('angular-mocks');
diff --git a/lab-khalid/webpack.config.js b/lab-khalid/webpack.config.js
new file mode 100644
index 0000000..92c4dc9
--- /dev/null
+++ b/lab-khalid/webpack.config.js
@@ -0,0 +1,36 @@
+'use strict';
+
+const
+ HTMLPlugin = require('html-webpack-plugin'),
+ ExtractTextPlugin = require('extract-text-webpack-plugin');
+
+module.exports = {
+ entry: `${__dirname}/app/entry.js`,
+ output:{
+ filename: 'bundle.js',
+ path: 'build'
+ },
+ plugins:[
+ new HTMLPlugin({
+ template: `${__dirname}/app/index.html`
+ }),
+ new ExtractTextPlugin('bundle.css')
+ ],
+ module: {
+ loaders:[
+ {
+ test: /\.js$/,
+ exclude: /node_modules/,
+ loader: 'babel'
+ },
+ {
+ test: /\.scss$/,
+ loader: 'style!css!sass!'
+ },
+ {
+ test: /\.(eot|woff|ttf|svg).*/,
+ loader: 'url?limit=10000&name=fonts/[hash].[ext]'
+ }
+ ]
+ }
+};
\ No newline at end of file