├── .github └── FUNDING.yml ├── .gitignore ├── .storybook ├── addons.js ├── config.js └── preview-head.html ├── .travis.yml ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html └── manifest.json └── src ├── App.jsx ├── App.test.jsx ├── common └── dropdown-toggle-select │ ├── index.css │ ├── index.jsx │ ├── index.story.jsx │ └── index.test.jsx ├── editor ├── document │ ├── index.css │ ├── index.jsx │ ├── index.story.jsx │ └── index.test.jsx ├── index.jsx ├── index.story.jsx ├── index.test.jsx └── toolbar │ ├── font-dropdown │ ├── __snapshots__ │ │ └── index.test.jsx.snap │ ├── index.jsx │ ├── index.story.jsx │ └── index.test.jsx │ ├── index.jsx │ ├── index.story.jsx │ ├── index.test.jsx │ ├── size-dropdown │ ├── __snapshots__ │ │ └── index.test.jsx.snap │ ├── index.jsx │ ├── index.story.jsx │ └── index.test.jsx │ └── theme-dropdown │ ├── __snapshots__ │ └── index.test.jsx.snap │ ├── index.jsx │ ├── index.story.jsx │ └── index.test.jsx ├── heart ├── __snapshots__ │ └── index.test.jsx.snap ├── index.jsx ├── index.story.jsx └── index.test.jsx ├── index.css ├── index.jsx ├── navbar ├── index.jsx ├── index.story.jsx └── index.test.jsx ├── not-found ├── __snapshots__ │ └── index.test.jsx.snap ├── index.jsx ├── index.story.jsx └── index.test.jsx ├── registerServiceWorker.js └── setupTests.js /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: jaredpetersen 4 | custom: https://paypal.me/jaredtpetersen -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | /storybook-static 9 | 10 | # production 11 | /build 12 | 13 | # misc 14 | .DS_Store 15 | .env.local 16 | .env.development.local 17 | .env.test.local 18 | .env.production.local 19 | 20 | npm-debug.log* 21 | yarn-debug.log* 22 | yarn-error.log* 23 | -------------------------------------------------------------------------------- /.storybook/addons.js: -------------------------------------------------------------------------------- 1 | import '@storybook/addon-actions/register'; 2 | import '@storybook/addon-links/register'; 3 | -------------------------------------------------------------------------------- /.storybook/config.js: -------------------------------------------------------------------------------- 1 | import { configure } from '@storybook/react'; 2 | 3 | function loadStories() { 4 | require('../src/common/dropdown-toggle-select/index.story.jsx'); 5 | require('../src/navbar/index.story.jsx'); 6 | require('../src/editor/index.story.jsx'); 7 | require('../src/editor/toolbar/index.story.jsx'); 8 | require('../src/editor/toolbar/font-dropdown/index.story.jsx'); 9 | require('../src/editor/toolbar/size-dropdown/index.story.jsx'); 10 | require('../src/editor/toolbar/theme-dropdown/index.story.jsx'); 11 | require('../src/editor/document/index.story.jsx'); 12 | require('../src/heart/index.story.jsx'); 13 | require('../src/not-found/index.story.jsx'); 14 | } 15 | 16 | configure(loadStories, module); 17 | -------------------------------------------------------------------------------- /.storybook/preview-head.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "node" 4 | - "lts/*" 5 | - "9" 6 | - "8" 7 | script: 8 | - npm run build 9 | - npm test -- --coverage 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Jared Petersen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # codeprinter 2 | 3 | [![Build Status](https://travis-ci.org/jaredpetersen/codeprinter.svg?branch=master)](https://travis-ci.org/jaredpetersen/codeprinter) [![Donate](https://img.shields.io/badge/donate-%E2%9D%A4-F33452.svg)](https://paypal.me/jaredtpetersen) 4 | 5 | codeprinter's goal is pretty self-explanatory: to make it easier to print out code on paper. Many IDE's either don't allow you to print or have some weird quirks like adding unnecessary headers and footers, not allowing you to change your font size, or not providing syntax highlighting on your printout. 6 | 7 | codeprinter makes it easy. Simply copy your code into the text box, select your desired font, font size, syntax highlighting theme, and whether or not you would like line numbers and then hit print. 8 | 9 | ## Usage 10 | 11 | codeprinter is currently hosted by GitHub Pages at http://jaredpetersen.github.io/codeprinter/. 12 | 13 | If you're concerned about pasting code into some website, you can use it locally as well. codeprinter is a React project that uses NPM and Node.js, so [you'll need both installed](https://nodejs.org/en/download/) in order to do so. Right now, codeprinter supports Node 8.x or higher, but the latest LTS version is always recommended. 14 | 15 | Once that's out of the way, run the following commands to install the dependencies, build the application, and run it: 16 | 17 | ``` 18 | npm install 19 | npm run build 20 | npm start 21 | ``` 22 | 23 | ## Screenshots 24 | 25 | ![Paste your code](https://i.imgur.com/adhS1Cz.png) 26 | ![Change your font size](https://i.imgur.com/TlgIxpi.png) 27 | ![Print your code](https://i.imgur.com/IYHqP0z.png) 28 | 29 | ## How to Contribute 30 | 31 | Find a bug? Want to request a new feature? Awesome! Create an [issue](https://github.com/jaredpetersen/codeprinter/issues) and/or submit a [pull request](https://github.com/jaredpetersen/codeprinter/pulls). Just want to show your support for the project? [Buy me a cup of coffee](https://paypal.me/jaredtpetersen). 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "codeprinter", 3 | "version": "1.1.2", 4 | "private": true, 5 | "homepage": "http://jaredpetersen.github.io/codeprinter", 6 | "engines": { 7 | "node": ">=8.6.0" 8 | }, 9 | "scripts": { 10 | "start": "react-scripts start", 11 | "build": "react-scripts build", 12 | "test": "react-scripts test --watchAll=false", 13 | "pretty": "prettier --print-width 120 --single-quote --write \"**/*.{js,jsx,css,json,md}\"", 14 | "build-storybook": "build-storybook -s public", 15 | "storybook": "start-storybook -p 9009 -s public", 16 | "predeploy": "npm run build", 17 | "deploy": "gh-pages -d build" 18 | }, 19 | "jest": { 20 | "collectCoverageFrom": [ 21 | "src/**/*.{js,jsx}", 22 | "!/node_modules/", 23 | "!src/index.jsx", 24 | "!src/registerServiceWorker.js", 25 | "!setupTests.js", 26 | "!src/**/*.story.{js,jsx}" 27 | ], 28 | "coverageReporters": [ 29 | "json", 30 | "lcov", 31 | "html", 32 | "text" 33 | ], 34 | "transformIgnorePatterns": [ 35 | "/node_modules/(?!(react-syntax-highlighter)/)" 36 | ] 37 | }, 38 | "husky": { 39 | "hooks": { 40 | "pre-commit": "lint-staged" 41 | } 42 | }, 43 | "lint-staged": { 44 | "**/*.{js,jsx,css,json,md}": [ 45 | "prettier --print-width 120 --single-quote --write", 46 | "git add" 47 | ] 48 | }, 49 | "dependencies": { 50 | "bootstrap": "^4.4.1", 51 | "prop-types": "^15.7.2", 52 | "react": "^16.12.0", 53 | "react-dom": "^16.12.0", 54 | "react-router-dom": "^5.1.2", 55 | "react-scripts": "^3.3.0", 56 | "react-syntax-highlighter": "^11.0.2", 57 | "reactstrap": "^8.2.0" 58 | }, 59 | "devDependencies": { 60 | "@storybook/addon-actions": "^5.2.8", 61 | "@storybook/addon-links": "^5.2.8", 62 | "@storybook/addons": "^5.2.8", 63 | "@storybook/react": "^5.2.8", 64 | "enzyme": "^3.10.0", 65 | "enzyme-adapter-react-16": "^1.15.1", 66 | "gh-pages": "^2.1.1", 67 | "husky": "^3.1.0", 68 | "lint-staged": "^9.5.0", 69 | "prettier": "1.19.1" 70 | }, 71 | "browserslist": [ 72 | ">0.2%", 73 | "not dead", 74 | "not ie <= 11", 75 | "not op_mini all" 76 | ] 77 | } 78 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredpetersen/codeprinter/3850412a767d4c6a5e2ee5fc80ec123f51c10750/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | codeprinter 12 | 13 | 14 | 15 | 16 | 17 | 20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "codeprinter", 3 | "name": "codeprinter", 4 | "description": "Print out code easily", 5 | "icons": [ 6 | { 7 | "src": "favicon.ico", 8 | "sizes": "64x64 32x32 24x24 16x16", 9 | "type": "image/x-icon" 10 | } 11 | ], 12 | "start_url": "/", 13 | "display": "standalone", 14 | "theme_color": "#343a40", 15 | "background_color": "#ffffff" 16 | } 17 | -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'; 3 | import Navbar from './navbar'; 4 | import Editor from './editor'; 5 | import Heart from './heart'; 6 | import NotFound from './not-found'; 7 | 8 | class App extends Component { 9 | constructor(props) { 10 | super(props); 11 | 12 | this.state = {}; 13 | } 14 | 15 | render() { 16 | return ( 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 |
27 | ); 28 | } 29 | } 30 | 31 | export default App; 32 | -------------------------------------------------------------------------------- /src/App.test.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import App from './App'; 4 | 5 | describe('App', () => { 6 | it('renders without crashing', () => { 7 | shallow(); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /src/common/dropdown-toggle-select/index.css: -------------------------------------------------------------------------------- 1 | .dropdown-toggle-select::after { 2 | display: inline-block; 3 | width: 0; 4 | height: 0; 5 | margin-left: 0.255em; 6 | vertical-align: 0.255em; 7 | content: ''; 8 | border-top: 0.3em solid; 9 | border-right: 0.3em solid transparent; 10 | border-bottom: 0; 11 | border-left: 0.3em solid transparent; 12 | position: absolute; 13 | right: 12px; 14 | top: 45%; 15 | } 16 | 17 | .btn-outline-dropdown-toggle-select { 18 | background-color: transparent; 19 | background-image: none; 20 | border-color: #ced4da; 21 | } 22 | 23 | .btn-outline-dropdown-toggle-select:hover { 24 | } 25 | -------------------------------------------------------------------------------- /src/common/dropdown-toggle-select/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { DropdownToggle } from 'reactstrap'; 4 | import './index.css'; 5 | 6 | const DropdownToggleSelect = props => { 7 | const style = Object.assign({}, { textAlign: 'left' }, props.style); 8 | 9 | return ( 10 | 18 | ); 19 | }; 20 | 21 | // Rely on the propTypes provided by reacstrap 22 | DropdownToggleSelect.propTypes = { 23 | className: PropTypes.string, 24 | style: PropTypes.object, 25 | disabled: PropTypes.bool, 26 | onClick: PropTypes.func, 27 | 'data-toggle': PropTypes.string, 28 | 'aria-haspopup': PropTypes.bool, 29 | // For DropdownToggle usage inside a Nav 30 | nav: PropTypes.bool, 31 | // Defaults to Button component 32 | tag: PropTypes.any 33 | }; 34 | 35 | export default DropdownToggleSelect; 36 | -------------------------------------------------------------------------------- /src/common/dropdown-toggle-select/index.story.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { storiesOf } from '@storybook/react'; 3 | import { action } from '@storybook/addon-actions'; 4 | import 'bootstrap/dist/css/bootstrap.css'; 5 | import { UncontrolledDropdown, DropdownMenu, DropdownItem } from 'reactstrap'; 6 | import DropdownToggleSelect from './index.jsx'; 7 | 8 | storiesOf('Common/DropdownToggleSelect', module).add('default', () => ( 9 | 10 | Dropdown 11 | 12 | Item 1 13 | Item 2 14 | Item 3 15 | 16 | 17 | )); 18 | -------------------------------------------------------------------------------- /src/common/dropdown-toggle-select/index.test.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import DropdownToggleSelect from './index.jsx'; 4 | 5 | describe('Common DropdownToggleSelect', () => { 6 | it('renders without crashing', () => { 7 | shallow(Dropdown); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /src/editor/document/index.css: -------------------------------------------------------------------------------- 1 | @media only print { 2 | .no-print { 3 | display: none !important; 4 | } 5 | } 6 | 7 | @media only screen { 8 | .only-print { 9 | display: none !important; 10 | } 11 | } 12 | 13 | code { 14 | counter-reset: line; 15 | } 16 | 17 | .code-line { 18 | counter-increment: line; 19 | position: relative; 20 | display: block; 21 | margin-left: 2.5em; 22 | padding-left: 1em; 23 | border-left: 1px solid transparent; 24 | } 25 | 26 | .code-line:before { 27 | content: ' ' counter(line); 28 | position: absolute; 29 | margin-left: -3.5em; 30 | color: #212529; 31 | } 32 | 33 | .code-line:nth-child(n + 10):before { 34 | content: ' ' counter(line); 35 | } 36 | 37 | .code-line:nth-child(n + 100):before { 38 | content: counter(line); 39 | } 40 | 41 | .code-line-vertical { 42 | counter-increment: line; 43 | position: relative; 44 | display: block; 45 | margin-left: 2.5em; 46 | padding-left: 1em; 47 | border-left: 1px solid #212529; 48 | } 49 | 50 | .code-line-vertical:before { 51 | content: ' ' counter(line); 52 | position: absolute; 53 | margin-left: -3.5em; 54 | color: #212529; 55 | } 56 | 57 | .code-line-vertical:nth-child(n + 10):before { 58 | content: ' ' counter(line); 59 | } 60 | 61 | .code-line-vertical:nth-child(n + 100):before { 62 | content: counter(line); 63 | } 64 | -------------------------------------------------------------------------------- /src/editor/document/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Row, Col, Input } from 'reactstrap'; 4 | import SyntaxHighlighter from 'react-syntax-highlighter'; 5 | import { 6 | defaultStyle, 7 | arduinoLight, 8 | ascetic, 9 | docco, 10 | githubGist, 11 | grayscale, 12 | idea, 13 | tomorrow, 14 | vs, 15 | xcode 16 | } from 'react-syntax-highlighter/dist/esm/styles/hljs'; 17 | import './index.css'; 18 | 19 | const themeMap = { 20 | Arduino: arduinoLight, 21 | Ascetic: ascetic, 22 | Docco: docco, 23 | GitHub: githubGist, 24 | Grayscale: grayscale, 25 | hljs: defaultStyle, 26 | Idea: idea, 27 | Tomorrow: tomorrow, 28 | VS: vs, 29 | Xcode: xcode 30 | }; 31 | 32 | const themes = Object.keys(themeMap); 33 | 34 | class Document extends Component { 35 | constructor(props) { 36 | super(props); 37 | 38 | this.defaultCode = 39 | '// Welcome to codeprinter!\n' + 40 | 'const foo = () => {\n' + 41 | " console.log('This is where your code will be printed out!');\n" + 42 | '};'; 43 | 44 | this.state = { 45 | code: this.defaultCode 46 | }; 47 | 48 | this.onChange = this.onChange.bind(this); 49 | } 50 | 51 | onChange(event) { 52 | const code = event.target.value === '' ? this.defaultCode : event.target.value; 53 | this.setState({ code }); 54 | } 55 | 56 | render() { 57 | let placeholder = 'Paste your code in here!'; 58 | 59 | const lineNumberStyle = lineNumbers => { 60 | switch (lineNumbers) { 61 | case 'standard': 62 | return { className: 'code-line' }; 63 | case 'vertical': 64 | return { className: 'code-line-vertical' }; 65 | default: 66 | return null; 67 | } 68 | }; 69 | 70 | return ( 71 |
72 | 73 | 74 | 81 | 82 | 83 | 84 |
85 | 95 | {this.state.code} 96 | 97 |
98 |
99 | ); 100 | } 101 | } 102 | 103 | Document.propTypes = { 104 | font: PropTypes.string.isRequired, 105 | size: PropTypes.number.isRequired, 106 | theme: PropTypes.oneOf(['None', ...Object.keys(themeMap)]).isRequired, 107 | lineNumbers: PropTypes.oneOf(['none', 'standard', 'vertical']).isRequired 108 | }; 109 | 110 | export default Document; 111 | export { Document, themes }; 112 | -------------------------------------------------------------------------------- /src/editor/document/index.story.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { storiesOf } from '@storybook/react'; 3 | import 'bootstrap/dist/css/bootstrap.css'; 4 | import Document from './index'; 5 | 6 | storiesOf('Editor/Document', module).add('default', () => ( 7 | 8 | )); 9 | -------------------------------------------------------------------------------- /src/editor/document/index.test.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import { Document, themes } from './index'; 4 | import { Input } from 'reactstrap'; 5 | import SyntaxHighlighter from 'react-syntax-highlighter'; 6 | 7 | describe('Editor Document', () => { 8 | it('renders without crashing', () => { 9 | shallow(); 10 | }); 11 | 12 | it('renders the print section with some default code when text is not provided', () => { 13 | const document = shallow(); 14 | const expectedDefaultCode = 15 | '// Welcome to codeprinter!\n' + 16 | 'const foo = () => {\n' + 17 | " console.log('This is where your code will be printed out!');\n" + 18 | '};'; 19 | const syntaxHighlighter = document.find(SyntaxHighlighter); 20 | 21 | expect(syntaxHighlighter.children().text()).toEqual(expectedDefaultCode); 22 | }); 23 | 24 | it('adds text to the print section when typing in the text area', () => { 25 | const document = shallow(); 26 | const expectedCode = 'some code'; 27 | 28 | document.find(Input).simulate('change', { target: { value: expectedCode } }); 29 | 30 | const syntaxHighlighter = document.find(SyntaxHighlighter); 31 | 32 | expect(syntaxHighlighter.children().text()).toEqual(expectedCode); 33 | }); 34 | 35 | it('renders the placeholder', () => { 36 | const document = shallow(); 37 | const expectedPlaceholder = 'Paste your code in here!'; 38 | 39 | const textarea = document.find(Input); 40 | 41 | expect(textarea.prop('placeholder')).toEqual(expectedPlaceholder); 42 | }); 43 | 44 | it('renders the code with the specified font', () => { 45 | const expectedFont = 'Space Mono'; 46 | const document = shallow(); 47 | 48 | const syntaxHighlighter = document.find(SyntaxHighlighter); 49 | 50 | expect(syntaxHighlighter.prop('codeTagProps').style.fontFamily).toEqual(`"${expectedFont}", monospace`); 51 | }); 52 | 53 | it('renders the code with the specified font size', () => { 54 | const expectedSize = 14; 55 | const document = shallow( 56 | 57 | ); 58 | 59 | const syntaxHighlighter = document.find(SyntaxHighlighter); 60 | 61 | expect(syntaxHighlighter.prop('codeTagProps').style.fontSize).toEqual(`${expectedSize}pt`); 62 | }); 63 | 64 | it('renders the code with the specified theme', () => { 65 | const document = shallow(); 66 | 67 | const syntaxHighlighter = document.find(SyntaxHighlighter); 68 | 69 | expect(syntaxHighlighter.prop('style')).not.toEqual(''); 70 | }); 71 | 72 | it('renders the code with no theme if the theme passed is None', () => { 73 | const document = shallow(); 74 | 75 | const syntaxHighlighter = document.find(SyntaxHighlighter); 76 | 77 | expect(syntaxHighlighter.prop('style')).toEqual(''); 78 | }); 79 | 80 | it('renders the code without line numbers', () => { 81 | const lineNumbers = 'none'; 82 | const document = shallow(); 83 | 84 | const syntaxHighlighter = document.find(SyntaxHighlighter); 85 | 86 | expect(syntaxHighlighter.prop('lineProps')).toBeNull(); 87 | }); 88 | 89 | it('renders the code with standard line numbers', () => { 90 | const lineNumbers = 'standard'; 91 | const document = shallow(); 92 | 93 | const syntaxHighlighter = document.find(SyntaxHighlighter); 94 | 95 | expect(syntaxHighlighter.prop('lineProps')).toEqual({ className: 'code-line' }); 96 | }); 97 | 98 | it('renders the code with vertical line numbers', () => { 99 | const lineNumbers = 'vertical'; 100 | const document = shallow(); 101 | 102 | const syntaxHighlighter = document.find(SyntaxHighlighter); 103 | 104 | expect(syntaxHighlighter.prop('lineProps')).toEqual({ className: 'code-line-vertical' }); 105 | }); 106 | }); 107 | 108 | describe('Editor Document Theme Map', () => { 109 | it('returns a map of the available themes', () => { 110 | const expectedThemes = [ 111 | 'Arduino', 112 | 'Ascetic', 113 | 'Docco', 114 | 'GitHub', 115 | 'Grayscale', 116 | 'hljs', 117 | 'Idea', 118 | 'Tomorrow', 119 | 'VS', 120 | 'Xcode' 121 | ]; 122 | 123 | expect(themes).toEqual(expectedThemes); 124 | }); 125 | }); 126 | -------------------------------------------------------------------------------- /src/editor/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Container } from 'reactstrap'; 3 | import Toolbar from './toolbar'; 4 | import { Document, themes } from './document'; 5 | 6 | class Editor extends Component { 7 | constructor(props) { 8 | super(props); 9 | 10 | this.fonts = [ 11 | 'Anonymous Pro', 12 | 'Cousine', 13 | 'Cutive Mono', 14 | 'Fira Mono', 15 | 'IBM Plex Mono', 16 | 'Inconsolata', 17 | 'Nanum Gothic Coding', 18 | 'Nova Mono', 19 | 'Overpass Mono', 20 | 'Oxygen Mono', 21 | 'PT Mono', 22 | 'Roboto Mono', 23 | 'Share Tech Mono', 24 | 'Source Code Pro', 25 | 'Space Mono', 26 | 'Ubuntu Mono' 27 | ]; 28 | 29 | this.sizes = [8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18]; 30 | 31 | this.themes = ['None', ...themes]; 32 | 33 | this.state = { 34 | style: { 35 | font: this.fonts[11], 36 | size: this.sizes[2], 37 | theme: this.themes[0], 38 | lineNumbers: 'none' 39 | } 40 | }; 41 | 42 | this.onChange = this.onChange.bind(this); 43 | } 44 | 45 | onPrint() { 46 | window.print(); 47 | } 48 | 49 | onChange(toolbar) { 50 | const style = { 51 | font: toolbar.activeFont, 52 | size: toolbar.activeSize, 53 | theme: toolbar.activeTheme, 54 | lineNumbers: toolbar.lineNumbers 55 | }; 56 | this.setState({ style }); 57 | } 58 | 59 | render() { 60 | return ( 61 |
62 | 73 | 74 | 80 | 81 |
82 | ); 83 | } 84 | } 85 | 86 | Editor.propTypes = {}; 87 | 88 | export default Editor; 89 | -------------------------------------------------------------------------------- /src/editor/index.story.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { storiesOf } from '@storybook/react'; 3 | import 'bootstrap/dist/css/bootstrap.css'; 4 | import Editor from './index'; 5 | 6 | storiesOf('Editor', module).add('default', () => ); 7 | -------------------------------------------------------------------------------- /src/editor/index.test.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow, mount } from 'enzyme'; 3 | import Editor from './index'; 4 | import Toolbar from './toolbar'; 5 | import Document from './document'; 6 | import FontDropdown from './toolbar/font-dropdown'; 7 | import SizeDropdown from './toolbar/size-dropdown'; 8 | import ThemeDropdown from './toolbar/theme-dropdown'; 9 | import { Button, DropdownItem } from 'reactstrap'; 10 | 11 | describe('Editor', () => { 12 | it('renders without crashing', () => { 13 | shallow(); 14 | }); 15 | 16 | it('renders a toolbar', () => { 17 | const editor = shallow(); 18 | const toolbar = editor.find(Toolbar); 19 | 20 | const expectedFonts = [ 21 | 'Anonymous Pro', 22 | 'Cousine', 23 | 'Cutive Mono', 24 | 'Fira Mono', 25 | 'IBM Plex Mono', 26 | 'Inconsolata', 27 | 'Nanum Gothic Coding', 28 | 'Nova Mono', 29 | 'Overpass Mono', 30 | 'Oxygen Mono', 31 | 'PT Mono', 32 | 'Roboto Mono', 33 | 'Share Tech Mono', 34 | 'Source Code Pro', 35 | 'Space Mono', 36 | 'Ubuntu Mono' 37 | ]; 38 | 39 | expect(toolbar.prop('fonts')).toEqual(expectedFonts); 40 | expect(toolbar.prop('activeFont')).toEqual(expectedFonts[11]); 41 | 42 | const expectedSizes = [8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18]; 43 | 44 | expect(toolbar.prop('sizes')).toEqual(expectedSizes); 45 | expect(toolbar.prop('activeSize')).toEqual(expectedSizes[2]); 46 | 47 | const expectedThemes = [ 48 | 'None', 49 | 'Arduino', 50 | 'Ascetic', 51 | 'Docco', 52 | 'GitHub', 53 | 'Grayscale', 54 | 'hljs', 55 | 'Idea', 56 | 'Tomorrow', 57 | 'VS', 58 | 'Xcode' 59 | ]; 60 | 61 | expect(toolbar.prop('themes')).toEqual(expectedThemes); 62 | expect(toolbar.prop('activeTheme')).toEqual(expectedThemes[0]); 63 | 64 | expect(toolbar.prop('lineNumbers')).toEqual('none'); 65 | }); 66 | 67 | it('renders a document', () => { 68 | const editor = shallow(); 69 | const document = editor.find(Document); 70 | 71 | expect(document.prop('font')).toEqual('Roboto Mono'); 72 | expect(document.prop('size')).toEqual(10); 73 | expect(document.prop('theme')).toEqual('None'); 74 | expect(document.prop('lineNumbers')).toEqual('none'); 75 | }); 76 | 77 | it('prints the page when the print button is pressed', () => { 78 | global.print = jest.fn(); 79 | const editor = shallow(); 80 | const toolbar = editor.find(Toolbar); 81 | const printButton = toolbar 82 | .dive() 83 | .find(Button) 84 | .find('#print'); 85 | 86 | printButton.simulate('click'); 87 | 88 | expect(global.print).toHaveBeenCalledTimes(1); 89 | }); 90 | 91 | it('renders a font change on toolbar font select', () => { 92 | const editor = shallow(); 93 | const toolbar = editor.find(Toolbar); 94 | const fontDropdown = toolbar.dive().find(FontDropdown); 95 | const dropdownItems = fontDropdown.dive().find(DropdownItem); 96 | 97 | dropdownItems.at(2).simulate('click'); 98 | editor.update(); 99 | 100 | const document = editor.find(Document); 101 | 102 | expect(document.prop('font')).not.toEqual('Roboto Mono'); 103 | }); 104 | 105 | it('renders a size change on toolbar size select', () => { 106 | const editor = shallow(); 107 | const toolbar = editor.find(Toolbar); 108 | const sizeDropdown = toolbar.dive().find(SizeDropdown); 109 | const dropdownItems = sizeDropdown.dive().find(DropdownItem); 110 | 111 | dropdownItems.at(6).simulate('click'); 112 | editor.update(); 113 | 114 | const document = editor.find(Document); 115 | 116 | expect(document.prop('size')).not.toEqual(10); 117 | }); 118 | 119 | it('renders a theme change on toolbar theme select', () => { 120 | const editor = shallow(); 121 | const toolbar = editor.find(Toolbar); 122 | const themeDropdown = toolbar.dive().find(ThemeDropdown); 123 | const dropdownItems = themeDropdown.dive().find(DropdownItem); 124 | 125 | dropdownItems.at(2).simulate('click'); 126 | editor.update(); 127 | 128 | const document = editor.find(Document); 129 | 130 | expect(document.prop('size')).not.toEqual('None'); 131 | }); 132 | }); 133 | -------------------------------------------------------------------------------- /src/editor/toolbar/font-dropdown/__snapshots__/index.test.jsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Editor Toolbar FontDropdown renders all of the fonts as dropdown options 1`] = ` 4 |
8 | 24 |
30 | 44 | 58 |
59 |
60 | `; 61 | -------------------------------------------------------------------------------- /src/editor/toolbar/font-dropdown/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { UncontrolledDropdown, DropdownMenu, DropdownItem } from 'reactstrap'; 4 | import DropdownToggleSelect from '../../../common/dropdown-toggle-select'; 5 | 6 | const FontDropdown = ({ fonts, active, onSelect }) => { 7 | const dropdownItems = fonts.map(font => { 8 | return ( 9 | onSelect(font)} 13 | style={{ fontFamily: `"${font}", monospace` }} 14 | > 15 | {font} 16 | 17 | ); 18 | }); 19 | 20 | return ( 21 | 22 | {active} 23 | {dropdownItems} 24 | 25 | ); 26 | }; 27 | 28 | FontDropdown.propTypes = { 29 | fonts: PropTypes.arrayOf(PropTypes.string).isRequired, 30 | active: PropTypes.string.isRequired, 31 | onSelect: PropTypes.func 32 | }; 33 | 34 | export default FontDropdown; 35 | -------------------------------------------------------------------------------- /src/editor/toolbar/font-dropdown/index.story.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { storiesOf } from '@storybook/react'; 3 | import { action } from '@storybook/addon-actions'; 4 | import 'bootstrap/dist/css/bootstrap.css'; 5 | import FontDropdown from './index'; 6 | 7 | storiesOf('Editor/Toolbar/FontDropdown', module).add('default', () => { 8 | const fonts = ['Anonymous Pro', 'Cousine', 'Cutive Mono']; 9 | 10 | return ; 11 | }); 12 | -------------------------------------------------------------------------------- /src/editor/toolbar/font-dropdown/index.test.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import renderer from 'react-test-renderer'; 4 | import FontDropdown from './index'; 5 | import { DropdownItem } from 'reactstrap'; 6 | 7 | describe('Editor Toolbar FontDropdown', () => { 8 | it('renders without crashing', () => { 9 | const fonts = ['serif', 'sans-serif']; 10 | shallow( {}} />); 11 | }); 12 | 13 | it('renders all of the fonts as dropdown options', () => { 14 | const fonts = ['serif', 'sans-serif']; 15 | const tree = renderer.create( {}} />).toJSON(); 16 | 17 | expect(tree).toMatchSnapshot(); 18 | }); 19 | 20 | it('calls the onSelect function on selecting a dropdown option', () => { 21 | const fonts = ['serif', 'sans-serif']; 22 | const onSelect = jest.fn(); 23 | const fontDropdown = shallow(); 24 | 25 | fontDropdown 26 | .find(DropdownItem) 27 | .last() 28 | .simulate('click'); 29 | 30 | expect(onSelect).toHaveBeenCalledTimes(1); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /src/editor/toolbar/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Col, Navbar, Button } from 'reactstrap'; 4 | import FontDropdown from './font-dropdown'; 5 | import SizeDropdown from './size-dropdown'; 6 | import ThemeDropdown from './theme-dropdown'; 7 | 8 | const Toolbar = ({ fonts, activeFont, sizes, activeSize, themes, activeTheme, lineNumbers, onChange, onPrint }) => { 9 | return ( 10 | 11 |
12 | 13 | onChange({ activeFont, activeSize, activeTheme, lineNumbers })} 17 | /> 18 | 19 | 20 | onChange({ activeFont, activeSize, activeTheme, lineNumbers })} 24 | /> 25 | 26 | 27 | onChange({ activeFont, activeSize, activeTheme, lineNumbers })} 31 | /> 32 | 33 | 34 | 44 | 45 | 46 | 56 | 57 | 58 | 68 | 69 | 70 | 73 | 74 |
75 |
76 | ); 77 | }; 78 | 79 | Toolbar.propTypes = { 80 | fonts: PropTypes.arrayOf(PropTypes.string).isRequired, 81 | activeFont: PropTypes.string.isRequired, 82 | sizes: PropTypes.arrayOf(PropTypes.number).isRequired, 83 | activeSize: PropTypes.number.isRequired, 84 | themes: PropTypes.arrayOf(PropTypes.string).isRequired, 85 | activeTheme: PropTypes.string.isRequired, 86 | lineNumbers: PropTypes.oneOf(['none', 'standard', 'vertical']).isRequired, 87 | onChange: PropTypes.func, 88 | onPrint: PropTypes.func 89 | }; 90 | 91 | export default Toolbar; 92 | -------------------------------------------------------------------------------- /src/editor/toolbar/index.story.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { storiesOf } from '@storybook/react'; 3 | import { action } from '@storybook/addon-actions'; 4 | import 'bootstrap/dist/css/bootstrap.css'; 5 | import Toolbar from './index'; 6 | 7 | storiesOf('Editor/Toolbar', module).add('default', () => { 8 | const fonts = ['Anonymous Pro', 'Cousine', 'Cutive Mono']; 9 | 10 | const sizes = [8, 9, 10, 11, 12]; 11 | 12 | const themes = ['GitHub', 'VS', 'Xcode']; 13 | 14 | return ( 15 | 26 | ); 27 | }); 28 | -------------------------------------------------------------------------------- /src/editor/toolbar/index.test.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import Toolbar from './index'; 4 | import FontDropdown from './font-dropdown'; 5 | import SizeDropdown from './size-dropdown'; 6 | import ThemeDropdown from './theme-dropdown'; 7 | 8 | describe('Editor Toolbar', () => { 9 | it('renders without crashing', () => { 10 | const fonts = ['Anonymous Pro', 'Cousine', 'Cutive Mono']; 11 | 12 | const sizes = [8, 9, 10, 11, 12]; 13 | 14 | const themes = ['GitHub', 'VS', 'Xcode']; 15 | 16 | shallow( 17 | {}} 26 | onPrint={() => {}} 27 | /> 28 | ); 29 | }); 30 | 31 | it('renders the fonts in a dropdown', () => { 32 | const fonts = ['Anonymous Pro', 'Cousine', 'Cutive Mono']; 33 | 34 | const sizes = [8, 9, 10, 11, 12]; 35 | 36 | const themes = ['GitHub', 'VS', 'Xcode']; 37 | 38 | const toolbar = shallow( 39 | {}} 48 | onPrint={() => {}} 49 | /> 50 | ); 51 | 52 | const fontDropdown = toolbar.find(FontDropdown); 53 | 54 | expect(fontDropdown.prop('fonts')).toEqual(fonts); 55 | expect(fontDropdown.prop('active')).toEqual(fonts[0]); 56 | }); 57 | 58 | it('renders the sizes in a dropdown', () => { 59 | const fonts = ['Anonymous Pro', 'Cousine', 'Cutive Mono']; 60 | 61 | const sizes = [8, 9, 10, 11, 12]; 62 | 63 | const themes = ['GitHub', 'VS', 'Xcode']; 64 | 65 | const toolbar = shallow( 66 | {}} 75 | onPrint={() => {}} 76 | /> 77 | ); 78 | 79 | const sizeDropdown = toolbar.find(SizeDropdown); 80 | 81 | expect(sizeDropdown.prop('sizes')).toEqual(sizes); 82 | expect(sizeDropdown.prop('active')).toEqual(sizes[0]); 83 | }); 84 | 85 | it('renders the themes in a dropdown', () => { 86 | const fonts = ['Anonymous Pro', 'Cousine', 'Cutive Mono']; 87 | 88 | const sizes = [8, 9, 10, 11, 12]; 89 | 90 | const themes = ['GitHub', 'VS', 'Xcode']; 91 | 92 | const toolbar = shallow( 93 | {}} 102 | onPrint={() => {}} 103 | /> 104 | ); 105 | 106 | const themeDropdown = toolbar.find(ThemeDropdown); 107 | 108 | expect(themeDropdown.prop('themes')).toEqual(themes); 109 | expect(themeDropdown.prop('active')).toEqual(themes[0]); 110 | }); 111 | 112 | it('renders none line numbers (none)', () => { 113 | const fonts = ['Anonymous Pro', 'Cousine', 'Cutive Mono']; 114 | 115 | const sizes = [8, 9, 10, 11, 12]; 116 | 117 | const themes = ['GitHub', 'VS', 'Xcode']; 118 | 119 | const toolbar = shallow( 120 | {}} 129 | onPrint={() => {}} 130 | /> 131 | ); 132 | 133 | const lineNumbersNoneButton = toolbar.find('#line-numbers-none'); 134 | const lineNumbersStandardButton = toolbar.find('#line-numbers-standard'); 135 | const lineNumbersVerticalButton = toolbar.find('#line-numbers-vertical'); 136 | 137 | expect(lineNumbersNoneButton.prop('active')).toEqual(true); 138 | expect(lineNumbersStandardButton.prop('active')).toEqual(false); 139 | expect(lineNumbersVerticalButton.prop('active')).toEqual(false); 140 | }); 141 | 142 | it('renders none line numbers (standard)', () => { 143 | const fonts = ['Anonymous Pro', 'Cousine', 'Cutive Mono']; 144 | 145 | const sizes = [8, 9, 10, 11, 12]; 146 | 147 | const themes = ['GitHub', 'VS', 'Xcode']; 148 | 149 | const toolbar = shallow( 150 | {}} 159 | onPrint={() => {}} 160 | /> 161 | ); 162 | 163 | const lineNumbersNoneButton = toolbar.find('#line-numbers-none'); 164 | const lineNumbersStandardButton = toolbar.find('#line-numbers-standard'); 165 | const lineNumbersVerticalButton = toolbar.find('#line-numbers-vertical'); 166 | 167 | expect(lineNumbersNoneButton.prop('active')).toEqual(false); 168 | expect(lineNumbersStandardButton.prop('active')).toEqual(true); 169 | expect(lineNumbersVerticalButton.prop('active')).toEqual(false); 170 | }); 171 | 172 | it('renders none line numbers (none)', () => { 173 | const fonts = ['Anonymous Pro', 'Cousine', 'Cutive Mono']; 174 | 175 | const sizes = [8, 9, 10, 11, 12]; 176 | 177 | const themes = ['GitHub', 'VS', 'Xcode']; 178 | 179 | const toolbar = shallow( 180 | {}} 189 | onPrint={() => {}} 190 | /> 191 | ); 192 | 193 | const lineNumbersNoneButton = toolbar.find('#line-numbers-none'); 194 | const lineNumbersStandardButton = toolbar.find('#line-numbers-standard'); 195 | const lineNumbersVerticalButton = toolbar.find('#line-numbers-vertical'); 196 | 197 | expect(lineNumbersNoneButton.prop('active')).toEqual(false); 198 | expect(lineNumbersStandardButton.prop('active')).toEqual(false); 199 | expect(lineNumbersVerticalButton.prop('active')).toEqual(true); 200 | }); 201 | }); 202 | -------------------------------------------------------------------------------- /src/editor/toolbar/size-dropdown/__snapshots__/index.test.jsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Editor Toolbar SizeDropdown renders all of the sizes as dropdown options 1`] = ` 4 |
8 | 23 |
29 | 38 | 47 | 56 | 65 | 74 |
75 |
76 | `; 77 | -------------------------------------------------------------------------------- /src/editor/toolbar/size-dropdown/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { UncontrolledDropdown, DropdownMenu, DropdownItem } from 'reactstrap'; 4 | import DropdownToggleSelect from '../../../common/dropdown-toggle-select'; 5 | 6 | const SizeDropdown = ({ sizes, active, onSelect }) => { 7 | const dropdownItems = sizes.map(size => ( 8 | onSelect(size)}> 9 | {size} 10 | 11 | )); 12 | 13 | return ( 14 | 15 | {active} 16 | {dropdownItems} 17 | 18 | ); 19 | }; 20 | 21 | SizeDropdown.propTypes = { 22 | sizes: PropTypes.arrayOf(PropTypes.number).isRequired, 23 | active: PropTypes.number.isRequired, 24 | onSelect: PropTypes.func 25 | }; 26 | 27 | export default SizeDropdown; 28 | -------------------------------------------------------------------------------- /src/editor/toolbar/size-dropdown/index.story.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { storiesOf } from '@storybook/react'; 3 | import { action } from '@storybook/addon-actions'; 4 | import 'bootstrap/dist/css/bootstrap.css'; 5 | import SizeDropdown from './index'; 6 | 7 | storiesOf('Editor/Toolbar/SizeDropdown', module).add('default', () => { 8 | const sizes = [8, 9, 10, 11, 12]; 9 | 10 | return ; 11 | }); 12 | -------------------------------------------------------------------------------- /src/editor/toolbar/size-dropdown/index.test.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import renderer from 'react-test-renderer'; 4 | import SizeDropdown from './index'; 5 | import { DropdownItem } from 'reactstrap'; 6 | 7 | describe('Editor Toolbar SizeDropdown', () => { 8 | it('renders without crashing', () => { 9 | const sizes = [8, 9, 10, 11, 12]; 10 | shallow( {}} />); 11 | }); 12 | 13 | it('renders all of the sizes as dropdown options', () => { 14 | const sizes = [8, 9, 10, 11, 12]; 15 | const tree = renderer.create( {}} />).toJSON(); 16 | 17 | expect(tree).toMatchSnapshot(); 18 | }); 19 | 20 | it('calls the onSelect function on selecting a dropdown option', () => { 21 | const sizes = [8, 9, 10, 11, 12]; 22 | const onSelect = jest.fn(); 23 | const sizeDropdown = shallow(); 24 | 25 | sizeDropdown 26 | .find(DropdownItem) 27 | .last() 28 | .simulate('click'); 29 | 30 | expect(onSelect).toHaveBeenCalledTimes(1); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /src/editor/toolbar/theme-dropdown/__snapshots__/index.test.jsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Editor Toolbar ThemeDropdown renders all of the themes as dropdown options 1`] = ` 4 |
8 | 23 |
29 | 38 | 47 | 56 |
57 |
58 | `; 59 | -------------------------------------------------------------------------------- /src/editor/toolbar/theme-dropdown/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { UncontrolledDropdown, DropdownMenu, DropdownItem } from 'reactstrap'; 4 | import DropdownToggleSelect from '../../../common/dropdown-toggle-select'; 5 | 6 | const ThemeDropdown = ({ themes, active, onSelect }) => { 7 | const dropdownItems = themes.map(theme => ( 8 | onSelect(theme)}> 9 | {theme} 10 | 11 | )); 12 | 13 | return ( 14 | 15 | {active} 16 | {dropdownItems} 17 | 18 | ); 19 | }; 20 | 21 | ThemeDropdown.propTypes = { 22 | themes: PropTypes.arrayOf(PropTypes.string).isRequired, 23 | active: PropTypes.string.isRequired, 24 | onSelect: PropTypes.func 25 | }; 26 | 27 | export default ThemeDropdown; 28 | -------------------------------------------------------------------------------- /src/editor/toolbar/theme-dropdown/index.story.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { storiesOf } from '@storybook/react'; 3 | import { action } from '@storybook/addon-actions'; 4 | import 'bootstrap/dist/css/bootstrap.css'; 5 | import ThemeDropdown from './index'; 6 | 7 | storiesOf('Editor/Toolbar/ThemeDropdown', module).add('default', () => { 8 | const themes = ['GitHub', 'VS', 'Xcode']; 9 | 10 | return ; 11 | }); 12 | -------------------------------------------------------------------------------- /src/editor/toolbar/theme-dropdown/index.test.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import renderer from 'react-test-renderer'; 4 | import ThemeDropdown from './index'; 5 | import { DropdownItem } from 'reactstrap'; 6 | 7 | describe('Editor Toolbar ThemeDropdown', () => { 8 | it('renders without crashing', () => { 9 | const themes = ['GitHub', 'VS', 'Xcode']; 10 | shallow( {}} />); 11 | }); 12 | 13 | it('renders all of the themes as dropdown options', () => { 14 | const themes = ['GitHub', 'VS', 'Xcode']; 15 | const tree = renderer.create( {}} />).toJSON(); 16 | 17 | expect(tree).toMatchSnapshot(); 18 | }); 19 | 20 | it('calls the onSelect function on selecting a dropdown option ', () => { 21 | const themes = ['GitHub', 'VS', 'Xcode']; 22 | const onSelect = jest.fn(); 23 | const themeDropdown = shallow(); 24 | 25 | themeDropdown 26 | .find(DropdownItem) 27 | .last() 28 | .simulate('click'); 29 | 30 | expect(onSelect).toHaveBeenCalledTimes(1); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /src/heart/__snapshots__/index.test.jsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Heart renders correctly 1`] = ` 4 |
12 |
15 |
18 |

19 | 23 | codeprinter? 24 |

25 |
26 |

27 | That's awesome! Let the developer know on 28 | 29 | 34 | Twitter 35 | 36 | , 37 | 38 | 43 | donate 44 | 45 | , or star the repository on 46 | 47 | 52 | GitHub 53 | 54 | . 55 |

56 |

57 | Found a bug? Want to request a new feature? Great! Just create an issue on GitHub. We're welcome to pull requests. 58 |

59 |

60 | 64 | About the Developer 65 |

66 |
67 |

68 | This software was developed by Jared Petersen. 69 |

70 |

71 | You can check out his other repositories on 72 | 73 | 78 | GitHub 79 | 80 | 81 | or look at his résumé on 82 | 83 | 88 | LinkedIn 89 | 90 |

91 |
92 |
93 |
94 | `; 95 | -------------------------------------------------------------------------------- /src/heart/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Container, Row, Col } from 'reactstrap'; 3 | 4 | const Heart = () => { 5 | return ( 6 | 7 | 8 | 9 |

10 | codeprinter? 11 |

12 |
13 |

14 | That's awesome! Let the developer know on{' '} 15 | 16 | Twitter 17 | 18 | ,{' '} 19 | 20 | donate 21 | 22 | , or star the repository on{' '} 23 | 24 | GitHub 25 | 26 | . 27 |

28 |

29 | Found a bug? Want to request a new feature? Great! Just create an issue on GitHub. We're welcome to pull 30 | requests. 31 |

32 | 33 |

34 | About the Developer 35 |

36 |
37 |

This software was developed by Jared Petersen.

38 |

39 | You can check out his other repositories on{' '} 40 | 41 | GitHub 42 | {' '} 43 | or look at his résumé on{' '} 44 | 45 | LinkedIn 46 | 47 |

48 | 49 |
50 |
51 | ); 52 | }; 53 | 54 | Heart.propTypes = {}; 55 | 56 | export default Heart; 57 | -------------------------------------------------------------------------------- /src/heart/index.story.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { storiesOf } from '@storybook/react'; 3 | import 'bootstrap/dist/css/bootstrap.css'; 4 | import Heart from './index'; 5 | 6 | storiesOf('Heart', module).add('default', () => ); 7 | -------------------------------------------------------------------------------- /src/heart/index.test.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import renderer from 'react-test-renderer'; 4 | import Heart from './index'; 5 | 6 | describe('Heart', () => { 7 | it('renders without crashing', () => { 8 | shallow(); 9 | }); 10 | 11 | it('renders correctly', () => { 12 | const tree = renderer.create().toJSON(); 13 | 14 | expect(tree).toMatchSnapshot(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | 7 | body, 8 | html, 9 | #root { 10 | height: 100%; 11 | } 12 | 13 | @media screen and (max-width: 767px) { 14 | .responsive-container { 15 | height: calc(100% - 254px); 16 | } 17 | } 18 | 19 | @media screen and (min-width: 768px) { 20 | .responsive-container { 21 | height: calc(100% - 130px); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import 'bootstrap/dist/css/bootstrap.css'; 5 | import App from './App'; 6 | import registerServiceWorker from './registerServiceWorker'; 7 | 8 | ReactDOM.render(, document.getElementById('root')); 9 | registerServiceWorker(); 10 | -------------------------------------------------------------------------------- /src/navbar/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Collapse, Navbar, NavbarToggler, NavbarBrand, Nav, NavItem, NavLink, Badge } from 'reactstrap'; 3 | import { Link } from 'react-router-dom'; 4 | 5 | class CustomNavbar extends React.Component { 6 | constructor(props) { 7 | super(props); 8 | 9 | this.state = { 10 | isOpen: false, 11 | sizes: Array.from({ length: 9 }, (x, i) => i + 8) 12 | }; 13 | 14 | this.toggle = this.toggle.bind(this); 15 | } 16 | 17 | toggle() { 18 | this.setState({ isOpen: !this.state.isOpen }); 19 | } 20 | 21 | render() { 22 | return ( 23 | 24 | 25 | codeprinter 26 | 27 | 28 | 29 | 41 | 42 | 43 | ); 44 | } 45 | } 46 | 47 | CustomNavbar.propTypes = {}; 48 | 49 | export default CustomNavbar; 50 | -------------------------------------------------------------------------------- /src/navbar/index.story.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { storiesOf } from '@storybook/react'; 3 | import { BrowserRouter as Router } from 'react-router-dom'; 4 | import 'bootstrap/dist/css/bootstrap.css'; 5 | import Navbar from './index'; 6 | 7 | storiesOf('Navbar', module).add('default', () => ( 8 | 9 | 10 | 11 | )); 12 | -------------------------------------------------------------------------------- /src/navbar/index.test.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import Navbar from './index'; 4 | import { NavbarToggler, Collapse } from 'reactstrap'; 5 | 6 | describe('Navbar', () => { 7 | it('renders without crashing', () => { 8 | shallow(); 9 | }); 10 | 11 | it('toggles the navbar when clicking on the navbar toggler', () => { 12 | const navbar = shallow(); 13 | const navbarToggler = navbar.find(NavbarToggler); 14 | 15 | expect(navbar.find(Collapse).prop('isOpen')).toEqual(false); 16 | 17 | navbarToggler.simulate('click'); 18 | 19 | expect(navbar.find(Collapse).prop('isOpen')).toEqual(true); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/not-found/__snapshots__/index.test.jsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`NotFound renders correctly 1`] = ` 4 |
12 |
15 |
18 |

19 | 22 | 404 Not Found 23 |

24 |
25 |

26 | It looks like you're lost. Let's go 27 | 31 | home 32 | 33 | . 34 |

35 |
36 |
37 |
38 | `; 39 | -------------------------------------------------------------------------------- /src/not-found/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Container, Row, Col } from 'reactstrap'; 3 | import { Link } from 'react-router-dom'; 4 | 5 | const NotFound = () => { 6 | return ( 7 | 8 | 9 | 10 |

11 | 404 Not Found 12 |

13 |
14 |

15 | It looks like you're lost. Let's go home. 16 |

17 | 18 |
19 |
20 | ); 21 | }; 22 | 23 | NotFound.propTypes = {}; 24 | 25 | export default NotFound; 26 | -------------------------------------------------------------------------------- /src/not-found/index.story.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { storiesOf } from '@storybook/react'; 3 | import { BrowserRouter as Router } from 'react-router-dom'; 4 | import 'bootstrap/dist/css/bootstrap.css'; 5 | import NotFound from './index'; 6 | 7 | storiesOf('Not Found', module).add('default', () => ( 8 | 9 | 10 | 11 | )); 12 | -------------------------------------------------------------------------------- /src/not-found/index.test.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import renderer from 'react-test-renderer'; 4 | import NotFound from './index'; 5 | import { BrowserRouter as Router } from 'react-router-dom'; 6 | 7 | describe('NotFound', () => { 8 | it('renders without crashing', () => { 9 | shallow(); 10 | }); 11 | 12 | it('renders correctly', () => { 13 | const tree = renderer 14 | .create( 15 | 16 | 17 | 18 | ) 19 | .toJSON(); 20 | 21 | expect(tree).toMatchSnapshot(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | // In production, we register a service worker to serve assets from local cache. 2 | 3 | // This lets the app load faster on subsequent visits in production, and gives 4 | // it offline capabilities. However, it also means that developers (and users) 5 | // will only see deployed updates on the "N+1" visit to a page, since previously 6 | // cached resources are updated in the background. 7 | 8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy. 9 | // This link also includes instructions on opting out of this behavior. 10 | 11 | const isLocalhost = Boolean( 12 | window.location.hostname === 'localhost' || 13 | // [::1] is the IPv6 localhost address. 14 | window.location.hostname === '[::1]' || 15 | // 127.0.0.1/8 is considered localhost for IPv4. 16 | window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/) 17 | ); 18 | 19 | export default function register() { 20 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 21 | // The URL constructor is available in all browsers that support SW. 22 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location); 23 | if (publicUrl.origin !== window.location.origin) { 24 | // Our service worker won't work if PUBLIC_URL is on a different origin 25 | // from what our page is served on. This might happen if a CDN is used to 26 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 27 | return; 28 | } 29 | 30 | window.addEventListener('load', () => { 31 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 32 | 33 | if (isLocalhost) { 34 | // This is running on localhost. Lets check if a service worker still exists or not. 35 | checkValidServiceWorker(swUrl); 36 | 37 | // Add some additional logging to localhost, pointing developers to the 38 | // service worker/PWA documentation. 39 | navigator.serviceWorker.ready.then(() => { 40 | console.log( 41 | 'This web app is being served cache-first by a service ' + 42 | 'worker. To learn more, visit https://goo.gl/SC7cgQ' 43 | ); 44 | }); 45 | } else { 46 | // Is not local host. Just register service worker 47 | registerValidSW(swUrl); 48 | } 49 | }); 50 | } 51 | } 52 | 53 | function registerValidSW(swUrl) { 54 | navigator.serviceWorker 55 | .register(swUrl) 56 | .then(registration => { 57 | registration.onupdatefound = () => { 58 | const installingWorker = registration.installing; 59 | installingWorker.onstatechange = () => { 60 | if (installingWorker.state === 'installed') { 61 | if (navigator.serviceWorker.controller) { 62 | // At this point, the old content will have been purged and 63 | // the fresh content will have been added to the cache. 64 | // It's the perfect time to display a "New content is 65 | // available; please refresh." message in your web app. 66 | console.log('New content is available; please refresh.'); 67 | } else { 68 | // At this point, everything has been precached. 69 | // It's the perfect time to display a 70 | // "Content is cached for offline use." message. 71 | console.log('Content is cached for offline use.'); 72 | } 73 | } 74 | }; 75 | }; 76 | }) 77 | .catch(error => { 78 | console.error('Error during service worker registration:', error); 79 | }); 80 | } 81 | 82 | function checkValidServiceWorker(swUrl) { 83 | // Check if the service worker can be found. If it can't reload the page. 84 | fetch(swUrl) 85 | .then(response => { 86 | // Ensure service worker exists, and that we really are getting a JS file. 87 | if (response.status === 404 || response.headers.get('content-type').indexOf('javascript') === -1) { 88 | // No service worker found. Probably a different app. Reload the page. 89 | navigator.serviceWorker.ready.then(registration => { 90 | registration.unregister().then(() => { 91 | window.location.reload(); 92 | }); 93 | }); 94 | } else { 95 | // Service worker found. Proceed as normal. 96 | registerValidSW(swUrl); 97 | } 98 | }) 99 | .catch(() => { 100 | console.log('No internet connection found. App is running in offline mode.'); 101 | }); 102 | } 103 | 104 | export function unregister() { 105 | if ('serviceWorker' in navigator) { 106 | navigator.serviceWorker.ready.then(registration => { 107 | registration.unregister(); 108 | }); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/setupTests.js: -------------------------------------------------------------------------------- 1 | import { configure } from 'enzyme'; 2 | import Adapter from 'enzyme-adapter-react-16'; 3 | 4 | configure({ adapter: new Adapter() }); 5 | --------------------------------------------------------------------------------