Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add more testing docs #211

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ module.exports = {
'eslint:recommended',
'plugin:react/recommended',
'plugin:prettier/recommended',
'plugin:flowtype/recommended',
],
parserOptions: {
ecmaFeatures: {
Expand Down Expand Up @@ -45,7 +44,7 @@ module.exports = {
{
// Flow specific rules
files: ['src/index.js.flow', '*/*flow.js', 'examples/*-flow/*/*.js'],
// extends: ['plugin:flowtype/recommended'],
extends: ['plugin:flowtype/recommended'],
plugins: ['flowtype'],
rules: {
'flowtype/generic-spacing': ['off'],
Expand Down
32 changes: 10 additions & 22 deletions docs/recipes/typescript.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {
} from 'react-sweet-state';

type State = { count: number };
type Actions = typeof actions;

const initialState: State = {
count: 0,
Expand All @@ -30,18 +29,17 @@ const actions = {

const CounterContainer = createContainer();

// Note: most times TS will be able to infer the generics
const Store = createStore<State, Actions>({
const Store = createStore({
initialState,
actions,
containedBy: CounterContainer,
});

const CounterSubscriber = createSubscriber(Store);
const useCounter = createHook(Store);
const CounterSubscriber = createSubscriber(Store);
```

You don't have to manually type all the `create*` methods, as they can be inferred for most use cases.
You should not need to manually type methods, as TS can correctly infer most times.

#### Actions patterns

Expand All @@ -64,36 +62,26 @@ const actions = {
If you provide a selector to your components, you need to define two additional TypeScript arguments on `createHook`/`createSubscriber`: the selector output and the selector props.

```js
type SelectorState = boolean;
const selector = (state: State): SelectorState => state.count > 0;
const selector = (state: State) => state.count > 0;

// this hook does not accept arguments
const useCounter = createHook<State, Actions, SelectorState, void>(Store, {
selector
});
const useCounter = createHook(Store, { selector });

// this component does not accept props
const CounterSubscriber = createSubscriber<State, Actions, SelectorState, void>(Store, {
selector
});
const CounterSubscriber = createSubscriber(Store, { selector });
```

In case your component/hook also needs some props, you can define them as the fourth argument:
In case your component/hook also needs some props:

```js
type SelectorProps = { min: number };
type SelectorState = boolean;
const selector = (state: State, props: SelectorProps): SelectorState => state.count > props.min;
const selector = (state: State, props: SelectorProps) => state.count > props.min;

// this hook requires an argument
const useCounter = createHook<State, Actions, SelectorState, SelectorProps>(Store, {
selector
});
const useCounter = createHook(Store, { selector });

// this component requires props
const CounterSubscriber = createSubscriber<State, Actions, SelectorState, SelectorProps>(Store, {
selector
});
const CounterSubscriber = createSubscriber(Store, { selector });
```

#### createContainer patterns
Expand Down
1 change: 1 addition & 0 deletions docs/testing/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
## Testing

- [Actions](./actions.md)
- [Integration](./integration.md)
52 changes: 52 additions & 0 deletions docs/testing/integration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
## Integration tests

If you want to assert how your components are behaving, we recommend using Containers to ensure each test is isolated.

```js
it('should increase the count', async () => {
const TestContainer = createContainer(CounterStore);
const TestComponent = () => {
const [state, actions] = useCounter();
return <button onClick={actions.increment}>count: {state.count}</button>;
};

render(
<TestContainer>
<TestComponent />
</TestContainer>
);
const btn = screen.getByRole('button');
await userEvent.click(btn);

expect(btn).toHaveTextContent(`count: 2`);
});
```

In case you also need to provide some initial data, Container `onInit` action is perfect for that:

```js
const TestContainer = createContainer(CounterStore, {
onInit:
() =>
({ setState }) =>
setState(mockData),
});
```

If every test component is wrapped in a Container, then there is no risk of global store leaks between tests.
Moreover, to ensure it does not accidentally happen, you add a check on `defaultRegistry` after each test and error if any store has been created there:

```js
import { defaultRegistry } from 'react-sweet-state';

afterEach(() => {
if (defaultRegistry.stores.size > 0) {
const keys = Array.from(defaultRegistry.stores.keys()).join(', ');
throw new Error(
`Some stores (${keys}) are not contained. Please wrap the component with a container to avoid leaks.`
);
}
});
```

In case you don't want to fail the test, you can call `defaultRegistry.stores.clear()` in the `afterEach` to force global stores to be removed between tests.
Loading