Profiscience / ko-component-tester

:vertical_traffic_light: TDD Helpers for Knockout JS
15 stars 3 forks source link

Shallow render components #12

Open k-ode opened 8 years ago

k-ode commented 8 years ago

One of the nice things about testing React components with Enzyme is that you can test your components as shallow. In this case, only the top level component gets rendered and all child components are ignored. This makes a lot of sense for unit testing, since you want to test your components as small, independent parts.

It would be nice if you could just call renderShallowComponent(...), but I'm guessing that would also be really involved. I can't say I have any idea how this would be implemented, maybe override the component binding somehow?

A simpler method would be an option to ignore certain components. So if you have a TodoList you could do:

renderComponent({
    viewModel: TodoList,
    template: todoListTemplate,
    ignoreComponents: ['Todo']
})

These sub components would be registered as empty components. This is what I currently do in a before-call, but it would be tidier to have the functionality built in.

Thoughts?

caseyWebb commented 8 years ago

This is pretty much the default if you don't register the child components. If you want to block those registrations you can do it with a component loader.

ko.components.loaders.splice(1, 0, {
  getConfig(name, callback) {
    const isSUT = name === '_SUT' // this is the name the tester uses to register components
    const emptyComponentConfig = { template: document.createElement('div') }
    callback(isSUT ? null : emptyComponentConfig)
  }
})

We insert at the 2nd position in the array to preserve $.fn.getComponentParams which uses a component loader itself to intercept the params. See here. Now you can go one step farther and assert your child components were passed the correct params.

const parent = {
  viewModel() { this.childText = 'foo' },
  template: '<child params="text: childText"></child>'
}

const $parent = renderComponent(parent)
const $child = $('child', $el)

$child.getComponentParams() === { text: 'foo' }
caseyWebb commented 8 years ago

This could — and should — be incorporated into the api somehow.

k-ode commented 8 years ago

Thanks for the clarification! That's very useful information.

However, I forgot to mention that we use the component-binding syntax for a lot for our sub components. And while it is true that custom elements are ignored if they are not registered, the component binding actually complains with Uncaught Error: Unknown component 'todo'.

We have to use the component binding sometimes because - unlike custom elements - it can be used as a virtual node. This is important if you use something like flexbox which doesn't work right with the extra dom elements that a custom element brings. It's also useful if you have to pass a lot arguments to a component, then you can just pass it as a object in the parent view model.

k-ode commented 8 years ago

The custom loader you outlined does solve this, I just wanted to clarify our use case.

I had to change the argument to template to an array though.

ko.components.loaders.splice(1, 0, {
    getConfig(name, callback) {
        const isSUT = name === '_SUT'; // this is the name the tester uses to register components
        const emptyComponentConfig = { template: [document.createElement('div')] };
        callback(isSUT ? null : emptyComponentConfig);
    }
});