Skip to content

Commit 1d653ab

Browse files
committed
feat: update input tel, remove '+', add spec/tests for input-tel.
1 parent c6ce4a5 commit 1d653ab

File tree

4 files changed

+187
-9
lines changed

4 files changed

+187
-9
lines changed

docs/ru/inputs/input-tel.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<script setup>
2+
import {FormField, Form, useFormValues} from '../../../src';
3+
4+
const form = new Form();
5+
const values = useFormValues(form);
6+
7+
</script>
8+
9+
# Поле tel
10+
11+
Поле используется для ввода численных значений.
12+
13+
- Ключевое слово `tel`.
14+
- WhatWG [Спецификация](https://html.spec.whatwg.org/multipage/input.html#telephone-state-(type=tel)).
15+
16+
## Параметры
17+
18+
### autofocus <Badge type = "info">Необязательный</Badge>
19+
20+
- Тип `boolean | 'true' | 'false'`
21+
22+
Если данный параметр передаётся, при установке данного поля, на него будет автоматически передано управление.
23+
24+
____
25+
26+
Так же все параметры, общие для всех `FormField`. Информацию о них можно посмотреть на [этой странице](./form-field.md#params).
27+
28+
## Значение
29+
Данное поле работает со строковым значением.
30+
31+
- Поле доступно при использовании `Tab` и `Shift + Tab`.
32+
- Блокировка отменяет навигацию через `Tab`.
33+
- Блокировка поля изменяет стилистику `number`.
34+
- Ошибка валидации изменяет стилистику `number`.
35+
- Результирующим значением поля является строка цифр.
36+
- Формат поля используется исключительно для ввода мобильных номеров. В иных случаях необходимо
37+
воспользоваться `text` и атрибутами `pretty/modify`.
38+
- Поле должно отображать иконку страны, к которой принадлежит введённый телефон, пустую - в случае,
39+
если номер не удалось обработать. Анализ введённых данных лежит на `libphonenumber-js`.
40+
- Иконки, используемые для отображения принадлежности к стране, берутся с [flagcdn.com](https://flagcdn.com).
41+
42+
43+
44+
## Пример
45+
46+
Для данного поля нет дополнительных обязательных параметров, по этому нам необходимо
47+
указать лишь `type` и `name`:
48+
49+
::: code-group
50+
```html
51+
<form-field name = "phone" type="tel"/>
52+
```
53+
54+
```ts
55+
import {FormField} from "jenesius-vue-form";
56+
```
57+
:::
58+
59+
60+
Поле по умолчанию:
61+
<FormField type = "tel" name = "phone" label = "Введите значение" />
62+
63+
____
64+
65+
В заблокированном состоянии:
66+
<FormField type = "tel" name = "phone" disabled label = "Заблокированное" />
67+
68+
____
69+
70+
Поле не прошло валидацию:
71+
<FormField type = "tel" name = "phone" :errors = "['The password is too simple']" label = "С ошибкой" />
72+
73+
----
74+
Текущее состояние формы:
75+
```ts-vue
76+
{{values}}
77+
```

project/pages/test/App.vue

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,10 @@
66
<div :key = "pureValue">Pure values: {{pureValue}}</div>
77
<div :key = "pureAvailabilities">Pure av: {{pureAvailabilities}}</div>
88

9-
<form-field name = "age" type = "number" step = "10" v-if = "show"/>
9+
<form-field name = "phone" type = "tel" v-if = "show"/>
1010
<button @click = "show = !show">{{labelButton}}</button>
1111
<br>
1212

13-
<textarea rows="10"/>
1413

1514
<button @click = "change">change field name</button>
1615
<button @click = "clean">clean values</button>

src/widgets/inputs/input-tel/widget-input-tel.vue

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
import FieldWrap from "../field-wrap.vue";
2121
import WidgetInputTelCode from "./widget-input-tel-code.vue";
2222
import {computed, ref} from "vue";
23-
import {parsePhoneNumber, AsYouType} from 'libphonenumber-js'
23+
import {AsYouType, parsePhoneNumber} from 'libphonenumber-js'
2424
2525
interface Props {
2626
errors: string[],
@@ -40,17 +40,24 @@ const inputTel = ref<HTMLInputElement>();
4040
* @description Using libphonenumber-js return pretty value of phone number. In Error case return user's target value.
4141
* */
4242
const prettyValue = computed(() => prettifyPhoneValue(props.modelValue))
43+
44+
// Метод просто добавляет + в начало строки, если его там ещё нет
45+
function addPlus(str: string) {
46+
if (!str.length) return '';
47+
return str.startsWith('+') ? str : '+' + str
48+
}
49+
4350
function prettifyPhoneValue(value: unknown) {
4451
try {
4552
if (typeof value !=='string') return '';
46-
return new AsYouType().input(value)
53+
return new AsYouType().input(addPlus(value))
4754
} catch (e) {
4855
return props.modelValue;
4956
}
5057
}
5158
const countryCode = computed(() => {
5259
try {
53-
return parsePhoneNumber(props.modelValue).country?.toLocaleLowerCase();
60+
return parsePhoneNumber(addPlus(props.modelValue)).country?.toLocaleLowerCase();
5461
} catch (e) {
5562
return "";
5663
}
@@ -63,12 +70,10 @@ function onInput(e: any) {
6370
}
6471
6572
/**
66-
* @description Put just numeric and start +
73+
* @description Put just numeric
6774
*/
6875
function parseTelStr(a: string) {
69-
const numericStr = a.replace(/\D+/g, '');
70-
71-
return numericStr.length ? '+' + numericStr : '';
76+
return a.replace(/\D+/g, '');
7277
}
7378
7479
</script>
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import {DOMWrapper, mount, VueWrapper} from "@vue/test-utils";
2+
import EmptyApp from "../components/EmptyApp.vue";
3+
import {defineComponent, h} from "vue";
4+
import {InputField, Form, FormField} from "../../../src/index";
5+
import STORE from "./../../../src/config/store";
6+
7+
const name = 'phone'
8+
const label = 'Your phone'
9+
function defineTelComponent() {
10+
return defineComponent({
11+
template: `<div><form-field type = "tel" name = "${name}" required label = "${label}" /></div>`,
12+
components: {FormField}
13+
})
14+
}
15+
function defaultMount(component = defineTelComponent()) {
16+
return mount(EmptyApp, {
17+
slots: { default: component },
18+
attachTo: document.body
19+
})
20+
}
21+
22+
describe("Input tel", () => {
23+
let app: VueWrapper<any>;
24+
let form: Form
25+
let input: DOMWrapper<HTMLInputElement>
26+
beforeEach(() => {
27+
app = defaultMount()
28+
form = (app.vm as any).form
29+
input = app.find('input')
30+
})
31+
32+
test("По умолчанию поле не должно ничего показывать", async () => {
33+
expect(input.element.value).toBe("")
34+
})
35+
test("В пустом поле отображается должен отображаться лишь label", async () => {
36+
expect(app.text()).toBe(label + '?')
37+
})
38+
test("Ввод поля изменяет form", async () => {
39+
await input.setValue("+1234");
40+
expect(form.getValueByName(name)).toBe("1234")
41+
})
42+
test("Установка значения в form меняет поле", async () => {
43+
form.setValues({ [name]: "+000" })
44+
await app.vm.$nextTick();
45+
expect(input.element.value).toBe("+000")
46+
})
47+
test("Поле должно отображать значение с +, даже если в форме хранится без", async () => {
48+
form.setValues({ [name]: "222" })
49+
await app.vm.$nextTick();
50+
expect(input.element.value).toBe("+222")
51+
})
52+
test("При удалении значения из формы, поле должно стать пустым", async () => {
53+
form.setValues({ [name]: "222" })
54+
await app.vm.$nextTick();
55+
form.cleanField(name);
56+
await app.vm.$nextTick();
57+
expect(input.element.value).toBe("")
58+
})
59+
test("При очистки поля(вставки ''), в форме должна хранится пустая строка.", async () => {
60+
await input.setValue("+1234");
61+
await input.setValue("");
62+
expect(form.getValueByName(name)).toBe("")
63+
})
64+
test("Блокировка поля блокирует input", async () => {
65+
form.disable()
66+
await app.vm.$nextTick();
67+
expect(input.element.disabled).toBe(true);
68+
})
69+
test("Блокировка input должна запрещать ввод", async () => {
70+
await input.setValue("23");
71+
form.disable()
72+
await app.vm.$nextTick();
73+
await input.setValue("+1234");
74+
expect(form.getValueByName(name)).toBe("23")
75+
})
76+
test("Ошибка валидации должна отображаться", async () => {
77+
form.validate()
78+
await app.vm.$nextTick();
79+
expect(app.text()).toBe(label + '?' + STORE.requiredMessage)
80+
})
81+
test("Иконка страны должна меняться при установки новых значений", async () => {
82+
await input.setValue("4523");
83+
const icon = app.find('.input-tel-code') // Иконка появляется только после первой установки
84+
85+
expect(icon.element.getAttribute('src')?.includes('dk.svg')).toBe(true)
86+
87+
await input.setValue("35626");
88+
expect(icon.element.getAttribute('src')?.includes('mt.svg')).toBe(true)
89+
90+
await input.setValue("37529");
91+
expect(icon.element.getAttribute('src')?.includes('by.svg')).toBe(true)
92+
93+
94+
95+
})
96+
97+
})

0 commit comments

Comments
 (0)