├── .istanbul.yml ├── CNAME ├── src ├── shim │ ├── babel-polyfill.js │ └── react.js ├── _html │ ├── favicon.ico │ └── index.html ├── keyboard-shortcut │ ├── util.js │ ├── browser-event-util.js │ ├── test │ │ ├── to-printable-keys-spec.js │ │ ├── ie-compat-spec.js │ │ ├── convinience-api-spec.js │ │ ├── special-dom-handling-spec.js │ │ ├── util.js │ │ ├── shortcut-spec.js │ │ ├── shortcut-handling-spec.js │ │ └── shortcut-detection-spec.js │ ├── shortcut.js │ ├── keyboard-event-util.js │ └── shortcut-processor.js ├── components │ ├── style.css │ ├── config.css │ ├── keyboard-shortcut.js │ ├── keyboard-shortcut-overlay.css │ ├── main.css │ ├── es6-katas-navigation.css │ ├── keyboard-shortcut-overlay.js │ ├── img │ │ ├── plausible_badge.svg │ │ ├── heart.svg │ │ ├── tddbin_logo.svg │ │ └── github_icon.svg │ ├── es6-katas-navigation.js │ ├── main.js │ ├── navigation-bar.js │ └── navigation-bar.css ├── _test-helper │ ├── sinon-cleanup.js │ └── assert.js ├── _external-deps │ ├── xhr.js │ └── ace.js ├── test-runner │ ├── jasmine │ │ ├── spec-runner.js │ │ └── spec-runner.html │ ├── iframe.js │ ├── test │ │ ├── marked-line-prefix-spec.js │ │ ├── runtime-error-spec.js │ │ ├── stack-trace-spec.js │ │ └── line-prefix-spec.js │ ├── katas │ │ ├── spec-runner.html │ │ ├── spec-runner.js │ │ └── reporter.js │ ├── mocha │ │ ├── spec-runner.html │ │ ├── spec-runner-spec.js │ │ └── spec-runner.js │ ├── stack-trace.js │ ├── line-prefix.js │ ├── runner.js │ └── runtime-error.js ├── editor.js ├── _util.js ├── _aceDefaultShortcuts.js ├── kata-url.js ├── startup.js ├── _test │ ├── kata-url-spec.js │ └── startup-spec.js ├── index.js └── main-controller.js ├── .babelrc ├── .gitignore ├── .jscsrc ├── shell.nix ├── .editorconfig ├── .travis.yml ├── vendor ├── react │ └── download-react.sh ├── ace │ ├── download-ace.sh │ ├── 1.1.8 │ │ └── min │ │ │ └── mode-javascript.js │ └── 1.1.9 │ │ └── min │ │ └── mode-javascript.js ├── mocha │ └── 3.1.2 │ │ ├── mocha.min.css.map │ │ └── mocha.min.css └── jasmine │ └── 2.2.1 │ ├── boot.js │ ├── jasmine-html.js │ └── jasmine.min.css ├── default.nix ├── scripts └── build │ ├── build-offline-dev.sh │ └── build.sh ├── LICENSE ├── ROADMAP.md ├── package.json ├── CHANGELOG.md └── README.md /.istanbul.yml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | tddbin.com 2 | -------------------------------------------------------------------------------- /src/shim/babel-polyfill.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/shim/react.js: -------------------------------------------------------------------------------- 1 | const react = global.React; 2 | export default react; 3 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-react"] 3 | } 4 | 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | tmp 4 | npm-debug.log 5 | coverage 6 | .idea 7 | .DS_Store -------------------------------------------------------------------------------- /src/_html/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tddbin/tddbin-frontend/HEAD/src/_html/favicon.ico -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "google", 3 | "maximumLineLength": 120, 4 | "esprima": "esprima-fb", 5 | "fileExtensions": [".js"] 6 | } 7 | -------------------------------------------------------------------------------- /src/keyboard-shortcut/util.js: -------------------------------------------------------------------------------- 1 | export const toPrintableKeys = function(keys, map) { 2 | return keys.map(key => map[key] || key); 3 | }; 4 | -------------------------------------------------------------------------------- /src/keyboard-shortcut/browser-event-util.js: -------------------------------------------------------------------------------- 1 | export const browserEventUtil = { 2 | onWindowBlur(fn) { 3 | window.addEventListener('blur', fn); 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /src/components/style.css: -------------------------------------------------------------------------------- 1 | @import "./config.css"; 2 | @import "./main.css"; 3 | @import "./navigation-bar.css"; 4 | @import "./keyboard-shortcut-overlay.css"; 5 | @import "./es6-katas-navigation.css"; 6 | -------------------------------------------------------------------------------- /src/_test-helper/sinon-cleanup.js: -------------------------------------------------------------------------------- 1 | import sinon from 'sinon'; 2 | 3 | beforeEach(function() { 4 | this.sinon = sinon.createSandbox(); 5 | }); 6 | 7 | afterEach(function() { 8 | this.sinon.restore(); 9 | }); 10 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | with import (fetchTarball https://github.com/nixos/nixpkgs/tarball/f52505fac8c82716872a616c501ad9eff188f97f) { }; 2 | 3 | stdenv.mkDerivation { 4 | name = "dev-shell"; 5 | src = null; 6 | buildInputs = [ nodejs-11_x ]; 7 | } 8 | -------------------------------------------------------------------------------- /src/_external-deps/xhr.js: -------------------------------------------------------------------------------- 1 | import atomic from 'atomicjs'; 2 | 3 | export function xhrGet(url, onError, onSuccess) { 4 | atomic.ajax({method: 'GET', url, responseType: ''}) 5 | .success(onSuccess) 6 | .error(onError) 7 | ; 8 | } 9 | 10 | -------------------------------------------------------------------------------- /src/components/config.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --runner-background-color: #EEE; 3 | --navigation-background-color: #363738; 4 | --navigation-foreground-color: #9c9e9f; 5 | --navigation-hover-color: #454445; 6 | --navigation-hover-background-color: #fedb00; 7 | } 8 | -------------------------------------------------------------------------------- /src/test-runner/jasmine/spec-runner.js: -------------------------------------------------------------------------------- 1 | const jasmine = window.jasmine; 2 | const env = jasmine.getEnv(); 3 | 4 | function consumeMessage(messageData) { 5 | const specCode = messageData.data; 6 | 7 | eval(specCode); 8 | env.execute(); 9 | } 10 | 11 | window.addEventListener('message', consumeMessage, false); 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor support: editorconfig.org/#download 2 | root = true 3 | 4 | [*] 5 | # EditorConfig supported properties 6 | indent_style = space 7 | indent_size = 2 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | 16 | -------------------------------------------------------------------------------- /src/test-runner/iframe.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default class Iframe extends React.Component { 4 | 5 | getIframeRef() { 6 | return document.getElementById('iframe'); 7 | } 8 | 9 | render() { 10 | return ( 11 | 12 | ); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/components/keyboard-shortcut.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default class KeyboardShortcut extends React.Component { 4 | 5 | render() { 6 | const {printableKeys, helpText} = this.props.shortcut; 7 | return ( 8 |
9 | {printableKeys} 10 | {helpText} 11 |
12 | ); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/_test-helper/assert.js: -------------------------------------------------------------------------------- 1 | import './sinon-cleanup'; 2 | import nodeAssert from 'assert'; 3 | import sinon from 'sinon'; 4 | 5 | const assignFunctionsTo = (fromObj, toObj) => { 6 | Object.keys(fromObj) 7 | .forEach((key) => toObj[key] = fromObj[key]); 8 | }; 9 | 10 | const assert = nodeAssert.ok; 11 | assignFunctionsTo(nodeAssert, assert); 12 | assignFunctionsTo(sinon.assert, assert); 13 | 14 | export default assert; 15 | -------------------------------------------------------------------------------- /src/editor.js: -------------------------------------------------------------------------------- 1 | import Ace from './_external-deps/ace.js'; 2 | 3 | export default class Editor { 4 | 5 | constructor(domNodeId) { 6 | this.ace = new Ace(); 7 | this.ace.setDomNodeId(domNodeId); 8 | } 9 | 10 | setContent(content) { 11 | this.ace.setContent(content); 12 | } 13 | 14 | getContent() { 15 | return this.ace.getContent(); 16 | } 17 | 18 | resize() { 19 | this.ace.resize(); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "11" 4 | 5 | before_deploy: 6 | - npm run build 7 | 8 | deploy: 9 | provider: pages 10 | skip-cleanup: true 11 | local-dir: dist 12 | name: Auto Deploy through Travis 13 | target-branch: gh-pages 14 | github-token: $GH_TOKEN # Set in the settings page of your repository, as a secure variable 15 | keep-history: true 16 | on: 17 | branch: master 18 | 19 | env: 20 | - KATAS_SERVICE_URL=https://katas.tddbin.com 21 | -------------------------------------------------------------------------------- /src/test-runner/test/marked-line-prefix-spec.js: -------------------------------------------------------------------------------- 1 | import assert from '../../_test-helper/assert'; 2 | import {MarkedLinePrefix} from '../line-prefix.js'; 3 | 4 | describe('MarkedLinePrefix', () => { 5 | 6 | const getPrefix = MarkedLinePrefix.getPrefix; 7 | 8 | describe('has the `>` in front', () => { 9 | it('line number 1', () => assert.equal(getPrefix(1, 1), '> 1 | ')); 10 | it('line number 1234', () => assert.equal(getPrefix(1234, 4), '> 1234 | ')); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/test-runner/katas/spec-runner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Mocha Tests 6 | 7 | 8 | 9 |
10 |

11 |   
12 |   
13 | 
14 | 
15 | 


--------------------------------------------------------------------------------
/src/_util.js:
--------------------------------------------------------------------------------
 1 | import {toPrintableKeys} from '../src/keyboard-shortcut/util';
 2 | import Shortcut from '../src/keyboard-shortcut/shortcut';
 3 | 
 4 | const isMac = navigator.platform.indexOf('Mac') === 0;
 5 | 
 6 | const map = {
 7 |   Meta: '⌘Command',
 8 |   Shift: '⇧Shift',
 9 | };
10 | 
11 | const format = function(keys) {
12 |   return toPrintableKeys(keys, map);
13 | };
14 | 
15 | export const getShortcutObject = function(keys, fn, helpText) {
16 |   const shortcut = new Shortcut(keys, fn, helpText);
17 |   shortcut.printableKeysFormatter = format;
18 |   return shortcut;
19 | };
20 | 
21 | export const metaKey = isMac ? 'Meta' : 'Control';
22 | 


--------------------------------------------------------------------------------
/vendor/react/download-react.sh:
--------------------------------------------------------------------------------
 1 | #!/usr/bin/env bash
 2 | 
 3 | CUR_DIR=.
 4 | REACT_VERSION=$1
 5 | DESTINATION_DIR=$CUR_DIR/$REACT_VERSION/
 6 | 
 7 | if [ $# -eq 0 ]; then
 8 |   echo "Please give an REACT version number to download, like '0.13.2'"
 9 |   exit
10 | fi
11 | 
12 | if [ -d "$DESTINATION_DIR" ]; then
13 |   echo "Not downloading!"
14 |   echo "Directory '$DESTINATION_DIR' exists already. Most probably REACT version $REACT_VERSION had already been downloaded!"
15 |   exit
16 | fi
17 | 
18 | mkdir -p $DESTINATION_DIR
19 | cd "$DESTINATION_DIR"
20 | 
21 | wget "http://cdnjs.cloudflare.com/ajax/libs/react/$REACT_VERSION/react-with-addons.min.js"
22 | 


--------------------------------------------------------------------------------
/src/test-runner/mocha/spec-runner.html:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | 
 4 |   
 5 |   Mocha Tests
 6 |   
 7 | 
 8 | 
 9 |   
10 |

11 |   
12 |   
13 |   
14 | 
15 | 
16 | 


--------------------------------------------------------------------------------
/src/test-runner/stack-trace.js:
--------------------------------------------------------------------------------
 1 | export class StackTrace {
 2 |   constructor(dump) {
 3 |     this.dump = dump;
 4 |   }
 5 |   lineOfOrigin() {
 6 |     const lineNumber = this.firstLineOfDump().split(':');
 7 |     return parseInt(lineNumber[lineNumber.length - 2]);
 8 |   }
 9 |   columnOfOrigin() {
10 |     const lineNumber = this.firstLineOfDump().split(':');
11 |     return parseInt(lineNumber[lineNumber.length - 1]);
12 |   }
13 |   firstLineOfDump() {
14 |     // may look something like this:
15 |     //    at eval (eval at consumeMessage (http://tddbin/dist/mocha/spec-runner.js:53280:10), :11:22)
16 |     return this.dump.split('\n')[1];
17 |   }
18 | }
19 | 


--------------------------------------------------------------------------------
/src/test-runner/jasmine/spec-runner.html:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | 
 4 |   Spec Runner
 5 |   
 6 |   
 7 |   
 8 |   
 9 |   
10 | 
11 | 
12 | 
13 | 


--------------------------------------------------------------------------------
/src/_aceDefaultShortcuts.js:
--------------------------------------------------------------------------------
 1 | import {getShortcutObject, metaKey} from './_util';
 2 | 
 3 | const noop = function() {};
 4 | 
 5 | export const shortcuts = [
 6 |   getShortcutObject([metaKey, 'D'], noop, 'Delete line'),
 7 |   getShortcutObject([metaKey, 'Z'], noop, 'Undo'),
 8 |   getShortcutObject([metaKey, 'Shift', 'D'], noop, 'Duplicate line'),
 9 |   getShortcutObject([metaKey, 'Shift', 'Z'], noop, 'Redo'),
10 |   //
11 |   //getShortcutObject([metaKey, 'I', 'E'], noop, '???'),
12 |   //getShortcutObject([metaKey, 'I', 'I'], noop, '???'),
13 |   //getShortcutObject([metaKey, 'I', 'E', 'E'], noop, '???')
14 |   getShortcutObject([metaKey, '/'], noop, 'Comment in/out line'),
15 | ];
16 | 


--------------------------------------------------------------------------------
/src/test-runner/line-prefix.js:
--------------------------------------------------------------------------------
 1 | export class LinePrefix {
 2 |   static getPrefix(lineNumber, maxDigits) {
 3 |     let leadingSpaces = getLeadingSpaces(lineNumber, maxDigits);
 4 |     return `${getSpaces(leadingSpaces)}${lineNumber} | `;
 5 |   }
 6 | }
 7 | 
 8 | const DEFAULT_LEADING_SPACE = 2;
 9 | 
10 | const getLeadingSpaces = (number, maxDigits) => {
11 |   const numberLength = number.toString().length;
12 |   return DEFAULT_LEADING_SPACE + maxDigits - numberLength;
13 | };
14 | 
15 | const getSpaces = (howMany) => {
16 |   return new Array(howMany + 1).join(' ');
17 | };
18 | 
19 | export class MarkedLinePrefix extends LinePrefix {
20 |   static getPrefix() {
21 |     const defaultPrefix = LinePrefix.getPrefix(...arguments).substr(1);
22 |     return `>${defaultPrefix}`;
23 |   }
24 | }
25 | 


--------------------------------------------------------------------------------
/default.nix:
--------------------------------------------------------------------------------
 1 | 
 2 | # not working yet, but close :)
 3 | 
 4 | # let
 5 | #   pkgs = import  {};
 6 | #   stdenv = pkgs.stdenv;
 7 | # in rec {
 8 | #   nodejs = stdenv.mkDerivation rec {
 9 | #     name = "tddbin-frontend-environment";
10 | #     version = "7.10.0";
11 | #     src = pkgs.fetchurl {
12 | #       url = "https://nodejs.org/download/release/v${version}/node-v${version}.tar.xz";
13 | #       sha256 = "08czj7ssvzgv13zvhg2y9mhy4cc6pvm4bcp7rbzj3a2ba8axsd6w";
14 | #     };
15 | #
16 | #     preConfigure = stdenv.lib.optionalString stdenv.isDarwin ''export PATH=/usr/bin:/usr/sbin:$PATH'';
17 | #       buildInputs = [ pkgs.python ] ++ stdenv.lib.optional stdenv.isLinux pkgs.utillinux;
18 | #     };
19 | #
20 | #     shellHook = ''
21 | #       ${nodejs}/bin/npm i --no-optional
22 | #     '';
23 | # }


--------------------------------------------------------------------------------
/src/kata-url.js:
--------------------------------------------------------------------------------
 1 | /* global process */
 2 | export default class KataUrl {
 3 | 
 4 |   constructor() {
 5 |     this.kataName = '';
 6 |   }
 7 | 
 8 |   static fromQueryString(queryString) {
 9 |     const kataName = queryString.match(/kata=([^&]+)/);
10 |     if (kataName && kataName.length === 2) {
11 |       return KataUrl.fromKataName(kataName[1]);
12 |     }
13 |     return new KataUrl();
14 |   }
15 | 
16 |   static fromKataName(kataName) {
17 |     let kataUrl = new KataUrl();
18 |     kataUrl.kataName = kataName;
19 |     return kataUrl;
20 |   }
21 | 
22 |   toString() {
23 |     if (this.kataName) {
24 |       return `${process.env.KATAS_SERVICE_URL}/katas/${this.kataName}.js`;
25 |     }
26 |     return '';
27 |   }
28 | 
29 |   get isEs6Kata() {
30 |     return this.kataName.startsWith('es6/language/');
31 |   }
32 | }
33 | 


--------------------------------------------------------------------------------
/vendor/ace/download-ace.sh:
--------------------------------------------------------------------------------
 1 | #!/usr/bin/env bash
 2 | 
 3 | CUR_DIR=.
 4 | ACE_VERSION=$1
 5 | DESTINATION_DIR=$CUR_DIR/$ACE_VERSION/min
 6 | 
 7 | if [ $# -eq 0 ]; then
 8 |   echo "Please give an ACE version number to download, like '1.1.9'"
 9 |   exit
10 | fi
11 | 
12 | if [ -d "$DESTINATION_DIR" ]; then
13 |   echo "Not downloading!"
14 |   echo "Directory '$DESTINATION_DIR' exists already. Most probably ACE version $ACE_VERSION had already been downloaded!"
15 |   exit
16 | fi
17 | 
18 | mkdir -p $DESTINATION_DIR
19 | cd "$DESTINATION_DIR"
20 | 
21 | wget "http://cdn.jsdelivr.net/ace/$ACE_VERSION/min/ace.js"
22 | wget "http://cdn.jsdelivr.net/ace/$ACE_VERSION/min/ext-language_tools.js"
23 | wget "http://cdn.jsdelivr.net/ace/$ACE_VERSION/min/mode-javascript.js"
24 | wget "http://cdn.jsdelivr.net/ace/$ACE_VERSION/min/worker-javascript.js"
25 | 


--------------------------------------------------------------------------------
/src/keyboard-shortcut/test/to-printable-keys-spec.js:
--------------------------------------------------------------------------------
 1 | import assert from '../../_test-helper/assert';
 2 | import {toPrintableKeys} from '../util';
 3 | 
 4 | const keyToSignMap = {
 5 |   Meta: 'Meta',
 6 |   Shift: 'Shift',
 7 | };
 8 | describe('convert key-strings to key signs', () => {
 9 | 
10 |   it('convert `Meta` to according sign', () => {
11 |     assert.deepEqual(toPrintableKeys(['Meta', 'S'], keyToSignMap), [keyToSignMap.Meta, 'S']);
12 |   });
13 | 
14 |   it('convert multiple matches', () => {
15 |     const expected = [keyToSignMap.Meta, keyToSignMap.Shift, 'A'];
16 |     assert.deepEqual(toPrintableKeys(['Meta', 'Shift', 'A'], keyToSignMap), expected);
17 |   });
18 | 
19 |   it('leave unmappables as they are', () => {
20 |     assert.deepEqual(toPrintableKeys(['Metas', 'Alt', 'A'], keyToSignMap), ['Metas', 'Alt', 'A']);
21 |   });
22 | 
23 | });
24 | 


--------------------------------------------------------------------------------
/scripts/build/build-offline-dev.sh:
--------------------------------------------------------------------------------
 1 | #!/bin/bash
 2 | 
 3 | ORIGIN_ROOT="."
 4 | DIST_ROOT="$ORIGIN_ROOT/dist"
 5 | 
 6 | cp -r $ORIGIN_ROOT/vendor $DIST_ROOT;
 7 | 
 8 | # replace online refs using offline-refs (so I can work offline too)
 9 | if [[ $OSTYPE == darwin* ]]; then
10 |   # replace in index.html
11 |   sed -i'' "s/\/\/cdn.rawgit.com\/jpillora/..\/vendor/g" $DIST_ROOT/index.html
12 |   sed -i'' "s/\/\/cdn.jsdelivr.net/..\/vendor/g" $DIST_ROOT/index.html
13 |   sed -i'' "s/\/\/cdnjs.cloudflare.com\/ajax\/libs/..\/vendor/g" $DIST_ROOT/index.html
14 |   # replace in spec-runners
15 |   sed -i'' "s/\/\/cdnjs.cloudflare.com\/ajax\/libs/..\/vendor/g" $DIST_ROOT/mocha/spec-runner.html
16 |   sed -i'' "s/\/\/cdnjs.cloudflare.com\/ajax\/libs/..\/vendor/g" $DIST_ROOT/jasmine/spec-runner.html
17 | else
18 |   echo "To do: make '$0' work on non-Macs";
19 |   exit 1;
20 | fi;
21 | 


--------------------------------------------------------------------------------
/src/components/keyboard-shortcut-overlay.css:
--------------------------------------------------------------------------------
 1 | .keyboard-shortcut-overlay {
 2 |   flex: 0;
 3 |   display:none;
 4 | 
 5 |   /* positioning */
 6 |   position: absolute;
 7 |   top: 50%;
 8 |   left: 50%;
 9 |   margin-left: -225px;
10 |   z-index: 100;
11 | 
12 |   /* styling */
13 |   background-color: #DDD;
14 |   color: #222;
15 |   padding: 1em;
16 |   border: 12px solid rgba(0, 0, 0, 0.4);
17 |   border-radius: 8px;
18 | }
19 | 
20 |   .keyboard-shortcut-overlay .shortcut {
21 |     color: #e74141;
22 |     font-family: monospace;
23 |     min-width: 3em;
24 |     display: inline-block;
25 |     margin-right: 0.5em;
26 |   }
27 | 
28 |   .keyboard-shortcut-overlay .run {
29 |     color: green;
30 |   }
31 | 
32 |   .keyboard-shortcut-overlay .subShortcut {
33 |     margin-left: 2em;
34 |     font-size: smaller;
35 |   }
36 | 
37 |   .keyboard-shortcut-overlay .hint {
38 |     font-size: small;
39 |   }
40 | 


--------------------------------------------------------------------------------
/src/components/main.css:
--------------------------------------------------------------------------------
 1 | html, body {
 2 |   height: 100%;
 3 | }
 4 | 
 5 | body {
 6 |   margin: 0;
 7 |   font-family: sans-serif;
 8 | }
 9 | 
10 | #tddbin, #tddbin > div {
11 |   height: 100%;
12 | }
13 | #tddbin > div {
14 |   display: flex;
15 |   flex-direction: column;
16 | }
17 | 
18 | .flex-rows-full-height {
19 |   display: flex;
20 |   flex-direction: column;
21 | }
22 | .flex-columns-full-width {
23 |   display: flex;
24 |   flex-direction: row;
25 | }
26 | 
27 | .editor-and-runner {
28 |   flex: 1;
29 | }
30 |   .editor-and-runner .editor,
31 |   .editor-and-runner .runner {
32 |     flex: 1;
33 |   }
34 |   .editor-and-runner .editor,
35 |   .editor-and-runner .runner iframe {
36 |     border: none;
37 |   }
38 | 
39 |   .editor-and-runner .runner {
40 |     background-color: var(--runner-background-color);
41 |   }
42 |     .editor-and-runner .runner iframe {
43 |       flex: 1;
44 |     }
45 | 


--------------------------------------------------------------------------------
/src/keyboard-shortcut/test/ie-compat-spec.js:
--------------------------------------------------------------------------------
 1 | import assert from '../../_test-helper/assert';
 2 | import Shortcut from '../shortcut';
 3 | import ShortcutProcessor from '../shortcut-processor';
 4 | import {KeyPressEmulation} from './util';
 5 | 
 6 | describe('IE specifics', function() {
 7 |   it('ignore multiple consecutive keydown-events for Control, Alt, etc.', function() {
 8 |     const keyPressEmulation = new KeyPressEmulation();
 9 |     const callback = this.sinon.spy();
10 | 
11 |     const processor = new ShortcutProcessor();
12 |     processor.registerShortcut(new Shortcut(['Control', 'S'], callback));
13 | 
14 |     // Fire `Control` key as Windows does, multiple times when being held down.
15 |     keyPressEmulation.keyDownByKeyNames(['Control', 'Control', 'Control', 'S']);
16 |     keyPressEmulation.keyUpByKeyNames(['S', 'Control']);
17 | 
18 |     assert.called(callback);
19 |   });
20 | 
21 | });
22 | 


--------------------------------------------------------------------------------
/src/components/es6-katas-navigation.css:
--------------------------------------------------------------------------------
 1 | #katas-navigation {
 2 |   width: 100%;
 3 |   color: var(--navigation-foreground-color);
 4 |   background-color: var(--navigation-background-color);
 5 | }
 6 | #katas-navigation .headline,
 7 | #katas-navigation a {
 8 |   padding: 0.5em;
 9 |   white-space: nowrap;
10 | }
11 | 
12 | #katas-navigation a {
13 |   background-color: var(--navigation-hover-color);
14 |   color: var(--navigation-foreground-color);
15 |   margin-right: 1px;
16 |   text-decoration: none;
17 | }
18 | #katas-navigation a.selected:hover,
19 | #katas-navigation a:hover {
20 |   color: var(--navigation-hover-color);
21 |   background-color: var(--navigation-hover-background-color);
22 | }
23 | #katas-navigation a.selected {
24 |   background-color: color(var(--navigation-hover-background-color) blackness(50%));
25 | }
26 | #katas-navigation .scrollable {
27 |   overflow: auto;
28 |   display: flex;
29 | }
30 | 


--------------------------------------------------------------------------------
/src/_external-deps/ace.js:
--------------------------------------------------------------------------------
 1 | /* global ace */
 2 | export default class Ace {
 3 | 
 4 |   setDomNodeId(domNodeId) {
 5 |     this._domNodeId = domNodeId;
 6 |     this._init();
 7 |   }
 8 | 
 9 |   _init() {
10 |     ace.require('ace/ext/language_tools');
11 |     const editor = ace.edit(this._domNodeId);
12 |     this._editor = editor;
13 |     editor.getSession().setMode('ace/mode/javascript');
14 |     editor.setOptions({
15 |       enableBasicAutocompletion: true,
16 |     });
17 | 
18 |     editor.getSession().setTabSize(2);
19 |     document.getElementById(this._domNodeId).style.fontSize = '12px';
20 |     document.getElementById(this._domNodeId).style.backgroundColor = 'white';
21 |   }
22 | 
23 |   setContent(content) {
24 |     this._editor.selectAll();
25 |     this._editor.insert(content);
26 |   }
27 | 
28 |   getContent() {
29 |     return this._editor.getValue();
30 |   }
31 | 
32 |   resize() {
33 |     this._editor.resize();
34 |   }
35 | }
36 | 


--------------------------------------------------------------------------------
/src/keyboard-shortcut/shortcut.js:
--------------------------------------------------------------------------------
 1 | export default class Shortcut {
 2 | 
 3 |   constructor(keys, fn, helpText) {
 4 |     this._keys = keys;
 5 |     this._fn = fn;
 6 |     this.helpText = helpText;
 7 |     this._printableKeysFormatter = null;
 8 |   }
 9 | 
10 |   isStartOfKeyCombo(pressedKeys) {
11 |     const shortcut = this._keys;
12 |     return pressedKeys.every((key, idx) => shortcut[idx] === key);
13 |   }
14 | 
15 |   isKeyCombo(pressedKeys) {
16 |     return pressedKeys.join('+') === this._keys.join('+');
17 |   }
18 | 
19 |   fireAssignedCallback() {
20 |     this._fn();
21 |   }
22 | 
23 |   set printableKeysFormatter(formatterFunction) {
24 |     this._printableKeysFormatter = formatterFunction;
25 |   }
26 | 
27 |   get printableKeys() {
28 |     const format = this._printableKeysFormatter;
29 |     let keys = this._keys;
30 |     if (format) {
31 |       keys = format(this._keys);
32 |     }
33 |     return keys.join('+');
34 |   }
35 | }
36 | 


--------------------------------------------------------------------------------
/src/keyboard-shortcut/test/convinience-api-spec.js:
--------------------------------------------------------------------------------
 1 | import assert from '../../_test-helper/assert';
 2 | import Shortcut from '../shortcut';
 3 | import ShortcutProcessor from '../shortcut-processor';
 4 | import {KeyPressEmulation} from './util';
 5 | import sinon from 'sinon';
 6 | 
 7 | const noop = function() {};
 8 | 
 9 | describe('registering multiple shortcuts', function() {
10 |   it('shall work', function() {
11 |     // TODO simplify the necessary mocking for every shortcut test
12 |     new KeyPressEmulation();
13 |     const processor = new ShortcutProcessor();
14 |     sinon.spy(processor, 'registerShortcut');
15 | 
16 |     const shortcutMap = [
17 |       new Shortcut(['Meta', 'S'], noop),
18 |       new Shortcut(['Ctrl', 'S'], noop),
19 |     ];
20 |     processor.registerShortcuts(shortcutMap);
21 | 
22 |     assert.calledWith(processor.registerShortcut, shortcutMap[0]);
23 |     assert.calledWith(processor.registerShortcut, shortcutMap[1]);
24 |   });
25 | });
26 | 


--------------------------------------------------------------------------------
/src/components/keyboard-shortcut-overlay.js:
--------------------------------------------------------------------------------
 1 | import React from 'react';
 2 | import KeyboardShortcut from './keyboard-shortcut.js';
 3 | 
 4 | export default class KeyboardShortcutOverlay extends React.Component {
 5 | 
 6 |   render() {
 7 |     const {shortcuts, metaKeySymbol} = this.props;
 8 |     const isVisible = shortcuts.length > 0;
 9 |     const styleProps = {display: isVisible ? 'block' : 'none'};
10 |     return (
11 |       
12 | {shortcuts.map((shortcut, idx) => )} 13 |
14 | Note: All keyboard shortcuts fire when you release the {metaKeySymbol} key.
15 | This allows for combinations such as {metaKeySymbol}+I+E and {metaKeySymbol}+I+E+E , and way more
16 | combinations for faster working with your code. 17 |
18 |
19 | ); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/components/img/plausible_badge.svg: -------------------------------------------------------------------------------- 1 | analyticsanalyticsplausibleplausible 2 | -------------------------------------------------------------------------------- /src/_html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | No-install TDD environment 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 uxebu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /src/components/es6-katas-navigation.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default class KatasNavigation extends React.Component { 4 | 5 | render() { 6 | if (!this.props.katas) { 7 | return null; 8 | } 9 | const selectedKataId = null; 10 | const katas = this.props.katas.items; 11 | return ( 12 |
13 |
ES6 Katas
14 |
15 | {katas.map(kata => )} 16 |
17 |
18 | ); 19 | } 20 | 21 | } 22 | 23 | class KataLink extends React.Component { 24 | 25 | render() { 26 | const {kata, selected} = this.props; 27 | const {id, path, groupName, name} = kata; 28 | const uniqueString = new Date().getMilliseconds(); 29 | const url = `${window.location.pathname}?${uniqueString}#?kata=es6/language/${path}`; 30 | const className = selected ? 'selected' : ''; 31 | return ( 32 | {id} 33 | ); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/startup.js: -------------------------------------------------------------------------------- 1 | export default class StartUp { 2 | constructor(xhrGet, xhrGetDefaultKata) { 3 | this.xhrGet = xhrGet; 4 | this.xhrGetDefaultKata = xhrGetDefaultKata; 5 | } 6 | 7 | loadSourceCode(kataUrl, withSourceCode) { 8 | const sourceCode = localStorage.getItem('code'); 9 | if (kataUrl) { 10 | this.loadKataFromUrl(kataUrl, withSourceCode); 11 | } else if (sourceCode) { 12 | withSourceCode(sourceCode); 13 | } else { 14 | this.loadDefaultKata(withSourceCode); 15 | } 16 | window.location.hash = window.location.hash.replace(/kata=([^&]+)/, ''); 17 | } 18 | 19 | loadDefaultKata(onLoaded) { 20 | this.xhrGetDefaultKata( 21 | (_, {status}) => 22 | onLoaded(`// Default kata not found (status ${status})\n// Maybe try a different kata (see URL).`), 23 | data => { onLoaded(data); } 24 | ); 25 | } 26 | 27 | loadKataFromUrl(kataUrl, onLoaded) { 28 | this.xhrGet( 29 | kataUrl, 30 | (_, {status}) => 31 | onLoaded(`// Kata at "${kataUrl}" not found (status ${status})\n// Maybe try a different kata (see URL).`), 32 | data => { onLoaded(data); } 33 | ); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/_test/kata-url-spec.js: -------------------------------------------------------------------------------- 1 | /* global process */ 2 | import assert from '../_test-helper/assert.js'; 3 | import KataUrl from '../kata-url.js'; 4 | 5 | process.env.KATAS_SERVICE_URL = 'https://katas.tddbin.test'; 6 | 7 | describe('KataUrl', () => { 8 | 9 | it('create it out of the query string', () => { 10 | const kataUrlParam = 'kata=my/kata'; 11 | const expectedUrl = `${process.env.KATAS_SERVICE_URL}/katas/my/kata.js`; 12 | assert.equal(KataUrl.fromQueryString(kataUrlParam), expectedUrl); 13 | }); 14 | 15 | describe('if no valid kata is given', () => { 16 | it('returns a new instance', () => { 17 | assert.ok(KataUrl.fromQueryString('') instanceof KataUrl); 18 | }); 19 | it('toString() returns en empty string', () => { 20 | const kataUrl = KataUrl.fromQueryString('').toString(); 21 | assert.equal(kataUrl, ''); 22 | }); 23 | }); 24 | 25 | }); 26 | 27 | describe('report if a kata URL is a ES6 kata', () => { 28 | it('true if kata starts with `es6/language/`', () => { 29 | const kataUrlParam = 'kata=es6/language/...'; 30 | const kataUrl = KataUrl.fromQueryString(kataUrlParam); 31 | assert.equal(kataUrl.isEs6Kata, true); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /src/test-runner/runner.js: -------------------------------------------------------------------------------- 1 | import ReactDOM from 'react-dom'; 2 | import Iframe from './iframe'; 3 | 4 | export default class TestRunner { 5 | 6 | constructor(domNode, eventReceiver) { 7 | this._onStats = null; 8 | this._domNode = domNode; 9 | (eventReceiver || window).addEventListener('message', this.handleDataReceived.bind(this), false); 10 | } 11 | 12 | render(iframeSrc) { 13 | const iframe = ReactDOM.render(