quasarframework / quasar-testing

Testing Harness App Extensions for the Quasar Framework 2.0+
https://testing.quasar.dev
MIT License
180 stars 65 forks source link

Vitest harness testing components with QFooter or QHeader produces an exception #269

Closed mpont91 closed 2 years ago

mpont91 commented 2 years ago

Software version

OS: MacBook Pro M1 - v12.4 Node: 16.15.1 NPM: 8.11.0

What did you get as the error?

An exception:

TypeError: Cannot read properties of undefined (reading 'value')
 ❯ ReactiveEffect.fn node_modules/quasar/src/components/footer/QFooter.js:48:23
     46|     const fixed = computed(() =>
     47|       props.reveal === true
     48|       || $layout.view.value.indexOf('F') > -1
       |                       ^
     49|       || ($q.platform.is.ios && $layout.isContainer.value === true)
     50|     )

What were you expecting?

Pass the tests.

What steps did you take, to get the error?

  1. Create components

src/components/TheHeaderComponent.vue

<template>
  <q-header>
    <q-toolbar>
      <q-toolbar-title> Header </q-toolbar-title>
    </q-toolbar>
  </q-header>
</template>

src/components/TheFooterComponent.vue

<template>
  <q-footer>
    <q-toolbar>
      <q-toolbar-title> Footer </q-toolbar-title>
    </q-toolbar>
  </q-footer>
</template>
  1. Create unit tests

test/vitest/__tests__/TheHeaderComponent.spec.js

import { describe, expect, it } from 'vitest'
import { mount } from '@vue/test-utils'
import { installQuasar } from '@quasar/quasar-app-extension-testing-unit-vitest'
import TheHeaderComponent from 'components/TheHeaderComponent.vue'

installQuasar()

describe('<TheHeaderComponent>', () => {
  it('renders default properly', () => {
    const wrapper = mount(TheHeaderComponent)
    expect(wrapper.exists()).to.be.true
  })
})

test/vitest/__tests__/TheFooterComponent.spec.js

import { describe, expect, it } from 'vitest'
import { mount } from '@vue/test-utils'
import { installQuasar } from '@quasar/quasar-app-extension-testing-unit-vitest'
import TheFooterComponent from 'components/TheFooterComponent.vue'

installQuasar()

describe('<TheFooterComponent>', () => {
  it('renders default properly', () => {
    const wrapper = mount(TheFooterComponent)
    expect(wrapper.exists()).to.be.true
  })
})
  1. Run unit test
npm run test:unit TheHeaderComponent TheFooterComponent
  1. Output
 FAIL  test/vitest/__tests__/components/TheHeaderComponent.spec.js > <TheHeaderComponent> > renders default properly
TypeError: Cannot read properties of undefined (reading 'value')
 ❯ ReactiveEffect.fn node_modules/quasar/src/components/header/QHeader.js:45:23
     43|     const fixed = computed(() =>
     44|       props.reveal === true
     45|       || $layout.view.value.indexOf('H') > -1
       |                       ^
     46|       || ($q.platform.is.ios && $layout.isContainer.value === true)
     47|     )
 ❯ ReactiveEffect.run node_modules/@vue/reactivity/dist/reactivity.cjs.js:189:25
 ❯ ComputedRefImpl.get value [as value] node_modules/@vue/reactivity/dist/reactivity.cjs.js:1136:39
 ❯ ReactiveEffect.fn node_modules/quasar/src/components/header/QHeader.js:53:17
 ❯ ReactiveEffect.run node_modules/@vue/reactivity/dist/reactivity.cjs.js:189:25
 ❯ ComputedRefImpl.get value [as value] node_modules/@vue/reactivity/dist/reactivity.cjs.js:1136:39
 ❯ ReactiveEffect.getter [as fn] node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:1683:31
 ❯ ReactiveEffect.run node_modules/@vue/reactivity/dist/reactivity.cjs.js:189:25
 ❯ doWatch node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:1814:31
 ❯ Module.watch node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:1661:12

 FAIL  test/vitest/__tests__/components/TheFooterComponent.spec.js > <TheFooterComponent> > renders default properly
TypeError: Cannot read properties of undefined (reading 'value')
 ❯ ReactiveEffect.fn node_modules/quasar/src/components/footer/QFooter.js:48:23
     46|     const fixed = computed(() =>
     47|       props.reveal === true
     48|       || $layout.view.value.indexOf('F') > -1
       |                       ^
     49|       || ($q.platform.is.ios && $layout.isContainer.value === true)
     50|     )
 ❯ ReactiveEffect.run node_modules/@vue/reactivity/dist/reactivity.cjs.js:189:25
 ❯ ComputedRefImpl.get value [as value] node_modules/@vue/reactivity/dist/reactivity.cjs.js:1136:39
 ❯ ReactiveEffect.fn node_modules/quasar/src/components/footer/QFooter.js:62:17
 ❯ ReactiveEffect.run node_modules/@vue/reactivity/dist/reactivity.cjs.js:189:25
 ❯ ComputedRefImpl.get value [as value] node_modules/@vue/reactivity/dist/reactivity.cjs.js:1136:39
 ❯ ReactiveEffect.getter [as fn] node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:1683:31
 ❯ ReactiveEffect.run node_modules/@vue/reactivity/dist/reactivity.cjs.js:189:25
 ❯ doWatch node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:1814:31
 ❯ Module.watch node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:1661:12

Test Files  2 failed (2)
     Tests  2 failed (2)
      Time  27ms
  1. 'Fixing' the tests

If I remove the QHeader and QFooter components the tests pass:

<template>
  <p>Header</p>
</template>

src/components/TheFooterComponent.vue

<template>
  <p>Footer</p>
</template>
 ✓ test/vitest/__tests__/components/TheHeaderComponent.spec.js (1)
 ✓ test/vitest/__tests__/components/TheFooterComponent.spec.js (1)

Test Files  2 passed (2)
     Tests  2 passed (2)
      Time  1.67s (in thread 30ms, 5569.60%)
mpont91 commented 2 years ago

I tried to contribute solving this problem. I thought this code would work:

packages/unit-vitest/src/helpers/layout-injections.ts

/**
 * Injections for Components with a QPage root Element
 */
export function qLayoutInjections() {
  return {
    // pageContainerKey
    _q_pc_: true,
    // layoutKey
    _q_l_: {
      header: { size: 0, offset: 0, space: false },
      right: { size: 300, offset: 0, space: false },
      footer: { size: 0, offset: 0, space: false },
      left: { size: 300, offset: 0, space: false },
      isContainer: false,
      view: 'hhh lpr fff' // Added this line
    },
  };
}

But the same error keeps appearing... What am I doing wrong? Seems that is not injecting...

IlCallo commented 2 years ago

You probably need to add view: { value: 'hhh lpr fff' }, since Quasar expects that to be a ref and access it as layout.view.value

jdk2pq commented 2 years ago

I haven't switched to using '@quasar/quasar-app-extension-testing-unit-vitest' yet, but I based my own install-quasar-plugin.ts on the original work from that PR. Here's what worked for me. Note that some of the layout values are probably not 100% valid, but it got things working 😄 :

export function installQuasar(options?: Partial<QuasarPluginOptions>) {
    const globalConfigBackup = cloneDeep(config.global);

    beforeAll(() => {
        // Mock out uid implementation so that snapshots will work and won't generate a new ID on form elements
        vi.spyOn(uid, 'default').mockImplementation(() => 'quasar-uid');

        config.global.plugins.unshift([Quasar, options]);
        config.global.provide._q_l_ = {
            header: { size: 1200, offset: 0, space: false },
            right: { size: 1200, offset: 0, space: false },
            footer: { size: 1200, offset: 0, space: false },
            left: { size: 1200, offset: 0, space: false },
            view: ref('lHh Lpr lff'),
            rows: ref({ top: 'lHh', bottom: 'Lpr', left: 0, right: 0 }),
            instances: ref({}),
            isContainer: ref(false),
            update: vi.fn(),
            animate: vi.fn(),
            totalWidth: ref(1200),
            scroll: ref({ position: 0, direction: 'up' }),
            scrollbarWidth: ref(125)
        };
    });

    afterAll(() => {
        config.global = globalConfigBackup;
    });
}

If anyone wants to use what I have to open a PR, feel free! Otherwise, I can try to open a PR later today.

mpont91 commented 2 years ago

You probably need to add view: { value: 'hhh lpr fff' }, since Quasar expects that to be a ref and access it as layout.view.value

Oh it's just I based on isContainer property. shouldn't be isContainer: { value: false } as well? 😅

I tried using refs like @jdk2pq but when I run npm run sync:vitest it fails with this message repeatly:

[5/5] 🔨  Building fresh packages...
$ rimraf dist && tsc --declaration --declarationDir dist/types
../../node_modules/@vue/runtime-core/dist/runtime-core.d.ts:1960:9 - error TS2451: Cannot redeclare block-scoped variable 'defineProps'.

1960   const defineProps: _defineProps
             ~~~~~~~~~~~

  node_modules/@vue/runtime-core/dist/runtime-core.d.ts:2014:9
    2014   const defineProps: _defineProps
                 ~~~~~~~~~~~
    'defineProps' was also declared here.

../../node_modules/@vue/runtime-core/dist/runtime-core.d.ts:1961:9 - error TS2451: Cannot redeclare block-scoped variable 'defineEmits'.

1961   const defineEmits: _defineEmits
             ~~~~~~~~~~~

So we have to transform all ref() to {value: xxx}?

I see there is no PR yet, so I'll do it with @jdk2pq help :D

mpont91 commented 2 years ago

270