1

Given an isolated component built with CycleJs (the component works fine) :

import isolate from '@cycle/isolate';
import { div, ul, li, a } from '@cycle/dom';

function intent(domSource){
    const changeString$ =  domSource
        .select('.string').events('click')
        .map( ev => ev.target.dataset.position);

    return { changeString$ };
}

function model(actions, props$){

    const { changeString$ } = actions;

    return props$.map( props => {

        return changeString$
            .startWith(props.initialString)
            .map( activePosition => ({ activePosition, preset : props.preset }));

    }).flatten().remember();
}

function view(state$){
    return state$.map( state => (
        div(
            ul(
                state.preset.map( (string, position) => (
                    li(
                        a({
                            attrs : {
                                href : '#',
                                'data-pitch' : string.pitch,
                                'data-frequency' : string.frequency,
                                'data-position' : position,
                                class : ['string', parseInt(state.activePosition, 10) === position ? 'active' : ''].join(' ')
                            }
                        },
                        string.name)
                    )
                ))
            )
        )
    ));
}

function stringSelector(sources){
    const actions = intent(sources.DOM);
    const state$  = model(actions, sources.props);
    const vdom$   = view(state$);

    return {
        DOM: vdom$,
        value: state$
    };
}

export default isolate(stringSelector, '.string-selector');

I have tried to test the behavior using @cycle/time :

import test from 'tape';
import xs   from 'xstream';
import { mockDOMSource } from '@cycle/dom';
import { mockTimeSource } from '@cycle/time';
import stringSelector from '../../../src/js/component/stringSelector/index.js';

test('string selector', t => {

    t.plan(1);

    const Time = mockTimeSource();

    const e2Click$       = Time.diagram('-------x-------|');
    const a2Click$       = Time.diagram('---x------x----|');
    const expectedState$ = Time.diagram('0--1---0--1----|');

    const DOM = mockDOMSource({
        '.string[data-picth=e2]': {
            click: e2Click$
        },
        '.string[data-picth=a2]': {
            click: a2Click$
        },
    });

    const selector = stringSelector({
        DOM,
        props: xs.of({
            preset: {
                strings: [{
                    name: 'E',
                    pitch: 'e2',
                    frequency: 82.41
                }, {
                    name: 'A',
                    pitch: 'a2',
                    frequency: 110.0
                }]
            },
            initialString: 0
        })
    });

    const activePosition$ = selector.value.map( state => state.activePosition );

    Time.assertEqual(activePosition$, expectedState$);

    Time.run(t.end.bind(t));
});

But the activePosition$ stream ends directly. I don't know if it comes from the way the DOM is mocked (the events doesn't seems to be triggered) or the way I build the activePosition$ stream ?

When running my test, I have the following message :

Expected

0--1---0--1----|

Got

(0|)

Failed because:

 * Length of actual and expected differs
 * Expected type next at time 0 but got complete
 * Expected stream to complete at 60 but completed at 0
krampstudio
  • 3,519
  • 2
  • 43
  • 63

1 Answers1

1

I think I spotted the problem.

The thing is, with the mocked DOM driver, you need to create event for the exact same selector as the one used in DOM.select('...').events.

In your case you select .string but you mock an event on .string[data-pitch=..], which will then not match on the app's side.

Keep in mind that there is no real DOM involved on the testing side. In your case, when you fake a click on the selector .string, no matter what your component renders, it will only generate one event.

I think what you want to achieve here is to fake the data that is in the event.

You can probably do something like this:

const clicks = Time.diagram('---0---1----0---|').map(position => {
    // this will create a fake DOM event that contains the properties you are reading
    return {target: {dataset: {position: position }}}
})

const DOM = mockDOMSource({
    '.string': {
        click: clicks
    }
});
atomrc
  • 2,543
  • 1
  • 16
  • 20
  • Got it, however the stream is still closing after the first value and the event never triggered. I think it's related to the isolation, I've already tried `mockDOMSource({ '.string-selector': { '.string': { click: clicks } } });` – krampstudio Dec 20 '18 at 12:56
  • I've also seen `mockDOMSource` has an `isloateSource` method but didn't found how to actually implement it. – krampstudio Dec 21 '18 at 11:01
  • OOohh I see, I missed the fact that you `isolate` FROM the component!! This is something you don't want to do. Isolation should be on the component's consumer side. The component itself should not be aware of any isolation – atomrc Dec 21 '18 at 13:42
  • 1
    The consumer might want to isolate with different name, or only isolate a few drivers... So to fix your problem, you should just remove the `isolate` from the export – atomrc Dec 21 '18 at 13:43
  • That was the problem. It looks like I still have a lot to learn in cyclejs – krampstudio Dec 29 '18 at 14:36