目的:
仿Element-UI中的基礎表格實現表格渲染,自定義列模板,排序,單元測試
靜態效果:
<template> <div></div></template><script>export default {};</script><style lang="scss" scoped></style>
JSX改版ElTable<script>export default { props: { ... }, computed: { ... }, render() { return ( <table> <thead> <tr> {this.columns.map((column) => { return <th key={column.label}>{column.label}</th>; })} </tr> </thead> <tbody> {/*透過遍歷rows後再次遍歷每行row的物件的key來填充keys.len長度的td*/} {this.rows.map((data, index) => { const tds = Object.keys(data).map((key) => { return <td key={key}>{data[key]}</td>; }); return <tr key={index}>{tds}</tr>; })} </tbody> </table> ); },};</script>
自定義列模板普通VNode設定v-slot:default="scope"後的VNode開始調整<script>export default { props: { ... }, computed: { columns() { return this.$slots.default.map(({ data: { attrs, scopedSlots } }) => { const column = { ...attrs }; // 判斷是否存在scopedSlots if (scopedSlots) { // 為column物件增加用於渲染相關的renderCell屬性 column.renderCell = (row, i) => ( // 訪問作用域插槽,返回若干 VNode,並且可以透過default傳遞引數 <div>{scopedSlots.default({ row, $index: i })}</div> ); } else { // 預設渲染label column.renderCell = (row) => <div>{row[column.prop]}</div>; } return column; }); }, }, render() { return ( <table> <thead> <tr> {this.columns.map((column) => { return <th key={column.label}>{column.label}</th>; })} </tr> </thead> <tbody> {this.data.map((row, rowIndex) => { return ( <tr key={rowIndex}> {this.columns.map((column, columnIndex) => { return ( <td key={columnIndex}> {column.renderCell(row, rowIndex)} </td> ); })} </tr> ); })} </tbody> </table> ); },};</script>
排序<script>export default { data() { return { // 排序欄位 orderField: "", // 排序方式 orderBy: "desc", }; }, props: { ... }, computed: { columns() { ... }, }, created() { // 預設處理排序 this.columns.forEach((column) => { // 獲取排序標識sortable const hasSortable = Object.prototype.hasOwnProperty.call( column, "sortable" ); if (hasSortable && column.prop && !this.orderField) { // 執行排序函式 this.sort(column.prop, this.orderBy); } }); }, methods: { sort(filed, by) { // 更新排序列和方式 this.orderField = filed; this.orderBy = by; // 排序 this.data.sort((a, b) => { const v1 = a[filed]; const v2 = b[filed]; if (typeof v1 === "number") { return this.orderBy === "desc" ? v2 - v1 : v1 - v2; } else { return this.orderBy === "desc" ? v2.localeCompare(v1) : v1.localeCompare(v2); } }); }, // th進行排序的事件函式 taggleSort(filed) { const by = this.orderBy === "desc" ? "asc" : "desc"; this.sort(filed, by); }, }, render() { return ( <table> <thead> <tr> {this.columns.map((column) => { // 獲取排序標識 const hasSortable = Object.prototype.hasOwnProperty.call( column, "sortable" ); // 是否支援排序,無prop得不支援 if (hasSortable && column.prop) { let orderArrow = "↑↓"; if (this.orderField === column.prop) { orderArrow = this.orderBy === "desc" ? "↓" : "↑"; } return ( <th key={column.label} onClick={() => { this.taggleSort(column.prop); }} > {column.label} <span>{orderArrow}</span> </th> ); } else { return <th key={column.label}>{column.label}</th>; } })} </tr> </thead> <tbody> ... </tbody> </table> ); },};</script>
單元測試
<template> <div></div></template><script>export default {};</script><style lang="scss" scoped></style>
<script>export default { props: { ... }, computed: { ... }, render() { return ( <table> <thead> <tr> {this.columns.map((column) => { return <th key={column.label}>{column.label}</th>; })} </tr> </thead> <tbody> {/*透過遍歷rows後再次遍歷每行row的物件的key來填充keys.len長度的td*/} {this.rows.map((data, index) => { const tds = Object.keys(data).map((key) => { return <td key={key}>{data[key]}</td>; }); return <tr key={index}>{tds}</tr>; })} </tbody> </table> ); },};</script>
自定義列模板普通VNode設定v-slot:default="scope"後的VNode開始調整<script>export default { props: { ... }, computed: { columns() { return this.$slots.default.map(({ data: { attrs, scopedSlots } }) => { const column = { ...attrs }; // 判斷是否存在scopedSlots if (scopedSlots) { // 為column物件增加用於渲染相關的renderCell屬性 column.renderCell = (row, i) => ( // 訪問作用域插槽,返回若干 VNode,並且可以透過default傳遞引數 <div>{scopedSlots.default({ row, $index: i })}</div> ); } else { // 預設渲染label column.renderCell = (row) => <div>{row[column.prop]}</div>; } return column; }); }, }, render() { return ( <table> <thead> <tr> {this.columns.map((column) => { return <th key={column.label}>{column.label}</th>; })} </tr> </thead> <tbody> {this.data.map((row, rowIndex) => { return ( <tr key={rowIndex}> {this.columns.map((column, columnIndex) => { return ( <td key={columnIndex}> {column.renderCell(row, rowIndex)} </td> ); })} </tr> ); })} </tbody> </table> ); },};</script>
排序<script>export default { data() { return { // 排序欄位 orderField: "", // 排序方式 orderBy: "desc", }; }, props: { ... }, computed: { columns() { ... }, }, created() { // 預設處理排序 this.columns.forEach((column) => { // 獲取排序標識sortable const hasSortable = Object.prototype.hasOwnProperty.call( column, "sortable" ); if (hasSortable && column.prop && !this.orderField) { // 執行排序函式 this.sort(column.prop, this.orderBy); } }); }, methods: { sort(filed, by) { // 更新排序列和方式 this.orderField = filed; this.orderBy = by; // 排序 this.data.sort((a, b) => { const v1 = a[filed]; const v2 = b[filed]; if (typeof v1 === "number") { return this.orderBy === "desc" ? v2 - v1 : v1 - v2; } else { return this.orderBy === "desc" ? v2.localeCompare(v1) : v1.localeCompare(v2); } }); }, // th進行排序的事件函式 taggleSort(filed) { const by = this.orderBy === "desc" ? "asc" : "desc"; this.sort(filed, by); }, }, render() { return ( <table> <thead> <tr> {this.columns.map((column) => { // 獲取排序標識 const hasSortable = Object.prototype.hasOwnProperty.call( column, "sortable" ); // 是否支援排序,無prop得不支援 if (hasSortable && column.prop) { let orderArrow = "↑↓"; if (this.orderField === column.prop) { orderArrow = this.orderBy === "desc" ? "↓" : "↑"; } return ( <th key={column.label} onClick={() => { this.taggleSort(column.prop); }} > {column.label} <span>{orderArrow}</span> </th> ); } else { return <th key={column.label}>{column.label}</th>; } })} </tr> </thead> <tbody> ... </tbody> </table> ); },};</script>
單元測試
<script>export default { props: { ... }, computed: { columns() { return this.$slots.default.map(({ data: { attrs, scopedSlots } }) => { const column = { ...attrs }; // 判斷是否存在scopedSlots if (scopedSlots) { // 為column物件增加用於渲染相關的renderCell屬性 column.renderCell = (row, i) => ( // 訪問作用域插槽,返回若干 VNode,並且可以透過default傳遞引數 <div>{scopedSlots.default({ row, $index: i })}</div> ); } else { // 預設渲染label column.renderCell = (row) => <div>{row[column.prop]}</div>; } return column; }); }, }, render() { return ( <table> <thead> <tr> {this.columns.map((column) => { return <th key={column.label}>{column.label}</th>; })} </tr> </thead> <tbody> {this.data.map((row, rowIndex) => { return ( <tr key={rowIndex}> {this.columns.map((column, columnIndex) => { return ( <td key={columnIndex}> {column.renderCell(row, rowIndex)} </td> ); })} </tr> ); })} </tbody> </table> ); },};</script>
<script>export default { data() { return { // 排序欄位 orderField: "", // 排序方式 orderBy: "desc", }; }, props: { ... }, computed: { columns() { ... }, }, created() { // 預設處理排序 this.columns.forEach((column) => { // 獲取排序標識sortable const hasSortable = Object.prototype.hasOwnProperty.call( column, "sortable" ); if (hasSortable && column.prop && !this.orderField) { // 執行排序函式 this.sort(column.prop, this.orderBy); } }); }, methods: { sort(filed, by) { // 更新排序列和方式 this.orderField = filed; this.orderBy = by; // 排序 this.data.sort((a, b) => { const v1 = a[filed]; const v2 = b[filed]; if (typeof v1 === "number") { return this.orderBy === "desc" ? v2 - v1 : v1 - v2; } else { return this.orderBy === "desc" ? v2.localeCompare(v1) : v1.localeCompare(v2); } }); }, // th進行排序的事件函式 taggleSort(filed) { const by = this.orderBy === "desc" ? "asc" : "desc"; this.sort(filed, by); }, }, render() { return ( <table> <thead> <tr> {this.columns.map((column) => { // 獲取排序標識 const hasSortable = Object.prototype.hasOwnProperty.call( column, "sortable" ); // 是否支援排序,無prop得不支援 if (hasSortable && column.prop) { let orderArrow = "↑↓"; if (this.orderField === column.prop) { orderArrow = this.orderBy === "desc" ? "↓" : "↑"; } return ( <th key={column.label} onClick={() => { this.taggleSort(column.prop); }} > {column.label} <span>{orderArrow}</span> </th> ); } else { return <th key={column.label}>{column.label}</th>; } })} </tr> </thead> <tbody> ... </tbody> </table> ); },};</script>
單元測試
未透過腳手架配置單元測試的執行1,2
1. 安裝 Jest 和 Vue Test Utils 等相關依賴
npm install --save-dev jest @vue/test-utils vue-jest babel-core@^7.0.0-bridge.0 babel-jest jest-serializer-vue
2. 在package.json配置執行命令
{ "scripts": { "test": "jest" }}
3. Jest擴充套件配置: jest.config.js
module.exports = { moduleFileExtensions: ["js", "json", "vue"], transform: { ".*\\.(vue)$": "vue-jest", "^.+\\.js$": "<rootDir>/node_modules/babel-jest", }, // 處理 webpack 別名 moduleNameMapper: { "^@/(.*)$": "<rootDir>/src/$1", }, // 快照的序列化工具 snapshotSerializers: ["jest-serializer-vue"], // 配置覆蓋率 collectCoverage: true, collectCoverageFrom: ["**/src/**/*.{js,vue}", "!**/node_modules/**"], coverageReporters: ["html", "text"],};
4. 單元測試
/* eslint-disable no-undef */import { mount } from '@vue/test-utils';import ElTable from '@/components/ElTable.vue';import ElTableColumn from '@/components/ElTableColumn.vue';describe('ElTable.vue', () => { test('基礎表格', () => { const template = ` <el-table :data="tableData" style="width: 100%"> <el-table-column prop="date" label="日期" width="180"></el-table-column> <el-table-column prop="name" label="姓名" width="180"></el-table-column> <el-table-column prop="address" label="地址"></el-table-column> </el-table> ` const comp = { template, components: { ElTable, ElTableColumn, }, data() { return { tableData: [ { date: "2016-05-02", name: "王小虎", address: "上海市普陀區金沙江路 1518 弄", }, { date: "2016-05-04", name: "李小虎", address: "上海市普陀區金沙江路 1517 弄", }, { date: "2016-05-01", name: "張小虎", address: "上海市普陀區金沙江路 1519 弄", }, { date: "2016-05-03", name: "趙小虎", address: "上海市普陀區金沙江路 1516 弄", }, ], } }, } const wrapper = mount(comp) expect(wrapper.find('table').exists()).toBe(true) expect(wrapper.findAll('th').length).toBe(3) expect(wrapper.findAll('tbody>tr').length).toBe(4) expect(wrapper.find('tbody>tr').text()).toMatch('上海市普陀區金沙江路 1518 弄') });});
摘錄自開課吧學習資料 :
1. stmts是語句覆蓋率(statement coverage):是不是每個語句都執⾏了?
2. Branch分⽀覆蓋率(branch coverage):是不是每個if程式碼塊都執⾏了?
3. Funcs函式覆蓋率(function coverage):是不是每個函式都調⽤了?
4. Lines⾏覆蓋率(line coverage):是不是每⼀⾏都執⾏了?
測試報告
執行測試命令後在專案根目錄生成覆蓋報告(coverage),透過檢視報告完善單元測試
最新評論