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

Separate fixture props #312

Merged
merged 23 commits into from Mar 25, 2017
Merged

Separate fixture props #312

merged 23 commits into from Mar 25, 2017

Conversation

jlc467
Copy link
Collaborator

@jlc467 jlc467 commented Mar 11, 2017

Issue: #217

With this PR, fixture props are expected to be attributes of fixture.props instead of the the root-level fixture.

Also in this PR: local-state example shows how to use props in a fixture.

To test backwards compatibility, I copied the local-state example and put props at the root-level, then added a FixFixturePropsProxy to cosmos config to confirm the props come through. Before merging, I imagine this example will be deleted and the proxy extracted into some kind of markdown documentation in the release notes.

Haven't looked into codemods yet, but figure that can come later.

Copy link
Member

@ovidiuch ovidiuch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great, thanks so much for picking it up!

  • Please upgrade with latest master, there were a few PRs merged since you started this branch
  • Leaving out the codemod is fine, but we can't deliver this without the proxy because it will break user's integration without an easy upgrade path (those who already depend on the 2.0.0 beta). The good news is you've already implemented the proxy, we just need to wrap it up nicely. I've added some comments inline. It should also be created as an independent package (see context or Redux proxies). I can help with structure/docs.
  • We should turn the temp example into a real example. Either that or – in order to not duplicate the Counter code – we just use the proxy in the local-state example and have both new style and old style fixtures in the same example
  • You also need to update StateProxy

Lastly, there's an unresolved issue with this. Let's find an elegant solution together. Cosmos has this concept of splitting serializable and unserializable fixture parts. We do this to be able to serialize part of the fixture into JSON and make it editable in the fixture editor. The unserializable parts are hidden, but passed to the component, while the serializable ones are visible and editable. This separation is shallow, meaning that if a nested fixture attribute isn't serializable, it's completely hidden. Long story short, now that props are nested under fixture.props, if you have any prop callback no prop will be shown in the editor.

All props are serializable:
image

Just one props is unserializable:
image

This might be a lot to chew, so let me know if you have any questions. You've just opened the Cosmos Pandora's box! 🤡

}
// TODO: bring notProps from cosmos config
const notProps = ['children', 'state'];
const fixedFixture = reduce(fixture, (final, value, key) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use lodash.omit to replace the reduce logic into a single line

return fixture;
}
// TODO: bring notProps from cosmos config
const notProps = ['children', 'state'];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add reduxState and we might postpone making this an option indefinitely (unless anybody requests it).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

context as well

// the fixture doesn't need fixing if it has props
return fixture;
}
// TODO: bring notProps from cosmos config
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even if this list were configurable (which I don't recommend for now), it shouldn't be an option inside cosmos.config.js, but rather a Proxy option (see example of Proxy options in Redux Proxy).

return fixedFixture;
};

class FixFixturePropsProxy extends React.Component {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about NormalizePropsProxy?

@jlc467
Copy link
Collaborator Author

jlc467 commented Mar 11, 2017

Well explained... I'll make these changes before the end of the weekend. Wondering how many levels deep I need to check to find unserializable props? I'll start with props root for starters.

@ovidiuch
Copy link
Member

One level deep (root props) is as good as before. Ideally the splitting would be recursive and would be any level deep. Wondering if there's an easy algorithm for this. The key here is how we merge the serializable/unserializable parts back, which until now was trivial.

Finally, as further context, my wish is to someday add a notice at the bottom of the fixture editor like this:

Some fixture attributes are unserializable and not shown here: props.onClick, foo.bar, baz

@jlc467
Copy link
Collaborator Author

jlc467 commented Mar 12, 2017

Still todo:

  • Make NormalizeStateProps a working package (not sure what is needed here as far as lerna and tooling config)

  • Tests for NormalizeStateProps

  • More tests for StateProxy and splitUnserializableParts

  • Code review on latest changes (see below changes that I'm iffy about)

  • Finish backwards compatibility example (my vote is to move it in a folder separate from /examples to avoid confusion with old API)

  • I use _.pick + _.omit in the proxy because I thought we don't want to leave behind what we transfer to fixture.props

  • StateProxy passes fixture untouched to PropsProxy now

Copy link
Member

@ovidiuch ovidiuch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

StateProxy passes fixture untouched to PropsProxy now

This is good. Why does it worry you?

I use _.pick + _.omit in the proxy because I thought we don't want to leave behind what we transfer to fixture.props

Also good. Provided some ideas for improvement inline.

Make NormalizeStateProps a working package (not sure what is needed here as far as lerna and tooling config)

Should already be fine. Run npm run build in the root dir to make sure the lib is generated.

Finish backwards compatibility example (my vote is to move it in a folder separate from /examples to avoid confusion with old API)

👍

Thanks again for this massive contribution!

@@ -83,7 +83,7 @@ describe('fixture without state and component without initial state', () => {

commonTests();

test('omits state from fixture sent to next proxy', () => {
test('does not omit state from fixture sent to next proxy', () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this test case is useless and can be removed

unserializable: { }
};

const { serializable, unserializable } = splitResult;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks convoluted. Why not go back to just

const serializable = {};
const unserializable = {};
// ...
return { serializable, unserializable };


Object.keys(obj).forEach(key => {
if (isSerializable(obj[key])) {
serializable[key] = obj[key];
} else if (key === 'props' && isPlainObject(obj[key])) {
Object.keys(obj.props).forEach(propKey => {
let propHome = 'serializable';
Copy link
Member

@ovidiuch ovidiuch Mar 12, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moreover, you could do something like

const propVal = obj.props[propKey];
const propHome = isSerializable(propVal) ? serializable : unserializable;

if (!propHome.props) {
  propHome.props = {};
}
propHome.props[propKey] = propVal;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But it would be wise to add the new tests first and then beautify the implementation.

return fixture;
}

const notProps = ['children', 'state', 'context', 'reduxState'];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can put this outside of getFixedFixture since they never change.

const fixedFixture = Object.assign(pick(fixture, notProps), {
props: omit(fixture, notProps)
});
return fixedFixture;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about

return {
  ...pick(fixture, notProps),
  props: omit(fixture, notProps)
};

{
"name": "react-cosmos-normalize-props-proxy",
"version": "2.0.0-beta.6",
"description": "Seperate props in fixtures",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Proxy for backwards compatibility with old react-cosmos fixture format"

class NormalizePropsProxy extends React.Component {
render() {
const { nextProxy, fixture } = this.props;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove line

export default {
componentPaths: ['components'],
// TODO: use package for normalize proxy
proxies: ['../../packages/react-cosmos-normalize-props-proxy/src']
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for a TODO since examples are explicitly working with relative packages, but you should rely on the compiled lib by just pointing to '../../packages/react-cosmos-normalize-props-proxy'

@jlc467
Copy link
Collaborator Author

jlc467 commented Mar 12, 2017

Awesome... looks like this is pretty close to done. Review makes sense to me, so I'll make the changes as suggested. Should be able to get it done during the week, if not definitely over next weekend.

@jlc467
Copy link
Collaborator Author

jlc467 commented Mar 18, 2017

@skidding Implemented feedback and added tests. Not sure where you want the NormalizePropsProxy example to live. I've seen projects split up the examples folder into e.g. simple-examples-start-here, advanced-examples, real-world. I think this is a real-world example of how to achieve backwards compatibility, but simple everything else.

Copy link
Member

@ovidiuch ovidiuch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking great! Just one tiny comment regarding tests. Will also try to integrate this branch in some existing projects to try it out in real life.

expect(childProps.fixture.context).toEqual({});
expect(childProps.fixture.children).toEqual([]);
expect(childProps.fixture.state).toEqual({});
expect(childProps.fixture.reduxState).toEqual({});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These checks can become false positives in time. How about creating some constants like const mockContext = {}, use them when constructing the fixture in renderProxy calls and then do strict checks like expect(childProps.fixture.context).toBe(mockContext);

@ovidiuch
Copy link
Member

Discovered a problem with how fixture parts are merged. The problem is when you have one or more unserializable props and one or more serializable props, the unserializable props are overridden by the serializable ones. We need to merge the parts recursively. _.merge should do the trick.

Additionally, while trying to integrate in own projects I realized I had more magic fixture fields that aren't props (designed to be picked up by custom proxies), which are being normalized into props. It would help to make notProps customizable via proxy option (e.g. fixture key for context proxy). Is it possible to add this as well?

Thanks! 🙌

@jlc467
Copy link
Collaborator Author

jlc467 commented Mar 19, 2017

It would help to make notProps customizable via proxy option (e.g. fixture key for context proxy). Is it possible to add this as well?

Yeah, sure. No telling what kind of magical custom proxies people are running. To pass the options, the correct approach is to wrap the proxy, e.g. as seen here, correct?

Will fix the bug too. Not totally clear on why the fixture is split and then merged again (why not just use the original fixture?), so will read more into that.

Should be able to get this done tomorrow.

@ovidiuch
Copy link
Member

Yeah, sure. No telling what kind of magical custom proxies people are running. To pass the options, the correct approach is to wrap the proxy, e.g. as seen here, correct?

Yes... but in that example we're composing proxies. I think the Redux proxy is also good inspiration. The idea is to put the whole proxy component inside a create function, in order to have access to the options passed to that create function.

image

Will fix the bug too. Not totally clear on why the fixture is split and then merged again (why not just use the original fixture?), so will read more into that.

Because of the fixture editor. Users can edit the serializable part of the fixture, which is merged with the unserializable part (you could call that part immutable) to form the new fixture.

image

Let me know if this makes sense. Thanks!

@jlc467
Copy link
Collaborator Author

jlc467 commented Mar 19, 2017

Because of the fixture editor. Users can edit the serializable part of the fixture, which is merged with the unserializable part (you could call that part immutable) to form the new fixture.

Makes sense!

Yes... but in that example we're composing proxies. I think the Redux proxy is also good inspiration. The idea is to put the whole proxy component inside a create function, in order to have access to the options passed to that create function.

I think my question is geared towards composing proxies. How would the user pass options to the new NormalizePropsProxy? I will definitely reference the Redux proxy to implement the ability to pass notProps option to NormalizePropsProxy

@ovidiuch
Copy link
Member

Oh, you're right about the proxy wrapping, that's how you pass options. It's also seen in the two proxy examples from the README: https://github.com/react-cosmos/react-cosmos#react-cosmos-redux-proxy

In the future we could allow proxy options inside cosmos.config (similar to webpack loader options), but I don't think Cosmos is popular enough yet for that sort of customization :P

@jlc467
Copy link
Collaborator Author

jlc467 commented Mar 23, 2017

In the future we could allow proxy options inside cosmos.config (similar to webpack loader options), but I don't think Cosmos is popular enough yet for that sort of customization :P

Yeah, I'm not sure the overall idea of developing components in isolation has hit mainstream yet. People seem more keen on the UI explorer/documentation aspect. Sometimes the killer feature of a project is misidentified early on, and it isn't until later that people realize that the better benefit is something else. Personally, I'm only doing cosmos-first development from now on, where cosmos playground is the entry point and you delay writing nav / router type components that can distract from the app's idea.

@ovidiuch
Copy link
Member

Yeah, I'm not sure the overall idea of developing components in isolation has hit mainstream yet. People seem more keen on the UI explorer/documentation aspect. Sometimes the killer feature of a project is misidentified early on, and it isn't until later that people realize that the better benefit is something else.

Yes, I couldn't agree more! ❤️

Personally, I'm only doing cosmos-first development from now on, where cosmos playground is the entry point and you delay writing nav / router type components that can distract from the app's idea.

Same here! 🥂

@jlc467
Copy link
Collaborator Author

jlc467 commented Mar 24, 2017

@skidding Let me know if you think of anything else. A new test to cover the serializable overwriting unserializable crossed my mind, but wasn't immediately obvious how to tackle that. I imagine such a test would live with the fixture editor tests.

@ovidiuch
Copy link
Member

I think this is great and ready for prime time. 🥇

@ovidiubute @flaviusone @RadValentin you might want to take a look at this! It is breaking, so we need to make sure your react-cosmos version(s) are locked at 2.0.0-beta.8 until you upgrade, or be able to upgrade right away. Since I think think you have any custom proxies, adding react-cosmos-normalize-props-proxy to your proxy list should be enough. Let me know!

@valentin-radulescu-hs
Copy link

We're good on our end, the other guys are still on 1.0.1 and we're locked to 2.0.0-beta.8 with yarn. Worst case we'll have to add the normalize proxy in one place which isn't a problem.

Fire away! 💣

@ovidiuch ovidiuch merged commit 9a502ac into react-cosmos:master Mar 25, 2017
@ovidiuch
Copy link
Member

Great contribution @jlc467, thanks again making this happen! Oh, and I sent you an invitation to become a collaborator 🙌

@jlc467
Copy link
Collaborator Author

jlc467 commented Mar 25, 2017

@skidding Sweet! And thanks for helping me with the contributions 🙏. I know README updates are still missing for both this and cosmos-export, so if that hasn't been picked up in the next couple weeks, I will try to contribute there.

@ovidiuch
Copy link
Member

@jlc467 Welcome on board! ⚔️

Here are two examples where I've already upgraded:

Regarding docs, sure! I hope the latest structural README changes make it easier to contribute. You're also invited to join the Slack channel.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
No open projects
Development

Successfully merging this pull request may close these issues.

None yet

3 participants