Files
react/compiler/forget/packages/fixture-test-utils
Mofei Zhang d2983888f4 [sprout] Initial draft of sprout 🌱 test runner
--- 

## Sprout 🌱 Overview 

**(Overview copied from 
[sprout/README.md](https://github.com/facebook/react-forget/blob/0468ddf8bbee54a9394317a667ea4ed96bb3bef3/forget/packages/sprout/README.md))** 

React Forget test framework that executes compiler fixtures. 

Currently, Sprout runs each fixture with a known set of inputs and annotations. 
We hope to add fuzzing capabilities to Sprout, synthesizing sets of program 
inputs based on type and/or effect annotations. 

Sprout is currently WIP and only executes files listed in 
`src/SproutOnlyFilterTodoRemove.ts`. 

### Milestones: 

- [] Render fixtures with React runtime / `testing-library/react`. 

- [ ] Make Sprout CLI -runnable and report results in process exit code. 

After this point: 

- Sprout can be enabled by default and added to the Github Actions pipeline. 

- `SproutOnlyFilterTodoRemove` can be renamed to `SproutSkipFilter`. 

- All new tests should provide a `FIXTURE_ENTRYPOINT`. 

- [ ] Annotate `FIXTURE_ENTRYPOINT` (fn entrypoint and params) for rest of 
fixtures. 

- [ ] Edit rest of fixtures to use shared functions or define their own helpers. 

- [ ] *(optional)* Store Sprout output as snapshot files. i.e. each fixture 
could have a `fixture.js`, `fixture.snap.md`, and `fixture.sprout.md`. 

### Constraints 

Each fixture test executed by Sprout needs to export a `FIXTURE_ENTRYPOINT`, a 
single function and parameter set with the following type signature. 

```js 

type FIXTURE_ENTRYPOINT<T> = { 

// function to be invoked 

fn: ((...params: Array<T>) => any), 

// params to pass to fn 

params: Array<T>, 

// true if fn should be rendered as a React Component 

//  i.e. returns jsx or primitives 

isComponent?: boolean, 

} 

``` 

Example: 

```js 

// test.js 

function MyComponent(props) { 

return <div>{props.a + props.b}</div>; 

} 

export const FIXTURE_ENTRYPOINT = { 

fn: MyComponent, 

params: [{a: "hello ", b: "world"}], 

isComponent: true, 

}; 

``` 

--- 

## Implementation Details 

- jest-worker test orchestrator (similar to Snap 🫰). 

I chose to write a test runner instead of directly using Jest for flexibility 
and speed. 

- Sprout 🌱 currently runs much more code per fixture (2 babel transform 
pipelines + 2 `exec(...)` than snap, so all scaling concerns apply. 

- Sprout may need more customization in the future (e.g. fuzzing component 
inputs, caching artifacts) 

- We probably want to add snapshot files for Sprout, which is much easier with a 
custom runner. 

This is also one of the main reasons we wrote Snap. Jest consolidates all 
external snapshots (i.e. non-inline snapshots) from a test into [a single 
file](https://github.com/facebook/react-forget/blob/d0a006ffa9842ee9db6b23ada94622af901eb07f/forget/src/__tests__/__snapshots__/compiler-test.ts.snap). 
This was painful mainly for rebasing changes, but also for small papercuts (e.g. 
needing to run `yarn test -u` twice when deleting a fixture) 

- Currently does not save output to snapshot file, but we can easily add this 
later (i.e. each test would have a `.snap.md` and `.sprout.md` file) 

- Supports filter mode (same testfilter.txt file as snap) 

- Currently does not support watch mode. I expect that sprout's primary use will 
be catching bugs in the PR / Github Actions phase. Can be changed if we need to 
iterate on sprout output while developing. 

- All tests are run with `react-test-renderer` to access the React Runtime, 
which is needed for calls to `useMemoCache` (and other potential hooks). 

- react-test-renderer required a mocked DOM, so I ~~used js-dom~~ did a terrible 
js-dom hack to add document, window, and other browser globals to the 
jest-worker globals, then exec the test code in the jest worker global. 

I can clean this up later by bundling library code (e.g. react, 
react-test-renderer) to not use `require(...)`, then calling `exec` on all "test 
client code" in the js-dom mock global (instead of the real jest worker one) 

- Tests marked `isComponent` need to return valid jsx or primitives. Values 
returned by all other tests are converted to a string primitive via 
JSON.stringify. 

```sh 

# install new dependencies to node_modules 

$ yarn 

$ cd forget/packages/babel-plugin-react-forget 

$ yarn sprout:build && yarn sprout 

PASS  alias-nested-member-path 

PASS  assignment-variations-complex-lvalue 

PASS  assignment-variations 

PASS  chained-assignment-expressions 

PASS  computed-call-evaluation-order 

PASS  const-propagation-into-function-expression-primitive 

PASS  constant-propagation-for 

PASS  constant-propagation-while 

PASS  constant-propagation 

... 

  Done in 3.91s. 

```
2023-08-14 19:26:02 -04:00
..