bahmutov / cypress-svelte-unit-test

Unit testing Svelte components in Cypress E2E test runner
162 stars 21 forks source link

Updating component props after mounted? #250

Closed alejandroiglesias closed 3 years ago

alejandroiglesias commented 3 years ago

I cannot seem to find anywhere how to change component props after mounting the component to test that the component updates correctly. This is how I'm mounting it:

mount(Button, { props: { label: 'Button' } })
cy.contains('Button')
// Change label prop... How?
// cy.contains('Changed label')

In cypress-react-unit-test it can be done like this:

mount(<Button label={'Button'} />)
cy.contains('Button')
cy.get(Button).invoke('setState', { label: 'Changed label' })
cy.contains('Changed label')

By the way, is this the correct way to test a component by the way? Or should I test it purely as a user? Besides this, I'm thinking there are some cases that deserved to be tested not only as an end-user since these components will be a part of an application that will trigger props updates and methods, so it may still be useful to do this kind of testing. I appreciate your insights.

JohnnyFun commented 3 years ago

mount returns a svelte component instance, so you can call it's $set function. Something like:

import { mount } from 'cypress-svelte-unit-test'
mount(MyComponent, { { props: value: 2 } }).then(myComponentInstance => {
  setTimeout(() => myComponentInstance.$set({ value: 4 }), 1000)
})

More info here: https://svelte.dev/docs#$set

alejandroiglesias commented 3 years ago

@JohnnyFun mount does not return a Svelte component instance. It returns a Cypress.Chainable instance and when trying to use the promise fulfillment then method, it returns me a CSS string:

image

Any other ideas?

alejandroiglesias commented 3 years ago

@JohnnyFun @bahmutov ok, it seems I found a bug. When calling the mount function, if I provide the third styleOptions parameter, then the resolve function returns the stylesheet string as the parameter instead of the component instance. Steps to reproduce:

mount(Button, {}, { cssFile: 'dist/main.css' }).then(buttonInstance => { /* buttonInstance contains Button CSS file */ })

If I don't provide the styleOptions parameter, then I get the instance correctly:

mount(Button, {}).then(buttonInstance => { /* buttonInstance contains Button component instance */ })

It seems to me that in the case of providing the styleOptions parameter, the stylesheet should be returned as a second parameter to the resolve function (if need to be returned at all since I don't see a good reason for it).

JohnnyFun commented 3 years ago

Take a look at the source code for this repo: https://github.com/bahmutov/cypress-svelte-unit-test/blob/master/lib/index.ts

The svelte component is instantiated and is returned in a cy.wrap call. So you could console.log what you get back via .then and/or log the component before it's wrapped and that'll probably track down what's going wrong pretty quickly.

It might also depend on what parameters you're passing to the svelte compiler--they provide different instance methods depending on what you tell it to spit out.

alejandroiglesias commented 3 years ago

@JohnnyFun I looked into the source code. If I log the instantiated component as you suggested, it is correct. Even if I log the wrapped component and check the resolved promise, it is correct:

console.log(cy.wrap(component).then(console.log))
// Button { ... }

But somehow, if in my test spec I log what I get in the resolved promise of the mount function, I get the stylesheet:

mount(Button, { props: { active: true } }, { cssFile: 'dist/main.css' }).then(console.log)
/*! minireset.css v0.0.6 | MIT License | github.com/jgthms/minireset.css */
html,
body,
p,
ol,
ul,
li,
dl,
dt,
dd,
blockquote,
figure,
fieldset,
legend,
textarea,
pre,
iframe,
hr,
h1,
h2,
h3,
h4,
h5,
h6 {
  margin: 0;
  padding: 0;
}

/* ...*/

Here is the Rollup config for setting up the Svelte compiler:

module.exports = {
  plugins: [
    svelte({
      // You can restrict which files are compiled
      // using `include` and `exclude`
      include: 'src/**/*.svelte',

      // Emit CSS as "files" for other plugins to process. default is true
      emitCss: false,

      // You can pass any of the Svelte compiler oiptions
      compilerOptions: { dev: true }
    }),
    commonjs(),
    resolve({ browser: true })
  ]
}

I also tried commenting out the emitCss and compilerOptions properties without producing any different results. Any other ideas?

alejandroiglesias commented 3 years ago

@JohnnyFun @bahmutov did you have any chance to check this? Thanks in advance

JohnnyFun commented 3 years ago

Huh, strange. I re-created it. I load my styles into the style option by reading the css file myself and storing it in a global let, so I've never ran into that. So that'd be a workaround in the meantime for you.

The issue must have something to do with how it's loading in css files with the cssFile option. Looks like it just reads all the css files with bluebird promise and plops them into style blocks in the dom. I wonder if cypress does something special with that bluebird promise that wonks up the cy.wrap. 🤷

JohnnyFun commented 3 years ago

I bet cy.readFile(cssFilename, ... has something to do with it, but I don't know enough about cypress internals.

JohnnyFun commented 3 years ago

Ok, figured it out--just need to await the call to injectStylesBeforeElement and make the rest follow. I'll make a PR for that in a bit.