Skip to content

Commit

Permalink
feat(vfg): convert field-radio [khcp-11342] (#1556)
Browse files Browse the repository at this point in the history
Convert `FieldRadio` to use kongponents for [KHCP-11342](https://konghq.atlassian.net/browse/KHCP-11342).
  • Loading branch information
kaiarrowood committed Aug 16, 2024
1 parent b98d21d commit 06e4c9d
Show file tree
Hide file tree
Showing 4 changed files with 245 additions and 53 deletions.
14 changes: 14 additions & 0 deletions packages/core/forms/sandbox/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -57,19 +57,33 @@ const fieldSchema = {
textOff: 'Not Cute',
styleClasses: 'field-switch hide-label',
},
// FieldRadio
{
type: 'radio',
model: 'gender',
id: 'gender',
name: 'gender',
label: 'Gender',
values: [
{ name: 'Male', value: 'male' },
{ name: 'Female', value: 'female' },
],
},
],
}
const fieldModelDefault = ref({
cat_name: 'TK Meowstersmith',
is_friendly: true,
is_cute: true,
gender: 'male',
})
const fieldModelModified = ref({
cat_name: 'BratCat',
is_friendly: false,
is_cute: false,
gender: null,
})
</script>

Expand Down
2 changes: 1 addition & 1 deletion packages/core/forms/sandbox/FieldTester.vue
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ const handleModelUpdated = (model: Record<string, any>, key: string): void => {
// verify programmatic updates to model
const handleUpdate = (): void => {
updatedFormModel.value = props.modifiedModel
updatedFormModel.value = JSON.parse(JSON.stringify(props.modifiedModel))
}
</script>

Expand Down
129 changes: 77 additions & 52 deletions packages/core/forms/src/components/fields/FieldRadio.vue
Original file line number Diff line number Diff line change
@@ -1,72 +1,97 @@
<template>
<div class="selection-group">
<div
<div class="radio-selection-group">
<KRadio
v-for="(option, i) in schema.values"
:key="i"
class="option-group"
>
<div class="form-group">
<label
class="k-label"
:class="option.name"
>
<input
:id="schema.name+'-'+i"
:checked="checkOption(option)"
class="k-input"
:name="schema.name"
type="radio"
:value="option.value"
@change="onChange"
>
{{ option.name }}
</label>
</div>
</div>
:id="schema.name+'-'+i"
:key="option.value"
v-model="inputValue"
:label="option.name"
:name="schema.name"
:selected-value="option.value"
@change="onChange"
/>
</div>
</template>

<script>
import abstractField from './abstractField'
<script lang="ts" setup>
import { toRefs, type PropType } from 'vue'
import composables from '../../composables'
export default {
mixins: [abstractField],
const props = defineProps({
disabled: {
type: Boolean,
default: false,
},
formOptions: {
type: Object as PropType<Record<string, any>>,
default: () => undefined,
},
model: {
type: Object as PropType<Record<string, any>>,
default: () => undefined,
},
schema: {
type: Object as PropType<Record<string, any>>,
required: true,
},
vfg: {
type: Object,
required: true,
},
/**
* TODO: stronger type
* TODO: pass this down to KInput error and errorMessage
*/
errors: {
type: Array,
default: () => [],
},
hint: {
type: String,
default: '',
},
})
emits: ['model-updated'],
const emit = defineEmits<{
(event: 'modelUpdated', value: any, model: Record<string, any>): void
}>()
methods: {
updateModel(options) {
// Emit value of field to EntityForm
this.$emit('model-updated', options, this.schema.model)
},
const propsRefs = toRefs(props)
checkOption(option) {
if (this.model[this.schema.model]) {
return option.value.toString() === this.model[this.schema.model].toString()
}
const { updateModelValue, value: inputValue, clearValidationErrors } = composables.useAbstractFields({
model: propsRefs.model,
schema: props.schema,
formOptions: props.formOptions,
emitModelUpdated: (data: { value: any, model: Record<string, any> }): void => {
emit('modelUpdated', data.value, data.model)
},
})
return option.checked
},
defineExpose({
clearValidationErrors,
})
onChange(e) {
let updatedValue = e.target.value.split(',')
if (!this.schema.array) {
updatedValue = updatedValue.toString()
}
const onChange = (val: string | number | boolean | object | null) => {
let updatedValue = val
if (typeof val === 'string') {
updatedValue = val.split(',')
if (!props.schema.array) {
updatedValue = updatedValue.toString()
}
}
this.updateModel(updatedValue)
},
},
updateModelValue(updatedValue, val)
}
</script>

<style lang="scss" scoped>
.selection-group {
margin-bottom: 8px;
width: 100%;
.radio-selection-group {
align-items: center;
display: flex;
gap: $kui-space-60;
.form-group {
margin-bottom: 0;
:deep(.k-radio) {
align-items: baseline;
}
}
</style>
153 changes: 153 additions & 0 deletions packages/core/forms/src/components/fields/__tests__/FieldRadio.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import type { FormSchema } from '../../../types'
import FieldTester from '../../../../sandbox/FieldTester.vue'

describe('<FieldTester /> - FieldRadio', () => {
const fieldKey = 'gender'
const fieldLabel = 'Gender'
const fieldValue = 'male'
const updatedFieldValue = 'female'
const schema: FormSchema = {
fields: [{
type: 'radio',
model: fieldKey,
id: fieldKey,
name: fieldKey,
label: fieldLabel,
values: [
{ name: 'Male', value: fieldValue },
{ name: 'Female', value: updatedFieldValue },
],
}],
}

it('renders default state correctly - without model', () => {
cy.mount(FieldTester, {
props: {
schema,
},
})

cy.get('.field-tester-container').should('exist')

// check VFG input value
cy.get(`#${fieldKey}-0`).should('be.visible')
cy.get(`#${fieldKey}-0`).should('not.be.checked')
cy.get(`#${fieldKey}-1`).should('be.visible')
cy.get(`#${fieldKey}-1`).should('not.be.checked')

// initial model is empty after load
cy.getTestId(`field-tester-form-model-${fieldKey}-value`).should('not.exist')

// check VFG label is set correctly
cy.get(`.form-group-label[for="${fieldKey}"]`).should('be.visible')
cy.get(`.form-group-label[for="${fieldKey}"]`).should('contain.text', fieldLabel)
})

it('renders default state correctly - with model', () => {
cy.mount(FieldTester, {
props: {
schema,
model: {
[fieldKey]: fieldValue,
},
},
})

cy.get('.field-tester-container').should('exist')

// check VFG input value
cy.get(`#${fieldKey}-0`).should('be.visible')
cy.get(`#${fieldKey}-0`).should('be.checked')

// check field test form model matches
cy.getTestId(`field-tester-form-model-${fieldKey}-value`).should('be.visible')
cy.getTestId(`field-tester-form-model-${fieldKey}-value`).should('contain.text', fieldValue)
})

it('handles input changes', () => {
cy.mount(FieldTester, {
props: {
schema,
model: {
[fieldKey]: fieldValue,
},
},
})

cy.get('.field-tester-container').should('exist')

// edit the input value, by clicking second radio button
cy.get(`#${fieldKey}-1`).should('be.visible')
cy.get(`#${fieldKey}-1`).click()

// check VFG input value
cy.get(`#${fieldKey}-0`).should('not.be.checked')
cy.get(`#${fieldKey}-1`).should('be.checked')
// check field test form model
cy.getTestId(`field-tester-form-model-${fieldKey}-value`).should('contain.text', updatedFieldValue)
})

it('handles programmatic input changes', () => {
cy.mount(FieldTester, {
props: {
schema,
model: {
[fieldKey]: fieldValue,
},
modifiedModel: {
[fieldKey]: updatedFieldValue,
},
},
})

cy.get('.field-tester-container').should('exist')

// initial value loaded
cy.get(`#${fieldKey}-0`).should('be.checked')
cy.getTestId(`field-tester-form-model-${fieldKey}-value`).should('be.visible')
cy.getTestId(`field-tester-form-model-${fieldKey}-value`).should('contain.text', fieldValue)

// programmatic update
cy.getTestId('tester-update-button').should('be.visible')
cy.getTestId('tester-update-button').click()

// check VFG input value
cy.get(`#${fieldKey}-0`).should('not.be.checked')
cy.get(`#${fieldKey}-1`).should('be.checked')
// check field test form model also matches
cy.getTestId(`field-tester-form-model-${fieldKey}-value`).should('contain.text', updatedFieldValue)
})

it('allows unsetting the model (set to null)', () => {
const nullValue = null
cy.mount(FieldTester, {
props: {
schema,
model: {
[fieldKey]: fieldValue,
},
modifiedModel: {
[fieldKey]: nullValue,
},
},
})

cy.get('.field-tester-container').should('exist')

// initial value loaded
cy.get(`#${fieldKey}-0`).should('be.checked')
cy.getTestId(`field-tester-form-model-${fieldKey}-value`).should('be.visible')
cy.getTestId(`field-tester-form-model-${fieldKey}-value`).should('contain.text', fieldValue)

// programmatic update
cy.getTestId('tester-update-button').should('be.visible')
cy.getTestId('tester-update-button').click()

// check VFG input value
cy.get(`#${fieldKey}-0`).should('not.be.checked')
cy.get(`#${fieldKey}-1`).should('not.be.checked')
// check field test form model also matches
cy.getTestId(`field-tester-form-model-${fieldKey}-value`).should('not.contain.text', fieldValue)
cy.getTestId(`field-tester-form-model-${fieldKey}-value`).should('not.contain.text', updatedFieldValue)
})
})

0 comments on commit 06e4c9d

Please sign in to comment.