Vite Vue單元測試實例-登入

登入頁的設計

基礎元件功能

  • 確認 email、password、verification、submit 元素存在
  • 確認 Email 輸入框可以填資料
  • 確認 Password 輸入框可以填資料
  • Email、Password 輸入後,data 對應的 formData 有存入相應的值
  • 預設 Password 輸入框的 type 為 password,不直接顯示

測試錯誤的 Email、Password、verification驗證情境

  • 當 Email 沒有填寫時,跳出 Required 的錯誤訊息
  • 當 Email 格式錯誤時,跳出相關錯誤訊息
  • 確認 Password 輸入框可以填資料
  • 當 Password 沒有填寫時,跳出 Required 的錯誤訊息
  • 當 Password 字數不符合 6~30 字符時,跳出相關錯誤訊息
  • 當 verification 沒有填寫時,跳出 Required 的錯誤訊息
  • 當 verification 字數不符合 4 字符時,跳出相關錯誤訊息

元件屬性的改變的行為

  • 當 Email、Password 未填寫時,登入按鈕為 Disabled
  • 當 Email、Password 格式錯誤時,登入按鈕為 Disabled
  • 當 Email、Password 格式正確時,登入按鈕可點擊
  • 點擊顯示 Password 的 icon 時,type 會轉換成 text

登入功能

  • 輸入錯誤的帳號密碼,會彈出警視窗
  • 確認 Router 可以順利導向

登入表單

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
//父組件
<script setup lang="ts">
import { ref,onMounted, getCurrentInstance,watchEffect,watch,computed } from 'vue'
//登入表單
const loginTitle = ref<string>('請先登入')
//登入表單標題Labels
const loginLabels = ref<loginFormType>({
email: '電子郵件',
password: '密碼',
verification: '驗證碼',
})
const loginForms = ref<loginFormType>({
email: '',
password: '',
verification: '',
})
//電子郵件驗證錯誤訊息
const messageCheckEmail = ref<string>('')
//電子郵件驗證函式
const checkEmail = () => {
const regEmail = /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/;
if (loginForms.value.email == "") {
messageCheckEmail.value = '電子郵件不能為空!';
errorCheckEmail.value = true;
} else if (loginForms.value.email?.search(regEmail) == -1) {
messageCheckEmail.value = '電子郵件不符合規格!';
errorCheckEmail.value = true;
} else {
messageCheckEmail.value = '';
errorCheckEmail.value = false;
}
}

//密碼表單驗證錯誤訊息
const messageCheckPassword = ref<string>('');
//密碼表單驗證函式
const checkPassword = () => {
const regPassword = /[a-zA-Z0-9]{6,30}/;
if (loginForms.value.password == "") {
messageCheckPassword.value = '密碼不能為空!';
} else if (loginForms.value.password?.search(regPassword) == -1) {
console.log(loginForms.value.password.length,regPassword)
messageCheckPassword.value = '密碼必須是6~30個字符以上!';
} else {
messageCheckPassword.value = '';
}
}
// 切換眼睛改變密碼顯示或是隱藏
const passwordVisible = ref<boolean>(false);
const passwordType = ref<string>('password');
const showPassword = () => {
passwordVisible.value = !passwordVisible.value;
passwordVisible.value ? passwordType.value = 'type' : passwordType.value = 'password';
}

//驗證碼表單驗證錯誤訊息
const messageCheckVerification = ref<string>('');
//驗證碼表單驗證錯誤訊息
const checkVerification = () => {
const regVerification = /[a-zA-Z0-9]{4}/;
if (loginForms.value.verification == "") {
messageCheckVerification.value = '驗證碼不能為空!';
} else if (loginForms.value.verification?.search(regVerification) == -1 ) {
messageCheckVerification.value = '驗證碼必須是4個字符以上!';

} else if (loginForms.value.verification !=code_box.value) {
messageCheckVerification.value = '驗證碼錯誤!';
} else {
messageCheckVerification.value = '';
}
}
const code_box = ref<string>('')
const generateCode =(length=4)=>{
// let chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
let chars = "0123456789";
let code = "";
for (var i = 0; i < length; i++) {
code += chars.charAt(Math.floor(Math.random() * chars.length));
}
code_box.value=code;
}

//點擊獲得新的驗證碼
const showCode =() => {
generateCode();
}
const checkLoginForms =computed(()=>{
if (loginForms.value.email == "" || loginForms.value.password == "" || loginForms.value.verification == "") {
return true;
} else if( messageCheckEmail.value!='' || messageCheckPassword.value!='' || messageCheckVerification.value!=''){
return true;
}else{
return false;
}
})

const loginSubmit = () => {
checkLoginForms.value?'':router.push({path: `/list`})
}
generateCode();
</script>

<route lang="yaml">
meta:
layout: frontLayout
</route>

<template>
<LoginForm
:loginTitle="loginTitle"
:loginLabels="loginLabels"
:loginForms="loginForms"
:messageCheckEmail="messageCheckEmail"
@sendCheckEmail ="checkEmail"
:messageCheckPassword="messageCheckPassword"
@sendCheckPassword ="checkPassword"
:messageCheckVerification=" messageCheckVerification"
@sendCheckVerification ="checkVerification"
:code_box="code_box"
@sendShowCode ="showCode"
:checkLoginForms="checkLoginForms"
@sendLoginSubmit ="loginSubmit"
:errorCheckEmail="errorCheckEmail"
:errorCheckPassword="errorCheckPassword"
:errorCheckVerification="errorCheckVerification"
:passwordVisible="passwordVisible"
:passwordType="passwordType"
@sendShowPassword="showPassword"
/>
</template>

子元件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
<script setup lang="ts">
const props = defineProps({
loginTitle: { type: String },
loginLabels: { type: Object },
loginForms: { type: Object },
messageCheckEmail: { type: String },
messageCheckPassword: { type: String },
messageCheckVerification: { type: String },
code_box: { type: String },
checkLoginForms: { type: Boolean },

errorCheckEmail: { type: Boolean },
errorCheckPassword: { type: Boolean },
errorCheckVerification: { type: Boolean },
passwordVisible: { type: Boolean },
passwordType: { type: String },
})
const emit = defineEmits(['sendCheckEmail','sendCheckPassword','sendCheckVerification','sendShowCode','sendLoginSubmit','sendShowPassword'])
const sendCheckEmail =()=>{
emit('sendCheckEmail')
}
const sendCheckPassword =()=>{
emit('sendCheckPassword')
}
const sendCheckVerification=()=>{
emit('sendCheckVerification')
}
const sendShowCode= ()=>{
emit('sendShowCode')
}
const sendLoginSubmit= ()=>{
emit('sendLoginSubmit')
}
const sendShowPassword = ()=>{
emit('sendShowPassword')
}
</script>

<template>
<div class="form login_form mt-5">
<div class="text-center mb-3">
<b data-test="title" >{{ loginTitle }}</b>
</div>
<div class="input_item">
<label>{{ loginLabels.email }}</label>
<input type="text"
data-test="email"
v-model="loginForms.email"
@blur="sendCheckEmail"
:placeholder="loginLabels.email"
:class="{error:errorCheckEmail}"
/>
<div
class="error_message"
data-test="emailMessage"
>{{ messageCheckEmail }}</div>
</div>

<div class="input_item">

<label>{{ loginLabels.password }}</label>
<div class="password_input">
<input
data-test="password"
:type="passwordType"
v-model="loginForms.password"

@blur="sendCheckPassword"
:class="{ error: errorCheckPassword }"
:placeholder="loginLabels.password"/>
<i class="icon-eye-close"
@click.prevent="sendShowPassword"
:class="passwordVisible === false ? 'icon-eye-close' : 'icon-eye'"
></i>



</div>
<div class="error_message"
data-test="passwordMessage">{{ messageCheckPassword }}</div>
</div>
<div class="input_item verification" >
<label>{{ loginLabels.verification }}</label>
<input type="text"
@blur="sendCheckVerification"
data-test="verification"
v-model="loginForms.verification"
:class="{error:errorCheckVerification}"
:placeholder="loginLabels.verification"/>
<div class="codeBox">{{ code_box }}</div>
<a class="btn_change">
<i @click="sendShowCode" class="icon-spinner11"></i>
</a>
<div class="error_message"
data-test="verificationMessage"
>{{ messageCheckVerification }}</div>
</div>
<div class="flex justify-center">
<div class="flex w-64">
<a class="btn cancel mr-3">取消</a>
<a class="btn submit"
@click="sendLoginSubmit"
:class="{ disabled: checkLoginForms }"
>送出</a>
</div>
</div>
</div>
</template>

單元測試的建置

1