Skip to content

Commit 08be025

Browse files
authored
Merge pull request #168 from Jenesius/issue_114
Issue 114
2 parents e1ef4a9 + 3a655fb commit 08be025

File tree

6 files changed

+227
-42
lines changed

6 files changed

+227
-42
lines changed

project/pages/test/App.vue

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
<div :key = "pureValue">Pure values: {{pureValue}}</div>
77
<div :key = "pureAvailabilities">Pure av: {{pureAvailabilities}}</div>
88

9+
<input type = "number" step = "10"/>
10+
11+
<form-field type = "number" name = "age" label = "Age" step = "10"/>
912
<form-field type = "country" name = "country" label = "Country" />
1013

1114
<form-field type = "tel" name = "phone" label = "Phone" required />

src/utils/parse-number.ts

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,31 @@
33
* */
44
export function parseNumber(data: unknown, defaultValue: number = 0) {
55
if (typeof data !== 'string') return defaultValue;
6-
7-
const parsedStr = data.replace(/[^\d,.+-]/g,'');
8-
if (parsedStr.length === 0) return defaultValue;
9-
return Number.parseFloat(parsedStr);
6+
7+
const parsedResult = new RegExp(/^([-+]?)([^.,+-]*)(.*)/g).exec(data);
8+
9+
if (parsedResult === null) return 0;
10+
11+
try {
12+
const parsedSting = [
13+
parsedResult[1].length ? parsedResult[1] : '+',
14+
parsedResult[2].replace(/[^0-9]/g, ''),
15+
'.',
16+
parsedResult[3].replace(/[^0-9]/g, ''),
17+
].join('')
18+
19+
// if parsedString is +.
20+
if (parsedSting.length === 2) return defaultValue;
21+
22+
const result = Number.parseFloat(parsedSting)
23+
return Number.isNaN(result) ? 0 : result
24+
} catch (e) {
25+
return 0
26+
}
27+
28+
29+
}
30+
31+
function test(str: string) {
32+
1033
}

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

Lines changed: 21 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<template>
2-
<field-wrap :label = "label">
2+
<field-wrap :label = "label" :errors = "errors">
33
<div class = "container-input-number"
44
:class = "{
55
'container-input-number_disabled': disabled,
@@ -8,21 +8,21 @@
88
>
99
<widget-number-step
1010
@step = "onStep"
11-
:disabled = "disabled"
11+
v-if = "!disabled"
1212
/>
1313
<input
1414
ref = "refInput"
1515
class = "input-number"
1616
type = "text"
17-
:value = "isFocused ? modelValue : executePretty(modelValue)"
18-
@input = "onInput($event.target.value)"
17+
:value = "isFocused ? modelValue : useModify(() => props.pretty)(modelValue)"
18+
@input = "handleInput($event.target.value)"
1919
:disabled = "disabled"
2020
:autofocus="autofocus"
2121

22-
@keyup.up = "onStep(true)"
23-
@keyup.down.prevent = "onStep(false)"
24-
@focusin = "isFocused = true"
25-
@focusout = "isFocused = false"
22+
@keydown.up = "onStep(true)"
23+
@keydown.down = "onStep(false)"
24+
@focusin = "isFocused = true"
25+
@focusout = "isFocused = false"
2626
>
2727
<span v-if = "suffix" class = "input-number-suffix">{{suffix}}</span>
2828
</div>
@@ -32,16 +32,17 @@
3232
<script setup lang = "ts">
3333
import WidgetNumberStep from "./widget-number-step.vue";
3434
import {ref, withDefaults} from "vue";
35-
import {StringModify} from "../../../types";
35+
import {StringModify, ValidationError} from "../../../types";
3636
import useModify from "../../../local-hooks/use-modify";
3737
import FieldWrap from "../field-wrap.vue";
38+
import {parseNumber} from "../../../utils/parse-number";
3839
interface Props{
39-
step?: number,
40+
step?: number | string,
4041
label?: string,
41-
errors: string[],
42-
modelValue?: number,
42+
errors: ValidationError[],
43+
modelValue: unknown,
4344
disabled: boolean,
44-
autofocus: boolean,
45+
autofocus? : boolean,
4546
name: string,
4647
pretty?: StringModify | StringModify[],
4748
suffix?: string,
@@ -52,31 +53,21 @@ const props = withDefaults(defineProps<Props>(), {
5253
})
5354
const isFocused = ref(false);
5455
55-
const executePretty = useModify(() => props.pretty);
56-
57-
5856
const emits = defineEmits<{
5957
(e: 'update:modelValue', value: any): void
6058
}>()
6159
const refInput = ref<HTMLInputElement>()
62-
function onInput(v: string | number) {
60+
function handleInput(data: string | number) {
61+
const value = (typeof data !== "number") ? parseNumber(data) : data
6362
64-
if (typeof v === "number") {
65-
emits('update:modelValue', v);
66-
}
67-
else {
68-
v = v.replace(/[^0-9.]/g, '');
69-
emits("update:modelValue", Number.parseFloat(v));
70-
}
71-
72-
if (refInput.value)
73-
refInput.value.value = String(v);
63+
if (value !== props.modelValue) emits("update:modelValue", value);
64+
if (refInput.value) refInput.value.value = String(data).replace(/[^0-9.,+-]|/g, '')
7465
}
7566
7667
function onStep(v: boolean) {
77-
if (typeof props.modelValue !== "number") return void onInput(0);
78-
79-
onInput(props.modelValue + (Number(props.step) * (v?1:-1)))
68+
const multiDir = v ? 1 : -1 // Direction of the step. +1 - Up, -1 -Down
69+
const value = typeof props.modelValue !== "number" ? 0 : props.modelValue
70+
handleInput(Number(props.step) * multiDir + value )
8071
}
8172
8273
</script>

src/widgets/inputs/input-number/widget-number-step.vue

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<template>
2-
<div class="widget-number-step" :class= "{'widget-number-step_disabled': disabled}">
2+
<div class="widget-number-step">
33
<div class="step-container step_up" @click="emits('step', true)">
44
<i class="vf-arrow vf-arrow_up"></i>
55
</div>
@@ -10,9 +10,7 @@
1010
</template>
1111

1212
<script setup lang="ts">
13-
const props = defineProps<{
14-
disabled: boolean
15-
}>()
13+
1614
const emits = defineEmits<{
1715
(name: 'step', a: boolean): void
1816
}>()
@@ -30,9 +28,6 @@ const emits = defineEmits<{
3028
border-radius: var(--vf-input-border-radius);
3129
transition: background-color 0.2s;
3230
}
33-
.widget-number-step_disabled {
34-
display: none;
35-
}
3631
3732
.widget-number-step:active {
3833
background-color: #fbfbfb;
@@ -51,7 +46,7 @@ const emits = defineEmits<{
5146
padding: 0 0 8px 0;
5247
}
5348
54-
.step-container:active > .arrow {
49+
.step-container:active > .vf-arrow {
5550
border-color: var(--vf-input-active);
5651
}
5752
</style>
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
import {defineComponent} from "vue";
2+
import {InputField, Form} from "../../src/index";
3+
import {mount, VueWrapper} from "@vue/test-utils";
4+
import EmptyApp from "./components/EmptyApp.vue";
5+
import {FormFieldValidationCallback} from "@/types";
6+
7+
function defineNumberComponent() {
8+
return defineComponent({
9+
template: '<div><input-field type = "number" name = "age"/></div>',
10+
components: {InputField}
11+
})
12+
}
13+
function defaultMount(component = defineNumberComponent()) {
14+
return mount(EmptyApp, {
15+
slots: {
16+
default: component
17+
},
18+
attachTo: document.body
19+
})
20+
}
21+
22+
describe("Input number", () => {
23+
24+
let wrap: VueWrapper<any>;
25+
beforeEach(() => wrap = defaultMount())
26+
27+
test("Should display value from Form", async () => {
28+
const form = wrap.vm.form as Form;
29+
form.setValues({
30+
age: 25
31+
})
32+
await wrap.vm.$nextTick()
33+
expect(wrap.find("input").element.value).toBe("25")
34+
})
35+
test("Should change form value", async () => {
36+
const form = wrap.vm.form as Form;
37+
form.setValues({
38+
age: 25
39+
})
40+
await wrap.vm.$nextTick()
41+
expect(wrap.find("input").element.value).toBe("25");
42+
form.setValues({
43+
age: 26
44+
})
45+
await wrap.vm.$nextTick()
46+
expect(wrap.find("input").element.value).toBe("26");
47+
form.setValues({
48+
age: 27
49+
})
50+
await wrap.vm.$nextTick()
51+
expect(wrap.find("input").element.value).toBe("27");
52+
})
53+
test("Step Controller Should be hidden if disabled and input should be disabled", async () => {
54+
const form = wrap.vm.form as Form;
55+
form.setValues({
56+
age: 25
57+
})
58+
form.disable('age')
59+
60+
await wrap.vm.$nextTick()
61+
expect(wrap.find("input").element.disabled).toBe(true);
62+
expect(wrap.find('.widget-number-step_disabled').exists()).toBe(false);
63+
64+
})
65+
test("Click on button should change the value(up arrow/down arrow)", async () => {
66+
const form = wrap.vm.form as Form;
67+
form.setValues({
68+
age: 25
69+
})
70+
71+
await wrap.vm.$nextTick()
72+
73+
const stepUp = wrap.find('.step_up');
74+
const stepDown = wrap.find('.step_down');
75+
76+
await stepUp.trigger('click');
77+
expect(form.values).toEqual({age: 26})
78+
await stepUp.trigger('click');
79+
expect(form.values).toEqual({age: 27})
80+
81+
await stepDown.trigger('click');
82+
expect(form.values).toEqual({age: 26});
83+
await stepDown.trigger('click');
84+
expect(form.values).toEqual({age: 25});
85+
})
86+
test("Press up and down should change the value", async () => {
87+
const form = wrap.vm.form as Form;
88+
form.setValues({
89+
age: 25
90+
})
91+
92+
await wrap.vm.$nextTick()
93+
94+
const input = wrap.find("input");
95+
await input.trigger('keydown.up');
96+
await input.trigger('keydown.up');
97+
98+
expect(form.values).toEqual({age: 27})
99+
100+
await input.trigger('keydown.down');
101+
expect(form.values).toEqual({age: 26})
102+
})
103+
test("If Step was provided it should change onStep", async () => {
104+
105+
wrap = defaultMount(defineComponent({
106+
template: '<div><input-field type = "number" name = "age" step = "10"/></div>',
107+
components: {InputField}
108+
}))
109+
110+
const form = wrap.vm.form as Form;
111+
form.setValues({age: 100});
112+
113+
await wrap.vm.$nextTick();
114+
115+
const input = wrap.find("input");
116+
await input.trigger('keydown.down');
117+
await input.trigger('keydown.down');
118+
await input.trigger('keydown.down');
119+
120+
expect(form.values).toEqual({age: 70})
121+
})
122+
test("If suffix was provided it should be displayed", async () => {
123+
124+
wrap = defaultMount(defineComponent({
125+
template: '<div><input-field type = "number" name = "age" suffix = "Years"/></div>',
126+
components: {InputField}
127+
}))
128+
129+
await wrap.vm.$nextTick();
130+
131+
132+
expect(wrap.text()).toBe("Years")
133+
})
134+
test("Should show error if validated failed", async () => {
135+
136+
const test:FormFieldValidationCallback[] = [
137+
x => (x >= 0 && x < 120) || 'Wrong age'
138+
];
139+
140+
wrap = defaultMount(defineComponent({
141+
template: `<div><input-field type = "number" name = "age" :validation = "${test}" /></div>`,
142+
components: {InputField}
143+
}))
144+
const form = wrap.vm.form as Form;
145+
await wrap.vm.$nextTick();
146+
147+
form.setValues({
148+
age: -1
149+
})
150+
151+
await wrap.vm.$nextTick()
152+
expect(wrap.find('input').element.value).toBe('-1')
153+
expect(form.validate()).toBe(false)
154+
await wrap.vm.$nextTick()
155+
expect(wrap.find('.container-input-number_error').exists()).toBe(true);
156+
expect(wrap.text()).toBe('Wrong age')
157+
158+
159+
form.setValues({
160+
age: 2
161+
})
162+
form.validate()
163+
await wrap.vm.$nextTick()
164+
expect(wrap.text()).toBe('')
165+
})
166+
167+
})

tests/units/utils/parse-number.spec.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,10 @@ describe("Parse Number", () => {
1616
test("Should parse empty string and return default value", () => {
1717
expect(parseNumber("oprty", 25)).toBe(25)
1818
})
19+
test("Float number", () => {
20+
expect(parseNumber("13123.10")).toBe(13123.10)
21+
})
22+
test("Float number with comma", () =>{
23+
expect(parseNumber("123,15")).toBe(123.15);
24+
})
1925
})

0 commit comments

Comments
 (0)