【PrimeVue /Vue3】有點曲折的第一次 PrimeVue 開專案過程

前言

最近被叫去弄一個新專案,是蠻複雜的 managerial 系統。
原本我算 Quasar 教徒,Quasar CLI 專案開起來,順風順水。然而,這個新專案有一個Table需要做 drag sort,Quasar提供的component偏偏沒有這功能。這時,我的小腦殼想出了以下方案:

1. Quasar q-table 搭配 Vue.Draggable: q-table 是一個很完整的component,要讓它完美的和Vue.Draggable一起運作,感覺有點抖

2. Quasar q-table:搭配自己寫 drag sort 功能:應該可以,但就麻煩

3. PrimeVue:改用已經有這個功能的 library,最後找到PrimeVue。不過因為沒用過,可能會有潛在的麻煩,風險也蠻高的

PrimeVue 是一個純粹UX Library,本身沒有樣式
使用 PrimeVue 的第一個問題,是引用的component沒有樣式
後來看到官方文件:「PrimeVue is a design agnostic library so unlike other UI libraries it does not enforce a certain styling such as material or bootstrap. 」所以跟Quasar不同,它本身沒帶SCSS或CSS

當然,PrimeVue也有配套,它提供 Material, Bootstrap, Saga......的UI,還蠻多種的,甚至有個編輯器可以輕鬆制定主題顏色。但這整套要用要錢
覺得上面不會出錢的我,想到的方法是...自己寫SCSS


VueCLI 配 Vue3 自架 SCSS

關於Vue CLI install sass-loader、vue.config.js 怎麼配,很多人講的比我清楚,就不詳述了。在這邊只講我最後的配置,和遇到的問題
最後,我的配置是:sass、sass-loaderstyle-resources-loader、vue-cli-plugin-style-resources-loader。引入一個 main.scss檔案管變數,一個 style.css 來制定UI樣式
vue.config.js的內容為:

module.exports = {
  pluginOptions: {
    'style-resources-loader': {
      preProcessor: 'scss',
      patterns: [
         './src/assets/scss/main.scss',
         './src/assets/css/style.css'
      ]
    }
  }
}

sass-loader的配置方式 Vue CLI都有教學,但我後來是採用 style-resources-loader 的配置。使用sass-loader的過程可能會出現以下報錯:

Syntax Error: TypeError: this.getOptions is not a function

錯誤的原因是 webpage4 沒辦法運作最新版的 sass-loader。要解決這個問題,在 package.json裡把 sass-loader版號改成 "^10.2.0",重新 npm install,就行了


Global引入PrimeVue component

像 Button 這種常常需要用到的組件,也可以使用全局引入。方法很簡單:

1. 打開 main.js

2. import Button from 'primevue/button'; << 引入 Button 組件

3. app.component('Button', Button)  << 讓app可以全局使用該組件


測試-面對Global引入的PrimeVue component

誠如上面示範,有些 components 在這個專案裡是以全局方式引入的。

寫 unit test 時就報了一個這樣的警告:

[Vue warn]: Failed to resolve component: Button

If this is a native custom element, make sure to exclude it from component resolution via compilerOptions.isCustomElement.

這是因為跑測試的時候只創建一個組件的實例,因此全局註冊在測試裡失效了。關於這個問題,我找了兩個解法:1. stubs(存根) 2. golbal component。

1. stubs 解法

最直覺的做法,用stubs通知<Button>的內容

每次 mout 都要設定嫌麻煩,也可以用這樣寫:
這是 vue3 (Vue Test Utils 2)的寫法,使用Vue2 的話要額外查一下


2. global component 解法

如果是全局註冊沒吃到,就直接在 mout 前註冊吧


同理。覺得每次 mount 都要寫很麻煩的話,用 config.global.components = { Button }  取代也可以。

關於 config.global


測試-需留意Vue Test Unit Next廢棄的部分

PrimeVue 的 components 引入、使用的方式和一般自己寫的components一樣。在Vue CLI 的配置下,不會像使用quasar時,因認不出 <q-input> 之類的標籤而需要做一些設定

不過這個專案是使用Vue3,連帶單元測試也是使用 Vue Test Unit Next,必須留意是不是用到已廢棄的功能

例如,我這邊想測試一個引用了Vuex store的component。

如下圖所見,computed 寫在 setup裡:

要測試的code

這時參考Vue Unit Test Next 來mock一個store,使用 provide。

使用 provide是,因為被測試的component在setup裡使用store的緣故。若是在 mounted 或以往的computed,那就必須使用 mock 或 plugins,這部分請參考

執行測試的code

上述模擬 store 的部分,若是在Vue2,就會使用createLocalVue來進行。但因createLocalVue在 Vue Unit Test Next 裡已廢棄,所以想使用它的話,會出現沒有這個 function 的報錯:



測試-快照測試(Snapshot)需注意的問題

這部分雖然和 PrimeVue 無關,但也是我在這個專案裡思考的問題。

這個專案是一個複雜的管理系統,必須顯示很多資料、穿插切換編輯用的UI,編輯內容需要驗證,api 返回的內容也會有訊息通知。我在做 snapshot 的時候就遇到一些問題。

我為一個含 data table 的 component 寫了一個 describe function分別測試一個會造成畫面更動(會改動 reference data的值)的method 和 snapshot。最後靠先測 snapshot,再測 method 的順序來通過測試,但坦白說我不是很滿意。

比較好的做法是看看能不能把 component 拆小用,做成更小的快照。或用shallowMount 取代 Mount,但這樣 data table 裡一些客製化的內容就測不到了,它們會被存根。

相關資訊可以參考Snapshot Testing for Frontends,他點出了幾個不適合 snapshot 的情況:

 1. 快照超過1000行

 2. 多語化情境

 3. 快照過大的情況下要解決快照衝突, 會是繁瑣的工作


測試-Dropdown 被stub後竟然報錯,那就自己設計stub啊

首先,Dropdown 是 PrimeVue 提供的組件。我們先來看看這個報錯:

[Vue warn]: Failed setting prop "scrollHeight" on <dropdown-stub>: value 200px is invalid. TypeError: Cannot set property scrollHeight of [object Element] which has only a getter

再來看看相關的code:

被測的內容:

<Dropdown
  class="selector p-ml-3" :options="inputOption" optionLabel="name"
  :filter="true" placeholder="Select Input field" @change="addInputItem" />

測試代碼:

describe('RulesDetail: Rule has no inputs (no list)', () => {
  let storeNoInput = JSON.parse(JSON.stringify(store))
  storeNoInput.state.rulesData.rules[0].criteria.singleInputs = []

  let wrapper = shallowMount(RuleDetail, {
    global: {
      provide: { store: storeNoInput }
    }
  })
  it('snapshot', () => {
  expect(wrapper.element).toMatchSnapshot('rule-detail-with-no-list')
  })
})

被測的內容雖然一堆 props ,但我沒有傳“scrollHeight”進去。打開PrimeVue 裡Dropdown component 一看,原來有個default value。

default 的 scrollHeight,不是我寫地~

Unit Test 基本原則是只測自己寫的東西,所以我們不需要管 Dropdown 真正的內容是什麼。既然問題看起來是無法在<dropdown-stub>上掛“scrollHeight = 200px”,那讓 Dropdown 的 stub 變成一般的div就行了吧。

改寫後的測試代碼:

describe('RulesDetail: Rule has no inputs (no list)', () => {
  let storeNoInput = JSON.parse(JSON.stringify(store))
  storeNoInput.state.rulesData.rules[0].criteria.singleInputs = []

  let wrapper = mount(RuleDetail, {
    global: {
      stubs: {
        Dropdown: {
          template: '<div class="deopdown-stub" />'
        }
      },
      provide: { store: storeNoInput }
    }
  })
  it('snapshot', () => {
  expect(wrapper.element).toMatchSnapshot('rule-detail-with-no-list')
  })
})

1. <dropdown-stub> 是 shallowMount 預設的stub方式,所以改成用 mount 來生成實例。

2. 在 stubs 裡寫自己替換 Dropdown 內容的template

結果測試成功。從snapshot也可以看到<dropdown-stub>被換成自己設計的 div 了。


(待續...)

留言