├── .babelrc ├── .csslintrc ├── .gitignore ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── LICENSE.md ├── README.md ├── examples ├── README.md ├── react-hot-loader │ ├── .babelrc │ ├── README.md │ ├── components │ │ └── App.js │ ├── index.html │ ├── index.js │ ├── package.json │ ├── server.js │ └── webpack.config.js └── react-transform-catch-errors │ ├── .babelrc │ ├── README.md │ ├── components │ └── App.js │ ├── devServer.js │ ├── error-catcher.js │ ├── index.html │ ├── index.js │ ├── package.json │ ├── reporterOptions.js │ └── webpack.config.dev.js ├── make-webpack-config.js ├── package-lock.json ├── package.json ├── src ├── index.js ├── lib.js └── style.js ├── tests ├── errorStackParserMock.js ├── framesStub.json ├── framesStubAbsoluteFilenames.json ├── framesStubMissingFilename.json ├── index.js ├── lib.js └── utils.js ├── types ├── redbox-react.d.ts ├── tests │ └── redbox.tsx └── tsconfig.json ├── webpack.config.js └── webpack.config.prod.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env", "react"], 3 | "plugins": [ 4 | "rewire", 5 | "transform-async-generator-functions", 6 | "transform-class-properties", 7 | "transform-do-expressions", 8 | "transform-export-extensions", 9 | "transform-object-rest-spread" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.csslintrc: -------------------------------------------------------------------------------- 1 | --ignore=box-sizing 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # common 2 | 3 | coverage 4 | node_modules 5 | tmp 6 | *.log 7 | .DS_Store 8 | 9 | # git specific 10 | 11 | dist 12 | lib 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | cache: 4 | directories: 5 | - node_modules 6 | notifications: 7 | email: false 8 | node_js: 9 | - "stable" 10 | before_script: 11 | - npm prune 12 | after_success: 13 | - npm run semantic-release 14 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. 4 | 5 | We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion. 6 | 7 | Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct. 8 | 9 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team. 10 | 11 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. 12 | 13 | This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/) 14 | 15 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 David Pfahler 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 | # redbox-react 2 | 3 | [![Build Status](https://travis-ci.org/commissure/redbox-react.svg?branch=master)](https://travis-ci.org/commissure/redbox-react) 4 | [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) 5 | 6 | The red box (aka red screen of death) renders an error in this “pretty” format: 7 | 8 | red screen of death 9 | 10 | ## Usage 11 | 12 | Catch an error and give it to `redbox-react`. Works great with 13 | 14 | - [react-transform-catch-errors] 15 | - [Example][react-transform-example] 16 | - [react-transform-boilerplate] 17 | - [react-hot-loader] 18 | - :warning: ️ based on `3.0.0-beta.2`! This depends on `ErrorBoundaries` which 19 | will likely not land in react! You should probably not use this 20 | before 3.0.0 comes out. 21 | - [Example][react-hot-loader-example] 22 | 23 | [react-transform-catch-errors]: https://github.com/gaearon/react-transform-catch-errors 24 | [react-transform-example]: https://github.com/commissure/redbox-react/tree/master/examples/react-transform-catch-errors 25 | [react-transform-boilerplate]: https://github.com/gaearon/react-transform-boilerplate/ 26 | [react-hot-loader]: https://github.com/gaearon/react-hot-loader 27 | [react-hot-loader-example]: https://github.com/commissure/redbox-react/tree/master/examples/react-hot-loader 28 | 29 | or manually: 30 | 31 | ```javascript 32 | import RedBox from 'redbox-react' 33 | 34 | const e = new Error('boom') 35 | const box = 36 | ``` 37 | 38 | Here is a more useful, full-fleged example: 39 | 40 | ```javascript 41 | /* global __DEV__ */ 42 | import React from 'react' 43 | import { render } from 'react-dom' 44 | import App from './components/App' 45 | 46 | const root = document.getElementById('root') 47 | 48 | if (__DEV__) { 49 | const RedBox = require('redbox-react').default 50 | try { 51 | render(, root) 52 | } catch (e) { 53 | render(, root) 54 | } 55 | } else { 56 | render(, root) 57 | } 58 | ``` 59 | 60 | ## What Is This Good For? 61 | 62 | An error that's only in the console is only half the fun. Now you can 63 | use all the wasted space where your app would be if it didn’t crash to 64 | display the error that made it crash. 65 | 66 | **Please use this in development only.** 67 | 68 | ## Will this catch errors for me? 69 | No. This is only a UI component for rendering errors and their stack 70 | traces. It is intended to be used with with other existing solutions 71 | that automate the error catching for you. See the list at the top of 72 | this document or take a look at the [examples]. 73 | 74 | [examples]: https://github.com/commissure/redbox-react/tree/master/examples 75 | 76 | ## Optional Props 77 | 78 | The `RedBox` component takes a couple of props that you can use to 79 | customize its behaviour: 80 | 81 | ### `editorScheme` `[?string]` 82 | If a filename in the stack trace is local, the component can create the 83 | link to open your editor using this scheme eg: 84 | - `subl` to create 85 | `subl://open?url=file:///filename`. 86 | - or `vscode` to create 87 | `vscode://file/filename`. 88 | 89 | ### `useLines` `[boolean=true]` 90 | Line numbers in the stack trace may be unreliable depending on the 91 | type of sourcemaps. You can choose to not display them with this flag. 92 | 93 | ### `useColumns` `[boolean=true]` 94 | Column numbers in the stack trace may be unreliable depending on the 95 | type of sourcemaps. You can choose to not display them with this flag. 96 | 97 | ### `style` `[?object]` 98 | Allows you to override the styles used when rendering the various parts of the 99 | component. It will be shallow-merged with the [default styles](./src/style.js). 100 | 101 | If you’re using [react-transform-catch-errors] you can add these 102 | options to your `.babelrc` through the [`imports` property][imports]. 103 | 104 | [imports]: https://github.com/gaearon/react-transform-catch-errors#installation 105 | 106 | ## Sourcemaps With Webpack 107 | 108 | If you’re using [Webpack](https://webpack.github.io) you can get 109 | accurate filenames in the stacktrace by setting the 110 | `output.devtoolModuleFilenameTemplate` settings to `/[absolute-resource-path]`. 111 | 112 | It's recommended to set the `devtool` setting to `'eval'`. 113 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | At the time of this writing, we are in a bit of a limbo. `react-hot-loader` [version 3](https://github.com/gaearon/react-hot-loader/pull/240) is about to be released, but (possibly among other things) is waiting on [ErrorBoundaries in react](https://github.com/facebook/react/pull/6020), which is likely to land in the next react release. 3 | 4 | Because of this, the examples have been updated to reflect the current state of deprecation and anticipation: 5 | * The [`react-hot-loader` example](react-hot-loader/) features the upcoming version 3 (currently 3.0.0-beta.2), but does not yet support error catching and rendering on updates, only on initial mount. This is the future, but it's not quite here. 6 | * The [`react-transform-catch-errors` example](react-transform-catch-errors/) shows how to catch and render errors with the deprecated `react-transform-catch-errors` plugin. This is the way of the past, but it works today. 7 | 8 | ## For the futurists 9 | If check out the [PR, which brings ErrorBoundaries to react](https://github.com/facebook/react/pull/6020), and build react from source, you can use the RHL 3 example __with hot reloading error capture__ today. 10 | -------------------------------------------------------------------------------- /examples/react-hot-loader/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-0", "react"], 3 | "env": { 4 | "development": { 5 | "plugins": ["react-hot-loader/babel"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/react-hot-loader/README.md: -------------------------------------------------------------------------------- 1 | # react-hot-loader example 2 | react-hot-loader 3 ([PR](https://github.com/gaearon/react-hot-loader/pull/240)) is coming soon. This examples shows how to use `redbox-react` with this upcoming version of RHL. Once [ErrorBoundaries](https://github.com/facebook/react/pull/6020) land in `react`, this approach will also work for capturing and rendering errors on updates (= when hot reloading). For now, this only works on initial mount. -------------------------------------------------------------------------------- /examples/react-hot-loader/components/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | 3 | export default class App extends Component { 4 | render () { 5 | return
{this.props()}
6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/react-hot-loader/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | redbox-react example 4 | 9 | 10 | 11 |
12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/react-hot-loader/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {render} from 'react-dom' 3 | import {AppContainer} from 'react-hot-loader' 4 | import App from './components/App' 5 | import Redbox from 'redbox-react' 6 | 7 | const root = document.getElementById('root') 8 | render(, root) 9 | 10 | if (module.hot) { 11 | module.hot.accept('./components/App', () => { 12 | render( 13 | 14 | 15 | , 16 | root 17 | ) 18 | }) 19 | } 20 | -------------------------------------------------------------------------------- /examples/react-hot-loader/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redbox-react-hot-loader-example", 3 | "version": "1.0.0", 4 | "description": "Use redbox-react with react-hot-loader", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "node server.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/library-boilerplate-author/library-boilerplate.git" 12 | }, 13 | "keywords": [ 14 | "library-boilerplate-keywords" 15 | ], 16 | "license": "MIT", 17 | "bugs": { 18 | "url": "https://github.com/library-boilerplate-author/library-boilerplate/issues" 19 | }, 20 | "homepage": "https://github.com/library-boilerplate-author/library-boilerplate", 21 | "dependencies": { 22 | "react": "^15.0.0", 23 | "react-dom": "^15.1.0" 24 | }, 25 | "devDependencies": { 26 | "babel-core": "^6.9.0", 27 | "babel-loader": "^6.2.4", 28 | "babel-preset-es2015": "^6.3.3", 29 | "babel-preset-react": "^6.5.0", 30 | "babel-preset-stage-0": "^6.3.13", 31 | "react-hot-loader": "^3.0.0-beta.2", 32 | "webpack": "^1.13.1", 33 | "webpack-dev-server": "^1.14.1" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/react-hot-loader/server.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack') 2 | var WebpackDevServer = require('webpack-dev-server') 3 | var config = require('./webpack.config') 4 | 5 | new WebpackDevServer(webpack(config), { 6 | publicPath: config.output.publicPath, 7 | hot: true, 8 | historyApiFallback: true, 9 | stats: { 10 | colors: true 11 | } 12 | }).listen(3000, 'localhost', function (err) { 13 | if (err) { 14 | console.log(err) 15 | } 16 | 17 | console.log('Listening at localhost:3000') 18 | }) 19 | -------------------------------------------------------------------------------- /examples/react-hot-loader/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var webpack = require('webpack') 3 | 4 | module.exports = { 5 | devtool: 'eval', 6 | entry: [ 7 | 'webpack-dev-server/client?http://localhost:3000', 8 | 'webpack/hot/only-dev-server', 9 | 'react-hot-loader/patch', 10 | './index' 11 | ], 12 | output: { 13 | path: path.join(__dirname, 'dist'), 14 | filename: 'bundle.js', 15 | publicPath: '/static/', 16 | devtoolModuleFilenameTemplate: '/[absolute-resource-path]' 17 | }, 18 | plugins: [ 19 | new webpack.HotModuleReplacementPlugin() 20 | ], 21 | resolve: { 22 | alias: { 23 | 'redbox-react': path.join(__dirname, '..', '..', 'src') 24 | }, 25 | extensions: ['', '.js'] 26 | }, 27 | module: { 28 | loaders: [{ 29 | test: /\.js$/, 30 | loaders: ['babel'], 31 | exclude: /node_modules/, 32 | include: __dirname 33 | }, { 34 | test: /\.js$/, 35 | loaders: ['babel'], 36 | include: path.join(__dirname, '..', '..', 'src') 37 | }] 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/react-transform-catch-errors/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-0", "react"], 3 | "env": { 4 | "development": { 5 | "plugins": [ 6 | [ 7 | "react-transform", { 8 | "transforms": [{ 9 | "transform": "react-transform-catch-errors", 10 | "imports": [ 11 | "react", 12 | "redbox-react", 13 | "../reporterOptions.js" 14 | ] 15 | }] 16 | } 17 | ] 18 | ] 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/react-transform-catch-errors/README.md: -------------------------------------------------------------------------------- 1 | # react-transform-catch-errors example 2 | [react-transform-catch](https://github.com/gaearon/react-transform-catch-errors) errors is deprectated, but for now, this examples shows you how to use `redbox-react` with it. -------------------------------------------------------------------------------- /examples/react-transform-catch-errors/components/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | 3 | export default class App extends Component { 4 | render () { 5 | return
{this.props()}
6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/react-transform-catch-errors/devServer.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var express = require('express'); 3 | var webpack = require('webpack'); 4 | var config = require('./webpack.config.dev'); 5 | 6 | var app = express(); 7 | var compiler = webpack(config); 8 | 9 | app.use(require('webpack-dev-middleware')(compiler, { 10 | noInfo: true, 11 | publicPath: config.output.publicPath 12 | })); 13 | 14 | app.use(require('webpack-hot-middleware')(compiler)); 15 | 16 | app.get('*', function(req, res) { 17 | res.sendFile(path.join(__dirname, 'index.html')); 18 | }); 19 | 20 | app.listen(3000, 'localhost', function(err) { 21 | if (err) { 22 | console.log(err); 23 | return; 24 | } 25 | 26 | console.log('Listening at http://localhost:3000'); 27 | }); 28 | -------------------------------------------------------------------------------- /examples/react-transform-catch-errors/error-catcher.js: -------------------------------------------------------------------------------- 1 | var catcherPath = 'babel-plugin-react-error-catcher' 2 | var reporterPath = '../../../src/' 3 | 4 | module.exports = require(catcherPath)(reporterPath) 5 | -------------------------------------------------------------------------------- /examples/react-transform-catch-errors/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | redbox-react example 4 | 9 | 10 | 11 |
12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/react-transform-catch-errors/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import App from './components/App' 4 | 5 | const root = document.getElementById('root') 6 | ReactDOM.render(, root) 7 | -------------------------------------------------------------------------------- /examples/react-transform-catch-errors/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redbox-babel-plugin-react-hot-example", 3 | "version": "1.0.0", 4 | "description": "Use redbox-react with react-transform-catch-errors", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node devServer.js" 8 | }, 9 | "license": "MIT", 10 | "dependencies": { 11 | "react": "^0.14.0 || ^15.0.0", 12 | "react-dom": "^15.1.0" 13 | }, 14 | "devDependencies": { 15 | "babel-cli": "^6.3.17", 16 | "babel-core": "^6.3.21", 17 | "babel-loader": "^6.0.0", 18 | "babel-plugin-react-transform": "^2.0.2", 19 | "babel-preset-es2015": "^6.3.3", 20 | "babel-preset-stage-0": "^6.3.13", 21 | "express": "^4.13.3", 22 | "react-transform-catch-errors": "^1.0.2", 23 | "rimraf": "^2.4.3", 24 | "webpack": "^1.9.6", 25 | "webpack-dev-middleware": "^1.2.0", 26 | "webpack-hot-middleware": "^2.0.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/react-transform-catch-errors/reporterOptions.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | style: { 3 | redbox: { 4 | boxSizing: 'border-box', 5 | fontFamily: 'sans-serif', 6 | padding: 10, 7 | top: 0, 8 | left: 0, 9 | bottom: 0, 10 | right: 0, 11 | width: '100%', 12 | background: 'rgb(173, 49, 49)', 13 | color: 'white', 14 | zIndex: 9999, 15 | textAlign: 'left', 16 | fontSize: '16px', 17 | lineHeight: 1.2 18 | }, 19 | message: { 20 | fontWeight: 'bold', 21 | fontSize: 20 22 | } 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /examples/react-transform-catch-errors/webpack.config.dev.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var webpack = require('webpack') 3 | 4 | module.exports = { 5 | entry: [ 6 | 'webpack-hot-middleware/client', 7 | './index' 8 | ], 9 | output: { 10 | path: path.join(__dirname, 'dist'), 11 | filename: 'bundle.js', 12 | publicPath: '/static/', 13 | devtoolModuleFilenameTemplate: '/[absolute-resource-path]' 14 | }, 15 | devtool: 'eval', 16 | plugins: [ 17 | new webpack.HotModuleReplacementPlugin(), 18 | new webpack.NoErrorsPlugin() 19 | ], 20 | resolve: { 21 | alias: { 22 | 'redbox-react': path.join(__dirname, '..', '..', 'src') 23 | }, 24 | extensions: ['', '.js'] 25 | }, 26 | module: { 27 | loaders: [{ 28 | test: /\.js$/, 29 | loaders: ['babel'], 30 | exclude: /node_modules/, 31 | include: __dirname 32 | }, { 33 | test: /\.js$/, 34 | loaders: ['babel'], 35 | exclude: /node_modules/, 36 | include: path.join(__dirname, '..', '..', 'src') 37 | }] 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /make-webpack-config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // imports 3 | var webpack = require('webpack') 4 | 5 | module.exports = function(options){ 6 | 7 | // base set of plugins, used in any configuration 8 | var plugins = [ 9 | new webpack.optimize.OccurenceOrderPlugin(), 10 | new webpack.DefinePlugin({ 11 | 'process.env.NODE_ENV': JSON.stringify(options.nodeEnv) 12 | }) 13 | ] 14 | 15 | // production configuration 16 | if (options.minimize) { 17 | plugins.push( 18 | new webpack.optimize.UglifyJsPlugin({ 19 | compressor: { 20 | screw_ie8: true, 21 | warnings: false 22 | } 23 | }) 24 | ) 25 | } 26 | 27 | return { 28 | module: { 29 | loaders: [{ 30 | test: /\.js$/, 31 | loaders: ['babel-loader'], 32 | exclude: /node_modules/ 33 | }] 34 | }, 35 | externals: { 36 | 'react': { 37 | root: 'React', 38 | commonjs2: 'react', 39 | commonjs: 'react', 40 | amd: 'react' 41 | } 42 | }, 43 | output: { 44 | library: 'redbox', 45 | libraryTarget: 'umd' 46 | }, 47 | plugins: plugins 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redbox-react", 3 | "description": "A redbox (rsod) component to display your errors.", 4 | "main": "lib/index.js", 5 | "types": "types/redbox-react.d.ts", 6 | "files": [ 7 | "dist", 8 | "lib", 9 | "src", 10 | "types" 11 | ], 12 | "scripts": { 13 | "clean": "rimraf dist lib tmp", 14 | "build:umd": "webpack src/index.js dist/redbox.js", 15 | "build:umd:min": "webpack --config webpack.config.prod.js src/index.js dist/redbox.min.js", 16 | "build:lib": "babel src --out-dir lib", 17 | "build": "npm run build:umd && npm run build:umd:min && npm run build:lib", 18 | "prepublish": "npm run clean && npm run build", 19 | "start": "babel src --out-dir lib -w", 20 | "test": "standard ./src && babel-node ./tests | tap-spec && tsc --project ./types", 21 | "semantic-release": "semantic-release pre && npm publish && semantic-release post" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/commissure/redbox-react.git" 26 | }, 27 | "author": "David Pfahler", 28 | "license": "MIT", 29 | "bugs": { 30 | "url": "https://github.com/commissure/redbox-react/issues" 31 | }, 32 | "homepage": "https://github.com/commissure/redbox-react", 33 | "keywords": [ 34 | "redbox", 35 | "rsod", 36 | "react", 37 | "react-native" 38 | ], 39 | "devDependencies": { 40 | "@types/react": "^15.0.24", 41 | "babel-cli": "^6.9.0", 42 | "babel-core": "^6.9.0", 43 | "babel-loader": "^6.2.4", 44 | "babel-plugin-rewire": "1.0.0", 45 | "babel-plugin-transform-async-generator-functions": "^6.24.1", 46 | "babel-plugin-transform-class-properties": "^6.19.0", 47 | "babel-plugin-transform-do-expressions": "^6.22.0", 48 | "babel-plugin-transform-export-extensions": "^6.8.0", 49 | "babel-plugin-transform-object-rest-spread": "^6.19.0", 50 | "babel-preset-env": "^1.4.0", 51 | "babel-preset-react": "^6.5.0", 52 | "react": "^0.14.0 || ^15.0.0", 53 | "react-addons-test-utils": "^15.0.0", 54 | "react-dom": "^0.14.0 || ^15.0.0", 55 | "rimraf": "^2.5.2", 56 | "semantic-release": "^4.0.0", 57 | "standard": "^7.1.1", 58 | "tap-spec": "^4.0.2", 59 | "tape": "^4.5.1", 60 | "typescript": "^2.3.2", 61 | "webpack": "^1.13.1" 62 | }, 63 | "peerDependencies": { 64 | "react": "^0.14.0 || ^15.0.0 || ^16.0.0-beta || ^16.0.0", 65 | "react-dom": "^0.14.0 || ^15.0.0 || ^16.0.0-beta || ^16.0.0" 66 | }, 67 | "dependencies": { 68 | "error-stack-parser": "^1.3.6", 69 | "object-assign": "^4.0.1", 70 | "prop-types": "^15.5.4", 71 | "sourcemapped-stacktrace": "^1.1.6" 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React, {Component} from 'react' 3 | import ReactDOM from 'react-dom' 4 | import style from './style.js' 5 | import ErrorStackParser from 'error-stack-parser' 6 | import assign from 'object-assign' 7 | import {isFilenameAbsolute, makeUrl, makeLinkText} from './lib' 8 | import { mapStackTrace } from 'sourcemapped-stacktrace' 9 | 10 | export class RedBoxError extends Component { 11 | static propTypes = { 12 | error: PropTypes.instanceOf(Error).isRequired, 13 | filename: PropTypes.string, 14 | editorScheme: PropTypes.string, 15 | useLines: PropTypes.bool, 16 | useColumns: PropTypes.bool, 17 | style: PropTypes.object, 18 | className: PropTypes.string, 19 | } 20 | static displayName = 'RedBoxError' 21 | static defaultProps = { 22 | useLines: true, 23 | useColumns: true 24 | } 25 | 26 | // State is used to store the error mapped to the source map. 27 | state = { 28 | error: null, 29 | mapped: false 30 | } 31 | 32 | constructor(props) { 33 | super(props) 34 | this.mapOnConstruction(props.error) 35 | } 36 | 37 | componentDidMount() { 38 | if (!this.state.mapped) 39 | this.mapError(this.props.error) 40 | } 41 | 42 | // Try to map the error when the component gets constructed, this is possible 43 | // in some cases like evals. 44 | mapOnConstruction(error) { 45 | const stackLines = error.stack.split('\n') 46 | 47 | // There's no stack, only the error message. 48 | if (stackLines.length < 2) { 49 | this.state = { error, mapped: true } 50 | return 51 | } 52 | 53 | // Using the “eval” setting on webpack already gives the correct location. 54 | const isWebpackEval = stackLines[1].search(/\(webpack:\/{3}/) !== -1 55 | if (isWebpackEval) { 56 | // No changes are needed here. 57 | this.state = { error, mapped: true } 58 | return 59 | } 60 | 61 | // Other eval follow a specific pattern and can be easily parsed. 62 | const isEval = stackLines[1].search(/\(eval at/) !== -1 63 | if (!isEval) { 64 | // mapping will be deferred until `componentDidMount` 65 | this.state = { error, mapped: false } 66 | return 67 | } 68 | 69 | // The first line is the error message. 70 | let fixedLines = [stackLines.shift()] 71 | // The rest needs to be fixed. 72 | for (let stackLine of stackLines) { 73 | const evalStackLine = stackLine.match( 74 | /(.+)\(eval at (.+) \(.+?\), .+(\:[0-9]+\:[0-9]+)\)/ 75 | ) 76 | if (evalStackLine) { 77 | const [, atSomething, file, rowColumn] = evalStackLine 78 | fixedLines.push(`${atSomething} (${file}${rowColumn})`) 79 | } else { 80 | // TODO: When stack frames of different types are detected, try to load the additional source maps 81 | fixedLines.push(stackLine) 82 | } 83 | } 84 | error.stack = fixedLines.join('\n') 85 | this.state = { error, mapped: true } 86 | } 87 | 88 | mapError(error) { 89 | mapStackTrace(error.stack, mappedStack => { 90 | error.stack = mappedStack.join('\n') 91 | this.setState({ error, mapped: true }) 92 | }); 93 | } 94 | 95 | renderFrames (frames) { 96 | const {filename, editorScheme, useLines, useColumns} = this.props 97 | const {frame, file, linkToFile} = assign({}, style, this.props.style) 98 | return frames.map((f, index) => { 99 | let text 100 | let url 101 | 102 | if (index === 0 && filename && !isFilenameAbsolute(f.fileName)) { 103 | url = makeUrl(filename, editorScheme) 104 | text = makeLinkText(filename) 105 | } else { 106 | let lines = useLines ? f.lineNumber : null 107 | let columns = useColumns ? f.columnNumber : null 108 | url = makeUrl(f.fileName, editorScheme, lines, columns) 109 | text = makeLinkText(f.fileName, lines, columns) 110 | } 111 | 112 | return ( 113 |
114 |
{f.functionName}
115 |
116 | {text} 117 |
118 |
119 | ) 120 | }) 121 | } 122 | 123 | render () { 124 | // The error is received as a property to initialize state.error, which may 125 | // be updated when it is mapped to the source map. 126 | const {error} = this.state 127 | 128 | const {className} = this.props 129 | const {redbox, message, stack, frame} = assign({}, style, this.props.style) 130 | 131 | let frames 132 | let parseError 133 | try { 134 | frames = ErrorStackParser.parse(error) 135 | } catch (e) { 136 | parseError = new Error('Failed to parse stack trace. Stack trace information unavailable.') 137 | } 138 | 139 | if (parseError) { 140 | frames = ( 141 |
142 |
{parseError.message}
143 |
144 | ) 145 | } else { 146 | frames = this.renderFrames(frames) 147 | } 148 | 149 | return ( 150 |
151 |
{error.name}: {error.message}
152 |
{frames}
153 |
154 | ) 155 | } 156 | } 157 | 158 | // "Portal" component for actual RedBoxError component to 159 | // render to (directly under body). Prevents bugs as in #27. 160 | export default class RedBox extends Component { 161 | static propTypes = { 162 | error: PropTypes.instanceOf(Error).isRequired 163 | } 164 | static displayName = 'RedBox' 165 | componentDidMount () { 166 | this.el = document.createElement('div') 167 | document.body.appendChild(this.el) 168 | this.renderRedBoxError() 169 | } 170 | componentDidUpdate () { 171 | this.renderRedBoxError() 172 | } 173 | componentWillUnmount () { 174 | ReactDOM.unmountComponentAtNode(this.el) 175 | document.body.removeChild(this.el) 176 | this.el = null 177 | } 178 | renderRedBoxError () { 179 | ReactDOM.render(, this.el) 180 | } 181 | render () { 182 | return null 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/lib.js: -------------------------------------------------------------------------------- 1 | export const filenameWithoutLoaders = (filename = '') => { 2 | var index = filename.lastIndexOf('!') 3 | 4 | return index < 0 ? filename : filename.substr(index + 1) 5 | } 6 | 7 | export const filenameHasLoaders = (filename) => { 8 | const actualFilename = filenameWithoutLoaders(filename) 9 | 10 | return actualFilename !== filename 11 | } 12 | 13 | export const filenameHasSchema = (filename) => { 14 | return /^[\w]+\:/.test(filename) 15 | } 16 | 17 | export const isFilenameAbsolute = (filename) => { 18 | const actualFilename = filenameWithoutLoaders(filename) 19 | 20 | if (actualFilename.indexOf('/') === 0) { 21 | return true 22 | } 23 | 24 | return false 25 | } 26 | 27 | export const makeUrl = (filename, scheme, line, column) => { 28 | let actualFilename = filenameWithoutLoaders(filename) 29 | 30 | if (filenameHasSchema(filename)) { 31 | return actualFilename 32 | } 33 | 34 | let url = `file://${actualFilename}` 35 | 36 | if (scheme === 'vscode') { 37 | url = `${scheme}://file/${url}` 38 | url = url.replace(/file:\/\/\//, '') // visual studio code does not need file:/// in its scheme 39 | if (line && actualFilename === filename) { 40 | url = `${url}:${line}` 41 | 42 | if (column) { 43 | url = `${url}:${column}` 44 | } 45 | } 46 | } else if (scheme) { 47 | url = `${scheme}://open?url=${url}` 48 | 49 | if (line && actualFilename === filename) { 50 | url = `${url}&line=${line}` 51 | 52 | if (column) { 53 | url = `${url}&column=${column}` 54 | } 55 | } 56 | } 57 | 58 | return url 59 | } 60 | 61 | export const makeLinkText = (filename, line, column) => { 62 | let text = filenameWithoutLoaders(filename) 63 | 64 | if (line && text === filename) { 65 | text = `${text}:${line}` 66 | 67 | if (column) { 68 | text = `${text}:${column}` 69 | } 70 | } 71 | 72 | return text 73 | } 74 | -------------------------------------------------------------------------------- /src/style.js: -------------------------------------------------------------------------------- 1 | export default { 2 | redbox: { 3 | boxSizing: 'border-box', 4 | fontFamily: 'sans-serif', 5 | position: 'fixed', 6 | padding: 10, 7 | top: '0px', 8 | left: '0px', 9 | bottom: '0px', 10 | right: '0px', 11 | width: '100%', 12 | background: 'rgb(204, 0, 0)', 13 | color: 'white', 14 | zIndex: 2147483647, 15 | textAlign: 'left', 16 | fontSize: '16px', 17 | lineHeight: 1.2, 18 | overflow: 'auto' 19 | }, 20 | message: { 21 | fontWeight: 'bold' 22 | }, 23 | stack: { 24 | fontFamily: 'monospace', 25 | marginTop: '2em' 26 | }, 27 | frame: { 28 | marginTop: '1em' 29 | }, 30 | file: { 31 | fontSize: '0.8em', 32 | color: 'rgba(255, 255, 255, 0.7)' 33 | }, 34 | linkToFile: { 35 | textDecoration: 'none', 36 | color: 'rgba(255, 255, 255, 0.7)' 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /tests/errorStackParserMock.js: -------------------------------------------------------------------------------- 1 | export default (framesStub) => { 2 | return { 3 | parse: () => framesStub 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /tests/framesStub.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "functionName": "App.render", 4 | "fileName": "webpack:\/\/\/.\/components\/App.js?", 5 | "lineNumber": 45, 6 | "columnNumber": 12 7 | }, 8 | { 9 | "functionName": "App.render", 10 | "fileName": "webpack:\/\/\/.\/~\/react-hot-loader\/~\/react-hot-api\/modules\/makeAssimilatePrototype.js?", 11 | "lineNumber": 17, 12 | "columnNumber": 41 13 | } 14 | ] 15 | -------------------------------------------------------------------------------- /tests/framesStubAbsoluteFilenames.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "functionName": "App.render", 4 | "fileName": "\/components\/App.js", 5 | "lineNumber": 45, 6 | "columnNumber": 12 7 | }, 8 | { 9 | "functionName": "App.render", 10 | "fileName": "\/some-path\/modules\/makeAssimilatePrototype.js", 11 | "lineNumber": 17, 12 | "columnNumber": 41 13 | } 14 | ] 15 | -------------------------------------------------------------------------------- /tests/framesStubMissingFilename.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "functionName": "App.render", 4 | "fileName": "webpack:\/\/\/.\/components\/App.js?", 5 | "lineNumber": 45, 6 | "columnNumber": 12 7 | }, 8 | { 9 | "functionName": "App.render", 10 | "lineNumber": 17, 11 | "columnNumber": 41 12 | } 13 | ] 14 | -------------------------------------------------------------------------------- /tests/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import test from 'tape' 3 | import {createComponent} from './utils' 4 | import errorStackParserMock from './errorStackParserMock' 5 | import framesStub from './framesStub.json' 6 | import framesStubAbsoluteFilenames from './framesStubAbsoluteFilenames.json' 7 | import framesStubMissingFilename from './framesStubMissingFilename.json' 8 | import RedBox, {RedBoxError, __RewireAPI__} from '../src' 9 | import style from '../src/style' 10 | import './lib'; 11 | 12 | // There’s no DOM available during the tests and the error stack is just a stub 13 | // that don’t need to be mapped. 14 | RedBoxError.prototype.mapOnConstruction = function(error) { 15 | this.state = { error, mapped: true } 16 | } 17 | 18 | RedBoxError.prototype.mapError = function(error) { 19 | this.setState({ error, mapped: true }) 20 | } 21 | 22 | const beforeEach = (framesStub) => { 23 | __RewireAPI__.__Rewire__('ErrorStackParser', errorStackParserMock(framesStub)) 24 | } 25 | 26 | const afterEach = () => { 27 | __RewireAPI__.__ResetDependency__('ErrorStackParser') 28 | } 29 | 30 | // RedBox tests 31 | test('RedBox static displayName', t => { 32 | t.plan(1) 33 | t.equal( 34 | RedBox.displayName, 35 | 'RedBox', 36 | 'correct static displayName property on class RedBox' 37 | ) 38 | }) 39 | 40 | // TODO: add missing new tests for RedBox "portal" component 41 | 42 | // RedBoxError tests 43 | test('RedBoxError static displayName', t => { 44 | t.plan(1) 45 | t.equal( 46 | RedBoxError.displayName, 47 | 'RedBoxError', 48 | 'correct static displayName property on class RedBoxError' 49 | ) 50 | }) 51 | 52 | test('RedBoxError error message', t => { 53 | t.plan(3) 54 | beforeEach(framesStub) 55 | const ERR_MESSAGE = 'funny error name' 56 | const error = new Error(ERR_MESSAGE) 57 | const component = createComponent(RedBoxError, {error}) 58 | // renderedError = div.redbox > div.message > * 59 | const renderedError = component 60 | .props.children[0] 61 | .props.children 62 | t.equal( 63 | renderedError[0], 64 | 'Error', 65 | 'Main error message begins with error type' 66 | ) 67 | t.equal( 68 | renderedError[1], 69 | ': ', 70 | 'Error type is followed by a colon and white space.' 71 | ) 72 | t.equal( 73 | renderedError[2], 74 | ERR_MESSAGE, 75 | 'Main error message ends with message originally supplied to error constructor.' 76 | ) 77 | afterEach() 78 | }) 79 | 80 | test('RedBoxError stack trace', t => { 81 | t.plan(1) 82 | beforeEach(framesStub) 83 | const error = new Error() 84 | const component = createComponent(RedBoxError, {error}) 85 | 86 | const renderedStack = component 87 | .props.children[1] 88 | 89 | t.deepEqual( 90 | renderedStack, 91 |
92 |
93 |
App.render
94 | 97 |
98 | 104 |
105 | ) 106 | 107 | afterEach() 108 | }) 109 | 110 | test('RedBoxError with filename from react-transform-catch-errors', t => { 111 | t.plan(1) 112 | beforeEach(framesStub) 113 | const error = new Error() 114 | const filename = 'some-optional-webpack-loader!/filename' 115 | const component = createComponent(RedBoxError, {error, filename}) 116 | 117 | const renderedStack = component 118 | .props.children[1] 119 | 120 | t.deepEqual( 121 | renderedStack, 122 |
123 |
124 |
App.render
125 |
126 | /filename 127 |
128 |
129 | 135 |
136 | ) 137 | afterEach() 138 | }) 139 | 140 | test('RedBoxError with filename and editorScheme', t => { 141 | t.plan(1) 142 | beforeEach(framesStub) 143 | const error = new Error() 144 | const filename = 'some-optional-webpack-loader!/filename' 145 | const editorScheme = 'subl' 146 | const component = createComponent(RedBoxError, {error, filename, editorScheme}) 147 | 148 | const renderedStack = component 149 | .props.children[1] 150 | 151 | t.deepEqual( 152 | renderedStack, 153 |
154 |
155 |
App.render
156 |
157 | /filename 158 |
159 |
160 | 166 |
167 | ) 168 | afterEach() 169 | }) 170 | 171 | test('RedBoxError with filename and vscode like editorScheme', t => { 172 | t.plan(1) 173 | beforeEach(framesStub) 174 | const error = new Error() 175 | const filename = 'some-optional-webpack-loader!/filename' 176 | const editorScheme = 'vscode' 177 | const component = createComponent(RedBoxError, {error, filename, editorScheme}) 178 | 179 | const renderedStack = component 180 | .props.children[1] 181 | 182 | t.deepEqual( 183 | renderedStack, 184 |
185 |
186 |
App.render
187 |
188 | /filename 189 |
190 |
191 | 197 |
198 | ) 199 | afterEach() 200 | }) 201 | 202 | test('RedBoxError with absolute filenames', t => { 203 | t.plan(1) 204 | beforeEach(framesStubAbsoluteFilenames) 205 | const error = new Error() 206 | const filename = 'some-optional-webpack-loader!/filename' 207 | const editorScheme = 'subl' 208 | const component = createComponent(RedBoxError, {error, filename, editorScheme}) 209 | 210 | const renderedStack = component 211 | .props.children[1] 212 | 213 | t.deepEqual( 214 | renderedStack, 215 |
216 |
217 |
App.render
218 | 221 |
222 |
223 |
App.render
224 | 227 |
228 |
229 | ) 230 | afterEach() 231 | }) 232 | 233 | test('RedBoxError with absolute filenames but unreliable line numbers', t => { 234 | t.plan(1) 235 | beforeEach(framesStubAbsoluteFilenames) 236 | const error = new Error() 237 | const filename = 'some-optional-webpack-loader!/filename' 238 | const editorScheme = 'subl' 239 | const useLines = false 240 | const component = createComponent(RedBoxError, {error, filename, editorScheme, useLines}) 241 | 242 | const renderedStack = component 243 | .props.children[1] 244 | 245 | t.deepEqual( 246 | renderedStack, 247 |
248 |
249 |
App.render
250 |
251 | /components/App.js 252 |
253 |
254 |
255 |
App.render
256 | 259 |
260 |
261 | ) 262 | afterEach() 263 | }) 264 | 265 | test('RedBoxError with absolute filenames but unreliable column numbers', t => { 266 | t.plan(1) 267 | beforeEach(framesStubAbsoluteFilenames) 268 | const error = new Error() 269 | const filename = 'some-optional-webpack-loader!/filename' 270 | const editorScheme = 'subl' 271 | const useColumns = false 272 | const component = createComponent(RedBoxError, {error, filename, editorScheme, useColumns}) 273 | 274 | const renderedStack = component 275 | .props.children[1] 276 | 277 | t.deepEqual( 278 | renderedStack, 279 |
280 |
281 |
App.render
282 |
283 | /components/App.js:45 284 |
285 |
286 |
287 |
App.render
288 | 291 |
292 |
293 | ) 294 | afterEach() 295 | }) 296 | 297 | test('RedBoxError stack trace with missing filename', t => { 298 | t.plan(1) 299 | beforeEach(framesStubMissingFilename) 300 | const error = new Error() 301 | const component = createComponent(RedBoxError, {error}) 302 | 303 | const renderedStack = component 304 | .props.children[1] 305 | 306 | t.deepEqual( 307 | renderedStack, 308 |
309 |
310 |
App.render
311 | 314 |
315 |
316 |
App.render
317 |
318 | {''} 319 |
320 |
321 |
322 | ) 323 | 324 | afterEach() 325 | }) 326 | 327 | test('RedBoxError with throwing stack trace parser', t => { 328 | t.plan(3) 329 | __RewireAPI__.__Rewire__('ErrorStackParser', { 330 | parse: function () { 331 | // This mimicks the former behavior of stacktracejs, 332 | // see https://github.com/stacktracejs/stackframe/issues/11. 333 | throw new TypeError("Line Number must be a Number") 334 | } 335 | }) 336 | const ERR_MESSAGE = "original error message" 337 | const error = new TypeError(ERR_MESSAGE) 338 | const component = createComponent(RedBoxError, {error}) 339 | 340 | const renderedError = component 341 | .props.children[0] 342 | .props.children 343 | t.equal( 344 | renderedError[0], 345 | 'TypeError', 346 | 'Main error message begins with error type of original error' 347 | ) 348 | t.equal( 349 | renderedError[2], 350 | ERR_MESSAGE, 351 | 'Main error message ends with message originally supplied to error constructor.' 352 | ) 353 | const renderedStack = component 354 | .props.children[1] 355 | 356 | t.deepEqual( 357 | renderedStack, 358 |
359 |
360 |
Failed to parse stack trace. Stack trace information unavailable.
361 |
362 |
363 | ) 364 | 365 | afterEach() 366 | }) 367 | -------------------------------------------------------------------------------- /tests/lib.js: -------------------------------------------------------------------------------- 1 | import test from 'tape' 2 | import { 3 | filenameWithoutLoaders, 4 | filenameHasLoaders, 5 | isFilenameAbsolute, 6 | filenameHasSchema, 7 | makeUrl, 8 | makeLinkText 9 | } from '../src/lib' 10 | 11 | test('filenameWithoutLoaders', t => { 12 | t.plan(3) 13 | t.equal( 14 | filenameWithoutLoaders('/webpack-loader!/filename'), 15 | '/filename', 16 | 'Should handle webpack loader' 17 | ) 18 | t.equal( 19 | filenameWithoutLoaders('/filename'), 20 | '/filename', 21 | 'Should handle no loader' 22 | ) 23 | t.equal( 24 | filenameWithoutLoaders('http://doman/path'), 25 | 'http://doman/path', 26 | 'Should handle URL' 27 | ) 28 | }) 29 | 30 | test('filenameHasLoaders', t => { 31 | t.plan(3) 32 | t.true( 33 | filenameHasLoaders('/webpack-loader!/filename'), 34 | 'Should handle webpack loader' 35 | ) 36 | t.false( 37 | filenameHasLoaders('/filename'), 38 | 'Should handle no loader' 39 | ) 40 | t.false( 41 | filenameHasLoaders('http://doman/path'), 42 | 'Should handle URL' 43 | ) 44 | }) 45 | 46 | test('isFilenameAbsolute', t => { 47 | t.plan(4) 48 | t.false( 49 | isFilenameAbsolute('webpack://filename'), 50 | 'webpack://filename is not absolute' 51 | ) 52 | t.false( 53 | isFilenameAbsolute('./filename'), 54 | './filename is not absolute' 55 | ) 56 | t.true( 57 | isFilenameAbsolute('/filename'), 58 | '/filename is absolute' 59 | ) 60 | t.true( 61 | isFilenameAbsolute('loader!/filename'), 62 | 'loader!/filename is absolute' 63 | ) 64 | }) 65 | 66 | test('filenameHasSchema', t => { 67 | t.plan(3) 68 | t.false( 69 | filenameHasSchema('/filename'), 70 | '/filename has no schema' 71 | ) 72 | t.true( 73 | filenameHasSchema('http://filename'), 74 | 'http://filename has a schema' 75 | ) 76 | t.true( 77 | filenameHasSchema('webpack:filename'), 78 | 'webpack:filename has a schema' 79 | ) 80 | }) 81 | 82 | test('makeUrl', t => { 83 | t.plan(7) 84 | t.equal( 85 | makeUrl('/filename'), 86 | 'file:///filename', 87 | 'Should handle local file' 88 | ) 89 | t.equal( 90 | makeUrl('http://filename'), 91 | 'http://filename', 92 | 'Should handle URL' 93 | ) 94 | t.equal( 95 | makeUrl('/filename', 'subl'), 96 | 'subl://open?url=file:///filename', 97 | 'Should handle local file with scheme' 98 | ) 99 | t.equal( 100 | makeUrl('/filename', 'subl', 10), 101 | 'subl://open?url=file:///filename&line=10', 102 | 'Should handle local file with scheme and line' 103 | ) 104 | t.equal( 105 | makeUrl('/filename', 'subl', 10, 3), 106 | 'subl://open?url=file:///filename&line=10&column=3', 107 | 'Should handle local file with scheme, kine and column' 108 | ) 109 | t.equal( 110 | makeUrl('http://filename', 'subl'), 111 | 'http://filename', 112 | 'Should handle URL with scheme' 113 | ) 114 | t.equal( 115 | makeUrl('/loader!/filename', 'subl', 10), 116 | 'subl://open?url=file:///filename', 117 | 'Should handle webpack loader with line' 118 | ) 119 | }) 120 | 121 | test('makeLinkText', t => { 122 | t.plan(4) 123 | t.equal( 124 | makeLinkText('/filename'), 125 | '/filename', 126 | 'Should handle filename' 127 | ) 128 | t.equal( 129 | makeLinkText('/filename', 10), 130 | '/filename:10', 131 | 'Should handle filename and line' 132 | ) 133 | t.equal( 134 | makeLinkText('/filename', 10, 3), 135 | '/filename:10:3', 136 | 'Should handle filename, line and column' 137 | ) 138 | t.equal( 139 | makeLinkText('/loader!/filename', 10), 140 | '/filename', 141 | 'Should handle filename with webpack loader and line' 142 | ) 143 | }) 144 | -------------------------------------------------------------------------------- /tests/utils.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | const TestUtils = require('react-addons-test-utils'); 3 | 4 | export const createComponent = (component, props, ...children) => { 5 | const shallowRenderer = TestUtils.createRenderer() 6 | shallowRenderer.render(React.createElement(component, props, children.length > 1 ? children : children[0])) 7 | return shallowRenderer.getRenderOutput() 8 | } 9 | -------------------------------------------------------------------------------- /types/redbox-react.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | export interface RedBoxProps extends React.Props { 4 | error: Error 5 | 6 | filename?: string 7 | editorScheme?: string 8 | useLines?: boolean 9 | useColumns?: boolean 10 | style?: React.CSSProperties 11 | className?: string 12 | } 13 | 14 | export class RedBoxError extends React.Component {} 15 | export default class RedBox extends React.Component {} 16 | -------------------------------------------------------------------------------- /types/tests/redbox.tsx: -------------------------------------------------------------------------------- 1 | import RedBox, { RedBoxError } from 'redbox-react'; 2 | 3 | ; 4 | ; 5 | 6 | ; 16 | -------------------------------------------------------------------------------- /types/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "redbox-react.d.ts", 4 | "tests/redbox.tsx" 5 | ], 6 | "compilerOptions": { 7 | "module": "commonjs", 8 | "moduleResolution": "Classic", 9 | "lib": ["es6", "dom"], 10 | "jsx": "preserve", 11 | "noImplicitAny": true, 12 | "strictNullChecks": true, 13 | "noEmit": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require("./make-webpack-config")({ 2 | minimize: false, 3 | nodeEnv: "development" 4 | }) 5 | -------------------------------------------------------------------------------- /webpack.config.prod.js: -------------------------------------------------------------------------------- 1 | module.exports = require("./make-webpack-config")({ 2 | minimize: true, 3 | nodeEnv: 'production' 4 | }) 5 | --------------------------------------------------------------------------------