app/frontend/shared/components/Form/__tests__/apps/mobile/form-updater.mobile.spec.ts
// Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
import { waitFor } from '@testing-library/vue'
import { cloneDeep } from 'lodash-es'
import {
getByIconName,
queryByIconName,
} from '#tests/support/components/iconQueries.ts'
import { renderComponent } from '#tests/support/components/index.ts'
import type {
ExtendedMountingOptions,
ExtendedRenderResult,
} from '#tests/support/components/index.ts'
import { mockGraphQLApi } from '#tests/support/mock-graphql-api.ts'
import { waitForNextTick, waitUntil } from '#tests/support/utils.ts'
import Form from '#shared/components/Form/Form.vue'
import type { Props } from '#shared/components/Form/Form.vue'
import { ObjectManagerFrontendAttributesDocument } from '#shared/entities/object-attributes/graphql/queries/objectManagerFrontendAttributes.api.ts'
import frontendObjectAttributes from '#shared/entities/ticket/__tests__/mocks/frontendObjectAttributes.json'
import {
EnumFormUpdaterId,
EnumObjectManagerObjects,
type ObjectManagerScreenAttributes,
type ObjectManagerFrontendAttributesPayload,
type FormUpdaterQuery,
} from '#shared/graphql/types.ts'
import { FormUpdaterDocument } from '../../../graphql/queries/formUpdater.api.ts'
import additionalFrontendObjectAttributes from '../../mocks/additionalFrontendObjectAttributes.json'
import type { FormSchemaField, FormValues } from '../../../types.ts'
beforeAll(async () => {
// so we don't need to wait until it loads inside test
await import(
// Could be changed, when mobile is longer the default app and we have a shared treeselect?
// eslint-disable-next-line import/no-restricted-paths
'#mobile/components/Form/fields/FieldTreeSelect/FieldTreeSelectInputDialog.vue'
)
})
const wrapperParameters = {
form: true,
dialog: true,
attachTo: document.body,
}
const mergeFrontendObjectAttributes = (
mainObjectAttributes: ObjectManagerFrontendAttributesPayload,
additionalObjectAttributes: ObjectManagerFrontendAttributesPayload,
) => {
const localMainObjectAttributes = cloneDeep(mainObjectAttributes)
const localAdditionalObjectAttributes = cloneDeep(additionalObjectAttributes)
const attributes = localMainObjectAttributes.attributes.concat(
localAdditionalObjectAttributes.attributes,
)
const additionalObjectAttriutesScreenLookup =
localAdditionalObjectAttributes.screens.reduce(
(screenLookup: Record<string, string[]>, screenItem) => {
screenLookup[screenItem.name] = screenItem.attributes
return screenLookup
},
{},
)
const screens: ObjectManagerScreenAttributes[] = []
localMainObjectAttributes.screens.forEach((screenItem) => {
screenItem.attributes = screenItem.attributes.concat(
additionalObjectAttriutesScreenLookup[screenItem.name],
)
screens.push(screenItem)
})
return {
attributes,
screens,
}
}
const getOuterFieldElement = (wrapper: ExtendedRenderResult, label: string) => {
return wrapper.getByLabelText(label).closest('.formkit-outer')
}
const checkFieldHidden = (
wrapper: ExtendedRenderResult,
label: string,
hidden: boolean,
) => {
const outer = getOuterFieldElement(wrapper, label)
if (hidden) {
expect(outer).toHaveClass('hidden')
} else {
expect(outer).not.toHaveClass('hidden')
}
}
const checkFieldRequired = (
wrapper: ExtendedRenderResult,
label: string,
required: boolean,
) => {
const outer = getOuterFieldElement(wrapper, label)
if (required) {
expect(outer).toHaveAttribute('data-required')
} else {
expect(outer).not.toHaveAttribute('data-required')
}
}
const checkFieldDirty = (
wrapper: ExtendedRenderResult,
label: string,
dirty: boolean,
) => {
const outer = getOuterFieldElement(wrapper, label)
if (dirty) {
expect(outer).toHaveAttribute('data-dirty')
} else {
expect(outer).not.toHaveAttribute('data-dirty')
}
}
const checkFieldDisabled = (
wrapper: ExtendedRenderResult,
label: string,
disabled: boolean,
) => {
const outer = getOuterFieldElement(wrapper, label)
if (disabled) {
expect(outer).toHaveAttribute('data-disabled')
} else {
expect(outer).not.toHaveAttribute('data-disabled')
}
}
const checkDisplayValue = (
wrapper: ExtendedRenderResult,
label: string,
value: string | string[],
) => {
if (Array.isArray(value)) {
value.forEach((valueItem) => {
expect(wrapper.getByLabelText(label)).toHaveTextContent(valueItem)
})
} else {
expect(wrapper.getByLabelText(label)).toHaveTextContent(value)
}
}
const checkInputValue = (
wrapper: ExtendedRenderResult,
label: string,
value: string | number,
) => {
expect(wrapper.getByLabelText(label)).toHaveValue(value)
}
const checkSelected = (
wrapper: ExtendedRenderResult,
label: string,
selected: boolean,
) => {
if (selected) {
expect(wrapper.getByLabelText(label)).toBeChecked()
} else {
expect(wrapper.getByLabelText(label)).not.toBeChecked()
}
}
// Currently only the first level of a treeselect.
const checkSelectOptions = async (
wrapper: ExtendedRenderResult,
label: string,
options: string[],
) => {
await wrapper.events.click(wrapper.getByLabelText(label))
const selectOptions = wrapper.getAllByRole('option')
expect(selectOptions).toHaveLength(options.length)
selectOptions.forEach((selectOption, index) => {
expect(selectOption).toHaveTextContent(options[index])
})
// Treeselect dialog needs to be closed again.
const doneButton = wrapper.queryByRole('button', { name: 'Done' })
if (doneButton) {
await wrapper.events.click(doneButton)
}
}
const selectValue = async (
wrapper: ExtendedRenderResult,
label: string,
dsiaplyValue: string,
) => {
await wrapper.events.click(wrapper.getByLabelText(label))
wrapper.events.click(wrapper.getByText(dsiaplyValue))
await waitFor(() => {
expect(wrapper.emitted().changed).toBeTruthy()
})
}
const checkSelectClearable = (
wrapper: ExtendedRenderResult,
label: string,
clearable: boolean,
selectNewValue?: string,
) => {
if (selectNewValue) {
selectValue(wrapper, label, selectNewValue)
}
if (clearable) {
expect(
getByIconName(wrapper.getByLabelText(label), 'close-small'),
).toBeInTheDocument()
} else {
expect(
queryByIconName(wrapper.getByLabelText(label), 'close-small'),
).not.toBeInTheDocument()
}
}
const mergedObjectAttributes = mergeFrontendObjectAttributes(
frontendObjectAttributes,
additionalFrontendObjectAttributes,
)
const renderForm = async (
formUpdaterQueryResponse: FormUpdaterQuery | FormUpdaterQuery[],
options: ExtendedMountingOptions<Props> = {},
objectManagerFrontendAttributes = mergedObjectAttributes,
) => {
mockGraphQLApi(ObjectManagerFrontendAttributesDocument).willResolve({
objectManagerFrontendAttributes,
})
const mockFormUpdaterApi = mockGraphQLApi(FormUpdaterDocument).willResolve(
formUpdaterQueryResponse,
)
const wrapper = renderComponent(Form, {
...wrapperParameters,
...options,
props: {
useObjectAttributes: true,
schema: [
{
object: EnumObjectManagerObjects.Ticket,
name: 'title',
},
{
object: EnumObjectManagerObjects.Ticket,
screen: 'create_middle',
},
{
type: 'submit',
name: 'submit',
},
],
formUpdaterId: EnumFormUpdaterId.FormUpdaterUpdaterTicketCreate,
...(options.props || {}),
},
})
await waitUntil(() => wrapper.emitted().settled)
return {
wrapper,
mockFormUpdaterApi,
}
}
describe('Form.vue - Form Updater - Initialization', () => {
test('render initial form schema with select and treeselect fields', async () => {
const { wrapper } = await renderForm({
formUpdater: {
group_id: {
show: true,
options: [
{
label: 'Users',
value: 1,
},
],
clearable: false,
},
state_id: {
show: true,
options: [
{
label: 'new',
value: 1,
},
{
label: 'open',
value: 2,
},
],
clearable: true,
},
type: {
show: true,
options: [
{
label: 'Problem',
value: 'Problem',
},
{
label: 'Request for Change',
value: 'Request for Change',
},
],
value: 'Problem',
clearable: true,
},
treeselect: {
show: true,
options: [
{
label: 'Incident',
value: 'Incident',
},
{
label: 'Service request',
value: 'Service request',
},
],
value: 'Incident',
clearable: true,
},
multitreeselect: {
show: true,
options: [
{
label: 'Incident',
value: 'Incident',
},
{
label: 'Service request',
value: 'Service request',
},
],
},
shared: {
show: true,
options: [
{
label: 'No',
value: false,
},
{
label: 'Yes',
value: true,
},
],
},
},
})
await checkSelectOptions(wrapper, 'Group', ['Users'])
checkSelectClearable(wrapper, 'Group', false)
await checkSelectOptions(wrapper, 'State', ['new', 'open'])
checkSelectClearable(wrapper, 'State', true)
checkDisplayValue(wrapper, 'Type', 'Problem')
checkSelectClearable(wrapper, 'Type', true)
await checkSelectOptions(wrapper, 'Type', ['Problem', 'Request for Change'])
await checkSelectOptions(wrapper, 'Multi Select', [
'Key 1',
'Key 2',
'Key 3',
'Key 4',
])
await checkSelectOptions(wrapper, 'Treeselect', [
'Incident',
'Service request',
])
checkDisplayValue(wrapper, 'Treeselect', 'Incident')
checkSelectClearable(wrapper, 'Treeselect', true)
})
test('render initial form schema with input, textarea, number, date, datetime', async () => {
const { wrapper } = await renderForm({
formUpdater: {
title: {
show: false,
},
textarea: {
show: true,
value: 'Example description',
},
example: {
show: true,
required: true,
},
number: {
show: true,
value: 10,
},
},
})
expect(wrapper.queryByLabelText('Title')).not.toBeInTheDocument()
expect(wrapper.getByLabelText('Textarea')).toHaveValue(
'Example description',
)
expect(wrapper.getByLabelText('Number')).toHaveValue(10)
checkFieldRequired(wrapper, 'Example', true)
expect(wrapper.getByLabelText('Date Time')).toBeInTheDocument()
expect(wrapper.getByLabelText('Start Date')).toBeInTheDocument()
})
})
describe('Form.vue - Form Updater - reacts on form updater results', () => {
const formFields = {
type: 'Type',
multiselect: 'Multi Select',
treeselect: 'Treeselect',
multitreeselect: 'Multi Treeselect',
example: 'Example',
textarea: 'Textarea',
number: 'Number',
date_time: 'Date Time',
start_date: 'Start Date',
shared: 'Shared',
}
test('remove field after new form updater result comes in', async () => {
const { wrapper, mockFormUpdaterApi } = await renderForm([
{
formUpdater: Object.keys(formFields).reduce(
(showFields: Record<string, Partial<FormSchemaField>>, fieldName) => {
showFields[fieldName] = {
show: true,
}
return showFields
},
{},
),
},
{
formUpdater: Object.keys(formFields).reduce(
(
removeFields: Record<string, Partial<FormSchemaField>>,
fieldName,
) => {
removeFields[fieldName] = {
show: false,
}
return removeFields
},
{},
),
},
])
Object.values(formFields).forEach((fieldLabel) => {
expect(wrapper.getByLabelText(fieldLabel)).toBeInTheDocument()
})
await selectValue(wrapper, 'Type', 'Incident')
await waitUntil(() => mockFormUpdaterApi.calls.resolve === 2)
Object.values(formFields).forEach((fieldLabel) => {
expect(wrapper.queryByLabelText(fieldLabel)).not.toBeInTheDocument()
})
})
test('show field after new form updater result comes in (from remove)', async () => {
const { wrapper, mockFormUpdaterApi } = await renderForm([
{
formUpdater: {
...Object.keys(formFields).reduce(
(
removeFields: Record<string, Partial<FormSchemaField>>,
fieldName,
) => {
removeFields[fieldName] = {
show: false,
}
return removeFields
},
{},
),
state_id: {
show: true,
options: [
{
label: 'new',
value: 1,
},
{
label: 'open',
value: 2,
},
],
value: 1,
},
},
},
{
formUpdater: Object.keys(formFields).reduce(
(showFields: Record<string, Partial<FormSchemaField>>, fieldName) => {
showFields[fieldName] = {
show: true,
}
return showFields
},
{},
),
},
])
Object.values(formFields).forEach((fieldLabel) => {
expect(wrapper.queryByLabelText(fieldLabel)).not.toBeInTheDocument()
})
await selectValue(wrapper, 'State', 'open')
await waitUntil(() => mockFormUpdaterApi.calls.resolve === 2)
Object.values(formFields).forEach((fieldLabel) => {
expect(wrapper.getByLabelText(fieldLabel)).toBeInTheDocument()
})
})
test('show field again with a new initial value', async () => {
const { wrapper, mockFormUpdaterApi } = await renderForm([
{
formUpdater: {
example: {
show: false,
},
},
},
{
formUpdater: {
example: {
show: true,
value: 'A example text',
},
},
},
])
expect(wrapper.queryByLabelText('Example')).not.toBeInTheDocument()
await selectValue(wrapper, 'Type', 'Incident')
await waitUntil(() => mockFormUpdaterApi.calls.resolve === 2)
const exampleField = wrapper.getByLabelText('Example')
expect(exampleField).toBeInTheDocument()
expect(exampleField).toHaveValue('A example text')
})
test('show field after new form updater result comes in (from hidden)', async () => {
const { wrapper, mockFormUpdaterApi } = await renderForm([
{
formUpdater: {
...Object.keys(formFields).reduce(
(
hideFields: Record<string, Partial<FormSchemaField>>,
fieldName,
) => {
hideFields[fieldName] = {
show: true,
hidden: true,
}
return hideFields
},
{},
),
state_id: {
show: true,
options: [
{
label: 'new',
value: 1,
},
{
label: 'open',
value: 2,
},
],
value: 1,
},
},
},
{
formUpdater: Object.keys(formFields).reduce(
(showFields: Record<string, Partial<FormSchemaField>>, fieldName) => {
showFields[fieldName] = {
show: true,
hidden: false,
}
return showFields
},
{},
),
},
])
Object.values(formFields).forEach((fieldLabel) => {
expect(wrapper.getByLabelText(fieldLabel)).toBeInTheDocument()
checkFieldHidden(wrapper, fieldLabel, true)
})
await selectValue(wrapper, 'State', 'open')
await waitUntil(() => mockFormUpdaterApi.calls.resolve === 2)
Object.values(formFields).forEach((fieldLabel) => {
expect(wrapper.getByLabelText(fieldLabel)).toBeInTheDocument()
checkFieldHidden(wrapper, fieldLabel, false)
})
})
test('hide field after new form updater result comes in (from show)', async () => {
const { wrapper, mockFormUpdaterApi } = await renderForm([
{
formUpdater: {
...Object.keys(formFields).reduce(
(
showFields: Record<string, Partial<FormSchemaField>>,
fieldName,
) => {
showFields[fieldName] = {
show: true,
hidden: false,
}
return showFields
},
{},
),
},
},
{
formUpdater: Object.keys(formFields).reduce(
(hideFields: Record<string, Partial<FormSchemaField>>, fieldName) => {
hideFields[fieldName] = {
show: true,
hidden: true,
}
return hideFields
},
{},
),
},
])
Object.values(formFields).forEach((fieldLabel) => {
expect(wrapper.getByLabelText(fieldLabel)).toBeInTheDocument()
checkFieldHidden(wrapper, fieldLabel, false)
})
await selectValue(wrapper, 'Type', 'Incident')
await waitUntil(() => mockFormUpdaterApi.calls.resolve === 2)
Object.values(formFields).forEach((fieldLabel) => {
expect(wrapper.getByLabelText(fieldLabel)).toBeInTheDocument()
checkFieldHidden(wrapper, fieldLabel, true)
})
})
test('require field after new form updater result comes in (from not required)', async () => {
const { wrapper, mockFormUpdaterApi } = await renderForm([
{
formUpdater: {
...Object.keys(formFields).reduce(
(
notRequiredFields: Record<string, Partial<FormSchemaField>>,
fieldName,
) => {
notRequiredFields[fieldName] = {
required: false,
}
return notRequiredFields
},
{},
),
},
},
{
formUpdater: Object.keys(formFields).reduce(
(
requireFields: Record<string, Partial<FormSchemaField>>,
fieldName,
) => {
requireFields[fieldName] = {
required: true,
}
return requireFields
},
{},
),
},
])
Object.values(formFields).forEach((fieldLabel) => {
checkFieldRequired(wrapper, fieldLabel, false)
})
await selectValue(wrapper, 'Type', 'Incident')
await waitUntil(() => mockFormUpdaterApi.calls.resolve === 2)
Object.values(formFields).forEach((fieldLabel) => {
checkFieldRequired(wrapper, fieldLabel, true)
})
})
test('none require field after new form updater result comes in (from required)', async () => {
const { wrapper, mockFormUpdaterApi } = await renderForm([
{
formUpdater: {
...Object.keys(formFields).reduce(
(
requiredFields: Record<string, Partial<FormSchemaField>>,
fieldName,
) => {
requiredFields[fieldName] = {
required: true,
}
return requiredFields
},
{},
),
},
},
{
formUpdater: Object.keys(formFields).reduce(
(
notRequired: Record<string, Partial<FormSchemaField>>,
fieldName,
) => {
notRequired[fieldName] = {
required: false,
}
return notRequired
},
{},
),
},
])
Object.values(formFields).forEach((fieldLabel) => {
checkFieldRequired(wrapper, fieldLabel, true)
})
await selectValue(wrapper, 'Type', 'Incident')
await waitUntil(() => mockFormUpdaterApi.calls.resolve === 2)
Object.values(formFields).forEach((fieldLabel) => {
checkFieldRequired(wrapper, fieldLabel, false)
})
})
test('disable (readonly) field after new form updater result comes in (from not disabled)', async () => {
const { wrapper, mockFormUpdaterApi } = await renderForm([
{
formUpdater: {
...Object.keys(formFields).reduce(
(
notDisabledField: Record<string, Partial<FormSchemaField>>,
fieldName,
) => {
notDisabledField[fieldName] = {
disabled: false,
}
return notDisabledField
},
{},
),
},
},
{
formUpdater: Object.keys(formFields).reduce(
(
disableFields: Record<string, Partial<FormSchemaField>>,
fieldName,
) => {
disableFields[fieldName] = {
disabled: true,
}
return disableFields
},
{},
),
},
])
Object.values(formFields).forEach((fieldLabel) => {
checkFieldDisabled(wrapper, fieldLabel, false)
})
await selectValue(wrapper, 'Type', 'Incident')
await waitUntil(() => mockFormUpdaterApi.calls.resolve === 2)
Object.values(formFields).forEach((fieldLabel) => {
checkFieldDisabled(wrapper, fieldLabel, true)
})
})
test('not disabled (readonly) field after new form updater result comes in (from disabled)', async () => {
const { wrapper, mockFormUpdaterApi } = await renderForm([
{
formUpdater: {
...Object.keys(formFields).reduce(
(
disabledField: Record<string, Partial<FormSchemaField>>,
fieldName,
) => {
disabledField[fieldName] = {
disabled: true,
}
return disabledField
},
{},
),
state_id: {
show: true,
options: [
{
label: 'new',
value: 1,
},
{
label: 'open',
value: 2,
},
],
value: 1,
},
},
},
{
formUpdater: Object.keys(formFields).reduce(
(
notDisableFields: Record<string, Partial<FormSchemaField>>,
fieldName,
) => {
notDisableFields[fieldName] = {
disabled: false,
}
return notDisableFields
},
{},
),
},
])
Object.values(formFields).forEach((fieldLabel) => {
checkFieldDisabled(wrapper, fieldLabel, true)
})
await selectValue(wrapper, 'State', 'open')
await waitUntil(() => mockFormUpdaterApi.calls.resolve === 2)
Object.values(formFields).forEach((fieldLabel) => {
checkFieldDisabled(wrapper, fieldLabel, false)
})
})
test('set field value after new form updater result comes in', async () => {
const { wrapper, mockFormUpdaterApi } = await renderForm([
{
formUpdater: {
state_id: {
show: true,
options: [
{
label: 'new',
value: 1,
},
{
label: 'open',
value: 2,
},
],
value: 1,
},
},
},
{
formUpdater: {
type: {
value: 'Incident',
},
multiselect: {
value: ['Key 1', 'Key 3'],
},
treeselect: {
value: 'Service request',
},
multitreeselect: {
value: ['Service request', 'Incident::Hardware'],
},
example: {
value: 'example',
},
textarea: {
value: 'some more text',
},
number: {
value: 100,
},
shared: {
value: true,
},
},
},
])
await selectValue(wrapper, 'State', 'open')
await waitUntil(() => mockFormUpdaterApi.calls.resolve === 2)
checkDisplayValue(wrapper, 'Type', 'Incident')
checkDisplayValue(wrapper, 'Multi Select', ['Key 1', 'Key 3'])
checkDisplayValue(wrapper, 'Treeselect', 'Service request')
checkDisplayValue(wrapper, 'Multi Treeselect', [
'Incident \u203A Hardware',
'Service request',
])
checkInputValue(wrapper, 'Example', 'example')
checkInputValue(wrapper, 'Textarea', 'some more text')
checkInputValue(wrapper, 'Number', 100)
checkSelected(wrapper, 'Shared', true)
})
test('set field options after new form updater result comes in', async () => {
const { wrapper, mockFormUpdaterApi } = await renderForm([
{
formUpdater: {},
},
{
formUpdater: {
multiselect: {
options: [
{
label: 'Key 1',
value: 'Key 1',
},
{
label: 'Key 4',
value: 'Key 4',
},
],
},
multitreeselect: {
options: [
{
label: 'Service request',
value: 'Service request',
children: [
{
label: 'New hardware',
value: 'Service request::New hardware',
},
],
},
],
},
},
},
])
await checkSelectOptions(wrapper, 'Multi Select', [
'Key 1',
'Key 2',
'Key 3',
'Key 4',
])
await checkSelectOptions(wrapper, 'Multi Treeselect', [
'Incident',
'Service request',
])
await selectValue(wrapper, 'Type', 'Incident')
await waitUntil(() => mockFormUpdaterApi.calls.resolve === 2)
await checkSelectOptions(wrapper, 'Multi Select', ['Key 1', 'Key 4'])
await checkSelectOptions(wrapper, 'Multi Treeselect', ['Service request'])
})
})
describe('Form.vue - Form Updater - special situtations', () => {
test('show field again with a new initial value', async () => {
const { wrapper, mockFormUpdaterApi } = await renderForm([
{
formUpdater: {
example: {
show: false,
},
},
},
{
formUpdater: {
example: {
show: true,
value: 'A example text',
},
},
},
])
expect(wrapper.queryByLabelText('Example')).not.toBeInTheDocument()
await selectValue(wrapper, 'Type', 'Incident')
await waitUntil(() => mockFormUpdaterApi.calls.resolve === 2)
const exampleField = wrapper.getByLabelText('Example')
expect(exampleField).toBeInTheDocument()
expect(exampleField).toHaveValue('A example text')
})
test('change field value (should not trigger additional request)', async () => {
const { wrapper, mockFormUpdaterApi } = await renderForm([
{
formUpdater: {
state_id: {
show: true,
options: [
{
label: 'new',
value: 1,
},
{
label: 'open',
value: 2,
},
],
},
},
},
{
formUpdater: {
state_id: {
show: true,
options: [
{
label: 'new',
value: 1,
},
{
label: 'open',
value: 2,
},
],
value: 2,
},
},
},
])
checkDisplayValue(wrapper, 'State', 'new')
await selectValue(wrapper, 'Type', 'Incident')
await waitUntil(() => mockFormUpdaterApi.calls.resolve === 2)
checkDisplayValue(wrapper, 'State', 'open')
})
test('preselect (like a native select) first value when no longer clearable (should not trigger additional request)', async () => {
const { wrapper, mockFormUpdaterApi } = await renderForm([
{
formUpdater: {
state_id: {
show: true,
options: [
{
label: 'new',
value: 1,
},
{
label: 'open',
value: 2,
},
],
value: '',
clearable: true,
},
},
},
{
formUpdater: {
state_id: {
show: true,
options: [
{
label: 'new',
value: 1,
},
{
label: 'open',
value: 2,
},
],
clearable: false,
},
},
},
])
checkDisplayValue(wrapper, 'State', '')
await selectValue(wrapper, 'Type', 'Incident')
await waitUntil(() => mockFormUpdaterApi.calls.resolve === 2)
checkDisplayValue(wrapper, 'State', 'new')
})
test('delay submit and perform form updater request before', async () => {
const submitCallbackSpy = vi.fn()
const { wrapper, mockFormUpdaterApi } = await renderForm(
[
{
formUpdater: {
state_id: {
options: [
{
label: 'new',
value: 1,
},
{
label: 'open',
value: 2,
},
],
},
},
},
{
formUpdater: {
textarea: {
value: 'Some text',
},
group_id: {
required: false,
},
state_id: {
required: false,
},
},
},
],
{
props: {
onSubmit: (data: FormValues) => submitCallbackSpy(data),
},
},
)
const title = wrapper.getByLabelText('Title')
await wrapper.events.type(title, 'Example title')
await wrapper.events.type(title, '{Enter}')
await waitUntil(() => mockFormUpdaterApi.calls.resolve === 2)
await waitFor(() => {
expect(submitCallbackSpy).toHaveBeenCalledWith({
title: 'Example title',
textarea: 'Some text',
date_time: undefined,
example: '',
group_id: undefined,
multiselect: [],
multitreeselect: undefined,
number: '',
shared: false,
start_date: undefined,
state_id: 1,
treeselect: undefined,
type: undefined,
})
})
})
test('usage together with changeFields prop (fixed required field from frontend side)', async () => {
const { wrapper, mockFormUpdaterApi } = await renderForm(
[
{
formUpdater: {
example: {
required: false,
},
},
},
{
formUpdater: {
example: {
required: false,
},
},
},
],
{
props: {
changeFields: {
example: {
required: true,
},
},
},
},
)
checkFieldRequired(wrapper, 'Example', true)
await selectValue(wrapper, 'Type', 'Incident')
await waitUntil(() => mockFormUpdaterApi.calls.resolve === 2)
checkFieldRequired(wrapper, 'Example', true)
})
test('test dirty flag set for initialValues changes from form update', async () => {
const { wrapper } = await renderForm(
{
formUpdater: {
example: {
value: 'changed',
},
},
},
{
props: {
initialValues: {
example: 'test',
},
},
},
)
checkFieldDirty(wrapper, 'Example', true)
})
test('test dirty flag unset for initialValues changes from form update', async () => {
const { wrapper } = await renderForm(
{
formUpdater: {
example: {
value: 'test',
},
},
},
{
props: {
initialValues: {
example: 'test',
},
},
},
)
checkFieldDirty(wrapper, 'Example', false)
})
test('no endless loop (additional request) for non existing options for new field values', async () => {
const { wrapper, mockFormUpdaterApi } = await renderForm(
[
{
formUpdater: {},
},
{
formUpdater: {
multiselect: {
options: [
{
label: 'Key 1',
value: 'Key 1',
},
{
label: 'Key 4',
value: 'Key 4',
},
],
value: ['Key 1', 'Key 3'],
},
},
},
],
{
props: {
initialValues: {
multiselect: ['Key 1', 'Key 2'],
},
},
},
)
await waitUntil(() => {
return mockFormUpdaterApi.calls.resolve === 1
})
await selectValue(wrapper, 'Type', 'Incident')
await waitUntil(() => {
return mockFormUpdaterApi.calls.resolve === 2
})
await waitForNextTick()
await checkDisplayValue(wrapper, 'Multi Select', ['Key 1'])
})
test('no endless loop (additional request) for activated preselect mode in single select', async () => {
const { wrapper, mockFormUpdaterApi } = await renderForm(
[
{
formUpdater: {
type: {
options: [
{
label: 'Request for Change',
value: 'Request for Change',
},
],
value: 'Problem',
clearable: false,
rejectNonExistentValues: true,
},
},
},
],
{
props: {
initialValues: {
type: 'Incident',
},
},
},
)
await waitUntil(() => {
return mockFormUpdaterApi.calls.resolve === 1
})
await waitForNextTick()
// Should show the initial value after the form is initialized.
await checkDisplayValue(wrapper, 'Type', 'Request for Change')
})
test('add empty value for multiselect, when present in the current returned values', async () => {
const { wrapper, mockFormUpdaterApi } = await renderForm(
[
{
formUpdater: {},
},
{
formUpdater: {
multiselect: {
options: [
{
label: 'Key 1',
value: 'Key 1',
},
{
label: 'Key 4',
value: 'Key 4',
},
],
value: ['', 'Key 1', 'Key 4'],
},
},
},
],
{
props: {
initialValues: {
multiselect: ['Key 1', 'Key 2'],
},
},
},
)
await waitUntil(() => {
return mockFormUpdaterApi.calls.resolve === 1
})
await selectValue(wrapper, 'Type', 'Incident')
await waitUntil(() => {
return mockFormUpdaterApi.calls.resolve === 2
})
await waitForNextTick()
await checkSelectOptions(wrapper, 'Multi Select', ['Key 1', 'Key 4', '-'])
await checkDisplayValue(wrapper, 'Multi Select', ['-', 'Key 1', 'Key 4'])
})
})