Jest: Universal jest string fromat

Created on 16 Mar 2017  路  10Comments  路  Source: facebook/jest

The problem:
Jest needs to support different platforms when it prints its output (terminal, nuclide, html, terminal with no color support, snapshots)

There's a few ways we can implement that, but after our discussion with @kentaromiura we focused more on investigating some universal jest format.
Here's my thought on what it may look like:

  1. A jest-print-utils package that will export the following API:
type JestFormatedString = string;

type JestPrintUtils = {
  green: (input: string) => JestFormatedString,
  red: (input: string) => JestFormatedString,
  bold: (input: string) => JestFormatedString,
  dim: (input: string) => JestFormatedString,
 // ...
};

JestFormattedString type is jest a string that follows jest specific print format (a string with XML-like tags describing fonts)

Example:

import {red, green} from 'jest-print-utils';

const jestFormattedString = `hey, i'm ${green('green')}. and i'm ${red('red')}`;
console.log(jestFormattedString);
// hey i'm <JEST_GREEN_TAG_UUID>green</JEST_GREEN_TAG_UUID>. and i'm <JEST_RED_TAG_UUID>red</JEST_RED_TAG_UUID>

where UUID tags can be found in a common constant definition (to make sure they're unique and don't overlap with hmtl markup or similar)

JEST_UUID_TAGS: {
  RED: `RED_${uuid.gen()}`,
  GREEN: `GREEN_${uuid.gen()},
  // ...
}
  1. A jestFormattedString converters that can convert JestFormattedString type to any other string types (html, ASCII, strip tags completely, or something else)
    Example:
import {green, toHTML, toASCII, toNoColors, toSnapshotFormat} from 'jest-print-utils';

const str = `i'm ${green('green')}` // => 'i'm <JEST_GREEN_UUID>green</JEST_GREEN_UUID>

toHTML(str); // i'm <span style='color: green'>green</span>
toASCII(str); // i'm 0[m21green (or whatever the format is)
toNoColors(str) // i'm green
toSnapshotFormat(str) // i'm <green>green</green>

cc @kentaromiura @thymikee

Discussion

Most helpful comment

Are we seriously considering JSX to format messages? If so, I think I could give it a try and implement this (after we settle this is actually a good idea).

I imagine formatting message like this:

const {JestFormat, Default, Error} = require('jest-format');
const jsx = <Default>Some <Error>text</Error> message</Default>;

Which then would go through Babel to output something like this:

const jsx = JestFormat.createElement(
  Default,
  null,
  'Some ',
  JestFormat.createElement(Error, null, 'text'),
  ' message',
);

Which would format to something like this:

JestFormat.createElement = function(type, props, children) {
  const result = {
    $$typeof: Symbol.for('JEST_FORMAT'),
    props,
    type: Default,
  };
  return result;
};

This way we could easily write recursive renderers for html, ansi, etc and custom ones for editors support, as they would need to deal with JSON only.

Let me know what you think @dmitriiabramov @kentaromiura

All 10 comments

A couple of thoughts:

  • Let's please not call it "util". I hate "util"s. I want to get rid of all the files and packages called "util" in Jest.
  • If we do this, we gotta stop using red and green and give semantic meaning to the way we format text. Instead of red, we should use error. This will also allow to change what that looks like in different places, like Nuclide.
  • I'm a bit worried that the end result here is a slightly more structured string format that we'll regex over to turn it from one format to another, the added value isn't actually that much. How about we start using JSX with different renderers? That seems to me like the best way to build a structured data format for Jest.

Are we seriously considering JSX to format messages? If so, I think I could give it a try and implement this (after we settle this is actually a good idea).

I imagine formatting message like this:

const {JestFormat, Default, Error} = require('jest-format');
const jsx = <Default>Some <Error>text</Error> message</Default>;

Which then would go through Babel to output something like this:

const jsx = JestFormat.createElement(
  Default,
  null,
  'Some ',
  JestFormat.createElement(Error, null, 'text'),
  ' message',
);

Which would format to something like this:

JestFormat.createElement = function(type, props, children) {
  const result = {
    $$typeof: Symbol.for('JEST_FORMAT'),
    props,
    type: Default,
  };
  return result;
};

This way we could easily write recursive renderers for html, ansi, etc and custom ones for editors support, as they would need to deal with JSON only.

Let me know what you think @dmitriiabramov @kentaromiura

Yeah I think this could work. We don't need React, we just needn't JSX with the @jsx pragma. Curious what this would look like.

that actually looks amazing. How will we send serialized objects to other processes though?

A message will be JSON data after all, so it shouldn't be problematic.

As per Christoph suggestion, using the jsx pragma it will be possible to create a function like:

/** @jsx format */

const format = (type, _, ...parts) => {
  return {
    type,
    parts,
    [Symbol.for('custom-format')]: true
  }
}


var message = (
  <message>
    Error: expected value to be (using ===):
    <expected>"bar"</expected>
    Received:
    <received>"foo"</received>
  </message>
);

Then we could just feed pretty-format the most appropriate plugin to have that message output how we wants;
and example of the html render might be similar to this:

{
    test: x => x[Symbol.for('custom-format')],
    print: (val,print,opt,color) => (
      '<span class="jest-'+ val.type +'">' + val.parts.map(val => {
        if (typeof val === 'string') return val;
        return print(val, print, opt, color)
      }).join('') + '</span>')
}

https://jsfiddle.net/fjfuabya/

@kentaromiura that looks amazing!
we only need to fix the whitespace. but i think we can just add some padding to elements like <expected> and <received>

Actually we could adjust whitespace and indentation in props:

<message indent={1}>
    Error: expected value to be (using ===):
    <expected indent={1}>"bar"</expected>
    Received:
    <received indent={1}>"foo"</received>
</message>

Also, it's going to be a ton of work to change all the messages 馃槓

and what about newlines? :)
how to we tell

this is <green>green</green>

from

this is
<green>green</green>

I don't think there's any way to detect the new line from code.

/* @jsx h */
<div>
  foo
  bar
</div>
/* @jsx h */
h(
  "div",
  null,
  "foo bar"
);

Could have a <linebreak> or {'\n'}.

Was this page helpful?
0 / 5 - 0 ratings