Skip to content

Store is undefined in dynamically loaded child component #1486

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
mediafreakch opened this issue Mar 23, 2020 · 9 comments
Closed

Store is undefined in dynamically loaded child component #1486

mediafreakch opened this issue Mar 23, 2020 · 9 comments

Comments

@mediafreakch
Copy link

Version

1.0.0-beta.32

Reproduction link

https://github.com/mediafreakch/vue-test-utils-bug

Steps to reproduce

  1. Clone the repository
  2. run npm install
  3. run npm test

What is expected?

Tests pass.

What is actually happening?

1 Test fails.


Compare to https://codesandbox.io/s/github/mediafreakch/vue-test-utils-bug
(switch to tab "Test" and watch tests pass)

It seems I am missing something here in the build setup to make dynamic imports work, that codesandbox.io has...
But I am using the latest of @babel/core, which should include babel-plugin-dynamic-import-node.

@lmiller1990
Copy link
Member

Maybe related... #1279 ?

@mediafreakch
Copy link
Author

mediafreakch commented Apr 10, 2020

I think this is most likely related to vue-cli-plugin-unit-jest that still relies on an old version of jest and jest-environment-jsdom-fifteen. If I figure out how I will make a use-caese without vue-cli-plugin.

UPDATE:
Couldn't confirm my suspicion:
Tried with [email protected] (which is usingjsdom@16) and [email protected] and the test still fails.

@CodePlato3721
Copy link

vuex in dynamic import use the different vue instance.

How about using stubs to shallow mount Dashboard.vue , then test Dashboard.vue and Chart.vue separately?

For example:

Dashboard.test.js:

mount(Dashboard, {
      stubs: {
          GenericView: true
      }
...

@mediafreakch
Copy link
Author

@alexxiyang How do you figure that different vue instances are in play? And if that's the case, why only in the test environment and not when running in the browser?

How about using stubs to shallow mount Dashboard.vue , then test Dashboard.vue and Chart.vue separately?

My workaround for the tests to pass was indeed to using a stub for GenericView. But to me that's merely a workaround. I actually came here from Vue Testing Library, where working with Vue instances is discouraged. Instead I wanted to assert on actual DOM output.

@lmiller1990
Copy link
Member

I am interested in this too

vuex in dynamic import use the different vue instance.

I think he is saying that dynamic components are not using localVue - if this is the case, at least we have a clear path to look into fixing this.

@dobromir-hristov
Copy link
Contributor

Can you stub out a dynamic component with jest.mock?

@mediafreakch
Copy link
Author

mediafreakch commented May 15, 2020

@dobromir-hristov You can with jest.doMock. But what I meant was that, referring to my example, I stubbed GenericView when testing the Dashboard like this:

it("should initiate dashboard tiles", () => {
    mount(GenericView, {
      stubs: ["GenericView"],
      localVue,
      store,
      props: {
        kind: "Dashboard"
      }
    });

    expect(actions.fetchViewInfo).toHaveBeenCalledTimes(1);
});

@huynl-96
Copy link

huynl-96 commented May 3, 2021

Is there any solution for this?
I also ran into this problem when using vue-testing-library and my dynamic components do not have any access to $route as well as $store (they are both undefined)

@lmiller1990
Copy link
Member

As far as I know there is no clean cut solution. This isn't a Test Utils specific problem, but common to anyone using dynamic imports with babel and Jest, I think. It is not something we can fix here (could be wrong, but this is my understanding). Seems there are similar issues in other code bases, eg this one in Enzyme. Looks like they arrived at a solution, maybe that can help you?

matthew-white added a commit to getodk/central-frontend that referenced this issue Jun 23, 2021
Closes #173. Notes:

- avoriaz has some information on migrating to Vue Test Utils (VTU):
  https://github.com/eddyerburgh/avoriaz/blob/master/docs/guides/migrating-to-vue-test-utils.md
- VTU wrappers no longer have a data() method. Using the `vm` property
  to access data properties instead.
- Replace wrapper methods:
  - avoriaz() getProp() becomes VTU props()
  - hasClass() becomes classes()
  - getAttribute() and hasAttribute() become attributes()
  - first() becomes get() or getComponent()
  - find() becomes findAll() or findAllComponents()
    - Unless doing an existence check, in which case we call either
      find() or findComponent(), then call exists().
- For a wrapper of a Vue component, access the element using the
  `element` property rather than using vm.$el.
- The VTU wrapper method text() trims the text. The avoriaz wrapper
  method did not, so we often called trim() after text(). We now access
  element.textContent directly in the rare case that we do not want the
  text to be trimmed.
- The VTU wrapper method html() prettifies the HTML, which the avoriaz
  wrapper method did not. That may be useful for us in the future, but
  for our few existing tests that used the method (in
  SubmissionFeedEntry), we now use element.outerHTML instead.
- Replace our `trigger` object with the VTU wrapper methods trigger(),
  setValue(), and setChecked().
  - But keeping a version of trigger.dragAndDrop(), in the new
    test/util/file.js.
  - The methods of the `trigger` object returned a promise that resolved
    to the avoriaz wrapper. The VTU wrapper methods return a promise
    that doesn't resolve to a value, so I had to restructure some
    promise chains. In many cases, I rewrote the promise chain using
    async/await.
  - It is now possible to specify options when using VTU to trigger an
    event, so we don't need to use jQuery to mock events. I removed
    jQuery from FormNew and FormAttachmentUploadFiles, which only used
    jQuery so that we could mock events in testing.
  - The tests of FormNew have good examples of some of these changes.
- Use the VTU wrapper method emitted() instead of using Sinon to fake
  $emit(). (The tests of DateRangePicker have good examples of this
  change.)
- Replace attachToDocument with attachTo.
- avoriaz allowed a wrapper to be passed as a slot, but VTU does not.
  However, VTU allows a template string to be passed, which avoriaz did
  not. The only place this comes up is a test of LinkIfCan, where the
  wrapper could be replaced with a template string.
- Use the parentComponent option of mount() to account for i18n custom
  blocks. Previously, we overwrote the setProps() wrapper method.
- We inject the router in fewer tests. In some cases, we are able to
  switch from load() to mount(), making the test more isolated and often
  making it synchronous. (The tests of Download and FormDraftChecklist
  have good examples of that.)
  - If a component uses <router-link>, VTU can stub that.
  - If a component uses $route, VTU can mock that.
  - In order for VTU to mock $route, we use createLocalVue() to create a
    copy of Vue with the router and a copy without, which is what VTU
    recommends.
    - That means that src/setup.js can no longer import src/plugins.js.
      Instead, src/plugins.js is imported in src/main.js.
  - For some components, these changes result in a natural sequence of
    tests: tests first use mount() to test the behavior of the component
    before a request, then use mockHttp() to test behavior before a
    successful response, then use load() to test a route change and
    other behavior after a successful response. For some components (for
    example, AccountLogin), I reordered tests to match this sequence.
  - load() will now inject the router only if it is mounting the root
    component (App). In other cases, it stubs <router-link> and mocks
    $route.
  - The documentation for mockHttp() and load() describes some of this
    in more detail, including guidelines around which utility function
    to use in different cases.
  - One case where mocking $route doesn't work is if an async component
    uses $route: see
    vuejs/vue-test-utils#1486. This was an
    issue for ProjectSubmissionOptions, so it is no longer loaded async.
    I had actually already wanted not to load it async: it's not a large
    component, and it may be needed soon after the page renders. I think
    I set it up to load async because multiple components import it, but
    I don't think I realized at the time that webpack will automatically
    split out a large shared component into its own .js file.
  - test/index.js used to contain some complexity related to the router,
    but with these changes in place, it felt like the right time to
    remove the previous workaround. I was able to simplify things by
    having the router use abstract mode in testing. This change also
    sped up tests significantly.
  - Previously, one of the tests of AccountClaim would fail
    intermittently depending on how quickly the Password async component
    loaded. After these changes, the test failed more often (maybe
    because some of the tests before it are now faster?), so I updated
    the test to wait for the component to load. This involved a small
    change to FormGroup.
- There are benefits to mocking/stubbing for Vue Router, but there seem
  to be fewer benefits to doing so for our other plugins, Vuex and Vue
  I18n. The router contains a fair amount of logic and implements some
  of that behavior (some of which can be async) as soon as it is
  injected into a component. However, the same is not true of Vuex or
  Vue I18n.
- I'm not sure whether avoriaz created a parent component when mounting,
  but Vue Test Utils seems to. We destroy the parent component after
  each test. We used to destroy the component before removing it from
  the DOM, but we now do those in the reverse order. That matches what
  avoriaz and VTU do, and I don't see any obvious issues with that
  approach. (We introduced the previous logic in
  304e5db, maybe because Modal at the
  time used a ref in its beforeDestroy hook. However, that is no longer
  the case.)
- VTU automatically sets Vue.config.productionTip and
  Vue.config.devtools to `false`. This removes a message about Vue
  devtools that was previously shown in testing. Since VTU now sets
  productionTip, I have moved that configuration from src/setup.js to
  src/main.js.
- VTU wrappers have an isVisible() method, but I think we should
  continue using our visible() and hidden() assertions. One reason for
  that is that both assertions can be used to test style-based
  visibility. Also, the hidden() assertion is more specific than
  not.visible().
- Make other improvements to testing, including:
  - Remove deprecated functions and properties:
    - Replace mockRoute() with load() or mockHttp().
    - Replace mountAndMark() with mount().
    - Replace mockHttp().standardButton() with
      mockHttp().testStandardButton().
    - Replace testData.administrators with testData.standardUsers.
    - Remove the disabled() assertion.
  - Remove String.prototype.iTrim().
  - Use mockHttp().testModalToggles() in more tests, and call it with a
    single argument consistently.
  - Mount the root component (App) in fewer tests.
    - I initially made this change in too many cases. I've added a check
      to load() to help prevent that going forward.
  - Add an id or class attribute to certain elements to make it easier
    to select elements in testing. In other cases, shorten an existing
    class name.
  - Move tests:
    - From UserList to router.spec.js
    - From ProjectRow to ProjectIntroduction
  - Remove unneeded tests.
@ebisbe ebisbe closed this as completed Jan 26, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants