diff --git a/src/core/components/parameter-include-empty.jsx b/src/core/components/parameter-include-empty.jsx
new file mode 100644
index 00000000..9e90df30
--- /dev/null
+++ b/src/core/components/parameter-include-empty.jsx
@@ -0,0 +1,27 @@
+import React from "react"
+import cx from "classnames"
+import PropTypes from "prop-types"
+import ImPropTypes from "react-immutable-proptypes"
+
+export const ParameterIncludeEmpty = ({ param, isIncluded, onChange, isDisabled }) => {
+ const onCheckboxChange = e => {
+ onChange(e.target.checked)
+ }
+ if(!param.get("allowEmptyValue")) {
+ return null
+ }
+ return
+
+ Send empty value
+
+}
+ParameterIncludeEmpty.propTypes = {
+ param: ImPropTypes.map.isRequired,
+ isIncluded: PropTypes.bool.isRequired,
+ isDisabled: PropTypes.bool.isRequired,
+ onChange: PropTypes.func.isRequired,
+}
+
+export default ParameterIncludeEmpty
diff --git a/src/core/components/parameter-row.jsx b/src/core/components/parameter-row.jsx
index a756a4ce..73f490ae 100644
--- a/src/core/components/parameter-row.jsx
+++ b/src/core/components/parameter-row.jsx
@@ -15,6 +15,7 @@ export default class ParameterRow extends Component {
isExecute: PropTypes.bool,
onChangeConsumes: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
+ specActions: PropTypes.object.isRequired,
pathMethod: PropTypes.array.isRequired,
getConfigs: PropTypes.func.isRequired,
specPath: ImPropTypes.list.isRequired
@@ -61,7 +62,23 @@ export default class ParameterRow extends Component {
onChangeWrapper = (value, isXml = false) => {
let { onChange, rawParam } = this.props
- return onChange(rawParam, value, isXml)
+ let valueForUpstream
+
+ // Coerce empty strings and empty Immutable objects to null
+ if(value === "" || (value && value.size === 0)) {
+ valueForUpstream = null
+ } else {
+ valueForUpstream = value
+ }
+
+ return onChange(rawParam, valueForUpstream, isXml)
+ }
+
+ onChangeIncludeEmpty = (newValue) => {
+ let { specActions, param, pathMethod } = this.props
+ const paramName = param.get("name")
+ const paramIn = param.get("in")
+ return specActions.updateEmptyParamInclusion(pathMethod, paramName, paramIn, newValue)
}
setDefaultValue = () => {
@@ -120,6 +137,7 @@ export default class ParameterRow extends Component {
const ModelExample = getComponent("modelExample")
const Markdown = getComponent("Markdown")
const ParameterExt = getComponent("ParameterExt")
+ const ParameterIncludeEmpty = getComponent("ParameterIncludeEmpty")
let paramWithMeta = specSelectors.parameterWithMetaByIdentity(pathMethod, rawParam)
let format = param.get("format")
@@ -225,6 +243,16 @@ export default class ParameterRow extends Component {
: null
}
+ {
+ !bodyParam && isExecute ?
+
+ : null
+ }
+
diff --git a/src/core/components/parameters.jsx b/src/core/components/parameters.jsx
index e5ce323b..5245cc12 100644
--- a/src/core/components/parameters.jsx
+++ b/src/core/components/parameters.jsx
@@ -65,7 +65,8 @@ export default class Parameters extends Component {
fn,
getComponent,
getConfigs,
- specSelectors,
+ specSelectors,
+ specActions,
pathMethod
} = this.props
@@ -107,6 +108,7 @@ export default class Parameters extends Component {
onChange={ this.onChange }
onChangeConsumes={this.onChangeConsumesWrapper}
specSelectors={ specSelectors }
+ specActions={specActions}
pathMethod={ pathMethod }
isExecute={ isExecute }/>
)).toArray()
diff --git a/src/core/plugins/oas3/wrap-components/parameters.jsx b/src/core/plugins/oas3/wrap-components/parameters.jsx
index 220b02af..a9df082c 100644
--- a/src/core/plugins/oas3/wrap-components/parameters.jsx
+++ b/src/core/plugins/oas3/wrap-components/parameters.jsx
@@ -90,6 +90,7 @@ class Parameters extends Component {
getComponent,
getConfigs,
specSelectors,
+ specActions,
oas3Actions,
oas3Selectors,
pathMethod,
@@ -151,6 +152,7 @@ class Parameters extends Component {
onChange={ this.onChange }
onChangeConsumes={this.onChangeConsumesWrapper}
specSelectors={ specSelectors }
+ specActions={ specActions }
pathMethod={ pathMethod }
isExecute={ isExecute }/>
)).toArray()
diff --git a/src/core/plugins/spec/actions.js b/src/core/plugins/spec/actions.js
index 9099089f..980ba284 100644
--- a/src/core/plugins/spec/actions.js
+++ b/src/core/plugins/spec/actions.js
@@ -14,6 +14,7 @@ export const UPDATE_SPEC = "spec_update_spec"
export const UPDATE_URL = "spec_update_url"
export const UPDATE_JSON = "spec_update_json"
export const UPDATE_PARAM = "spec_update_param"
+export const UPDATE_EMPTY_PARAM_INCLUSION = "spec_update_empty_param_inclusion"
export const VALIDATE_PARAMS = "spec_validate_param"
export const SET_RESPONSE = "spec_set_response"
export const SET_REQUEST = "spec_set_request"
@@ -270,6 +271,18 @@ export const validateParams = ( payload, isOAS3 ) =>{
}
}
+export const updateEmptyParamInclusion = ( pathMethod, paramName, paramIn, includeEmptyValue ) =>{
+ return {
+ type: UPDATE_EMPTY_PARAM_INCLUSION,
+ payload:{
+ pathMethod,
+ paramName,
+ paramIn,
+ includeEmptyValue
+ }
+ }
+}
+
export function clearValidateParams( payload ){
return {
type: CLEAR_VALIDATE_PARAMS,
@@ -327,7 +340,28 @@ export const executeRequest = (req) =>
let { pathName, method, operation } = req
let { requestInterceptor, responseInterceptor } = getConfigs()
+
let op = operation.toJS()
+
+ // ensure that explicitly-included params are in the request
+
+ if(op && op.parameters && op.parameters.length) {
+ op.parameters
+ .filter(param => param && param.allowEmptyValue === true)
+ .forEach(param => {
+ if (specSelectors.parameterInclusionSettingFor([pathName, method], param.name, param.in)) {
+ req.parameters = req.parameters || {}
+ const paramValue = req.parameters[param.name]
+
+ // if the value is falsy or an empty Immutable iterable...
+ if(!paramValue || (paramValue && paramValue.size === 0)) {
+ // set it to empty string, so Swagger Client will treat it as
+ // present but empty.
+ req.parameters[param.name] = ""
+ }
+ }
+ })
+ }
// if url is relative, parseUrl makes it absolute by inferring from `window.location`
req.contextUrl = parseUrl(specSelectors.url()).toString()
diff --git a/src/core/plugins/spec/reducers.js b/src/core/plugins/spec/reducers.js
index a510bb50..ef0424b7 100644
--- a/src/core/plugins/spec/reducers.js
+++ b/src/core/plugins/spec/reducers.js
@@ -12,6 +12,7 @@ import {
UPDATE_URL,
UPDATE_JSON,
UPDATE_PARAM,
+ UPDATE_EMPTY_PARAM_INCLUSION,
VALIDATE_PARAMS,
SET_RESPONSE,
SET_REQUEST,
@@ -70,6 +71,22 @@ export default {
)
},
+ [UPDATE_EMPTY_PARAM_INCLUSION]: ( state, {payload} ) => {
+ let { pathMethod, paramName, paramIn, includeEmptyValue } = payload
+
+ if(!paramName || !paramIn) {
+ console.warn("Warning: UPDATE_EMPTY_PARAM_INCLUSION could not generate a paramKey.")
+ return state
+ }
+
+ const paramKey = `${paramName}.${paramIn}`
+
+ return state.setIn(
+ ["meta", "paths", ...pathMethod, "parameter_inclusions", paramKey],
+ includeEmptyValue
+ )
+ },
+
[VALIDATE_PARAMS]: ( state, { payload: { pathMethod, isOAS3 } } ) => {
let meta = state.getIn( [ "meta", "paths", ...pathMethod ], fromJS({}) )
let isXml = /xml/i.test(meta.get("consumes_value"))
diff --git a/src/core/plugins/spec/selectors.js b/src/core/plugins/spec/selectors.js
index 432d9698..cfc6b5fb 100644
--- a/src/core/plugins/spec/selectors.js
+++ b/src/core/plugins/spec/selectors.js
@@ -311,6 +311,11 @@ export const parameterWithMetaByIdentity = (state, pathMethod, param) => {
return mergedParams.find(curr => curr.get("in") === param.get("in") && curr.get("name") === param.get("name"), OrderedMap())
}
+export const parameterInclusionSettingFor = (state, pathMethod, paramName, paramIn) => {
+ const paramKey = `${paramName}.${paramIn}`
+ return state.getIn(["meta", "paths", ...pathMethod, "parameter_inclusions", paramKey], false)
+}
+
export const parameterWithMeta = (state, pathMethod, paramName, paramIn) => {
const opParams = specJsonWithResolvedSubtrees(state).getIn(["paths", ...pathMethod, "parameters"], OrderedMap())
diff --git a/src/core/presets/base.js b/src/core/presets/base.js
index 8165f1b3..50663213 100644
--- a/src/core/presets/base.js
+++ b/src/core/presets/base.js
@@ -42,6 +42,7 @@ import Response from "core/components/response"
import ResponseBody from "core/components/response-body"
import Parameters from "core/components/parameters"
import ParameterExt from "core/components/parameter-extension"
+import ParameterIncludeEmpty from "core/components/parameter-include-empty"
import ParameterRow from "core/components/parameter-row"
import Execute from "core/components/execute"
import Headers from "core/components/headers"
@@ -143,6 +144,7 @@ export default function() {
OperationExt,
OperationExtRow,
ParameterExt,
+ ParameterIncludeEmpty,
OperationTag,
OperationContainer,
DeepLink,
diff --git a/src/style/_table.scss b/src/style/_table.scss
index 45e0748c..95414542 100644
--- a/src/style/_table.scss
+++ b/src/style/_table.scss
@@ -139,6 +139,20 @@ table
@include text_code($table-parameter-deprecated-font-color);
}
+.parameter__empty_value_toggle {
+ font-size: 13px;
+ padding-top: 5px;
+ padding-bottom: 12px;
+
+ input {
+ margin-right: 7px;
+ }
+
+ &.disabled {
+ opacity: 0.7;
+ }
+}
+
.table-container
{
diff --git a/test/core/plugins/spec/actions.js b/test/core/plugins/spec/actions.js
index 47dd7955..235aabba 100644
--- a/test/core/plugins/spec/actions.js
+++ b/test/core/plugins/spec/actions.js
@@ -1,7 +1,7 @@
/* eslint-env mocha */
import expect, { createSpy } from "expect"
import { fromJS } from "immutable"
-import { execute, executeRequest, changeParamByIdentity } from "corePlugins/spec/actions"
+import { execute, executeRequest, changeParamByIdentity, updateEmptyParamInclusion } from "corePlugins/spec/actions"
describe("spec plugin - actions", function(){
@@ -207,4 +207,22 @@ describe("spec plugin - actions", function(){
})
})
})
+
+ describe("updateEmptyParamInclusion", function () {
+ it("should map its arguments to a payload", function () {
+ const pathMethod = ["/one", "get"]
+
+ const result = updateEmptyParamInclusion(pathMethod, "param", "query", true)
+
+ expect(result).toEqual({
+ type: "spec_update_empty_param_inclusion",
+ payload: {
+ pathMethod,
+ paramName: "param",
+ paramIn: "query",
+ includeEmptyValue: true
+ }
+ })
+ })
+ })
})
diff --git a/test/core/plugins/spec/reducer.js b/test/core/plugins/spec/reducer.js
index 89de197a..643d249f 100644
--- a/test/core/plugins/spec/reducer.js
+++ b/test/core/plugins/spec/reducer.js
@@ -178,4 +178,26 @@ describe("spec plugin - reducer", function(){
expect(value).toEqual(`{ "a": 123 }`)
})
})
+ describe("SPEC_UPDATE_EMPTY_PARAM_INCLUSION", function() {
+ it("should store parameter values by name+in", () => {
+ const updateParam = reducer["spec_update_empty_param_inclusion"]
+
+ const path = "/pet/post"
+ const method = "POST"
+
+ const state = fromJS({})
+
+ const result = updateParam(state, {
+ payload: {
+ pathMethod: [path, method],
+ paramName: "param",
+ paramIn: "query",
+ includeEmptyValue: true
+ }
+ })
+
+ const response = result.getIn(["meta", "paths", path, method, "parameter_inclusions", "param.query"])
+ expect(response).toEqual(true)
+ })
+ })
})
diff --git a/test/core/plugins/spec/selectors.js b/test/core/plugins/spec/selectors.js
index 39483171..412407b2 100644
--- a/test/core/plugins/spec/selectors.js
+++ b/test/core/plugins/spec/selectors.js
@@ -14,7 +14,8 @@ import Petstore from "./assets/petstore.json"
import {
operationWithMeta,
parameterWithMeta,
- parameterWithMetaByIdentity
+ parameterWithMetaByIdentity,
+ parameterInclusionSettingFor
} from "../../../../src/core/plugins/spec/selectors"
describe("spec plugin - selectors", function(){
@@ -712,4 +713,42 @@ describe("spec plugin - selectors", function(){
})
})
})
+ describe("parameterInclusionSettingFor", function() {
+ it("should support getting name+in param inclusion settings", function () {
+ const param = fromJS({
+ name: "param",
+ in: "query",
+ allowEmptyValue: true
+ })
+
+ const state = fromJS({
+ json: {
+ paths: {
+ "/": {
+ "get": {
+ parameters: [
+ param
+ ]
+ }
+ }
+ }
+ },
+ meta: {
+ paths: {
+ "/": {
+ "get": {
+ "parameter_inclusions": {
+ [`param.query`]: true
+ }
+ }
+ }
+ }
+ }
+ })
+
+ const result = parameterInclusionSettingFor(state, ["/", "get"], "param", "query")
+
+ expect(result).toEqual(true)
+ })
+ })
})
diff --git a/test/e2e/scenarios/bugs/4587.js b/test/e2e/scenarios/bugs/4587.js
new file mode 100644
index 00000000..39e5b161
--- /dev/null
+++ b/test/e2e/scenarios/bugs/4587.js
@@ -0,0 +1,36 @@
+describe("bug #4587: clearing header param values", function () {
+ let mainPage
+ beforeEach(function (client, done) {
+ mainPage = client
+ .url("localhost:3230")
+ .page.main()
+
+ client.waitForElementVisible(".download-url-input:not([disabled])", 5000)
+ .pause(2000)
+ .clearValue(".download-url-input")
+ .setValue(".download-url-input", "http://localhost:3230/test-specs/bugs/4587.yaml")
+ .click("button.download-url-button")
+ .pause(1000)
+
+ done()
+ })
+
+ afterEach(function (client, done) {
+ done()
+ })
+
+ it("sets a required initial value based the first enum value", function (client) {
+ client.waitForElementVisible(".opblock-tag-section", 10000)
+ .click("#operations-sql-execSql")
+ .waitForElementVisible(".opblock.is-open", 5000)
+ .click("button.btn.try-out__btn")
+ .setValue(`tr[data-param-name="x-irest-conn"] input`, "hi")
+ .click("button.btn.execute")
+ .waitForElementVisible(".request-url", 2000)
+ .setValue(`tr[data-param-name="x-irest-conn"] input`, `\u0008\u0008\u0008`) // backspaces
+ .pause(900)
+ .click("button.btn.execute")
+ .expect.element("textarea.curl").text
+ .to.not.contain(`x-irest-conn`)
+ })
+})
diff --git a/test/e2e/scenarios/features/allow-empty-value.openapi.js b/test/e2e/scenarios/features/allow-empty-value.openapi.js
new file mode 100644
index 00000000..77611f58
--- /dev/null
+++ b/test/e2e/scenarios/features/allow-empty-value.openapi.js
@@ -0,0 +1,433 @@
+describe("feature: OpenAPI 3 allowEmptyValue", function () {
+ beforeEach(function (client, done) {
+ client
+ .url("localhost:3230")
+ .page.main()
+
+ client.waitForElementVisible(".download-url-input:not([disabled])", 5000)
+ .clearValue(".download-url-input")
+ .setValue(".download-url-input", "/test-specs/features/allow-empty-value.openapi.yaml")
+ .click("button.download-url-button")
+ .waitForElementVisible(".opblock", 10000)
+
+ done()
+ })
+
+ afterEach(function (client, done) {
+ done()
+ })
+
+ describe("regular parameters", function () {
+ it("should set and unset an integer value", function (client) {
+ const inputSelector = `tr[data-param-name="int"] input`
+
+ client // open try-it-out
+ .click("#operations-default-get_regularParams")
+ .waitForElementVisible("button.btn.try-out__btn", 5000)
+ .click("button.btn.try-out__btn")
+ .pause(200)
+
+ client // set parameter, to ensure an initial value is set
+ .setValue(inputSelector, "123")
+ .click("button.btn.execute.opblock-control__btn")
+ .pause(200)
+
+ client // remove initial value, execute again
+ .setValue(inputSelector, "\u0008\u0008\u0008") // backspaces
+ .pause(200)
+ .click("button.btn.execute.opblock-control__btn")
+ .expect.element("textarea.curl").text
+ .to.contain(`GET "http://localhost:3230/regularParams"`)
+ })
+ it("should set and unset a string value", function (client) {
+ const inputSelector = `tr[data-param-name="str"] input`
+
+ client // open try-it-out
+ .click("#operations-default-get_regularParams")
+ .waitForElementVisible("button.btn.try-out__btn", 5000)
+ .click("button.btn.try-out__btn")
+ .pause(200)
+
+ client // set parameter, to ensure an initial value is set
+ .setValue(inputSelector, "123")
+ .click("button.btn.execute.opblock-control__btn")
+ .pause(200)
+
+ client // remove initial value, execute again
+ .setValue(inputSelector, "\u0008\u0008\u0008") // backspaces
+ .pause(200)
+ .click("button.btn.execute.opblock-control__btn")
+ .expect.element("textarea.curl").text
+ .to.contain(`GET "http://localhost:3230/regularParams"`)
+ })
+ it("should set and unset a number value", function (client) {
+ const inputSelector = `tr[data-param-name="num"] input`
+
+ client // open try-it-out
+ .click("#operations-default-get_regularParams")
+ .waitForElementVisible("button.btn.try-out__btn", 5000)
+ .click("button.btn.try-out__btn")
+ .pause(200)
+
+ client // set parameter, to ensure an initial value is set
+ .setValue(inputSelector, "123")
+ .click("button.btn.execute.opblock-control__btn")
+ .pause(200)
+
+ client // remove initial value, execute again
+ .setValue(inputSelector, "\u0008\u0008\u0008") // backspaces
+ .pause(200)
+ .click("button.btn.execute.opblock-control__btn")
+ .expect.element("textarea.curl").text
+ .to.contain(`GET "http://localhost:3230/regularParams"`)
+ })
+ it("should set and unset a boolean value", function (client) {
+ const inputSelector = `tr[data-param-name="bool"] select`
+
+ client // open try-it-out
+ .click("#operations-default-get_regularParams")
+ .waitForElementVisible("button.btn.try-out__btn", 5000)
+ .click("button.btn.try-out__btn")
+ .pause(200)
+
+ client // set parameter, to ensure an initial value is set
+ .click(`${inputSelector} [value="true"]`)
+ .click("button.btn.execute.opblock-control__btn")
+ .pause(200)
+
+ client // remove initial value, execute again
+ .click(`${inputSelector} [value=""]`)
+ .pause(200)
+ .click("button.btn.execute.opblock-control__btn")
+ .expect.element("textarea.curl").text
+ .to.contain(`GET "http://localhost:3230/regularParams"`)
+ })
+ it("should set and unset an array value", function (client) {
+ const inputSelector = `tr[data-param-name="arr"]`
+
+ client // open try-it-out
+ .click("#operations-default-get_regularParams")
+ .waitForElementVisible("button.btn.try-out__btn", 5000)
+ .click("button.btn.try-out__btn")
+ .pause(200)
+
+ client // set parameter, to ensure an initial value is set
+ .click(`${inputSelector} .json-schema-form-item-add`)
+ .setValue(`${inputSelector} input`, "asdf")
+ .click("button.btn.execute.opblock-control__btn")
+ .pause(200)
+
+ client // remove initial value, execute again
+ .click(`${inputSelector} .json-schema-form-item-remove`)
+ .pause(200)
+ .click("button.btn.execute.opblock-control__btn")
+ .expect.element("textarea.curl").text
+ .to.contain(`GET "http://localhost:3230/regularParams"`)
+ })
+ })
+
+ describe("allowEmptyValue parameters", function () {
+ describe("normal behavior", function () {
+ it("should set and unset an integer value", function (client) {
+ const inputSelector = `tr[data-param-name="int"] input`
+
+ client // open try-it-out
+ .click("#operations-default-get_emptyValueParams")
+ .waitForElementVisible("button.btn.try-out__btn", 5000)
+ .click("button.btn.try-out__btn")
+ .pause(200)
+
+ client // set parameter, to ensure an initial value is set
+ .setValue(inputSelector, "123")
+ .click("button.btn.execute.opblock-control__btn")
+ .pause(200)
+
+ client // remove initial value, execute again
+ .setValue(inputSelector, "\u0008\u0008\u0008") // backspaces
+ .pause(200)
+ .click("button.btn.execute.opblock-control__btn")
+ .expect.element("textarea.curl").text
+ .to.contain(`GET "http://localhost:3230/emptyValueParams"`)
+ })
+ it("should set and unset a string value", function (client) {
+ const inputSelector = `tr[data-param-name="str"] input`
+
+ client // open try-it-out
+ .click("#operations-default-get_emptyValueParams")
+ .waitForElementVisible("button.btn.try-out__btn", 5000)
+ .click("button.btn.try-out__btn")
+ .pause(200)
+
+ client // set parameter, to ensure an initial value is set
+ .setValue(inputSelector, "123")
+ .click("button.btn.execute.opblock-control__btn")
+ .pause(200)
+
+ client // remove initial value, execute again
+ .setValue(inputSelector, "\u0008\u0008\u0008") // backspaces
+ .pause(200)
+ .click("button.btn.execute.opblock-control__btn")
+ .expect.element("textarea.curl").text
+ .to.contain(`GET "http://localhost:3230/emptyValueParams"`)
+ })
+ it("should set and unset a number value", function (client) {
+ const inputSelector = `tr[data-param-name="num"] input`
+
+ client // open try-it-out
+ .click("#operations-default-get_emptyValueParams")
+ .waitForElementVisible("button.btn.try-out__btn", 5000)
+ .click("button.btn.try-out__btn")
+ .pause(200)
+
+ client // set parameter, to ensure an initial value is set
+ .setValue(inputSelector, "123")
+ .click("button.btn.execute.opblock-control__btn")
+ .pause(200)
+
+ client // remove initial value, execute again
+ .setValue(inputSelector, "\u0008\u0008\u0008") // backspaces
+ .pause(200)
+ .click("button.btn.execute.opblock-control__btn")
+ .expect.element("textarea.curl").text
+ .to.contain(`GET "http://localhost:3230/emptyValueParams"`)
+ })
+ it("should set and unset a boolean value", function (client) {
+ const inputSelector = `tr[data-param-name="bool"] select`
+
+ client // open try-it-out
+ .click("#operations-default-get_emptyValueParams")
+ .waitForElementVisible("button.btn.try-out__btn", 5000)
+ .click("button.btn.try-out__btn")
+ .pause(200)
+
+ client // set parameter, to ensure an initial value is set
+ .click(`${inputSelector} [value="true"]`)
+ .click("button.btn.execute.opblock-control__btn")
+ .pause(200)
+
+ client // remove initial value, execute again
+ .click(`${inputSelector} [value=""]`)
+ .pause(200)
+ .click("button.btn.execute.opblock-control__btn")
+ .expect.element("textarea.curl").text
+ .to.contain(`GET "http://localhost:3230/emptyValueParams"`)
+ })
+ it("should set and unset an array value", function (client) {
+ const inputSelector = `tr[data-param-name="arr"]`
+
+ client // open try-it-out
+ .click("#operations-default-get_emptyValueParams")
+ .waitForElementVisible("button.btn.try-out__btn", 5000)
+ .click("button.btn.try-out__btn")
+ .pause(200)
+
+ client // set parameter, to ensure an initial value is set
+ .click(`${inputSelector} .json-schema-form-item-add`)
+ .setValue(`${inputSelector} input`, "asdf")
+ .click("button.btn.execute.opblock-control__btn")
+ .pause(200)
+
+ client // remove initial value, execute again
+ .click(`${inputSelector} .json-schema-form-item-remove`)
+ .pause(200)
+ .click("button.btn.execute.opblock-control__btn")
+ .expect.element("textarea.curl").text
+ .to.contain(`GET "http://localhost:3230/emptyValueParams"`)
+ })
+ })
+ describe("send empty inital value behavior", function () {
+ it("should send an empty integer value", function (client) {
+ const paramSelector = `tr[data-param-name="int"]`
+
+ client // open try-it-out
+ .click("#operations-default-get_emptyValueParams")
+ .waitForElementVisible("button.btn.try-out__btn", 5000)
+ .click("button.btn.try-out__btn")
+ .pause(200)
+
+ client // tick "send empty value" box and execute
+ .click(`${paramSelector} .parameter__empty_value_toggle input`)
+ .click("button.btn.execute.opblock-control__btn")
+ .expect.element("textarea.curl").text
+ .to.contain(`GET "http://localhost:3230/emptyValueParams?int="`)
+ })
+ it("should send an empty string value", function (client) {
+ const paramSelector = `tr[data-param-name="str"]`
+
+ client // open try-it-out
+ .click("#operations-default-get_emptyValueParams")
+ .waitForElementVisible("button.btn.try-out__btn", 5000)
+ .click("button.btn.try-out__btn")
+ .pause(200)
+
+ client // tick "send empty value" box and execute
+ .click(`${paramSelector} .parameter__empty_value_toggle input`)
+ .click("button.btn.execute.opblock-control__btn")
+ .expect.element("textarea.curl").text
+ .to.contain(`GET "http://localhost:3230/emptyValueParams?str="`)
+ })
+ it("should send an empty number value", function (client) {
+ const paramSelector = `tr[data-param-name="num"]`
+
+ client // open try-it-out
+ .click("#operations-default-get_emptyValueParams")
+ .waitForElementVisible("button.btn.try-out__btn", 5000)
+ .click("button.btn.try-out__btn")
+ .pause(200)
+
+ client // tick "send empty value" box and execute
+ .click(`${paramSelector} .parameter__empty_value_toggle input`)
+ .click("button.btn.execute.opblock-control__btn")
+ .expect.element("textarea.curl").text
+ .to.contain(`GET "http://localhost:3230/emptyValueParams?num="`)
+ })
+ it("should send an empty boolean value", function (client) {
+ const paramSelector = `tr[data-param-name="bool"]`
+
+ client // open try-it-out
+ .click("#operations-default-get_emptyValueParams")
+ .waitForElementVisible("button.btn.try-out__btn", 5000)
+ .click("button.btn.try-out__btn")
+ .pause(200)
+
+ client // tick "send empty value" box and execute
+ .click(`${paramSelector} .parameter__empty_value_toggle input`)
+ .click("button.btn.execute.opblock-control__btn")
+ .expect.element("textarea.curl").text
+ .to.contain(`GET "http://localhost:3230/emptyValueParams?bool="`)
+ })
+ it("should send an empty array value", function (client) {
+ const paramSelector = `tr[data-param-name="arr"]`
+
+ client // open try-it-out
+ .click("#operations-default-get_emptyValueParams")
+ .waitForElementVisible("button.btn.try-out__btn", 5000)
+ .click("button.btn.try-out__btn")
+ .pause(200)
+
+ client // tick "send empty value" box and execute
+ .click(`${paramSelector} .parameter__empty_value_toggle input`)
+ .click("button.btn.execute.opblock-control__btn")
+ .expect.element("textarea.curl").text
+ .to.contain(`GET "http://localhost:3230/emptyValueParams?arr="`)
+ })
+ })
+ describe("modify and send empty behavior", function () {
+ it("should set, unset and send an empty integer value", function (client) {
+ const paramSelector = `tr[data-param-name="int"]`
+ const inputSelector = `${paramSelector} input`
+
+ client // open try-it-out
+ .click("#operations-default-get_emptyValueParams")
+ .waitForElementVisible("button.btn.try-out__btn", 5000)
+ .click("button.btn.try-out__btn")
+ .pause(200)
+
+ client // set parameter, to ensure an initial value is set
+ .setValue(inputSelector, "123")
+ .click("button.btn.execute.opblock-control__btn")
+ .pause(200)
+
+ client // remove initial value, click "send empty", execute again, assert
+ .setValue(inputSelector, "\u0008\u0008\u0008") // backspaces
+ .pause(400)
+ .click(`${paramSelector} .parameter__empty_value_toggle input`)
+ .click("button.btn.execute.opblock-control__btn")
+ .expect.element("textarea.curl").text
+ .to.contain(`GET "http://localhost:3230/emptyValueParams?int="`)
+ })
+ it("should set, unset and send an empty string value", function (client) {
+ const paramSelector = `tr[data-param-name="str"]`
+ const inputSelector = `${paramSelector} input`
+
+ client // open try-it-out
+ .click("#operations-default-get_emptyValueParams")
+ .waitForElementVisible("button.btn.try-out__btn", 5000)
+ .click("button.btn.try-out__btn")
+ .pause(200)
+
+ client // set parameter, to ensure an initial value is set
+ .setValue(inputSelector, "123")
+ .click("button.btn.execute.opblock-control__btn")
+ .pause(200)
+
+ client // remove initial value, click "send empty", execute again, assert
+ .setValue(inputSelector, "\u0008\u0008\u0008") // backspaces
+ .pause(400)
+ .click(`${paramSelector} .parameter__empty_value_toggle input`)
+ .click("button.btn.execute.opblock-control__btn")
+ .expect.element("textarea.curl").text
+ .to.contain(`GET "http://localhost:3230/emptyValueParams?str="`)
+ })
+ it("should set, unset and send an empty number value", function (client) {
+ const paramSelector = `tr[data-param-name="num"]`
+ const inputSelector = `${paramSelector} input`
+
+ client // open try-it-out
+ .click("#operations-default-get_emptyValueParams")
+ .waitForElementVisible("button.btn.try-out__btn", 5000)
+ .click("button.btn.try-out__btn")
+ .pause(200)
+
+ client // set parameter, to ensure an initial value is set
+ .setValue(inputSelector, "123")
+ .click("button.btn.execute.opblock-control__btn")
+ .pause(200)
+
+ client // remove initial value, click "send empty", execute again, assert
+ .setValue(inputSelector, "\u0008\u0008\u0008") // backspaces
+ .pause(400)
+ .click(`${paramSelector} .parameter__empty_value_toggle input`)
+ .click("button.btn.execute.opblock-control__btn")
+ .expect.element("textarea.curl").text
+ .to.contain(`GET "http://localhost:3230/emptyValueParams?num="`)
+ })
+ it("should set, unset and send an empty boolean value", function (client) {
+ const paramSelector = `tr[data-param-name="bool"]`
+ const inputSelector = `${paramSelector} select`
+
+ client // open try-it-out
+ .click("#operations-default-get_emptyValueParams")
+ .waitForElementVisible("button.btn.try-out__btn", 5000)
+ .click("button.btn.try-out__btn")
+ .pause(200)
+
+ client // set parameter, to ensure an initial value is set
+ .click(`${inputSelector} option[value="true"]`)
+ .click("button.btn.execute.opblock-control__btn")
+ .pause(200)
+
+ client // remove initial value, click "send empty", execute again, assert
+ .click(`${inputSelector} option[value=""]`)
+ .pause(400)
+ .click(`${paramSelector} .parameter__empty_value_toggle input`)
+ .click("button.btn.execute.opblock-control__btn")
+ .expect.element("textarea.curl").text
+ .to.contain(`GET "http://localhost:3230/emptyValueParams?bool="`)
+ })
+ it("should set, unset and send an empty array value", function (client) {
+ const paramSelector = `tr[data-param-name="arr"]`
+
+ client // open try-it-out
+ .click("#operations-default-get_emptyValueParams")
+ .waitForElementVisible("button.btn.try-out__btn", 5000)
+ .click("button.btn.try-out__btn")
+ .pause(200)
+
+ client // set parameter, to ensure an initial value is set
+ .click(`${paramSelector} .json-schema-form-item-add`)
+ .setValue(`${paramSelector} input`, "asdf")
+ .click("button.btn.execute.opblock-control__btn")
+ .pause(200)
+
+ client // remove initial value, execute again
+ .click(`${paramSelector} .json-schema-form-item-remove`)
+ .pause(400)
+ .click(`${paramSelector} .parameter__empty_value_toggle input`)
+ .click("button.btn.execute.opblock-control__btn")
+ .expect.element("textarea.curl").text
+ .to.contain(`GET "http://localhost:3230/emptyValueParams?arr="`)
+ })
+ })
+ })
+})
diff --git a/test/e2e/scenarios/features/allow-empty-value.swagger.js b/test/e2e/scenarios/features/allow-empty-value.swagger.js
new file mode 100644
index 00000000..bb118bac
--- /dev/null
+++ b/test/e2e/scenarios/features/allow-empty-value.swagger.js
@@ -0,0 +1,433 @@
+describe("feature: Swagger 2 allowEmptyValue", function () {
+ beforeEach(function (client, done) {
+ client
+ .url("localhost:3230")
+ .page.main()
+
+ client.waitForElementVisible(".download-url-input:not([disabled])", 5000)
+ .clearValue(".download-url-input")
+ .setValue(".download-url-input", "/test-specs/features/allow-empty-value.swagger.yaml")
+ .click("button.download-url-button")
+ .waitForElementVisible(".opblock", 10000)
+
+ done()
+ })
+
+ afterEach(function (client, done) {
+ done()
+ })
+
+ describe("regular parameters", function () {
+ it("should set and unset an integer value", function (client) {
+ const inputSelector = `tr[data-param-name="int"] input`
+
+ client // open try-it-out
+ .click("#operations-default-get_regularParams")
+ .waitForElementVisible("button.btn.try-out__btn", 5000)
+ .click("button.btn.try-out__btn")
+ .pause(200)
+
+ client // set parameter, to ensure an initial value is set
+ .setValue(inputSelector, "123")
+ .click("button.btn.execute.opblock-control__btn")
+ .pause(200)
+
+ client // remove initial value, execute again
+ .setValue(inputSelector, "\u0008\u0008\u0008") // backspaces
+ .pause(200)
+ .click("button.btn.execute.opblock-control__btn")
+ .expect.element("textarea.curl").text
+ .to.contain(`GET "http://localhost:3230/regularParams"`)
+ })
+ it("should set and unset a string value", function (client) {
+ const inputSelector = `tr[data-param-name="str"] input`
+
+ client // open try-it-out
+ .click("#operations-default-get_regularParams")
+ .waitForElementVisible("button.btn.try-out__btn", 5000)
+ .click("button.btn.try-out__btn")
+ .pause(200)
+
+ client // set parameter, to ensure an initial value is set
+ .setValue(inputSelector, "123")
+ .click("button.btn.execute.opblock-control__btn")
+ .pause(200)
+
+ client // remove initial value, execute again
+ .setValue(inputSelector, "\u0008\u0008\u0008") // backspaces
+ .pause(200)
+ .click("button.btn.execute.opblock-control__btn")
+ .expect.element("textarea.curl").text
+ .to.contain(`GET "http://localhost:3230/regularParams"`)
+ })
+ it("should set and unset a number value", function (client) {
+ const inputSelector = `tr[data-param-name="num"] input`
+
+ client // open try-it-out
+ .click("#operations-default-get_regularParams")
+ .waitForElementVisible("button.btn.try-out__btn", 5000)
+ .click("button.btn.try-out__btn")
+ .pause(200)
+
+ client // set parameter, to ensure an initial value is set
+ .setValue(inputSelector, "123")
+ .click("button.btn.execute.opblock-control__btn")
+ .pause(200)
+
+ client // remove initial value, execute again
+ .setValue(inputSelector, "\u0008\u0008\u0008") // backspaces
+ .pause(200)
+ .click("button.btn.execute.opblock-control__btn")
+ .expect.element("textarea.curl").text
+ .to.contain(`GET "http://localhost:3230/regularParams"`)
+ })
+ it("should set and unset a boolean value", function (client) {
+ const inputSelector = `tr[data-param-name="bool"] select`
+
+ client // open try-it-out
+ .click("#operations-default-get_regularParams")
+ .waitForElementVisible("button.btn.try-out__btn", 5000)
+ .click("button.btn.try-out__btn")
+ .pause(200)
+
+ client // set parameter, to ensure an initial value is set
+ .click(`${inputSelector} [value="true"]`)
+ .click("button.btn.execute.opblock-control__btn")
+ .pause(200)
+
+ client // remove initial value, execute again
+ .click(`${inputSelector} [value=""]`)
+ .pause(200)
+ .click("button.btn.execute.opblock-control__btn")
+ .expect.element("textarea.curl").text
+ .to.contain(`GET "http://localhost:3230/regularParams"`)
+ })
+ it("should set and unset an array value", function (client) {
+ const inputSelector = `tr[data-param-name="arr"]`
+
+ client // open try-it-out
+ .click("#operations-default-get_regularParams")
+ .waitForElementVisible("button.btn.try-out__btn", 5000)
+ .click("button.btn.try-out__btn")
+ .pause(200)
+
+ client // set parameter, to ensure an initial value is set
+ .click(`${inputSelector} .json-schema-form-item-add`)
+ .setValue(`${inputSelector} input`, "asdf")
+ .click("button.btn.execute.opblock-control__btn")
+ .pause(200)
+
+ client // remove initial value, execute again
+ .click(`${inputSelector} .json-schema-form-item-remove`)
+ .pause(200)
+ .click("button.btn.execute.opblock-control__btn")
+ .expect.element("textarea.curl").text
+ .to.contain(`GET "http://localhost:3230/regularParams"`)
+ })
+ })
+
+ describe("allowEmptyValue parameters", function () {
+ describe("normal behavior", function () {
+ it("should set and unset an integer value", function (client) {
+ const inputSelector = `tr[data-param-name="int"] input`
+
+ client // open try-it-out
+ .click("#operations-default-get_emptyValueParams")
+ .waitForElementVisible("button.btn.try-out__btn", 5000)
+ .click("button.btn.try-out__btn")
+ .pause(200)
+
+ client // set parameter, to ensure an initial value is set
+ .setValue(inputSelector, "123")
+ .click("button.btn.execute.opblock-control__btn")
+ .pause(200)
+
+ client // remove initial value, execute again
+ .setValue(inputSelector, "\u0008\u0008\u0008") // backspaces
+ .pause(200)
+ .click("button.btn.execute.opblock-control__btn")
+ .expect.element("textarea.curl").text
+ .to.contain(`GET "http://localhost:3230/emptyValueParams"`)
+ })
+ it("should set and unset a string value", function (client) {
+ const inputSelector = `tr[data-param-name="str"] input`
+
+ client // open try-it-out
+ .click("#operations-default-get_emptyValueParams")
+ .waitForElementVisible("button.btn.try-out__btn", 5000)
+ .click("button.btn.try-out__btn")
+ .pause(200)
+
+ client // set parameter, to ensure an initial value is set
+ .setValue(inputSelector, "123")
+ .click("button.btn.execute.opblock-control__btn")
+ .pause(200)
+
+ client // remove initial value, execute again
+ .setValue(inputSelector, "\u0008\u0008\u0008") // backspaces
+ .pause(200)
+ .click("button.btn.execute.opblock-control__btn")
+ .expect.element("textarea.curl").text
+ .to.contain(`GET "http://localhost:3230/emptyValueParams"`)
+ })
+ it("should set and unset a number value", function (client) {
+ const inputSelector = `tr[data-param-name="num"] input`
+
+ client // open try-it-out
+ .click("#operations-default-get_emptyValueParams")
+ .waitForElementVisible("button.btn.try-out__btn", 5000)
+ .click("button.btn.try-out__btn")
+ .pause(200)
+
+ client // set parameter, to ensure an initial value is set
+ .setValue(inputSelector, "123")
+ .click("button.btn.execute.opblock-control__btn")
+ .pause(200)
+
+ client // remove initial value, execute again
+ .setValue(inputSelector, "\u0008\u0008\u0008") // backspaces
+ .pause(200)
+ .click("button.btn.execute.opblock-control__btn")
+ .expect.element("textarea.curl").text
+ .to.contain(`GET "http://localhost:3230/emptyValueParams"`)
+ })
+ it("should set and unset a boolean value", function (client) {
+ const inputSelector = `tr[data-param-name="bool"] select`
+
+ client // open try-it-out
+ .click("#operations-default-get_emptyValueParams")
+ .waitForElementVisible("button.btn.try-out__btn", 5000)
+ .click("button.btn.try-out__btn")
+ .pause(200)
+
+ client // set parameter, to ensure an initial value is set
+ .click(`${inputSelector} [value="true"]`)
+ .click("button.btn.execute.opblock-control__btn")
+ .pause(200)
+
+ client // remove initial value, execute again
+ .click(`${inputSelector} [value=""]`)
+ .pause(200)
+ .click("button.btn.execute.opblock-control__btn")
+ .expect.element("textarea.curl").text
+ .to.contain(`GET "http://localhost:3230/emptyValueParams"`)
+ })
+ it("should set and unset an array value", function (client) {
+ const inputSelector = `tr[data-param-name="arr"]`
+
+ client // open try-it-out
+ .click("#operations-default-get_emptyValueParams")
+ .waitForElementVisible("button.btn.try-out__btn", 5000)
+ .click("button.btn.try-out__btn")
+ .pause(200)
+
+ client // set parameter, to ensure an initial value is set
+ .click(`${inputSelector} .json-schema-form-item-add`)
+ .setValue(`${inputSelector} input`, "asdf")
+ .click("button.btn.execute.opblock-control__btn")
+ .pause(200)
+
+ client // remove initial value, execute again
+ .click(`${inputSelector} .json-schema-form-item-remove`)
+ .pause(200)
+ .click("button.btn.execute.opblock-control__btn")
+ .expect.element("textarea.curl").text
+ .to.contain(`GET "http://localhost:3230/emptyValueParams"`)
+ })
+ })
+ describe("send empty inital value behavior", function () {
+ it("should send an empty integer value", function (client) {
+ const paramSelector = `tr[data-param-name="int"]`
+
+ client // open try-it-out
+ .click("#operations-default-get_emptyValueParams")
+ .waitForElementVisible("button.btn.try-out__btn", 5000)
+ .click("button.btn.try-out__btn")
+ .pause(200)
+
+ client // tick "send empty value" box and execute
+ .click(`${paramSelector} .parameter__empty_value_toggle input`)
+ .click("button.btn.execute.opblock-control__btn")
+ .expect.element("textarea.curl").text
+ .to.contain(`GET "http://localhost:3230/emptyValueParams?int="`)
+ })
+ it("should send an empty string value", function (client) {
+ const paramSelector = `tr[data-param-name="str"]`
+
+ client // open try-it-out
+ .click("#operations-default-get_emptyValueParams")
+ .waitForElementVisible("button.btn.try-out__btn", 5000)
+ .click("button.btn.try-out__btn")
+ .pause(200)
+
+ client // tick "send empty value" box and execute
+ .click(`${paramSelector} .parameter__empty_value_toggle input`)
+ .click("button.btn.execute.opblock-control__btn")
+ .expect.element("textarea.curl").text
+ .to.contain(`GET "http://localhost:3230/emptyValueParams?str="`)
+ })
+ it("should send an empty number value", function (client) {
+ const paramSelector = `tr[data-param-name="num"]`
+
+ client // open try-it-out
+ .click("#operations-default-get_emptyValueParams")
+ .waitForElementVisible("button.btn.try-out__btn", 5000)
+ .click("button.btn.try-out__btn")
+ .pause(200)
+
+ client // tick "send empty value" box and execute
+ .click(`${paramSelector} .parameter__empty_value_toggle input`)
+ .click("button.btn.execute.opblock-control__btn")
+ .expect.element("textarea.curl").text
+ .to.contain(`GET "http://localhost:3230/emptyValueParams?num="`)
+ })
+ it("should send an empty boolean value", function (client) {
+ const paramSelector = `tr[data-param-name="bool"]`
+
+ client // open try-it-out
+ .click("#operations-default-get_emptyValueParams")
+ .waitForElementVisible("button.btn.try-out__btn", 5000)
+ .click("button.btn.try-out__btn")
+ .pause(200)
+
+ client // tick "send empty value" box and execute
+ .click(`${paramSelector} .parameter__empty_value_toggle input`)
+ .click("button.btn.execute.opblock-control__btn")
+ .expect.element("textarea.curl").text
+ .to.contain(`GET "http://localhost:3230/emptyValueParams?bool="`)
+ })
+ it("should send an empty array value", function (client) {
+ const paramSelector = `tr[data-param-name="arr"]`
+
+ client // open try-it-out
+ .click("#operations-default-get_emptyValueParams")
+ .waitForElementVisible("button.btn.try-out__btn", 5000)
+ .click("button.btn.try-out__btn")
+ .pause(200)
+
+ client // tick "send empty value" box and execute
+ .click(`${paramSelector} .parameter__empty_value_toggle input`)
+ .click("button.btn.execute.opblock-control__btn")
+ .expect.element("textarea.curl").text
+ .to.contain(`GET "http://localhost:3230/emptyValueParams?arr="`)
+ })
+ })
+ describe("modify and send empty behavior", function () {
+ it("should set, unset and send an empty integer value", function (client) {
+ const paramSelector = `tr[data-param-name="int"]`
+ const inputSelector = `${paramSelector} input`
+
+ client // open try-it-out
+ .click("#operations-default-get_emptyValueParams")
+ .waitForElementVisible("button.btn.try-out__btn", 5000)
+ .click("button.btn.try-out__btn")
+ .pause(200)
+
+ client // set parameter, to ensure an initial value is set
+ .setValue(inputSelector, "123")
+ .click("button.btn.execute.opblock-control__btn")
+ .pause(200)
+
+ client // remove initial value, click "send empty", execute again, assert
+ .setValue(inputSelector, "\u0008\u0008\u0008") // backspaces
+ .pause(400)
+ .click(`${paramSelector} .parameter__empty_value_toggle input`)
+ .click("button.btn.execute.opblock-control__btn")
+ .expect.element("textarea.curl").text
+ .to.contain(`GET "http://localhost:3230/emptyValueParams?int="`)
+ })
+ it("should set, unset and send an empty string value", function (client) {
+ const paramSelector = `tr[data-param-name="str"]`
+ const inputSelector = `${paramSelector} input`
+
+ client // open try-it-out
+ .click("#operations-default-get_emptyValueParams")
+ .waitForElementVisible("button.btn.try-out__btn", 5000)
+ .click("button.btn.try-out__btn")
+ .pause(200)
+
+ client // set parameter, to ensure an initial value is set
+ .setValue(inputSelector, "123")
+ .click("button.btn.execute.opblock-control__btn")
+ .pause(200)
+
+ client // remove initial value, click "send empty", execute again, assert
+ .setValue(inputSelector, "\u0008\u0008\u0008") // backspaces
+ .pause(400)
+ .click(`${paramSelector} .parameter__empty_value_toggle input`)
+ .click("button.btn.execute.opblock-control__btn")
+ .expect.element("textarea.curl").text
+ .to.contain(`GET "http://localhost:3230/emptyValueParams?str="`)
+ })
+ it("should set, unset and send an empty number value", function (client) {
+ const paramSelector = `tr[data-param-name="num"]`
+ const inputSelector = `${paramSelector} input`
+
+ client // open try-it-out
+ .click("#operations-default-get_emptyValueParams")
+ .waitForElementVisible("button.btn.try-out__btn", 5000)
+ .click("button.btn.try-out__btn")
+ .pause(200)
+
+ client // set parameter, to ensure an initial value is set
+ .setValue(inputSelector, "123")
+ .click("button.btn.execute.opblock-control__btn")
+ .pause(200)
+
+ client // remove initial value, click "send empty", execute again, assert
+ .setValue(inputSelector, "\u0008\u0008\u0008") // backspaces
+ .pause(400)
+ .click(`${paramSelector} .parameter__empty_value_toggle input`)
+ .click("button.btn.execute.opblock-control__btn")
+ .expect.element("textarea.curl").text
+ .to.contain(`GET "http://localhost:3230/emptyValueParams?num="`)
+ })
+ it("should set, unset and send an empty boolean value", function (client) {
+ const paramSelector = `tr[data-param-name="bool"]`
+ const inputSelector = `${paramSelector} select`
+
+ client // open try-it-out
+ .click("#operations-default-get_emptyValueParams")
+ .waitForElementVisible("button.btn.try-out__btn", 5000)
+ .click("button.btn.try-out__btn")
+ .pause(200)
+
+ client // set parameter, to ensure an initial value is set
+ .click(`${inputSelector} option[value="true"]`)
+ .click("button.btn.execute.opblock-control__btn")
+ .pause(200)
+
+ client // remove initial value, click "send empty", execute again, assert
+ .click(`${inputSelector} option[value=""]`)
+ .pause(400)
+ .click(`${paramSelector} .parameter__empty_value_toggle input`)
+ .click("button.btn.execute.opblock-control__btn")
+ .expect.element("textarea.curl").text
+ .to.contain(`GET "http://localhost:3230/emptyValueParams?bool="`)
+ })
+ it("should set, unset and send an empty array value", function (client) {
+ const paramSelector = `tr[data-param-name="arr"]`
+
+ client // open try-it-out
+ .click("#operations-default-get_emptyValueParams")
+ .waitForElementVisible("button.btn.try-out__btn", 5000)
+ .click("button.btn.try-out__btn")
+ .pause(200)
+
+ client // set parameter, to ensure an initial value is set
+ .click(`${paramSelector} .json-schema-form-item-add`)
+ .setValue(`${paramSelector} input`, "asdf")
+ .click("button.btn.execute.opblock-control__btn")
+ .pause(200)
+
+ client // remove initial value, execute again
+ .click(`${paramSelector} .json-schema-form-item-remove`)
+ .pause(400)
+ .click(`${paramSelector} .parameter__empty_value_toggle input`)
+ .click("button.btn.execute.opblock-control__btn")
+ .expect.element("textarea.curl").text
+ .to.contain(`GET "http://localhost:3230/emptyValueParams?arr="`)
+ })
+ })
+ })
+})
diff --git a/test/e2e/specs/bugs/4587.yaml b/test/e2e/specs/bugs/4587.yaml
new file mode 100644
index 00000000..f8e08217
--- /dev/null
+++ b/test/e2e/specs/bugs/4587.yaml
@@ -0,0 +1,126 @@
+---
+swagger: '2.0'
+info:
+ version: 0.0.1
+ title: DDErl REST interface
+ description: RESTful access to IMEM DB and DDErl
+schemes:
+- http
+- https
+securityDefinitions:
+ basicAuth:
+ type: basic
+ description: HTTP Basic Authentication Username:Password
+basePath: "/dderlrest/0.0.1"
+paths:
+ "/sql/":
+ get:
+ tags:
+ - sql
+ security:
+ - basicAuth: []
+ summary: execute sql
+ operationId: execSql
+ description: Prepare and execute SQL statements
+ parameters:
+ - name: x-irest-conn
+ in: header
+ required: false
+ description: ErlImem connection identifier
+ type: string
+ produces:
+ - application/json
+ responses:
+ '200':
+ description: OK
+ schema:
+ type: object
+ headers:
+ x-irest-conn:
+ description: ErlImem connection identifier
+ type: string
+ '403':
+ description: Malformed/Invalid
+ schema:
+ "$ref": "#/definitions/ErrorResponse"
+definitions:
+ ErrorResponse:
+ readOnly: true
+ type: object
+ required:
+ - errorCode
+ - errorMessage
+ - errorDetails
+ properties:
+ errorCode:
+ description: Error Code
+ type: number
+ example: 1400
+ errorMessage:
+ description: Error Message
+ type: string
+ example: malformed
+ errorDetails:
+ description: Error Details
+ type: string
+ example: mandatory properties missing or bad type
+ ViewParams:
+ readOnly: true
+ type: array
+ items:
+ type: object
+ required:
+ - typ
+ - value
+ - name
+ properties:
+ name:
+ description: Name
+ type: string
+ example: ":atom_user"
+ value:
+ description: Value
+ type: string
+ example: system
+ typ:
+ description: Datatype
+ type: string
+ enum:
+ - atom
+ - binary
+ - raw
+ - blob
+ - rowid
+ - binstr
+ - clob
+ - nclob
+ - varchar2
+ - nvarchar2
+ - char
+ - nchar
+ - boolean
+ - datetime
+ - decimal
+ - float
+ - fun
+ - integer
+ - ipaddr
+ - list
+ - map
+ - number
+ - pid
+ - ref
+ - string
+ - term
+ - binterm
+ - timestamp
+ - tuple
+ - userid
+ example: atom
+ dir:
+ description: Direction
+ type: string
+ enum:
+ - in
+ - out
+ default: in
diff --git a/test/e2e/specs/features/allow-empty-value.openapi.yaml b/test/e2e/specs/features/allow-empty-value.openapi.yaml
new file mode 100644
index 00000000..7190097c
--- /dev/null
+++ b/test/e2e/specs/features/allow-empty-value.openapi.yaml
@@ -0,0 +1,64 @@
+openapi: "3.0.0"
+
+paths:
+ /regularParams:
+ get:
+ parameters:
+ - name: int
+ in: query
+ schema:
+ type: integer
+ - name: str
+ in: query
+ schema:
+ type: string
+ - name: num
+ in: query
+ schema:
+ type: number
+ - name: bool
+ in: query
+ schema:
+ type: boolean
+ - name: arr
+ in: query
+ schema:
+ type: array
+ items:
+ type: string
+ responses:
+ 200:
+ description: ok
+ /emptyValueParams:
+ get:
+ parameters:
+ - name: int
+ in: query
+ schema:
+ type: integer
+ allowEmptyValue: true
+ - name: str
+ in: query
+ schema:
+ type: string
+ allowEmptyValue: true
+ - name: num
+ in: query
+ schema:
+ type: number
+ allowEmptyValue: true
+ - name: bool
+ in: query
+ schema:
+ type: boolean
+ allowEmptyValue: true
+ - name: arr
+ in: query
+ schema:
+ type: array
+ items:
+ type: string
+ allowEmptyValue: true
+ responses:
+ 200:
+ description: ok
\ No newline at end of file
diff --git a/test/e2e/specs/features/allow-empty-value.swagger.yaml b/test/e2e/specs/features/allow-empty-value.swagger.yaml
new file mode 100644
index 00000000..9075987c
--- /dev/null
+++ b/test/e2e/specs/features/allow-empty-value.swagger.yaml
@@ -0,0 +1,57 @@
+swagger: "2.0"
+
+consumes:
+- application/json
+- multipart/form-data
+paths:
+ /regularParams:
+ get:
+ parameters:
+ - name: int
+ in: query
+ type: integer
+ - name: str
+ in: query
+ type: string
+ - name: num
+ in: query
+ type: number
+ - name: bool
+ in: query
+ type: boolean
+ - name: arr
+ in: query
+ type: array
+ items:
+ type: string
+ responses:
+ 200:
+ description: ok
+ /emptyValueParams:
+ get:
+ parameters:
+ - name: int
+ in: query
+ type: integer
+ allowEmptyValue: true
+ - name: str
+ in: query
+ type: string
+ allowEmptyValue: true
+ - name: num
+ in: query
+ type: number
+ allowEmptyValue: true
+ - name: bool
+ in: query
+ type: boolean
+ allowEmptyValue: true
+ - name: arr
+ in: query
+ type: array
+ items:
+ type: string
+ allowEmptyValue: true
+ responses:
+ 200:
+ description: ok
\ No newline at end of file