fbredius/storybook

View on GitHub
addons/jest/src/components/Message.tsx

Summary

Maintainability
A
2 hrs
Test Coverage
import React, { Fragment, FunctionComponent } from 'react';
import { styled } from '@storybook/theming';

const positiveConsoleRegex = /\[32m(.*?)\[39m/;
const negativeConsoleRegex = /\[31m(.*?)\[39m/;
const positiveType = 'positive';
const negativeType = 'negative';
const endToken = '[39m';
const failStartToken = '[31m';
const passStartToken = '[32m';
const stackTraceStartToken = 'at';
const titleEndToken = ':';

type MsgElement = string | JSX.Element;

class TestDetail {
  description: MsgElement[];

  result: MsgElement[];

  stackTrace: string;
}
const StackTrace = styled.pre<{}>(({ theme }) => ({
  background: theme.color.lighter,
  paddingTop: 4,
  paddingBottom: 4,
  paddingLeft: 6,
  borderRadius: 2,
  overflow: 'auto',
  margin: '10px 30px 10px 30px',
  whiteSpace: 'pre',
}));

const Results = styled.div({
  paddingTop: 10,
  marginLeft: 31,
  marginRight: 30,
});

const Description = styled.div<{}>(({ theme }) => ({
  paddingBottom: 10,
  paddingTop: 10,
  borderBottom: theme.appBorderColor,
  marginLeft: 31,
  marginRight: 30,
  overflowWrap: 'break-word',
}));

const StatusColor = styled.strong<{ status: string }>(({ status, theme }) => ({
  color: status === positiveType ? theme.color.positive : theme.color.negative,
  fontWeight: 500,
}));

const colorizeText: (msg: string, type: string) => MsgElement[] = (msg: string, type: string) => {
  if (type) {
    return msg
      .split(type === positiveType ? positiveConsoleRegex : negativeConsoleRegex)
      .map((i, index) =>
        index % 2 ? (
          <StatusColor key={`${type}_${i}`} status={type}>
            {i}
          </StatusColor>
        ) : (
          i
        )
      );
  }
  return [msg];
};

const getConvertedText: (msg: string) => MsgElement[] = (msg: string) => {
  let elementArray: MsgElement[] = [];

  if (!msg) return elementArray;

  const splitText = msg.split(/\[2m/).join('').split(/\[22m/);

  splitText.forEach((element) => {
    if (element && element.trim()) {
      if (
        element.indexOf(failStartToken) > -1 &&
        element.indexOf(failStartToken) < element.indexOf(endToken)
      ) {
        elementArray = elementArray.concat(colorizeText(element, negativeType));
      } else if (
        element.indexOf(passStartToken) > -1 &&
        element.indexOf(passStartToken) < element.indexOf(endToken)
      ) {
        elementArray = elementArray.concat(colorizeText(element, positiveType));
      } else {
        elementArray = elementArray.concat(element);
      }
    }
  });
  return elementArray;
};

const getTestDetail: (msg: string) => TestDetail = (msg: string) => {
  const lines = msg.split('\n').filter(Boolean);

  const testDetail: TestDetail = new TestDetail();
  testDetail.description = getConvertedText(lines[0]);
  testDetail.stackTrace = '';
  testDetail.result = [];

  for (let index = 1; index < lines.length; index += 1) {
    const current = lines[index];
    const next = lines[index + 1];

    if (current.trim().toLowerCase().indexOf(stackTraceStartToken) === 0) {
      testDetail.stackTrace += `${current.trim()}\n`;
    } else if (current.trim().indexOf(titleEndToken) > -1) {
      let title;
      let value = null;
      if (current.trim().indexOf(titleEndToken) === current.length - 1) {
        // there are breaks in the middle of result
        title = current.trim();
        value = getConvertedText(next);
        index += 1;
      } else {
        // results come in a single line
        title = current.substring(0, current.indexOf(titleEndToken)).trim();
        value = getConvertedText(current.substring(current.indexOf(titleEndToken), current.length));
      }
      testDetail.result = [...testDetail.result, title, ' ', ...value, <br key={index} />];
    } else {
      // results come in an unexpected format
      testDetail.result = [...testDetail.result, ' ', ...getConvertedText(current)];
    }
  }

  return testDetail;
};

interface MessageProps {
  msg: string;
}

export const Message: FunctionComponent<MessageProps> = (props) => {
  const { msg } = props;

  const detail: TestDetail = getTestDetail(msg);
  return (
    <Fragment>
      {detail.description ? <Description>{detail.description}</Description> : null}
      {detail.result ? <Results>{detail.result}</Results> : null}
      {detail.stackTrace ? <StackTrace>{detail.stackTrace}</StackTrace> : null}
    </Fragment>
  );
};

export default Message;