Skip to content

Commit

Permalink
test: Resolve #151, add static method getEventAvailabilityByName.
Browse files Browse the repository at this point in the history
  • Loading branch information
Jenesius committed Aug 25, 2023
1 parent 08be025 commit fb5ae0d
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 34 deletions.
26 changes: 8 additions & 18 deletions project/pages/test/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,10 @@
<div :key = "pureValue">Pure values: {{pureValue}}</div>
<div :key = "pureAvailabilities">Pure av: {{pureAvailabilities}}</div>

<input type = "number" step = "10"/>
<form-field name = "age" type = "number" step = "10" v-if = "show"/>
<button @click = "show = !show">{{labelButton}}</button>
<br>

<form-field type = "number" name = "age" label = "Age" step = "10"/>
<form-field type = "country" name = "country" label = "Country" />

<form-field type = "tel" name = "phone" label = "Phone" required />
<form-field type = "date" name = "birthday" label = "Bithday" />


<form-field :name="name" :label = "name === 'username' ? 'Username' : 'Age'"/>
<form-field name="username" label = "Username" />
<form-field name="name" label = "Name"/>
<widget-address/>
<form-field name="address.city.index" label = "City Index" />
<button @click = "change">change field name</button>
<button @click = "clean">clean values</button>

Expand All @@ -33,7 +23,7 @@
<script setup lang='ts'>
import Form from "../../../src/classes/Form";
import FormField from "./../../../src/widgets/form-field.vue";
import {ref} from "vue";
import {computed, ref} from "vue";
import WidgetAddress from "./widget-address.vue"
import copyObject from "./../../../src/utils/copy-object";
Expand All @@ -42,7 +32,8 @@ const form = window.form = new Form({
name: "main",
parent:false
});
const show = ref(false);
const labelButton = computed(() => show.value ? 'Hide' : 'Show')
setInterval(() => {
if (!form) return;
Expand All @@ -54,9 +45,8 @@ setInterval(() => {
}, 50);
form.oninput('birthday', v => {
console.log(v)
})
const values = ref(0);
const changes = ref({});
const pureValue= ref({});
Expand Down
9 changes: 4 additions & 5 deletions src/classes/Form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ export default class Form extends EventEmitter implements FormDependence {
static getEventValueByName(name: string) {
return `${Form.EVENT_VALUE}:${name}`
}

static getEventAvailabilityByName(name: string) {
return `${Form.EVENT_AVAILABLE}:${name}`
}
static restoreFullName<T extends { name?: string, parent?: Form }>(elem: T): string {
if (elem.parent) return `${Form.restoreFullName(elem.parent)}.${elem.name}`;
return elem.name || '';
Expand Down Expand Up @@ -661,15 +663,12 @@ export default class Form extends EventEmitter implements FormDependence {
return this.isAvailable;
}

private getAvailableEventName(fieldName: string) {
return `${Form.EVENT_AVAILABLE}:${fieldName}`
}
onavailable(callback: (disabled: boolean) => any): any
onavailable(fieldName: string, callback: (disabled: boolean) => any): any
onavailable(arg1: ((disabled: boolean) => any) | string, arg2?: (disabled: boolean) => any):any {
if (typeof arg1 === 'string') {
if (!arg2) throw new Error('For named handler you need provided callback.');
return this.on(this.getAvailableEventName(arg1), arg2);
return this.on(Form.getEventAvailabilityByName(arg1), arg2);
}

return this.on(Form.EVENT_AVAILABLE, arg1);
Expand Down
12 changes: 8 additions & 4 deletions src/hooks/use-form-input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ export default function useFormInput(parentForm: Form) {

if (!parentForm) return null;
let offDependency: undefined | (() => void );
let offOnInput: undefined | (() => void )
let offOnAvailability: undefined | (() => void )

const input = reactive<Partial<FormInput>>({ setValidation, setValue, setName, validationRules: [], deactivate }) as FormInput

return input;
Expand All @@ -21,9 +24,8 @@ export default function useFormInput(parentForm: Form) {
// Отписываемся от формы для старого поля, подписываемся для нового
deactivate()
offDependency = parentForm.subscribe(getNewDependency(input))

parentForm.oninput(name, updateInput)
parentForm.onavailable(name, updateAvailability)
offOnInput = parentForm.oninput(name, updateInput)
offOnAvailability = parentForm.onavailable(name, updateAvailability)

// Начальная инициализация состояния Input
updateInput();
Expand All @@ -46,7 +48,9 @@ export default function useFormInput(parentForm: Form) {
}

function deactivate() {
if (offDependency) offDependency();
offDependency?.();
offOnInput?.();
offOnAvailability?.()
}
}

Expand Down
16 changes: 11 additions & 5 deletions src/hooks/use-form-state.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
import Form from "../classes/Form";
import {reactive} from "vue";
import {onUnmounted, reactive} from "vue";

export default function useFormState(form: Form) {
const state = reactive<FormReactiveState>({
changed: form.changed,
disabled: form.disabled,
wait: form.wait
})

form.on(Form.EVENT_CHANGED, () => state.changed = form.changed);
form.onavailable(v => state.disabled = !v); // Так значение является isAvailable. Значит нам нужно инверсировать его.
form.on(Form.EVENT_WAIT, v => state.wait = v)

const offArray:any[] = []

offArray[0] = form.on(Form.EVENT_CHANGED, () => state.changed = form.changed);
offArray[1] = form.onavailable(v => state.disabled = !v); // Так значение является isAvailable. Значит нам нужно инверсировать его.
offArray[2] = form.on(Form.EVENT_WAIT, v => state.wait = v)

onUnmounted(() => {
offArray.forEach(off => off?.())
})

return state
}
Expand Down
9 changes: 7 additions & 2 deletions src/hooks/use-form-values.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
import Form from "../classes/Form";
import mergeObjects from "../utils/merge-objects";
import grandObject from "../utils/grand-object";
import {reactive} from "vue";
import {onUnmounted, reactive} from "vue";

/**
* @description Return dynamic form values.
* */
export default function useFormValues(form: Form) {
const values = reactive(form.values || {});
form.onvalue(data => {
const off = form.onvalue(function (data) {
const newValues = {
[data.name]: data.newValue
}
mergeObjects(values, grandObject(newValues));
})

onUnmounted(() => {
off?.()
})

return values;
}
129 changes: 129 additions & 0 deletions tests/integrations/clean-event-handlers.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/**
* Здесь проверяется очистка event из формы после уничтожения поля
* */
import {FormField, Form, useFormState, useFormValues} from "../../src";
import {mount} from "@vue/test-utils";
import {defineComponent, ref} from "vue";


describe("Clean event handlers", () => {
test("count of event should be empty when form was created", () => {
const form = new Form();
expect(Object.keys(form.events).length).toBe(0)
})
test("Add/Remove input should add 2 events and then remove them", async () => {
const component = defineComponent({
props: {
show: {
type: Boolean
}
},
template: `<div><form-field name = "age" v-if = "show"/></div>`,
components: {FormField},
data: () => ({
form: new Form()
})
})

const app = mount(component, {
attachTo: document.body
}) as any

const form = app.vm.form as Form

expect(Object.keys(form.events).length).toBe(0)
await app.setProps({ show: true })

expect(Object.keys(form.events).length).toBe(2)
expect(Object.keys(form.events[Form.getEventValueByName('age')]).length).toBe(1)
expect(Object.keys(form.events[Form.getEventAvailabilityByName('age')]).length).toBe(1)

await app.setProps({ show: false })

expect(Object.keys(form.events[Form.getEventValueByName('age')]).length).toBe(0)
expect(Object.keys(form.events[Form.getEventAvailabilityByName('age')]).length).toBe(0)

})
test("useFormState should clean hooks after component was removed", async () => {

const ComponentWithHook = defineComponent({
template: `<div> </div>`,
setup() {
const form = Form.getParentForm() as Form;
const formState = useFormState(form);
}
})

const app = mount({
props: {
show: {
type: Boolean
}
},
template: `<div><ComponentWithHook v-if = "show"/></div>`,
components: {ComponentWithHook},
data: () => ({
form: new Form()
})
}, {
attachTo: document.body
}) as any

const form = app.vm.form as Form;

expect(Object.keys(form.events).length).toBe(0);
await app.setProps({
show: true
})
expect(Object.keys(form.events).length).toBe(3);

expect(Object.keys(form.events[Form.EVENT_WAIT]).length).toBe(1);
expect(Object.keys(form.events[Form.EVENT_CHANGED]).length).toBe(1);
expect(Object.keys(form.events[Form.EVENT_AVAILABLE]).length).toBe(1);

await app.setProps({
show: false
})
expect(Object.keys(form.events[Form.EVENT_WAIT]).length).toBe(0);
expect(Object.keys(form.events[Form.EVENT_CHANGED]).length).toBe(0);
expect(Object.keys(form.events[Form.EVENT_AVAILABLE]).length).toBe(0);
})
test("useFormValues should clean hooks after component was removed", async () => {

const ComponentWithHook = defineComponent({
template: `<div> </div>`,
setup() {
const form = Form.getParentForm() as Form;
const values = useFormValues(form);
}
})

const app = mount({
props: {
show: {
type: Boolean
}
},
template: `<div><ComponentWithHook v-if = "show"/></div>`,
components: {ComponentWithHook},
data: () => ({
form: new Form()
})
}, {
attachTo: document.body
}) as any

const form = app.vm.form as Form;

expect(Object.keys(form.events).length).toBe(0);
await app.setProps({
show: true
})
expect(Object.keys(form.events).length).toBe(1);
await app.setProps({
show: false
})

expect(form.events[Form.EVENT_VALUE].length).toBe(0);
})
})

0 comments on commit fb5ae0d

Please sign in to comment.