無限スクロールと言えばVue-infinite-loadingで、導入記事は多いのですが、
テスト(Jest)の記事は見つからなかったのでメモしておきます。
Vue warnとTypeErrorがなかなか消せなかったり、
読み込み後のloaded/complete/errorを、どうテストすべきかで悩みました。

導入

Vue-infinite-loadingの導入は、公式通りにやればできるので、詳細は割愛します。
Guide | Vue-infinite-loading

spinner(ローディング中のくるくる)はデフォルトのを使う、
no-moreやno-resultsは不要(slotしないとデフォルトのが出ちゃう)、
errorは文言やボタンを変えたかったので、下記を参考にしました。
Configure Load Messages | Vue-infinite-loading

https://dev.azure.com/nightonly/nuxt-app-origin/_git/nuxt-app-origin?path=/pages/spaces/index.vue&version=GBspace_develop

        <InfiniteLoading
          v-if="space != null && space.current_page < space.total_pages"
          :identifier="page"
          @infinite="getNextSpaces"
        >
          <div slot="no-more" />
          <div slot="no-results" />
          <div slot="error" slot-scope="{ trigger }">
            取得できませんでした。
            <v-btn @click="trigger">再取得</v-btn>
          </div>
        </InfiniteLoading>

テストの流れ

ページを表示後、下にスクロールして、APIリクエスト+データ表示を繰り返すのを再現してみます。
実際にはスクロールはせず、下記のようにinfiniteをemitしてあげれば、イベントが発火します。

wrapper.findComponent(InfiniteLoading).vm.$emit('infinite')

呼び出している変数やメソッドは記載していないので、リポジトリを参照してください。
https://dev.azure.com/nightonly/nuxt-app-origin/_git/nuxt-app-origin?path=/test/page/spaces/index.spec.js&version=GBspace_develop

初回表示 → イベント発火 → loaded(続きあり) → イベント発火 → complete(完了)の流れ

    it('[無限スクロール]表示される', async () => {
      axiosGetMock = jest.fn()
        .mockImplementationOnce(() => Promise.resolve({ data: data1 }))
        .mockImplementationOnce(() => Promise.resolve({ data: data2 }))
        .mockImplementationOnce(() => Promise.resolve({ data: data3 }))
      const wrapper = mountFunction(false)
      helper.loadingTest(wrapper, Loading)

      await helper.sleep(1)
      apiCalledTest(1, params)
      const infiniteLoading = viewTest(wrapper, params, data1, '5件', true)
      let infiniteLoading = viewTest(wrapper, params, data1, '5件', true)
      const spaces = data1.spaces

      // スクロール(1回目)
      infiniteLoading.vm.$emit('infinite')
      spaces.push(...data2.spaces)

      await helper.sleep(1)
      apiCalledTest(2, params)
      viewTest(wrapper, params, { space: data2.space, spaces }, '5件', true, 'loaded')
      infiniteLoading = viewTest(wrapper, params, { space: data2.space, spaces }, '5件', true, 'loaded')
      const spaces = data1.spaces.concat(data2.spaces)
      infiniteLoading = viewTest(wrapper, params, { ...data2, spaces }, '5件', true, 'loaded')

      // スクロール(2回目)
      infiniteLoading.vm.$emit('infinite')
      spaces.push(...data3.spaces)

      await helper.sleep(1)
      apiCalledTest(3, params)
      viewTest(wrapper, params, { space: data3.space, spaces }, '5件', false, 'complete')
      viewTest(wrapper, params, { ...data3.space, spaces: spaces.concat(data3.spaces) }, '5件', false, 'complete')
    })

初回表示 → イベント発火 → error(再取得ボタン表示)の流れ

      it('[無限スクロール]エラーメッセージが表示される', async () => {
        axiosGetMock = jest.fn()
          .mockImplementationOnce(() => Promise.resolve({ data: data1 }))
          .mockImplementationOnce(() => Promise.resolve({ data: null }))
        const wrapper = mountFunction(false)
        helper.loadingTest(wrapper, Loading)

        await helper.sleep(1)
        apiCalledTest(1, params)
        const infiniteLoading = viewTest(wrapper, params, data1, '5件', true)

        // スクロール(1回目)
        infiniteLoading.vm.$emit('infinite')
        await helper.sleep(1)

        helper.mockCalledTest(toastedErrorMock, 1, locales.system.error)
        helper.mockCalledTest(toastedInfoMock, 0)
        helper.mockCalledTest(routerPushMock, 0)
        viewTest(wrapper, params, data1, '5件', true, 'error')
      })

テスト用に改修

$stateのerror/loaded/completeをMockが呼ばれるようにしたかったのですが、イベントのパラメータをMock等で受ける方法に辿り着けなっかたので、不本意(テストが通過しないコードが残る)ですが変数に格納して、チェックするようにしました。

ちなみに、$state.loaded()や.complete()は下記に書き換えれば通せます。emitのテストが書けそう。ただ、.error()に対応するのが無かったのと、難しく書くのは嫌なので、元のままにしました。

https://github.com/PeachScript/vue-infinite-loading/blob/master/src/components/InfiniteLoading.vue#L219

      loaded: () => {
        this.$emit('$InfiniteLoading:loaded', { target: this });
      },
      complete: () => {
        this.$emit('$InfiniteLoading:complete', { target: this });
      },
      error: () => {
        this.status = STATUS.ERROR;
        throttleer.reset();
      },

https://dev.azure.com/nightonly/nuxt-app-origin/_git/nuxt-app-origin?path=/pages/spaces/index.vue&version=GBspace_develop

  data () {
    return {
+      testState: null // Jest用
    }
  },
    // 次頁のスペースを取得
    async getNextSpaces ($state) {
      if (this.processing || this.space == null) { return }

      this.page = this.space.current_page + 1
      if (!await this.onSpaces()) {
+        if ($state == null) { this.testState = 'error'; return }

        $state.error()
      } else if (this.space.current_page < this.space.total_pages) {
+        if ($state == null) { this.testState = 'loaded'; return }

        $state.loaded()
      } else {
+        if ($state == null) { this.testState = 'complete'; return }

        $state.complete()
      }
    },

Vue warnとTypeErrorに対応

reading 'tagName'/'_infiniteScrollHeight'

[Vue warn]: Error in callback for immediate watcher "forceUseInfiniteWrapper": "TypeError: Cannot read properties of null (reading 'tagName')"
TypeError: Cannot read properties of null (reading 'tagName')
[Vue warn]: Error in event handler for "$InfiniteLoading:reset": "TypeError: Cannot read properties of null (reading '_infiniteScrollHeight')"
TypeError: Cannot read properties of null (reading '_infiniteScrollHeight')

stubsに入れることで解消

https://dev.azure.com/nightonly/nuxt-app-origin/_git/nuxt-app-origin?path=/test/page/spaces/index.spec.js&version=GBspace_develop

    const wrapper = mount(Page, {
      localVue,
      vuetify,
      stubs: {
        Lists: true,
+        InfiniteLoading: true
      },

reading 'loaded'/'complete'

[Vue warn]: Error in v-on handler (Promise/async): "TypeError: Cannot read properties of undefined (reading 'loaded')"
TypeError: Cannot read properties of undefined (reading 'loaded')
[Vue warn]: Error in v-on handler (Promise/async): "TypeError: Cannot read properties of undefined (reading 'complete')"
TypeError: Cannot read properties of undefined (reading 'complete')

テスト用に改修で対応済みですが、Jestからの場合、イベント引数の$stateがundefinedになる為。
ちなみにjsでは、$に特別な意味はないんですね。アルファベットと同じ扱いですが、可読性の為に公式でも$を使っている。

https://dev.azure.com/nightonly/nuxt-app-origin/_git/nuxt-app-origin?path=/pages/spaces/index.vue&version=GBspace_develop

      } else if (this.space.current_page < this.space.total_pages) {
+        if ($state == null) { this.testState = 'loaded'; return }

        $state.loaded()
      } else {
+        if ($state == null) { this.testState = 'complete'; return }

        $state.complete()
      }

reading 'push'

[Vue warn]: Error in v-on handler (Promise/async): "TypeError: Cannot read properties of undefined (reading 'push')"
TypeError: Cannot read properties of undefined (reading 'push')

これは単なる凡ミスです。mocksの定義忘れ。

https://dev.azure.com/nightonly/nuxt-app-origin/_git/nuxt-app-origin?path=/test/page/spaces/index.spec.js&version=GBspace_develop

    const wrapper = mount(Page, {
<省略>
      mocks: {
+        $router: {
+          push: routerPushMock
+        },

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です