Search code examples
javascriptcypresscypress-component-test-runner

Cypress cy.tick() not forwarding time the second time it is called


I have been reading the docs about cy.clock and using it with the component testing setup. But I seem to be doing something wrong here

 it.only('shows an estimate on when it is ready (fulfilled)', () => {
    const now = new Date()
    cy.clock(now)

    mount(
      <ResizeWrapper>
        <CartProvider>
          <PaymentSucceededMessage />
        </CartProvider>
      </ResizeWrapper>
    )

    // Time left
    cy.contains('10 min', { matchCase: false }).should('be.visible')
    cy.tick(1000 /*ms*/ * 60 /*sec*/ * 1 /*min*/ + 300 /*ms*/)
    cy.contains('9 min', { matchCase: false }).should('be.visible')
    // 🧨 Something breaks here 💥
    cy.tick(1000 /*ms*/ * 60 /*sec*/ * 1 /*min*/ + 300 /*ms*/)
    cy.contains('8 min', { matchCase: false }).should('be.visible') // FIXME: The test does not work but the real life version does
  })

enter image description here

the implementation (for now) goes like this

  const [minutesLeft, minutesLeftSet] = React.useState<number>(10)


  React.useEffect(() => {
    let timer
    if (minutesLeft > 0) {
      timer = setTimeout(() => {
        console.log(new Date())
        minutesLeftSet((minutesLeft) => minutesLeft - 1)
      }, 1000 /*ms*/ * 60 /*sec*/ * 1 /*min*/)
    }

    return () => {
      if (timer) clearTimeout(timer)
    }
  }, [minutesLeft])

The console only shows 1 print of the new Date()...?


Solution

  • I think for the same reason you often need to use the callback form of useState setter, e.g minutesLeftSet((minutesLeft) => minutesLeft - 1), you also need a beat in the test to allow React hooks to process.

    So, this works

    const now = new Date()
    cy.clock(now)
    
    mount(
      <ResizeWrapper>
        <CartProvider>
          <PaymentSucceededMessage />
        </CartProvider>
      </ResizeWrapper>
    )
    
    // Time left
    cy.contains('10 min', { matchCase: false }).should('be.visible')
    
    cy.tick(1000 /*ms*/ * 60 /*sec*/ * 1 /*min*/ + 300 /*ms*/)
    cy.wait(0)                                                      // yield to React hooks
    cy.contains('9 min', { matchCase: false }).should('be.visible')
    
    cy.tick(1000 /*ms*/ * 60 /*sec*/ * 1 /*min*/ + 300 /*ms*/)
    cy.wait(0)                                                      // yield to React hooks
    cy.contains('8 min', { matchCase: false }).should('be.visible')