diff --git a/packages/learn/gatsby-node.js b/packages/learn/gatsby-node.js index 30ca0b6b27..4349ac8e42 100644 --- a/packages/learn/gatsby-node.js +++ b/packages/learn/gatsby-node.js @@ -1,4 +1,6 @@ const path = require('path'); +const CopyWebpackPlugin = require('copy-webpack-plugin'); + const { dasherize } = require('./utils'); const { viewTypes } = require('./utils/challengeTypes'); const { blockNameify } = require('./utils/blockNameify'); @@ -84,3 +86,27 @@ exports.createPages = ({ graphql, boundActionCreators }) => { ); }); }; + +exports.modifyWebpackConfig = ({ config, stage, babelConfig }) => { + if (stage === 'build-javascript' || stage === 'develop') { + config.plugin('CopyWebpackPlugin', CopyWebpackPlugin, [ + [ + { + from: path.resolve(__dirname, './node_modules/monaco-editor/min/vs'), + to: 'vs' + } + ] + ]); + // remove the default 'js' loader so we can create our own + config.removeLoader('js'); + // these modules are shipped with es6 code, we need to transform them due + // to the version of the uglifyjs plugin gatsby is using + config.loader('js', { + test: /\.jsx?$/, + exclude: /(node_modules|bower_components)\/(?!ansi-styles|chalk)/, + loader: 'babel', + query: babelConfig + }); + } + return config; +}; diff --git a/packages/learn/jest.config.js b/packages/learn/jest.config.js index bd10382468..813e1c7457 100644 --- a/packages/learn/jest.config.js +++ b/packages/learn/jest.config.js @@ -1,13 +1,14 @@ module.exports = { moduleNameMapper: { - "\\.(jpg|jpeg|png|svg|woff|woff2)$": "/src/__mocks__/fileMock.js", - // Plain CSS - match css files that don't end with '.module.css' https://regex101.com/r/VzwrKH/4 - "^(?!.*\\.module\\.css$).*\\.css$": "/src/__mocks__/styleMock.js", + '\\.(jpg|jpeg|png|svg|woff|woff2)$': '/src/__mocks__/fileMock.js', + // Plain CSS - match css files that don't end with + // '.module.css' https://regex101.com/r/VzwrKH/4 + '^(?!.*\\.module\\.css$).*\\.css$': '/src/__mocks__/styleMock.js', // CSS Modules - match files that end with 'module.css' - "\\.module\\.css$": "identity-obj-proxy" // CSS modules + '\\.module\\.css$': 'identity-obj-proxy' }, - testPathIgnorePatterns: ["/node_modules/", "/.cache/"], + testPathIgnorePatterns: ['/node_modules/', '/.cache/'], globals: { - __PATH_PREFIX__: "" + __PATH_PREFIX__: '' } -}; \ No newline at end of file +}; diff --git a/packages/learn/package.json b/packages/learn/package.json index bb30231d5a..437c568898 100644 --- a/packages/learn/package.json +++ b/packages/learn/package.json @@ -10,7 +10,7 @@ "babel-standalone": "^6.26.0", "brace": "^0.11.1", "chai": "^4.1.2", - "codemirror": "^5.36.0", + "copy-webpack-plugin": "^4.5.1", "debug": "^3.1.0", "dotenv": "^5.0.1", "enzyme": "^3.3.0", @@ -29,9 +29,9 @@ "mongodb": "^3.0.5", "react": "16", "react-bootstrap": "^0.32.1", - "react-codemirror2": "^4.2.1", "react-dom": "16", "react-helmet": "^5.2.0", + "react-monaco-editor": "^0.14.1", "react-redux": "^5.0.7", "react-reflex": "^2.2.1", "react-router-redux": "^5.0.0-alpha.9", @@ -52,14 +52,14 @@ "build:frame-runner": "webpack --config ./webpack-frame-runner.js", "build:loop-protect": "webpack --config ./webpack-loop-protect.js", "develop": "yarn build:frame-runner && gatsby develop", - "format": "yarn format:gatsby && yarn format:src", + "format": "yarn format:gatsby && yarn format:src && yarn lint", "format:gatsby": "prettier --write './gatsby*.js'", "format:src": "prettier --write './src/**/*.js'", "lint": "yarn lint:gatsby && yarn lint:src", "lint:gatsby": "eslint ./gatsby*.js --fix", "lint:src": "eslint ./src . --fix", "pretty": "yarn format && yarn lint", - "test": "jest src", + "test": "yarn format && jest src", "test:watch": "jest --watch src" }, "jest": { diff --git a/packages/learn/src/__mocks__/gatsby-link.js b/packages/learn/src/__mocks__/gatsby-link.js index 5a9a2e2a54..b5977b6d4e 100644 --- a/packages/learn/src/__mocks__/gatsby-link.js +++ b/packages/learn/src/__mocks__/gatsby-link.js @@ -1,6 +1,6 @@ +/* eslint-disable */ import React from 'react'; const mockComponent = name => props => React.createElement(name, props, props.children); - export default mockComponent('MockedLink'); diff --git a/packages/learn/src/components/Map/Map.js b/packages/learn/src/components/Map/Map.js index e70d926903..db58cd46eb 100644 --- a/packages/learn/src/components/Map/Map.js +++ b/packages/learn/src/components/Map/Map.js @@ -13,7 +13,6 @@ const propTypes = { }; class ShowMap extends PureComponent { - renderSuperBlocks(superBlocks) { const { nodes } = this.props; return superBlocks.map(superBlock => ( diff --git a/packages/learn/src/components/Map/Map.test.js b/packages/learn/src/components/Map/Map.test.js index f88f9f05cb..f13a748567 100644 --- a/packages/learn/src/components/Map/Map.test.js +++ b/packages/learn/src/components/Map/Map.test.js @@ -12,8 +12,6 @@ Enzyme.configure({ adapter: new Adapter() }); const renderer = new ShallowRenderer(); test(' snapshot', () => { - const component = renderer.render( - , - ); + const component = renderer.render(); expect(component).toMatchSnapshot('Map'); }); diff --git a/packages/learn/src/components/Map/components/Block.test.js b/packages/learn/src/components/Map/components/Block.test.js index 2794baa0b5..ebc62957b2 100644 --- a/packages/learn/src/components/Map/components/Block.test.js +++ b/packages/learn/src/components/Map/components/Block.test.js @@ -15,65 +15,53 @@ const renderer = new ShallowRenderer(); test(' not expanded snapshot', () => { const toggleSpy = sinon.spy(); const componentToRender = ( - node.block === 'block-a' - ) - } - isExpanded={false} - toggleBlock={toggleSpy} - /> -); + node.block === 'block-a')} + isExpanded={false} + toggleBlock={toggleSpy} + /> + ); const component = renderer.render(componentToRender); expect(component).toMatchSnapshot('block-not-expanded'); - }); - test(' { const toggleSpy = sinon.spy(); const componentToRender = ( - node.block === 'block-a' - ) - } - isExpanded={true} - toggleBlock={toggleSpy} - /> -); + node.block === 'block-a')} + isExpanded={true} + toggleBlock={toggleSpy} + /> + ); const component = renderer.render(componentToRender); expect(component).toMatchSnapshot('block-expanded'); }); - test(' should handle toggle clicks correctly', () => { const toggleSpy = sinon.spy(); const props = { blockDashedName: 'block-a', - challenges: mockNodes.filter( - node => node.block === 'block-a' - ), + challenges: mockNodes.filter(node => node.block === 'block-a'), isExpanded: false, toggleBlock: toggleSpy }; - const componentToRender = ( - - ); + const componentToRender = ; const enzymeWrapper = Enzyme.shallow(componentToRender); expect(toggleSpy.called).toBe(false); expect( - enzymeWrapper.find('.map-title').find('h5').text() + enzymeWrapper + .find('.map-title') + .find('h5') + .text() ).toBe('Block A'); enzymeWrapper.find('.map-title').simulate('click'); @@ -84,9 +72,10 @@ test(' should handle toggle clicks correctly', () => { enzymeWrapper.setProps({ ...props, isExpanded: true }); expect( - enzymeWrapper.find('.map-title').find('h5').text() + enzymeWrapper + .find('.map-title') + .find('h5') + .text() ).toBe('Block A'); - expect( - enzymeWrapper.find('ul').length - ).toBe(1); + expect(enzymeWrapper.find('ul').length).toBe(1); }); diff --git a/packages/learn/src/components/Map/components/SuperBlock.test.js b/packages/learn/src/components/Map/components/SuperBlock.test.js index 0f27cc01f7..e20a663a93 100644 --- a/packages/learn/src/components/Map/components/SuperBlock.test.js +++ b/packages/learn/src/components/Map/components/SuperBlock.test.js @@ -20,13 +20,10 @@ test(' not expanded snapshot', () => { superBlock: 'Super Block One', toggleSuperBlock: toggleSpy }; - const componentToRender = ( - -); + const componentToRender = ; const component = renderer.render(componentToRender); expect(component).toMatchSnapshot('superBlock-not-expanded'); - }); test(' expanded snapshot', () => { @@ -37,9 +34,7 @@ test(' expanded snapshot', () => { superBlock: 'Super Block One', toggleSuperBlock: toggleSpy }; - const componentToRender = ( - - ); + const componentToRender = ; const component = renderer.render(componentToRender); expect(component).toMatchSnapshot('superBlock-expanded'); @@ -53,18 +48,17 @@ test(' { superBlock: 'Super Block One', toggleSuperBlock: toggleSpy }; - const componentToRender = ( - - ); + const componentToRender = ; const enzymeWrapper = Enzyme.shallow(componentToRender); expect(toggleSpy.called).toBe(false); expect( - enzymeWrapper.find('.map-title').find('h4').text() + enzymeWrapper + .find('.map-title') + .find('h4') + .text() ).toBe('Super Block One'); - expect( - enzymeWrapper.find('ul').length - ).toBe(0); + expect(enzymeWrapper.find('ul').length).toBe(0); enzymeWrapper.find('.map-title').simulate('click'); @@ -74,9 +68,10 @@ test(' { enzymeWrapper.setProps({ ...props, isExpanded: true }); expect( - enzymeWrapper.find('.map-title').find('h4').text() + enzymeWrapper + .find('.map-title') + .find('h4') + .text() ).toBe('Super Block One'); - expect( - enzymeWrapper.find('ul').length - ).toBe(1); + expect(enzymeWrapper.find('ul').length).toBe(1); }); diff --git a/packages/learn/src/components/util/Spacer.test.js b/packages/learn/src/components/util/Spacer.test.js index 3a122ad981..6a769390b6 100644 --- a/packages/learn/src/components/util/Spacer.test.js +++ b/packages/learn/src/components/util/Spacer.test.js @@ -10,10 +10,7 @@ import Spacer from './Spacer'; Enzyme.configure({ adapter: new Adapter() }); test(' snapshot', () => { - const component = renderer.create( - , - ); + const component = renderer.create(); let tree = component.toJSON(); expect(tree).toMatchSnapshot(); - }); diff --git a/packages/learn/src/templates/Challenges/views/classic/Editor.js b/packages/learn/src/templates/Challenges/views/classic/Editor.js index 86de58fccd..b33a841f04 100644 --- a/packages/learn/src/templates/Challenges/views/classic/Editor.js +++ b/packages/learn/src/templates/Challenges/views/classic/Editor.js @@ -2,18 +2,10 @@ import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; -import { Controlled as CodeMirror } from 'react-codemirror2'; +import MonacoEditor from 'react-monaco-editor'; import { executeChallenge, updateFile } from '../../redux'; -import 'codemirror/lib/codemirror.css'; -import 'codemirror/theme/material.css'; - -if (typeof window !== 'undefined') { - require('codemirror/mode/htmlmixed/htmlmixed'); - require('codemirror/mode/javascript/javascript'); -} - const propTypes = { contents: PropTypes.string, executeChallenge: PropTypes.func.isRequired, @@ -34,7 +26,8 @@ const mapDispatchToProps = dispatch => ); const modeMap = { - html: 'htmlmixed', + css: 'css', + html: 'html', js: 'javascript', jsx: 'javascript' }; @@ -42,60 +35,57 @@ const modeMap = { class Editor extends PureComponent { constructor(...props) { super(...props); - + this.options = { + selectOnLineNumbers: true + }; this._editor = null; + + this.focusEditor = this.focusEditor.bind(this); } - handleChange = editorValue => { + componentWillUnmount() { + document.removeEventListener('keyup', this.focusEditor); + } + + editorDidMount(editor, monaco) { + this._editor = editor; + document.addEventListener('keyup', this.focusEditor); + this._editor.addAction({ + id: 'execute-challenge', + label: 'Run tests', + keybindings: [ + /* eslint-disable no-bitwise */ + monaco.KeyMod.chord(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter) + ], + run: this.props.executeChallenge + }); + } + + focusEditor(e) { + // e key to focus editor + if (e.keyCode === 69) { + console.log('focusing'); + this._editor.focus(); + } + } + + onChange(editorValue) { const { updateFile, fileKey } = this.props; updateFile({ key: fileKey, editorValue }); - }; + } render() { - const { contents, executeChallenge, ext } = this.props; + const { contents, ext } = this.props; return (
- - this.handleChange(newValue) - } - options={{ - mode: modeMap[ext], - theme: 'material', - lineNumbers: true, - lineWrapping: true, - extraKeys: { - Esc() { - document.activeElement.blur(); - }, - Tab(cm) { - if (cm.somethingSelected()) { - return cm.indentSelection('add'); - } - const spaces = Array(cm.getOption('indentUnit') + 1).join(' '); - return cm.replaceSelection(spaces); - }, - 'Shift-Tab': function(cm) { - return cm.indentSelection('subtract'); - }, - 'Ctrl-Enter': function() { - executeChallenge(); - return false; - }, - 'Cmd-Enter': function() { - executeChallenge(); - return false; - } - // TODO: Not working in cm2 - // 'Ctrl-/': function(cm) { - // cm.toggleComment(); - // }, - // 'Cmd-/': function(cm) { - // cm.toggleComment(); - // } - } - }} + +
diff --git a/packages/learn/src/templates/Challenges/views/components/Output.js b/packages/learn/src/templates/Challenges/views/components/Output.js index 58537f54c9..0ba33c3304 100644 --- a/packages/learn/src/templates/Challenges/views/components/Output.js +++ b/packages/learn/src/templates/Challenges/views/components/Output.js @@ -1,28 +1,35 @@ -import React from 'react'; +import React, { Fragment } from 'react'; import PropTypes from 'prop-types'; -import { Controlled as CodeMirror } from 'react-codemirror2'; - -import './output.css'; - -const defaultOptions = { - lineNumbers: false, - lineWrapping: true, - mode: 'javascript', - readOnly: 'nocursor' -}; +import MonacoEditor from 'react-monaco-editor'; const propTypes = { defaultOutput: PropTypes.string, output: PropTypes.string }; +const options = { + lineNumbers: false, + minimap: { + enabled: false + }, + readOnly: true, + scrollbar: { + vertical: 'hidden', + horizontal: 'hidden' + } +}; + function Output({ output, defaultOutput }) { return ( - + + + + ); } diff --git a/packages/learn/src/templates/Challenges/views/components/output.css b/packages/learn/src/templates/Challenges/views/components/output.css deleted file mode 100644 index f8b4b62d83..0000000000 --- a/packages/learn/src/templates/Challenges/views/components/output.css +++ /dev/null @@ -1,3 +0,0 @@ -.react-codemirror2.challenge-log > .CodeMirror { - height: 150px; -} diff --git a/packages/learn/yarn.lock b/packages/learn/yarn.lock index c74dc60e19..606135259b 100644 --- a/packages/learn/yarn.lock +++ b/packages/learn/yarn.lock @@ -2101,10 +2101,6 @@ code-point-at@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" -codemirror@^5.36.0: - version "5.36.0" - resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.36.0.tgz#1172ad9dc298056c06e0b34e5ccd23825ca15b40" - collection-visit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" @@ -2326,6 +2322,19 @@ copy-descriptor@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" +copy-webpack-plugin@^4.5.1: + version "4.5.1" + resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-4.5.1.tgz#fc4f68f4add837cc5e13d111b20715793225d29c" + dependencies: + cacache "^10.0.4" + find-cache-dir "^1.0.0" + globby "^7.1.1" + is-glob "^4.0.0" + loader-utils "^1.1.0" + minimatch "^3.0.4" + p-limit "^1.0.0" + serialize-javascript "^1.4.0" + copyfiles@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/copyfiles/-/copyfiles-1.2.0.tgz#a8da3ac41aa2220ae29bd3c58b6984294f2c593c" @@ -2814,6 +2823,13 @@ diffie-hellman@^5.0.0: miller-rabin "^4.0.0" randombytes "^2.0.0" +dir-glob@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-2.0.0.tgz#0b205d2b6aef98238ca286598a8204d29d0a0034" + dependencies: + arrify "^1.0.1" + path-type "^3.0.0" + discontinuous-range@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/discontinuous-range/-/discontinuous-range-1.0.0.tgz#e38331f0844bba49b9a9cb71c771585aab1bc65a" @@ -4360,6 +4376,17 @@ globby@^6.1.0: pify "^2.0.0" pinkie-promise "^2.0.0" +globby@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/globby/-/globby-7.1.1.tgz#fb2ccff9401f8600945dfada97440cca972b8680" + dependencies: + array-union "^1.0.1" + dir-glob "^2.0.0" + glob "^7.1.2" + ignore "^3.3.5" + pify "^3.0.0" + slash "^1.0.0" + got@^6.7.1: version "6.7.1" resolved "https://registry.yarnpkg.com/got/-/got-6.7.1.tgz#240cd05785a9a18e561dc1b44b41c763ef1e8db0" @@ -4729,7 +4756,7 @@ iferr@^0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" -ignore@^3.3.3: +ignore@^3.3.3, ignore@^3.3.5: version "3.3.7" resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.7.tgz#612289bfb3c220e186a58118618d5be8c1bab021" @@ -5874,7 +5901,7 @@ loader-utils@^0.2.11, loader-utils@^0.2.15, loader-utils@^0.2.16, loader-utils@^ json5 "^0.5.0" object-assign "^4.0.1" -loader-utils@^1.0.2: +loader-utils@^1.0.2, loader-utils@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.1.0.tgz#c98aef488bcceda2ffb5e2de646d6a754429f5cd" dependencies: @@ -6342,6 +6369,10 @@ moment@2.x.x, moment@^2.16.0: version "2.21.0" resolved "https://registry.yarnpkg.com/moment/-/moment-2.21.0.tgz#2a114b51d2a6ec9e6d83cf803f838a878d8a023a" +monaco-editor@^0.10.0: + version "0.10.1" + resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.10.1.tgz#8c96c4f15b6b5258bf92cbde93cad8a7e3007e14" + mongodb-core@2.1.19: version "2.1.19" resolved "https://registry.yarnpkg.com/mongodb-core/-/mongodb-core-2.1.19.tgz#00fbd5e5a3573763b9171cfd844e60a8f2a3a18b" @@ -6891,7 +6922,7 @@ p-finally@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" -p-limit@^1.1.0: +p-limit@^1.0.0, p-limit@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.2.0.tgz#0e92b6bedcb59f022c13d0f1949dc82d15909f1c" dependencies: @@ -7063,7 +7094,7 @@ path-to-regexp@^1.7.0: dependencies: isarray "0.0.1" -path-type@3.0.0: +path-type@3.0.0, path-type@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" dependencies: @@ -7959,10 +7990,6 @@ react-bootstrap@^0.32.1: uncontrollable "^4.1.0" warning "^3.0.0" -react-codemirror2@^4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/react-codemirror2/-/react-codemirror2-4.2.1.tgz#4ad3c5c60ebbcb34880f961721b51527324ec021" - react-deep-force-update@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/react-deep-force-update/-/react-deep-force-update-2.1.1.tgz#8ea4263cd6455a050b37445b3f08fd839d86e909" @@ -8052,6 +8079,13 @@ react-measure@^2.0.2: prop-types "^15.5.10" resize-observer-polyfill "^1.4.2" +react-monaco-editor@^0.14.1: + version "0.14.1" + resolved "https://registry.yarnpkg.com/react-monaco-editor/-/react-monaco-editor-0.14.1.tgz#f5163e119e8a7dc79b992cb3fd7af887547d7efd" + dependencies: + monaco-editor "^0.10.0" + prop-types "^15.5.10" + react-overlays@^0.8.0: version "0.8.3" resolved "https://registry.yarnpkg.com/react-overlays/-/react-overlays-0.8.3.tgz#fad65eea5b24301cca192a169f5dddb0b20d3ac5"