Skip to content
This repository has been archived by the owner on Nov 14, 2022. It is now read-only.

Commit

Permalink
Implemented Store.get for better testing
Browse files Browse the repository at this point in the history
  • Loading branch information
vikingair committed Dec 10, 2018
1 parent e8bbbf0 commit d3941d6
Show file tree
Hide file tree
Showing 10 changed files with 95 additions and 64 deletions.
26 changes: 10 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,13 @@ store.set({ data: { user: { login: { email: '[email protected]' } } } });
// and this would affect ONLY the email if data, user and login
// would have been nodes instead of objects.
```
If you want to access the data directly e.g. outside any component, you can
do the following:
```js
const state = Store.get();
```
- ATTENTION: Modifications to the returned state can effect your application in
an unexpected way. Even if you know what you're doing, I do not recommend it.

## Known issues
First of all I want to mention, that the production functionality is not
Expand All @@ -158,24 +165,11 @@ related mostly to `flow` issues, which were not even solved for `react-redux`.

- It is necessary for wired **class components** to define the props as exact
type. But maybe this is even good, since you should try to always use exact
prop types.
prop types. `flow` itself now tries to establish a new default, that makes all
objects rather exact than inexact.
```js
class MyComponent extends Component<{| myProp: string |}> { ... }
```
- It is necessary for optional props to provide the key for this prop
either in returned object from `mapStateToProps` or wherever you use
the component.
```js
class MyComponent extends Component<{| myProp?: string, other: string |}> { ... }

// Option 1:
const MyWiredComponent = Store.wire<SP, OP>(MyComponent, state => ({ myProp: undefined, other: state.foo }))
const rendered = <JSX><MyWiredComponent /></JSX>;

// Option 2:
const MyWiredComponent = Store.wire<SP, OP>(MyComponent, state => ({ other: state.foo }))
const rendered = <JSX><MyWiredComponent myProp={undefined} /></JSX>;
```
- Currently **default props** are not correctly recognized on wired class
components. They are ignored since `React$ElementConfig` which contains
the logic to calculate the props could not be connected without using
Expand All @@ -192,7 +186,7 @@ class MyComponent extends Component<{| myProp: string |}> {
(see `jest` option "setupTestFrameworkScriptFile")
```js
Store.set = (Component, mapStateToProps) => {
const result = props => <Component {...mapStateToProps(Store.data)} {...props} />;
const result = props => <Component {...mapStateToProps(Store.get())} {...props} />;
result.displayName = `Wired(${Component.name})`;
return result;
```
Expand Down
3 changes: 0 additions & 3 deletions bin/dist.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,3 @@ NODE_ENV=production babel src/react-rewired --out-dir dist

# generate flow source maps
for i in `ls src/react-rewired/`; do cp src/react-rewired/$i dist/$i.flow; done

# remove redundant test files
rm `ls dist/*.test.js*`
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-rewired",
"version": "1.0.0",
"version": "2.0.0",
"private": false,
"description": "Wire your react app as easy as possible",
"main": "dist/index.js",
Expand Down Expand Up @@ -57,7 +57,7 @@
"react": "^16.6.3",
"react-dom": "^16.6.3",
"react-scripts": "^2.1.1",
"spy4js": "^1.9.1"
"spy4js": "^2.3.1"
},
"jest": {
"collectCoverageFrom": [
Expand Down
49 changes: 26 additions & 23 deletions src/react-rewired/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ type Root = React$ComponentType<RootProps>;

export type WiredStore<State: Object> = {
set: SetFunction<State>,
data: State,
get: void => State,
root: Root,
wire: <StoreProps: Object, OwnProps: Object>(
component: UnwiredComponent<StoreProps, OwnProps>,
Expand All @@ -26,42 +26,45 @@ export type WiredStore<State: Object> = {
};
export type WiredNode<N: Object> = $Shape<N>;

const nodeSymbol: any = Symbol.for('__WIRED_NODE__');
const _symbol = (Symbol && Symbol.for) || String;
const $$node: any = _symbol('__WIRED_NODE__');
const $$data: any = _symbol('__WIRED_DATA__');

const node = <N: Object>(data: N): WiredNode<N> => {
data[nodeSymbol] = true;
data[$$node] = true;
return data;
};

// ATTENTION: No keys can be added afterwards. You need at least to initialize them with undefined
const updateIfRequired = <State: Object>(prevData: State, nextData: Object): State => {
const result = {};
const keys = Object.keys(prevData);
const _update = <State: Object>(p: State, n: Object): State => {
const r = {};
const keys = Object.keys(p);
for (let index = 0; index < keys.length; index++) {
const key = keys[index];
const prev = prevData[key];
const next = nextData[key];
if (!(key in nextData) || prev === next) {
result[key] = prev;
} else if (prev && prev[nodeSymbol]) {
result[key] = node(updateIfRequired(prev, next));
const prev = p[key];
const next = n[key];
if (!(key in n) || prev === next) {
r[key] = prev;
} else if (prev && prev[$$node]) {
r[key] = node(_update(prev, next));
} else {
result[key] = next;
r[key] = next;
}
}
return (result: any);
return (r: any);
};

const internalSet = <State: Object>(Store: WiredStore<State>, param: Object | Function): void => {
const nextParams = typeof param === 'function' ? param(Store.data) : param;
Store.data = updateIfRequired(Store.data, nextParams);
const _set = <State: Object>(s: WiredStore<State>, param: Object | Function): void => {
const nextParams = typeof param === 'function' ? param(s[$$data]) : param;
s[$$data] = _update(s[$$data], nextParams);
};

const store = <State: Object>(initialData: State): WiredStore<State> => {
const Context = React.createContext(initialData);
const s = {
set: (d: SetFunctionParam<State>): void => internalSet((s: any), d),
data: initialData,
set: (d: SetFunctionParam<State>): void => _set((s: any), d),
get: () => s[$$data],
[$$data]: initialData,
root: ((undefined: any): Root), // little flow hack
wire: <StoreProps: Object, OwnProps: Object>(
Component: UnwiredComponent<StoreProps, OwnProps>,
Expand All @@ -81,15 +84,15 @@ const store = <State: Object>(initialData: State): WiredStore<State> => {
const rootFor = <State: Object>(Context: any, Store: WiredStore<State>): Root =>
class WiredRoot extends React.Component<RootProps, { s: State }> {
m = true;
update = () => this.m && this.setState({ s: Store.data });
update = () => this.m && this.setState({ s: Store[$$data] });
setStore = <S>(d: SetFunctionParam<S>): void => {
internalSet(Store, (d: any));
_set(Store, (d: any));
this.update();
};
state = { s: Store.data };
state = { s: Store[$$data] };
componentDidMount = () => {
Store.set = this.setStore;
if (Store.data !== this.state.s) this.update();
if (Store[$$data] !== this.state.s) this.update();
};
componentWillUnmount = () => {
this.m = false;
Expand Down
9 changes: 1 addition & 8 deletions src/setupTests.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
// @flow

import { Spy } from 'spy4js';
import 'spy4js';
import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import { createSerializer } from 'enzyme-to-json';

expect.addSnapshotSerializer(createSerializer({ mode: 'deep' }));

Enzyme.configure({ adapter: new Adapter() });

const oldDescribe = describe;
window.describe = (string, func) =>
oldDescribe(string, () => {
afterEach(Spy.restoreAll);
return func();
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import React from 'react';
import { shallow } from 'enzyme';
import { Spy } from 'spy4js';
import { Wired } from './index';
import { Wired } from '../react-rewired';

describe('WiredComponent', () => {
it('wires functional components', () => {
Expand Down
4 changes: 4 additions & 0 deletions src/test/wired-store-symbol-fallback.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// @flow

// necessary to test the fallback
Symbol[('for': any)] = undefined;
40 changes: 40 additions & 0 deletions src/test/wired-store-symbol-fallback.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// @flow

import './wired-store-symbol-fallback';
import { Wired } from '../react-rewired';

describe('WiredStore - without Symbols', () => {
const getDummyStore = () =>
Wired.store(
({
num: 12,
some: (undefined: boolean | void),
bool: true,
obj: {
str: 'here',
},
array: ['there', 12],
node: Wired.node({
not: 'visible',
as: 'node',
}),
}: any) // we need to manipulate flow, because else we can do illegal things in the tests
);

it('initializes the store', () => {
const store = getDummyStore();
expect(store.get()).toEqual({
num: 12,
bool: true,
obj: {
str: 'here',
},
array: ['there', 12],
node: {
__WIRED_NODE__: true,
not: 'visible',
as: 'node',
},
});
});
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// @flow

import { Wired } from './index';
import { Wired } from '../react-rewired';

describe('WiredStore', () => {
const getDummyStore = () =>
Expand All @@ -22,7 +22,7 @@ describe('WiredStore', () => {

it('initializes the store', () => {
const store = getDummyStore();
expect(store.data).toEqual({
expect(store.get()).toEqual({
num: 12,
bool: true,
obj: {
Expand All @@ -38,20 +38,20 @@ describe('WiredStore', () => {

it('updates store data with unknown key', () => {
const store = getDummyStore();
const dataBefore = store.data;
const dataBefore = store.get();

store.set({ unknownKey: 'foobar' });

expect(store.data).not.toBe(dataBefore); // but creates always a new object
expect(store.data).toEqual(dataBefore);
expect(store.get()).not.toBe(dataBefore); // but creates always a new object
expect(store.get()).toEqual(dataBefore);
});

it('updates store data with multiple updates on different types', () => {
const store = getDummyStore();

store.set({ obj: { some: 'newObject' }, node: { as: 'new', unknown: 'ignored' }, array: [1337], num: 42 });

expect(store.data).toEqual({
expect(store.get()).toEqual({
array: [1337], // completely new array
bool: true, // old value
node: Wired.node({
Expand All @@ -68,7 +68,7 @@ describe('WiredStore', () => {

store.set(state => ({ node: { as: 'new ' + state.node.as, unknown: 'ignored' }, some: true }));

expect(store.data).toEqual({
expect(store.get()).toEqual({
array: ['there', 12],
some: true,
bool: true,
Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -9525,10 +9525,10 @@ sprintf-js@~1.0.2:
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=

spy4js@^1.9.1:
version "1.9.1"
resolved "https://registry.yarnpkg.com/spy4js/-/spy4js-1.9.1.tgz#7e0f229ef8fceb11ba59f0a7452801a71a8a2e7a"
integrity sha512-3E2aOiByQ8hB9oChOIeLG3bWpfNocmBsVr3skZF9PZyEajsyH6t57uRHvYI+2XjDGvcrd9AlUm9GRn3kT2QVeQ==
spy4js@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/spy4js/-/spy4js-2.3.1.tgz#e459038144004ca6065222d11bbdb37a52d7fcf7"
integrity sha512-bOmfqa3mH3ZRlfDG3+2HTff0RP3UcBPnCQUHJgdRQP9PzRoCYmJyPcx6a2U2dFGMg/rkW7n324evbP7n9hHTZw==
dependencies:
serialize-as-code "^1.0.3"

Expand Down

0 comments on commit d3941d6

Please sign in to comment.