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:
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()},
// ...
}
jestFormattedString converters that can convert JestFormattedString type to any other string types (html, ASCII, strip tags completely, or something else)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
A couple of thoughts:
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.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>')
}
@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'}.
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:
Which then would go through Babel to output something like this:
Which would format to something like this:
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