<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          基于 TDD 模式編寫 Vue 評(píng)論組件(中):父子組件之間的通信測(cè)試

          共 13652字,需瀏覽 28分鐘

           ·

          2021-03-23 23:24

          一、拆分評(píng)論列表組件

          為了測(cè)試 Vue 父子組件之間的通信,我們需要將之前編寫的評(píng)論組件拆分成兩部分 —— 將評(píng)論列表拆分成獨(dú)立的子組件 CommentList,然后在 CommentComponent 中引入它。

          resources/js/components 目錄下新建 ComponentList.vue,并初始化組件代碼如下:

          <template>
              <div>
                  <h3>所有評(píng)論</h3>
                  <ul class="comments" v-show="comments.length > 0">
                      <li v-for="(content, index) in comments" :key="index" v-text="content"></li>
                  </ul>
              </div>

          </template>

          <script>
          export default {
              props: ['comments']
          }
          </
          script>

          然后在父組件 CommentComponent.vue 中引入評(píng)論列表子組件:

          <template>
              <div>
                  <form @submit.prevent="addNewComment">
                      ...
                  </form>
                  <ComponentList :comments="comments"></ComponentList>
              </div>

          </template>

          <script>
          import ComponentList from "./
          ComponentList";
          export default {
              components: {ComponentList},
              ...
          }
          </script>

          運(yùn)行 npm run test 進(jìn)行回歸測(cè)試:

          測(cè)試通過,說明此次代碼重構(gòu)沒有引入 bug,評(píng)論組件依然可以正常工作。

          二、通過 props 屬性傳遞數(shù)據(jù)到子組件

          接下來,我們?yōu)樽咏M件 CommentList 編寫單獨(dú)的測(cè)試用例用于測(cè)試父子組件之間的通信。

          前面在介紹 Vue 組件通信原理時(shí),我們已經(jīng)知曉父子組件之間的通信機(jī)制:父組件通過 props 屬性傳遞數(shù)據(jù)給子組件,子組件通過 $emit 以事件觸發(fā)的方式將消息變更上報(bào)給父組件。

          先來看通過 props 屬性實(shí)現(xiàn)從父組件與子組件的單向通信。

          tests/JavaScript 新建一個(gè)測(cè)試文件 comment-list.spec.js,編寫第一個(gè)測(cè)試用例代碼如下:

          import {mount} from "@vue/test-utils";
          import CommentList from "../../resources/js/components/CommentList.vue";

          describe('CommentList.vue', () => {
              let wrapper;

              beforeEach(() => {
                  wrapper = mount(CommentList, {
                      propsData: {
                          comments: ['測(cè)試評(píng)論']
                      }
                  })
              })

              it('should display comments passed from parent scope'function ({
                  expect(wrapper.find('ul.comments').isVisible()).toBe(true);
                  expect(wrapper.find('ul.comments').html()).toContain('測(cè)試評(píng)論');
              });
          })

          在這個(gè)測(cè)試用例中,我們只是簡(jiǎn)單斷言如果父組件通過 props 屬性傳遞數(shù)據(jù)到子組件后,是否可以正常渲染評(píng)論列表和評(píng)論數(shù)據(jù),這里我們?cè)?beforeEach 中初始化組件掛載時(shí)就設(shè)置了默認(rèn)的 props 屬性模擬數(shù)據(jù)。

          運(yùn)行 npm run watch-test 進(jìn)行自動(dòng)化測(cè)試,測(cè)試通過,表明通過 props 屬性從父組件傳遞數(shù)據(jù)到子組件后是可以正常工作的:

          三、通過 $emit 事件觸發(fā)上報(bào)子組件消息

          再來看通過 $emit 事件觸發(fā)實(shí)現(xiàn)從子組件到父組件的單向通信。

          為了演示這個(gè)事件觸發(fā)通信的測(cè)試用例編寫,我們?yōu)樵u(píng)論列表子組件中的評(píng)論添加點(diǎn)贊功能,假設(shè)每條評(píng)論后面都有一個(gè)點(diǎn)贊/取消點(diǎn)贊操作按鈕,初始化情況下是點(diǎn)贊操作,點(diǎn)擊該按鈕會(huì)觸發(fā)父組件中定義的監(jiān)聽事件切換對(duì)應(yīng)評(píng)論的點(diǎn)贊狀態(tài),再經(jīng)由上面的 props 屬性鏈路將狀態(tài)變更通知給子組件,進(jìn)而在子組件中將該評(píng)論的點(diǎn)贊按鈕切換成取消點(diǎn)贊按鈕。

          基于這樣的業(yè)務(wù)實(shí)現(xiàn)邏輯,我們?cè)?comment-list.spec.js 中編寫第二個(gè)測(cè)試用例代碼如下:

          it('默認(rèn)可以點(diǎn)贊,點(diǎn)擊點(diǎn)贊按鈕觸發(fā)父級(jí)事件', () => {
              // Given
              let button = wrapper.find('button.vote-up');  // 默認(rèn)是點(diǎn)贊按鈕
              expect(button.isVisible()).toBe(true);
              expect(button.text()).toBe('點(diǎn)贊');

              // When
              button.trigger('click');  // 點(diǎn)擊點(diǎn)贊按鈕

              // Then
              expect(wrapper.emitted().voteToggle).toBeTruthy();   // 觸發(fā)父級(jí)點(diǎn)贊切換事件函數(shù)
              expect(wrapper.emitted().voteToggle[0]).toEqual([0]);  // 斷言傳遞參數(shù)
          });

          默認(rèn)情況下是點(diǎn)贊按鈕,點(diǎn)擊該按鈕,會(huì)觸發(fā)父級(jí)事件函數(shù) voteToggle,并且我們?cè)谕ㄟ^ $emit 觸發(fā)該事件函數(shù)時(shí)還傳遞了當(dāng)前評(píng)論的索引,以便父組件可以對(duì)號(hào)操作,以上事件觸發(fā)相關(guān)邏輯都可以通過斷言來實(shí)現(xiàn),代碼如上所示。

          這個(gè)時(shí)候測(cè)試肯定不會(huì)通過:

          因?yàn)樵u(píng)論列表子組件中還沒有相應(yīng)的按鈕元素,另外,由于每條評(píng)論都新增了點(diǎn)贊狀態(tài),所以相應(yīng)的評(píng)論模型數(shù)據(jù)結(jié)構(gòu)也要做調(diào)整,先在子組件中 CommentList 中添加點(diǎn)贊和對(duì)應(yīng)的點(diǎn)贊狀態(tài)屬性:

          <template>
              <div>
                  <h3>所有評(píng)論</h3>
                  <ul class="comments" v-show="comments.length > 0">
                      <li v-for="(comment, index) in comments" :key="index">
                          {{ comment.content }}
                          <button class="vote-up" v-if="!comment.voted">點(diǎn)贊</button>
                          <button class="vote-down" v-if="comment.voted">取消點(diǎn)贊</button>
                      </li>
                  </ul>
              </div>

          </template>

          <script>
          export default {
              props: ['comments']
          }
          </
          script>

          然后在父組件 CommentComponent 中為評(píng)論模型添加 voted 屬性表示點(diǎn)贊狀態(tài):

          <template>
              <div>
                  <form @submit.prevent="addNewComment">
                      <div class="form-group">
                          <textarea v-model="content" class="form-control" name="content" rows="3" placeholder="請(qǐng)輸入評(píng)論內(nèi)容..."></textarea>
                      </div>
                      ...
                  </form>
                  ...
              </div>

          </template>

          <script>
          ...
          export default {
              ...
              data() {
                  return {
                      content: '',
                      comments: []
                  }
              },
              methods: {
                  addNewComment() {
                      let comment = {
                          content: this.content,
                          voted: false
                      }
                      this.comments.push(comment);
                      this.content = '';
                  }
              }
          }
          </
          script>

          保存代碼,此時(shí)原來的測(cè)試用例會(huì)不通過:

          因?yàn)樵u(píng)論模型數(shù)據(jù)結(jié)構(gòu)調(diào)整,所以需要修改 comment.spec.js 對(duì)應(yīng)的測(cè)試用例代碼:

          beforeEach(() => {
              wrapper = mount(CommentComponent, {
                  comment: {
                      content'',
                      votedfalse
                  },
                  comments: []
              })
          })

          ...

          it('typed comment will sync with model data 'function ({
              // Given
              let comment = '大家好,我是學(xué)院君。';
              wrapper.find('textarea[name=content]').element.value = comment;

              // When
              wrapper.find('textarea[name=content]').trigger('input');

              // Then
              expect(wrapper.vm.content).toBe(comment);
          });

          it('click submit button will render comment in comments list'function ({
              // Given
              expect(wrapper.find('ul.comments').isVisible()).toBe(false);
              let comment = '大家好,我是學(xué)院君。';
              wrapper.find('textarea[name=content]').element.value = comment;
              wrapper.find('textarea[name=content]').trigger('input');

              // When
              wrapper.find('button[type=submit]').trigger('submit');

              // Then
              expect(wrapper.vm.comments.length).toBe(1);
              expect(wrapper.vm.comments[0].content).toBe(comment);
              wrapper.vm.$nextTick(() => {
                  // 需要將這兩個(gè)斷言放到 Vue.nextTick 中執(zhí)行,因?yàn)樗鼈冃枰?nbsp;DOM 刷新之后才會(huì)生效
                  expect(wrapper.find('ul.comments').isVisible()).toBe(true);
                  expect(wrapper.find('ul.comments').html()).toContain(comment);
              })
          });

          這樣一來,父組件之前的測(cè)試用例都能正常通過了,但是子組件測(cè)試用例還是沒有通過:

          因?yàn)楦缸咏M件之間也還沒有實(shí)現(xiàn)對(duì)應(yīng)的事件觸發(fā)和處理代碼,我們?cè)谧咏M件中編寫點(diǎn)贊/取消點(diǎn)贊按鈕的事件觸發(fā)邏輯如下:

          <button class="vote-up" v-if="!comment.voted" @click="$emit('voteToggle', index)">點(diǎn)贊</button>
          <button class="vote-down" v-if="comment.voted" @click="$emit('voteToggle', index)">取消點(diǎn)贊</
          button>

          然后在父組件中編寫對(duì)應(yīng)的事件監(jiān)聽和處理代碼如下:

          <template>
              <div>
                  ...
                  <comment-list ref="comments" :comments="comments" @voteToggle="voteToggle"></comment-list>
              </div>
          </template>


          <script>
          import CommentList from "./CommentList.vue";
          export default {
              ...
              methods: {
                  ...
                  voteToggle(index) {
                      this.comments[index].voted = !this.comments[index].voted;
                  }
              }
          }
          </script>

          此時(shí),子組件通過 $emit 事件觸發(fā)機(jī)制與父組件通信的測(cè)試用例就通過了,當(dāng)然這個(gè)只限于子組件已經(jīng)將消息通知給父組件,

          至于父組件是否處理成功,則需要在父組件測(cè)試用例中編寫相應(yīng)的測(cè)試代碼:

          it('監(jiān)聽子組件觸發(fā)的 voteToggle 事件函數(shù)并進(jìn)行處理'function ({
              // Given
              wrapper.setData({
                  comments: [
                      {
                          content'測(cè)試評(píng)論',
                          votedfalse
                      }
                  ]
              })
              expect(wrapper.vm.comments[0].voted).toBe(false);

              // When
              wrapper.vm.$refs.comments.$emit('voteToggle'0);

              // Then
              expect(wrapper.vm.comments[0].voted).toBe(true);
          });

          測(cè)試通過,表明父組件可以正常處理子組件通過 $emit 觸發(fā)的事件函數(shù):

          最后,我們?cè)俚阶咏M件測(cè)試文件 comment-list.spec.js 中編寫第三個(gè)測(cè)試用例,測(cè)試如果評(píng)論的 voted 屬性為 true,對(duì)應(yīng)的按鈕渲染邏輯是否正常:

          it('如果評(píng)論狀態(tài)是已點(diǎn)贊,則按鈕狀態(tài)為取消點(diǎn)贊', () => {
              wrapper.setProps({
                  comments: [
                      {
                          content'測(cè)試評(píng)論',
                          votedtrue
                      }
                  ]
              });
              expect(wrapper.props('comments')[0].voted).toBe(true);
              wrapper.vm.$nextTick(() => {
                  let button = wrapper.find('button.vote-down');
                  expect(button.isVisible()).toBe(true);
                  expect(button.text()).toBe('取消點(diǎn)贊');
              })
          });

          測(cè)試通過,表明父子組件之間的通信可以順暢進(jìn)行,父子組件都可以正常工作:

          實(shí)際項(xiàng)目中,無論是新增評(píng)論還是評(píng)論點(diǎn)贊,都需要對(duì)數(shù)據(jù)和狀態(tài)進(jìn)行持久化,這就涉及到后端接口的調(diào)用,那么如何在 Vue 組件中測(cè)試接口調(diào)用呢,限于篇幅,學(xué)院君將在下篇教程給大家演示。

          本系列教程首發(fā)在Laravel學(xué)院(laravelacademy.org),你可以點(diǎn)擊頁面左下角閱讀原文鏈接查看最新更新的教程。

          瀏覽 49
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲91成人电影 | 中文字幕+乱码+中文字幕明步 | 成人免费A片在线观看直播96 | 日本国产中文字幕 | 亚洲操B|