skip to Main Content

It is interesting, that such standard behaviour in such a standard component is so hard to test.

Given an IonRange element in my Ionic React component:

<IonRange
  min={0}
  max={30}
  onIonInput={()=> console.log("changed")}
/>

When I manually test the IonRange, I get the console output as expected.
However, when I have my cypress test like this (pretty much copied over from the docs):

cy.get("ion-range")
  .as('range')
  .invoke('val', 10)
  .trigger('change')

I expect to see the log statement in the output.

Actual behaviour: the range knob jumps to the expected position but the onIonInout callback is never invoked.

I also tried other triggers, such as

  .trigger('ion-change')
  .trigger('input')
  .trigger('ion-input')
  .trigger('range-change')
  .trigger('ionChange')
  .trigger('ionInput')

all leading to the same behaviour.

I also tried to workaround using arrow keys.
That would be a very unsatisfying solution, because it does not generalise for cases with ranges of, say, 0 to 10,000. I tried it anyway:

cy.get("ion-range").type('{rightArrow}') // trows "this is a non-typable element"

The range knob jumps to the centre (weirdly).

The workaround that I ended up with is:

cy.get("ion-range").click() // clicks in the centre 

(also mentioned by @SuchAnIgnorantThingToDo-UKR)
and then I expect the value 15. But due to differences in rendering resolutions (even with a set viewport), the result flakes between 15 and 16. Also, this solution is very unsatisfying given we might want to assert for specific values)

I reviewed the following:

Please let me know if you have any idea for this very trivial every-day problem.

2

Answers


  1. The .click() command will alter the value and trigger the event handler, however it’s not as precise as setting the value directly.

    cy.window()
      .its('console')
      .then((console) => {
        cy.spy(console, 'log').as('log')
      })
    
    cy.get('ion-range')
      .click(300,0)
    
    cy.get('@log') 
      .should('have.been.calledWith', 'changed', 31)
      .invoke('getCalls')
      .then((calls) => {
        // open the DevTools to see the dumped table
        console.table(calls)
        expect(calls[0].args).to.deep.eq(['changed', 31]) // not exact, a +1 pixel occurs
      })
    

    from

    <IonRange onIonChange={({ detail })=> console.log("changed", detail.value)}></IonRange>
    

    The IonRange seems to have a different internal mechanism to the two linked examples you cited.

    Those have an <input> underlying, but IonRange does not have one. Instead, it’s a stencil based web component which responds the events in it’s own particular way.


    The range knob jumps to the centre (weirdly)

    I got the same with .click() but not specifying the coordinates to click at. I think that’s because the .type() has a .click() built in.

    The default click position, if not specified, is the center of the element.

    Login or Signup to reply.
  2. @SuchAnIgnorantThingToDo-UKR already answered in great detail.

    I just want to add how to test the dragging. You can do this:

    describe('Components', () => {
      it('ion-range', () => {
        cy.visit('https://ionicframework.com/docs/usage/v7/range/basic/demo.html?ionic:mode=ios')
    
        cy.get('ion-range').as('slider')
    
        const waitTime = 200
    
        cy.get('@slider') //
          .trigger('mouseenter')
          .wait(waitTime)
          .trigger('mousedown', 100, 0)
          .wait(waitTime)
          .trigger('mousemove', 150, 1, { force: true })
          .wait(waitTime)
          .trigger('mousemove', 200, 1, { force: true })
          .wait(waitTime)
          .trigger('mousemove', 300, 1, { force: true })
          .wait(waitTime)
          .trigger('mouseup', 300, 1)
      })
    })
    
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search