From df4ffbb17e87eaf1ce356cf1e6425dd5eb20612a Mon Sep 17 00:00:00 2001 From: Kai Arrowood Date: Fri, 10 Jan 2025 10:42:08 -0500 Subject: [PATCH] feat(field-multiselect): setup syntax [khcp-11341] (#1885) Convert KMultiselect to setup syntax for [KHCP-11341](https://konghq.atlassian.net/browse/KHCP-11341). Other fixes: - Add testing of style classes to other entities - add support for missing props to FieldMultiselect: - `id` - `classNames` - `disabled` - `name` - add `data-testid` to KPop --------- Co-authored-by: GU Yiling --- packages/core/forms/sandbox/App.vue | 29 +++ .../components/fields/FieldMultiselect.vue | 94 +++++++-- .../fields/__tests__/FieldCheckbox.cy.ts | 6 + .../fields/__tests__/FieldInput.cy.ts | 6 + .../fields/__tests__/FieldMultiselect.cy.ts | 198 ++++++++++++++++++ .../fields/__tests__/FieldRadio.cy.ts | 6 + .../fields/__tests__/FieldSelect.cy.ts | 8 + .../fields/__tests__/FieldSwitch.cy.ts | 6 + .../fields/__tests__/FieldTextArea.cy.ts | 6 + 9 files changed, 336 insertions(+), 23 deletions(-) create mode 100644 packages/core/forms/src/components/fields/__tests__/FieldMultiselect.cy.ts diff --git a/packages/core/forms/sandbox/App.vue b/packages/core/forms/sandbox/App.vue index a8a50e4049..2cf9673890 100644 --- a/packages/core/forms/sandbox/App.vue +++ b/packages/core/forms/sandbox/App.vue @@ -67,6 +67,31 @@ const fieldSchema = { { name: 'Female', value: 'female' }, ], }, + // FieldSelect + { + type: 'select', + model: 'https_redirect_status_code', + label: 'HTTPS Redirect Status Code', + values: [426, 301, 302, 307, 308], + }, + // FieldMultiselect + { + type: 'multiselect', + model: 'protocols', + label:'Protocols', + values: [ + { label: 'GRPC', value: 'grpc' }, + { label: 'GRPCS', value: 'grpcs' }, + { label: 'HTTP', value: 'http' }, + { label: 'HTTPS', value: 'https' }, + { label: 'TCP', value: 'tcp' }, + { label: 'TLS', value: 'tls' }, + { label: 'TLS_PASSTHROUGH', value: 'tls_passthrough' }, + { label: 'UDP', value: 'udp' }, + { label: 'WS', value: 'ws' }, + { label: 'WSS', value: 'wss' }, + ], + }, // FieldSwitch { type: 'switch', @@ -92,6 +117,8 @@ const fieldModelDefault = ref({ is_friendly: true, is_cute: true, gender: 'male', + https_redirect_status_code: '', + protocols: ['http', 'https'], }) const fieldModelModified = ref({ @@ -100,6 +127,8 @@ const fieldModelModified = ref({ is_cute: false, gender: null, personality: 'A little bit of a brat', + https_redirect_status_code: 307, + protocols: ['https', 'wss'], }) diff --git a/packages/core/forms/src/components/fields/FieldMultiselect.vue b/packages/core/forms/src/components/fields/FieldMultiselect.vue index a13123b3fc..ff219135e0 100644 --- a/packages/core/forms/src/components/fields/FieldMultiselect.vue +++ b/packages/core/forms/src/components/fields/FieldMultiselect.vue @@ -1,41 +1,89 @@ - diff --git a/packages/core/forms/src/components/fields/__tests__/FieldCheckbox.cy.ts b/packages/core/forms/src/components/fields/__tests__/FieldCheckbox.cy.ts index 22dabea83b..6048749fca 100644 --- a/packages/core/forms/src/components/fields/__tests__/FieldCheckbox.cy.ts +++ b/packages/core/forms/src/components/fields/__tests__/FieldCheckbox.cy.ts @@ -13,6 +13,7 @@ describe(' - FieldCheckbox', () => { label: fieldLabel, help: 'Check if the cat is cool.', required: true, + styleClasses: 'cool-cats', }], } @@ -43,6 +44,11 @@ describe(' - FieldCheckbox', () => { cy.get('.required').find(`.form-group-label[for="${fieldKey}"]`).should('not.exist') } + // check style classes + if (schema.fields[0].styleClasses) { + cy.get('.form-group.field-checkbox').should('have.class', schema.fields[0].styleClasses) + } + // check help text if (schema.fields[0].help) { cy.get(`label[for="${fieldKey}"] .info-icon`).should('be.visible') diff --git a/packages/core/forms/src/components/fields/__tests__/FieldInput.cy.ts b/packages/core/forms/src/components/fields/__tests__/FieldInput.cy.ts index 16b1d83268..524814faf5 100644 --- a/packages/core/forms/src/components/fields/__tests__/FieldInput.cy.ts +++ b/packages/core/forms/src/components/fields/__tests__/FieldInput.cy.ts @@ -12,6 +12,7 @@ describe(' - FieldInput', () => { id: fieldKey, inputType: 'text', label: fieldLabel, + styleClasses: 'awesome-cats', help: 'The name of the cat.', required: true, }], @@ -60,6 +61,11 @@ describe(' - FieldInput', () => { cy.get(`label[for="${fieldKey}"] .info-icon`).should('be.visible') cy.get(`label[for="${fieldKey}"]`).should('contain.text', schema.fields[0].help) } + + // check style classes + if (schema.fields[0].styleClasses) { + cy.get('.form-group.field-input').should('have.class', schema.fields[0].styleClasses) + } }) it('renders default state correctly - with model', () => { diff --git a/packages/core/forms/src/components/fields/__tests__/FieldMultiselect.cy.ts b/packages/core/forms/src/components/fields/__tests__/FieldMultiselect.cy.ts new file mode 100644 index 0000000000..e70cae832b --- /dev/null +++ b/packages/core/forms/src/components/fields/__tests__/FieldMultiselect.cy.ts @@ -0,0 +1,198 @@ +import type { FormSchema } from '../../../types' +import FieldTester from '../../../../sandbox/FieldTester.vue' + +describe(' - FieldMultiselect', () => { + const fieldKeys = ['https_redirect_status_code', 'protocols'] + const fieldLabels = ['HTTPS Redirect Status Code', 'Protocols'] + const fieldValues = [['400'], ['https']] + const updatedValues = [['426'], ['tls', 'ws']] + const styleClasses = ['', 'plugin-protocols-select'] + + const schema: FormSchema = { + fields: [ + // with elements.one_of + { + type: 'multiselect', + label: fieldLabels[0], + id: fieldKeys[0], + model: fieldKeys[0], + name: fieldKeys[0], + elements: { + one_of: [ + 301, + 302, + 307, + 308, + 400, + 426, + ], + }, + help: 'The status code Kong responds with when all properties of a Route match except the protocol i.e. if the protocol of the request is `HTTP` instead of `HTTPS`. `Location` header is injected by Kong if the field is set to 301, 302, 307 or 308. ', + }, + // with values + { + type: 'multiselect', + label: fieldLabels[1], + id: fieldKeys[1], + model: fieldKeys[1], + name: fieldKeys[1], + required: true, + styleClasses: styleClasses[1], + values: [ + { label: 'GRPC', value: 'grpc' }, + { label: 'GRPCS', value: 'grpcs' }, + { label: 'HTTP', value: 'http' }, + { label: 'HTTPS', value: 'https' }, + { label: 'TCP', value: 'tcp' }, + { label: 'TLS', value: 'tls' }, + { label: 'TLS_PASSTHROUGH', value: 'tls_passthrough' }, + { label: 'UDP', value: 'udp' }, + { label: 'WS', value: 'ws' }, + { label: 'WSS', value: 'wss' }, + ], + help: 'An array of the protocols this Route should allow. See the [Route Object](#route-object) section for a list of accepted protocols. When set to only `"https"`, HTTP requests are answered with an upgrade error. When set to only `"http"`, HTTPS requests are answered with an error. ', + }], + } + + for (let i = 0; i < fieldKeys.length; i++) { + const fieldKey = fieldKeys[i] + const fieldLabel = fieldLabels[i] + const fieldValue = fieldValues[i] + const updatedValue = updatedValues[i] + + 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}`).should('exist') + cy.get(`#${fieldKey}`).should('have.value', '') + + // 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) + + // check help text + if (schema.fields[i].help) { + cy.get(`label[for="${fieldKey}"] .info-icon`).should('be.visible') + cy.get(`label[for="${fieldKey}"]`).should('contain.text', schema.fields[i].help) + } + + // check required state + if (schema.fields[i].required) { + cy.get(`.field-multiselect.required label[for="${fieldKey}"]`).should('exist') + } else { + cy.get(`.field-multiselect.required label[for="${fieldKey}"]`).should('not.exist') + } + + // check style classes + if (schema.fields[i].styleClasses) { + cy.get('.form-group.field-multiselect').should('have.class', styleClasses[i]) + } + + // check all options render + cy.get(`#${fieldKey}`).click() + + const items = schema.fields[i].elements?.one_of || schema.fields[i].values + items.forEach((item: Record | string) => { + if (typeof item === 'object') { + cy.getTestId(`multiselect-item-${item.value}`).should('exist') + } else { + cy.getTestId(`multiselect-item-${item}`).should('exist') + } + }) + }) + + it('renders default state correctly - with model', () => { + cy.mount(FieldTester, { + props: { + schema, + model: { + [fieldKey]: fieldValue, + }, + }, + }) + + cy.get('.field-tester-container').should('exist') + + cy.get(`#${fieldKey}`).should('exist') + cy.getTestId(`field-tester-form-model-${fieldKey}-value`).should('be.visible') + + // check VFG input value + // check field test form model matches + for (let j = 0; j < fieldValue.length; j++) { + // label is to uppercase of value + cy.get(`[data-testid="${fieldKey}-items"] [data-testid="selection-badges-container"] .badge-content-wrapper`).should('contain.text', fieldValue[j].toUpperCase()) + cy.getTestId(`field-tester-form-model-${fieldKey}-value`).should('contain.text', fieldValue[j]) + } + }) + + it('handles input changes', () => { + cy.mount(FieldTester, { + props: { + schema, + }, + }) + + cy.get('.field-tester-container').should('exist') + + // edit the input value + cy.get(`#${fieldKey}`).should('exist') + cy.get(`#${fieldKey}`).click() + for (let j = 0; j < updatedValue.length; j++) { + cy.getTestId(`multiselect-item-${updatedValue[j]}`).find('button').should('exist') + cy.getTestId(`multiselect-item-${updatedValue[j]}`).find('button').scrollIntoView() + cy.getTestId(`multiselect-item-${updatedValue[j]}`).find('button').should('be.visible') + cy.getTestId(`multiselect-item-${updatedValue[j]}`).find('button').click() + } + + for (let j = 0; j < updatedValue.length; j++) { + // check VFG input value - label is to uppercase of value + cy.get(`[data-testid="${fieldKey}-items"] [data-testid="selection-badges-container"] .badge-content-wrapper`).should('contain.text', updatedValue[j].toUpperCase()) + // check field test form model + cy.getTestId(`field-tester-form-model-${fieldKey}-value`).should('contain.text', updatedValue[j]) + } + }) + + it('handles programmatic input changes', () => { + cy.mount(FieldTester, { + props: { + schema, + model: { + [fieldKey]: fieldValue, + }, + modifiedModel: { + [fieldKey]: updatedValue, + }, + }, + }) + + cy.get('.field-tester-container').should('exist') + + // initial value loaded + cy.getTestId(`field-tester-form-model-${fieldKey}-value`).should('be.visible') + for (let j = 0; j < fieldValue.length; j++) { + cy.getTestId(`field-tester-form-model-${fieldKey}-value`).should('contain.text', fieldValue[j]) + } + + // programmatic update + cy.getTestId('tester-update-button').should('be.visible') + cy.getTestId('tester-update-button').click() + + for (let j = 0; j < updatedValue.length; j++) { + // check VFG input value - label is to uppercase of value + cy.get(`[data-testid="${fieldKey}-items"] [data-testid="selection-badges-container"] .badge-content-wrapper`).should('contain.text', updatedValue[j].toUpperCase()) + // check field test form model also matches + cy.getTestId(`field-tester-form-model-${fieldKey}-value`).should('contain.text', updatedValue[j]) + } + }) + } +}) diff --git a/packages/core/forms/src/components/fields/__tests__/FieldRadio.cy.ts b/packages/core/forms/src/components/fields/__tests__/FieldRadio.cy.ts index fc94a34cbe..8754c8674f 100644 --- a/packages/core/forms/src/components/fields/__tests__/FieldRadio.cy.ts +++ b/packages/core/forms/src/components/fields/__tests__/FieldRadio.cy.ts @@ -15,6 +15,7 @@ describe(' - FieldRadio', () => { label: fieldLabel, required: true, help: 'Specify a gender if it is known', + styleClasses: 'cool-cats', values: [ { name: 'Male', value: fieldValue }, { name: 'Female', value: 'female' }, @@ -52,6 +53,11 @@ describe(' - FieldRadio', () => { cy.get('.required').find(`.form-group-label[for="${fieldKey}"]`).should('not.exist') } + // check style classes + if (schema.fields[0].styleClasses) { + cy.get('.form-group.field-radio').should('have.class', schema.fields[0].styleClasses) + } + // check help text if (schema.fields[0].help) { cy.get(`label[for="${fieldKey}"] .info-icon`).should('be.visible') diff --git a/packages/core/forms/src/components/fields/__tests__/FieldSelect.cy.ts b/packages/core/forms/src/components/fields/__tests__/FieldSelect.cy.ts index 6284c3e8cd..1ba97f9669 100644 --- a/packages/core/forms/src/components/fields/__tests__/FieldSelect.cy.ts +++ b/packages/core/forms/src/components/fields/__tests__/FieldSelect.cy.ts @@ -6,6 +6,8 @@ describe(' - FieldSelect', () => { const fieldLabels = ['HTTPS Redirect Status Code', 'Protocols'] const fieldValues = [307, 'http, https'] const updatedValues = [426, 'https'] + const styleClasses = ['', 'plugin-protocols-select'] + const schema: FormSchema = { fields: [ // with number[] and no groups @@ -38,6 +40,7 @@ describe(' - FieldSelect', () => { hideNoneSelectedText: true, }, required: true, + styleClasses: styleClasses[1], values: [ { name: 'grpc', @@ -180,6 +183,11 @@ describe(' - FieldSelect', () => { cy.get(`.field-select.required label[for="${fieldKey}"]`).should('not.exist') } + // check style classes + if (schema.fields[i].styleClasses) { + cy.get('.form-group.field-select').should('have.class', styleClasses[i]) + } + // check all groups / options render cy.get(`#${fieldKey}`).click() diff --git a/packages/core/forms/src/components/fields/__tests__/FieldSwitch.cy.ts b/packages/core/forms/src/components/fields/__tests__/FieldSwitch.cy.ts index 8ca4a997ed..7f2226c01c 100644 --- a/packages/core/forms/src/components/fields/__tests__/FieldSwitch.cy.ts +++ b/packages/core/forms/src/components/fields/__tests__/FieldSwitch.cy.ts @@ -12,6 +12,7 @@ describe(' - FieldSwitch', () => { id: fieldKey, inputType: 'text', label: fieldLabel, + styleClasses: 'cool-cats', required: true, }], } @@ -43,6 +44,11 @@ describe(' - FieldSwitch', () => { cy.get('.required').find(`.form-group-label[for="${fieldKey}"]`).should('not.exist') } + // check style classes + if (schema.fields[0].styleClasses) { + cy.get('.form-group.field-switch').should('have.class', schema.fields[0].styleClasses) + } + // check help text if (schema.fields[0].help) { cy.get(`label[for="${fieldKey}"] .info-icon`).should('be.visible') diff --git a/packages/core/forms/src/components/fields/__tests__/FieldTextArea.cy.ts b/packages/core/forms/src/components/fields/__tests__/FieldTextArea.cy.ts index 9ca6a99f22..9b663d29cf 100644 --- a/packages/core/forms/src/components/fields/__tests__/FieldTextArea.cy.ts +++ b/packages/core/forms/src/components/fields/__tests__/FieldTextArea.cy.ts @@ -12,6 +12,7 @@ describe(' - FieldTextArea', () => { id: fieldKey, label: fieldLabel, help: 'Describe the personality of the cat.', + styleClasses: 'cool-cats', required: true, }], } @@ -43,6 +44,11 @@ describe(' - FieldTextArea', () => { cy.get('.required').find(`.form-group-label[for="${fieldKey}"]`).should('not.exist') } + // check style classes + if (schema.fields[0].styleClasses) { + cy.get('.form-group.field-text-area').should('have.class', schema.fields[0].styleClasses) + } + // check help text if (schema.fields[0].help) { cy.get(`label[for="${fieldKey}"] .info-icon`).should('be.visible')