├── .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 | analytics analytics plausible plausible
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(