feat: Multiple Examples for OpenAPI 3 Parameters, Request Bodies, and Responses (via #5427)

* add opt-in Prettier config

* remove legacy `examples` implementation

* create ExamplesSelect

* support `Response.examples` in OpenAPI 3

* create response controls group

* prettier reformat

* prepare to break up Parameters

* reunify Parameters and OAS3 Parameters

* Parameter Examples

* Example component

* handle parameter value stringification correctly

* FOR REVIEW: add prop for controlling Select

* use regular header for param examples in Try-It-Out

* manage active examples member via Redux

* Request Body Try-It-Out examples

* remove special Response description styling

* omit Example value display in Try-It-Out

* support disabled text inputs in JsonSchemaForm

* Example.omitValue => Example.showValue

* ExamplesSelectValueRetainer

* styling for disabled inputs

* remove console.log

* support "Modified Values" in ExamplesSelect

* remove Examples component
(wasn't used anywhere)

* use ParameterRow.getParamKey for active examples member keying

* split-rendering of examples in ParameterRow

* send disabled prop to JsonSchemaForm

* use content type to key request body active examples members

* remove debugger

* rewire RequestBodyEditor to be a controlled component

REVIEW: does this have perf implications?

* trigger synthetic onSelect events in ExamplesSelect

* prettier updates

* remove outdated Examples usage in RequestBody

* don't handle examples changes in ESVR

* make RequestBodyEditor semi-controlled

* don't default to an empty Map for request bodies

* add namespaceKey to ESVR for state mgmt

* don't key RequestBody activeExampleKeys on media type

* tweak ESVR isModifiedValueSelected calculation

* add trace class to ExamplesSelect

* remove usage of ESVR.currentNamespace

* reset to first example if currentExampleKey is invalid

* add default values to RequestBody rendering

* stringify things in ESVR

* avoid null select value (silences React warning)

* detect user inputs that match any examples member's value

* add trace class for json-schema-array

* shallowly convert namespace state, to preserve Immutable stucts in state

* stringify RBE values; don't trim JSON in editor

* match user input to an example when non-primitives are expressed in state as strings

* update Cypress

* don't apply sample values in JsonSchema_Object

* support disabling all JsonSchemaForm subcomponents

* Core tests

* style changes to accomodate Examples

* fix version-checking error in Response

* disable SCU for Responses

* don't stringify Select values

* ModelExample: default to Model tab if no example is available; provide a default no example message

* don't trim JSON ParamBody inputs

* read directly from 2.0 Response.schema instead of inferring a value

* show current Example information in RequestBody

* show label for Examples dropdown by default

* rework Response content ordering

* style disabled textareas like other read-only blocks

* meta: fix sourcemaps

* refactor ESVR setNameForNamespace

* protect second half of ternary expession

* cypress: `select.examples-select` => `.examples-select > select`

* clarify ModelExample.componentWillReceiveProps

* add gates/defaults to prevent issues in very bare-boned documents

* fix test block organization problem

* simplify RequestBodyEditor interface

* linter fixes

* prettier updates

* use plugin system for new components

* move ME Cypress helpers to other file
This commit is contained in:
kyle
2019-06-29 19:52:51 +01:00
committed by GitHub
parent 332ddaedcd
commit 23d7260f92
34 changed files with 3148 additions and 653 deletions

5
.prettierrc.yaml Normal file
View File

@@ -0,0 +1,5 @@
semi: false
trailingComma: es5
endOfLine: lf
requirePragma: true
insertPragma: true

View File

@@ -150,7 +150,11 @@ module.exports = function(rules, options) {
}
},
devtool: specialOptions.sourcemaps ? "nosource-source-map" : false,
devtool: specialOptions.sourcemaps ? (
// If we're mangling, size is a concern -- so use trace-only sourcemaps
// Otherwise, provide heavy souremaps suitable for development
specialOptions.minimize ? "nosource-source-map" : "cheap-module-source-map"
): false,
plugins,

537
package-lock.json generated
View File

@@ -84,9 +84,9 @@
}
},
"@cypress/xvfb": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.3.tgz",
"integrity": "sha512-yYrK+/bgL3hwoRHMZG4r5fyLniCy1pXex5fimtewAY6vE/jsVs8Q37UsEO03tFlcmiLnQ3rBNMaZBYTi/+C1cw==",
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz",
"integrity": "sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==",
"dev": true,
"requires": {
"debug": "^3.1.0",
@@ -103,9 +103,9 @@
}
},
"ms": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
}
}
@@ -263,74 +263,6 @@
"integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==",
"dev": true
},
"@types/blob-util": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@types/blob-util/-/blob-util-1.3.3.tgz",
"integrity": "sha512-4ahcL/QDnpjWA2Qs16ZMQif7HjGP2cw3AGjHabybjw7Vm1EKu+cfQN1D78BaZbS1WJNa1opSMF5HNMztx7lR0w==",
"dev": true
},
"@types/bluebird": {
"version": "3.5.18",
"resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.18.tgz",
"integrity": "sha512-OTPWHmsyW18BhrnG5x8F7PzeZ2nFxmHGb42bZn79P9hl+GI5cMzyPgQTwNjbem0lJhoru/8vtjAFCUOu3+gE2w==",
"dev": true
},
"@types/chai": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.0.8.tgz",
"integrity": "sha512-m812CONwdZn/dMzkIJEY0yAs4apyTkTORgfB2UsMOxgkUbC205AHnm4T8I0I5gPg9MHrFc1dJ35iS75c0CJkjg==",
"dev": true
},
"@types/chai-jquery": {
"version": "1.1.35",
"resolved": "https://registry.npmjs.org/@types/chai-jquery/-/chai-jquery-1.1.35.tgz",
"integrity": "sha512-7aIt9QMRdxuagLLI48dPz96YJdhu64p6FCa6n4qkGN5DQLHnrIjZpD9bXCvV2G0NwgZ1FAmfP214dxc5zNCfgQ==",
"dev": true,
"requires": {
"@types/chai": "*",
"@types/jquery": "*"
}
},
"@types/jquery": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.3.6.tgz",
"integrity": "sha512-403D4wN95Mtzt2EoQHARf5oe/jEPhzBOBNrunk+ydQGW8WmkQ/E8rViRAEB1qEt/vssfGfNVD6ujP4FVeegrLg==",
"dev": true
},
"@types/lodash": {
"version": "4.14.87",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.87.tgz",
"integrity": "sha512-AqRC+aEF4N0LuNHtcjKtvF9OTfqZI0iaBoe3dA6m/W+/YZJBZjBmW/QIZ8fBeXC6cnytSY9tBoFBqZ9uSCeVsw==",
"dev": true
},
"@types/minimatch": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
"integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==",
"dev": true
},
"@types/mocha": {
"version": "2.2.44",
"resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-2.2.44.tgz",
"integrity": "sha512-k2tWTQU8G4+iSMvqKi0Q9IIsWAp/n8xzdZS4Q4YVIltApoMA00wFBFdlJnmoaK1/z7B0Cy0yPe6GgXteSmdUNw==",
"dev": true
},
"@types/sinon": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-7.0.0.tgz",
"integrity": "sha512-kcYoPw0uKioFVC/oOqafk2yizSceIQXCYnkYts9vJIwQklFRsMubTObTDrjQamUyBRd47332s85074cd/hCwxg==",
"dev": true
},
"@types/sinon-chai": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/@types/sinon-chai/-/sinon-chai-3.2.2.tgz",
"integrity": "sha512-5zSs2AslzyPZdOsbm2NRtuSNAI2aTWzNKOHa/GRecKo7a5efYD7qGcPxMZXQDayVXT2Vnd5waXxBvV31eCZqiA==",
"dev": true,
"requires": {
"@types/chai": "*",
"@types/sinon": "*"
}
},
"JSONSelect": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/JSONSelect/-/JSONSelect-0.4.0.tgz",
@@ -530,6 +462,12 @@
"integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
"dev": true
},
"arch": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/arch/-/arch-2.1.1.tgz",
"integrity": "sha512-BLM56aPo9vLLFVa8+/+pJLnrZ7QGGTVHWsCwieAWT9o9K8UeGaQbzZbGoabWLOo2ksBCztoXdqBZBplqLDDCSg==",
"dev": true
},
"are-we-there-yet": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz",
@@ -2422,6 +2360,12 @@
"integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=",
"dev": true
},
"buffer-from": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
"dev": true
},
"buffer-indexof": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz",
@@ -3266,13 +3210,10 @@
"dev": true
},
"common-tags": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.4.0.tgz",
"integrity": "sha1-EYe+Tz1M8MBCfUP3Tu8fc1AWFMA=",
"dev": true,
"requires": {
"babel-runtime": "^6.18.0"
}
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.0.tgz",
"integrity": "sha512-6P6g0uetGpW/sdyUy/iQQCbFF0kWVMSIVSyYz7Zgjcgh8mgw8PQzDNZeyZ5DQ2gM7LBoZPHmnjz8rUthkBG5tw==",
"dev": true
},
"commondir": {
"version": "1.0.1",
@@ -4679,65 +4620,97 @@
"dev": true
},
"cypress": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/cypress/-/cypress-3.1.5.tgz",
"integrity": "sha512-jzYGKJqU1CHoNocPndinf/vbG28SeU+hg+4qhousT/HDBMJxYgjecXOmSgBX/ga9/TakhqSrIrSP2r6gW/OLtg==",
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/cypress/-/cypress-3.3.1.tgz",
"integrity": "sha512-JIo47ZD9P3jAw7oaK7YKUoODzszJbNw41JmBrlMMiupHOlhmXvZz75htuo7mfRFPC9/1MDQktO4lX/V2+a6lGQ==",
"dev": true,
"requires": {
"@cypress/listr-verbose-renderer": "0.4.1",
"@cypress/xvfb": "1.2.3",
"@types/blob-util": "1.3.3",
"@types/bluebird": "3.5.18",
"@types/chai": "4.0.8",
"@types/chai-jquery": "1.1.35",
"@types/jquery": "3.3.6",
"@types/lodash": "4.14.87",
"@types/minimatch": "3.0.3",
"@types/mocha": "2.2.44",
"@types/sinon": "7.0.0",
"@types/sinon-chai": "3.2.2",
"@cypress/xvfb": "1.2.4",
"arch": "2.1.1",
"bluebird": "3.5.0",
"cachedir": "1.3.0",
"chalk": "2.4.1",
"chalk": "2.4.2",
"check-more-types": "2.24.0",
"commander": "2.11.0",
"common-tags": "1.4.0",
"debug": "3.1.0",
"commander": "2.15.1",
"common-tags": "1.8.0",
"debug": "3.2.6",
"execa": "0.10.0",
"executable": "4.1.1",
"extract-zip": "1.6.6",
"extract-zip": "1.6.7",
"fs-extra": "4.0.1",
"getos": "3.1.0",
"glob": "7.1.2",
"is-ci": "1.0.10",
"getos": "3.1.1",
"glob": "7.1.3",
"is-ci": "1.2.1",
"is-installed-globally": "0.1.0",
"lazy-ass": "1.6.0",
"listr": "0.12.0",
"lodash": "4.17.11",
"log-symbols": "2.2.0",
"minimist": "1.2.0",
"moment": "2.22.2",
"moment": "2.24.0",
"ramda": "0.24.1",
"request": "2.87.0",
"request-progress": "0.3.1",
"supports-color": "5.1.0",
"tmp": "0.0.31",
"request": "2.88.0",
"request-progress": "0.4.0",
"supports-color": "5.5.0",
"tmp": "0.1.0",
"url": "0.11.0",
"yauzl": "2.8.0"
"yauzl": "2.10.0"
},
"dependencies": {
"ajv": {
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz",
"integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==",
"dev": true,
"requires": {
"fast-deep-equal": "^2.0.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
}
},
"aws4": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz",
"integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==",
"dev": true
},
"bluebird": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.0.tgz",
"integrity": "sha1-eRQg1/VR7qKJdFOop3ZT+WYG1nw=",
"dev": true
},
"commander": {
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz",
"integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==",
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
}
},
"ci-info": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz",
"integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==",
"dev": true
},
"concat-stream": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
"integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
"dev": true,
"requires": {
"buffer-from": "^1.0.0",
"inherits": "^2.0.3",
"readable-stream": "^2.2.2",
"typedarray": "^0.0.6"
}
},
"cross-spawn": {
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
@@ -4752,12 +4725,12 @@
}
},
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
"dev": true,
"requires": {
"ms": "2.0.0"
"ms": "^2.1.1"
}
},
"execa": {
@@ -4775,6 +4748,56 @@
"strip-eof": "^1.0.0"
}
},
"extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
"dev": true
},
"extract-zip": {
"version": "1.6.7",
"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.7.tgz",
"integrity": "sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k=",
"dev": true,
"requires": {
"concat-stream": "1.6.2",
"debug": "2.6.9",
"mkdirp": "0.5.1",
"yauzl": "2.4.1"
},
"dependencies": {
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"dev": true,
"requires": {
"ms": "2.0.0"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
"dev": true
},
"yauzl": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz",
"integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=",
"dev": true,
"requires": {
"fd-slicer": "~1.0.1"
}
}
}
},
"fast-deep-equal": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
"integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=",
"dev": true
},
"form-data": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
@@ -4797,21 +4820,45 @@
"universalify": "^0.1.0"
}
},
"has-flag": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz",
"integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=",
"dev": true
},
"is-ci": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.0.10.tgz",
"integrity": "sha1-9zkzayYyNlBhqdSCcM1WrjNpMY4=",
"glob": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
"integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
"dev": true,
"requires": {
"ci-info": "^1.0.0"
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
},
"har-validator": {
"version": "5.1.3",
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
"integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
"dev": true,
"requires": {
"ajv": "^6.5.5",
"har-schema": "^2.0.0"
}
},
"is-ci": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz",
"integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==",
"dev": true,
"requires": {
"ci-info": "^1.5.0"
}
},
"json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true
},
"jsonfile": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-3.0.1.tgz",
@@ -4830,12 +4877,51 @@
"chalk": "^2.0.1"
}
},
"mime-db": {
"version": "1.40.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz",
"integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==",
"dev": true
},
"mime-types": {
"version": "2.1.24",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz",
"integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==",
"dev": true,
"requires": {
"mime-db": "1.40.0"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
"oauth-sign": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
"integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==",
"dev": true
},
"performance-now": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=",
"dev": true
},
"punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
"dev": true
},
"qs": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
"integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==",
"dev": true
},
"ramda": {
"version": "0.24.1",
"resolved": "https://registry.npmjs.org/ramda/-/ramda-0.24.1.tgz",
@@ -4843,59 +4929,112 @@
"dev": true
},
"request": {
"version": "2.87.0",
"resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz",
"integrity": "sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw==",
"version": "2.88.0",
"resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz",
"integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==",
"dev": true,
"requires": {
"aws-sign2": "~0.7.0",
"aws4": "^1.6.0",
"aws4": "^1.8.0",
"caseless": "~0.12.0",
"combined-stream": "~1.0.5",
"extend": "~3.0.1",
"combined-stream": "~1.0.6",
"extend": "~3.0.2",
"forever-agent": "~0.6.1",
"form-data": "~2.3.1",
"har-validator": "~5.0.3",
"form-data": "~2.3.2",
"har-validator": "~5.1.0",
"http-signature": "~1.2.0",
"is-typedarray": "~1.0.0",
"isstream": "~0.1.2",
"json-stringify-safe": "~5.0.1",
"mime-types": "~2.1.17",
"oauth-sign": "~0.8.2",
"mime-types": "~2.1.19",
"oauth-sign": "~0.9.0",
"performance-now": "^2.1.0",
"qs": "~6.5.1",
"safe-buffer": "^5.1.1",
"tough-cookie": "~2.3.3",
"qs": "~6.5.2",
"safe-buffer": "^5.1.2",
"tough-cookie": "~2.4.3",
"tunnel-agent": "^0.6.0",
"uuid": "^3.1.0"
"uuid": "^3.3.2"
}
},
"rimraf": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
"integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
"dev": true,
"requires": {
"glob": "^7.1.3"
}
},
"supports-color": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.1.0.tgz",
"integrity": "sha512-Ry0AwkoKjDpVKK4sV4h6o3UJmNRbjYm2uXhwfj3J56lMVdvnUNqzQVRztOOMGQ++w1K/TjNDFvpJk0F/LoeBCQ==",
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"requires": {
"has-flag": "^2.0.0"
"has-flag": "^3.0.0"
}
},
"tmp": {
"version": "0.0.31",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.31.tgz",
"integrity": "sha1-jzirlDjhcxXl29izZX6L+yd65Kc=",
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.1.0.tgz",
"integrity": "sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw==",
"dev": true,
"requires": {
"os-tmpdir": "~1.0.1"
"rimraf": "^2.6.3"
}
},
"tough-cookie": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
"integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==",
"dev": true,
"requires": {
"psl": "^1.1.24",
"punycode": "^1.4.1"
},
"dependencies": {
"punycode": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
"integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
"dev": true
}
}
},
"uri-js": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
"integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
"dev": true,
"requires": {
"punycode": "^2.1.0"
}
},
"uuid": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
"integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==",
"dev": true
},
"yauzl": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.8.0.tgz",
"integrity": "sha1-eUUK/yKyqcWkHvVOAtuQfM+/nuI=",
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
"integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=",
"dev": true,
"requires": {
"buffer-crc32": "~0.2.3",
"fd-slicer": "~1.0.1"
"fd-slicer": "~1.1.0"
},
"dependencies": {
"fd-slicer": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
"integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=",
"dev": true,
"requires": {
"pend": "~1.2.0"
}
}
}
}
}
@@ -7148,7 +7287,8 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"aproba": {
"version": "1.2.0",
@@ -7169,12 +7309,14 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -7189,17 +7331,20 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"core-util-is": {
"version": "1.0.2",
@@ -7316,7 +7461,8 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"ini": {
"version": "1.3.5",
@@ -7328,6 +7474,7 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@@ -7342,6 +7489,7 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@@ -7349,12 +7497,14 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"minipass": {
"version": "2.3.5",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@@ -7373,6 +7523,7 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
@@ -7453,7 +7604,8 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"object-assign": {
"version": "4.1.1",
@@ -7465,6 +7617,7 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@@ -7550,7 +7703,8 @@
"safe-buffer": {
"version": "5.1.2",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"safer-buffer": {
"version": "2.1.2",
@@ -7586,6 +7740,7 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@@ -7605,6 +7760,7 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@@ -7648,12 +7804,14 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"yallist": {
"version": "3.0.3",
"bundled": true,
"dev": true
"dev": true,
"optional": true
}
}
},
@@ -7827,21 +7985,21 @@
"dev": true
},
"getos": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/getos/-/getos-3.1.0.tgz",
"integrity": "sha512-i9vrxtDu5DlLVFcrbqUqGWYlZN/zZ4pGMICCAcZoYsX3JA54nYp8r5EThw5K+m2q3wszkx4Th746JstspB0H4Q==",
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/getos/-/getos-3.1.1.tgz",
"integrity": "sha512-oUP1rnEhAr97rkitiszGP9EgDVYnmchgFzfqRzSkgtfv7ai6tEi7Ko8GgjNXts7VLWEqrTWyhsOKLe5C5b/Zkg==",
"dev": true,
"requires": {
"async": "2.4.0"
"async": "2.6.1"
},
"dependencies": {
"async": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/async/-/async-2.4.0.tgz",
"integrity": "sha1-SZAgDxjqW4N8LMT4wDGmmFw4VhE=",
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz",
"integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==",
"dev": true,
"requires": {
"lodash": "^4.14.0"
"lodash": "^4.17.10"
}
}
}
@@ -11879,9 +12037,9 @@
"dev": true
},
"moment": {
"version": "2.22.2",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz",
"integrity": "sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y=",
"version": "2.24.0",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz",
"integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==",
"dev": true
},
"morgan": {
@@ -12100,6 +12258,12 @@
"integrity": "sha1-VfuN62mQcHB/tn+RpGDwRIKUx30=",
"dev": true
},
"node-eta": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/node-eta/-/node-eta-0.1.1.tgz",
"integrity": "sha1-QGYQmzk3HHYccrfr2pqeoKXeEh8=",
"dev": true
},
"node-fetch": {
"version": "1.7.3",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz",
@@ -12791,6 +12955,7 @@
"resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz",
"integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=",
"dev": true,
"optional": true,
"requires": {
"kind-of": "^3.0.2",
"longest": "^1.0.1",
@@ -14099,7 +14264,8 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz",
"integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=",
"dev": true
"dev": true,
"optional": true
},
"loose-envify": {
"version": "1.3.1",
@@ -18572,9 +18738,9 @@
"dev": true
},
"prettier": {
"version": "1.12.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-1.12.1.tgz",
"integrity": "sha1-wa0g6APndJ+vkFpAnSNn4Gu+cyU=",
"version": "1.18.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-1.18.2.tgz",
"integrity": "sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw==",
"dev": true
},
"pretty-bytes": {
@@ -20099,12 +20265,13 @@
}
},
"request-progress": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/request-progress/-/request-progress-0.3.1.tgz",
"integrity": "sha1-ByHBBdipasayzossia4tXs/Pazo=",
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/request-progress/-/request-progress-0.4.0.tgz",
"integrity": "sha1-wZVOOQhqqFJpxWYLzuAUKmpw1+c=",
"dev": true,
"requires": {
"throttleit": "~0.0.2"
"node-eta": "^0.1.1",
"throttleit": "^0.0.2"
}
},
"request-promise-core": {
@@ -23435,7 +23602,7 @@
"webpack-addons": "^1.1.5",
"yargs": "^9.0.1",
"yeoman-environment": "^2.0.0",
"yeoman-generator": "git://github.com/ev1stensberg/generator.git#9e24fa31c85302ca1145ae34fc68b4f133251ca0"
"yeoman-generator": "git://github.com/ev1stensberg/generator.git#Feature-getArgument"
},
"dependencies": {
"ansi-escapes": {

View File

@@ -101,7 +101,7 @@
"copy-webpack-plugin": "^4.0.1",
"cors": "^2.8.4",
"css-loader": "^0.28.11",
"cypress": "^3.1.5",
"cypress": "^3.3.1",
"dedent": "^0.7.0",
"deepmerge": "^2.1.0",
"enzyme": "^2.7.1",
@@ -131,6 +131,7 @@
"oauth2-server": "^2.4.1",
"open": "0.0.5",
"postcss-loader": "^2.1.5",
"prettier": "^1.18.2",
"raw-loader": "0.5.1",
"react-test-renderer": "^15.5.4",
"release-it": "^7.6.1",

View File

@@ -0,0 +1,42 @@
/**
* @prettier
*/
import React from "react"
import PropTypes from "prop-types"
import ImPropTypes from "react-immutable-proptypes"
import { stringify } from "core/utils"
export default function Example(props) {
const { example, showValue, getComponent } = props
const Markdown = getComponent("Markdown")
const HighlightCode = getComponent("highlightCode")
if(!example) return null
return (
<div className="example">
{example.get("description") ? (
<section className="example__section">
<div className="example__section-header">Example Description</div>
<p>
<Markdown source={example.get("description")} />
</p>
</section>
) : null}
{showValue && example.has("value") ? (
<section className="example__section">
<div className="example__section-header">Example Value</div>
<HighlightCode value={stringify(example.get("value"))} />
</section>
) : null}
</div>
)
}
Example.propTypes = {
example: ImPropTypes.map.isRequired,
showValue: PropTypes.bool,
getComponent: PropTypes.func.isRequired,
}

View File

@@ -0,0 +1,222 @@
/**
* @prettier
*/
import React from "react"
import { Map, List } from "immutable"
import PropTypes from "prop-types"
import ImPropTypes from "react-immutable-proptypes"
import { stringify } from "core/utils"
// This stateful component lets us avoid writing competing values (user
// modifications vs example values) into global state, and the mess that comes
// with that: tracking which of the two values are currently used for
// Try-It-Out, which example a modified value came from, etc...
//
// The solution here is to retain the last user-modified value in
// ExamplesSelectValueRetainer's component state, so that our global state can stay
// clean, always simply being the source of truth for what value should be both
// displayed to the user and used as a value during request execution.
//
// This approach/tradeoff was chosen in order to encapsulate the particular
// logic of Examples within the Examples component tree, and to avoid
// regressions within our current implementation elsewhere (non-Examples
// definitions, OpenAPI 2.0, etc). A future refactor to global state might make
// this component unnecessary.
//
// TL;DR: this is not our usual approach, but the choice was made consciously.
// Note that `currentNamespace` isn't currently used anywhere!
const stringifyUnlessList = input =>
List.isList(input) ? input : stringify(input)
export default class ExamplesSelectValueRetainer extends React.PureComponent {
static propTypes = {
examples: ImPropTypes.map,
onSelect: PropTypes.func,
updateValue: PropTypes.func, // mechanism to update upstream value
getComponent: PropTypes.func.isRequired,
currentUserInputValue: PropTypes.any,
currentKey: PropTypes.string,
currentNamespace: PropTypes.string,
// (also proxies props for Examples)
}
static defaultProps = {
examples: Map({}),
currentNamespace: "__DEFAULT__NAMESPACE__",
onSelect: (...args) =>
console.log( // eslint-disable-line no-console
"ExamplesSelectValueRetainer: no `onSelect` function was provided",
...args
),
updateValue: (...args) =>
console.log( // eslint-disable-line no-console
"ExamplesSelectValueRetainer: no `updateValue` function was provided",
...args
),
}
constructor(props) {
super(props)
const valueFromExample = this._getCurrentExampleValue()
this.state = {
// user edited: last value that came from the world around us, and didn't
// match the current example's value
// internal: last value that came from user selecting an Example
[props.currentNamespace]: Map({
lastUserEditedValue: this.props.currentUserInputValue,
lastDownstreamValue: valueFromExample,
isModifiedValueSelected:
// valueFromExample !== undefined &&
this.props.currentUserInputValue !== valueFromExample,
}),
}
}
_getStateForCurrentNamespace = () => {
const { currentNamespace } = this.props
return (this.state[currentNamespace] || Map()).toObject()
}
_setStateForCurrentNamespace = obj => {
const { currentNamespace } = this.props
return this._setStateForNamespace(currentNamespace, obj)
}
_setStateForNamespace = (namespace, obj) => {
const oldStateForNamespace = this.state[namespace] || Map()
const newStateForNamespace = oldStateForNamespace.mergeDeep(obj)
return this.setState({
[namespace]: newStateForNamespace,
})
}
_isCurrentUserInputSameAsExampleValue = () => {
const { currentUserInputValue } = this.props
const valueFromExample = this._getCurrentExampleValue()
return valueFromExample === currentUserInputValue
}
_getValueForExample = (exampleKey, props) => {
// props are accepted so that this can be used in componentWillReceiveProps,
// which has access to `nextProps`
const { examples } = props || this.props
return stringifyUnlessList(
(examples || Map({})).getIn([exampleKey, "value"])
)
}
_getCurrentExampleValue = props => {
// props are accepted so that this can be used in componentWillReceiveProps,
// which has access to `nextProps`
const { currentKey } = props || this.props
return this._getValueForExample(currentKey, props || this.props)
}
_onExamplesSelect = (key, { isSyntheticChange } = {}, ...otherArgs) => {
const { onSelect, updateValue, currentUserInputValue } = this.props
const { lastUserEditedValue } = this._getStateForCurrentNamespace()
const valueFromExample = this._getValueForExample(key)
if (key === "__MODIFIED__VALUE__") {
updateValue(stringifyUnlessList(lastUserEditedValue))
return this._setStateForCurrentNamespace({
isModifiedValueSelected: true,
})
}
if (typeof onSelect === "function") {
onSelect(key, { isSyntheticChange }, ...otherArgs)
}
this._setStateForCurrentNamespace({
lastDownstreamValue: valueFromExample,
isModifiedValueSelected:
isSyntheticChange &&
!!currentUserInputValue &&
currentUserInputValue !== valueFromExample,
})
// we never want to send up value updates from synthetic changes
if (isSyntheticChange) return
if (typeof updateValue === "function") {
updateValue(stringifyUnlessList(valueFromExample))
}
}
componentWillReceiveProps(nextProps) {
// update `lastUserEditedValue` as new currentUserInput values come in
const { currentUserInputValue: newValue, examples, onSelect } = nextProps
const {
lastUserEditedValue,
lastDownstreamValue,
} = this._getStateForCurrentNamespace()
const valueFromCurrentExample = this._getValueForExample(
nextProps.currentKey,
nextProps
)
const exampleMatchingNewValue = examples.find(
example =>
example.get("value") === newValue ||
// sometimes data is stored as a string (e.g. in Request Bodies), so
// let's check against a stringified version of our example too
stringify(example.get("value")) === newValue
)
if (exampleMatchingNewValue) {
onSelect(examples.keyOf(exampleMatchingNewValue), {
isSyntheticChange: true,
})
} else if (
newValue !== this.props.currentUserInputValue && // value has changed
newValue !== lastUserEditedValue && // value isn't already tracked
newValue !== lastDownstreamValue // value isn't what we've seen on the other side
) {
this._setStateForNamespace(nextProps.currentNamespace, {
lastUserEditedValue: nextProps.currentUserInputValue,
isModifiedValueSelected: newValue !== valueFromCurrentExample,
})
}
}
render() {
const { currentUserInputValue, examples, currentKey, getComponent } = this.props
const {
lastDownstreamValue,
lastUserEditedValue,
isModifiedValueSelected,
} = this._getStateForCurrentNamespace()
const ExamplesSelect = getComponent("ExamplesSelect")
return (
<ExamplesSelect
examples={examples}
currentExampleKey={currentKey}
onSelect={this._onExamplesSelect}
isModifiedValueAvailable={
!!lastUserEditedValue && lastUserEditedValue !== lastDownstreamValue
}
isValueModified={
currentUserInputValue !== undefined &&
isModifiedValueSelected &&
currentUserInputValue !== this._getCurrentExampleValue()
}
/>
)
}
}

View File

@@ -0,0 +1,138 @@
/**
* @prettier
*/
import React from "react"
import Im from "immutable"
import PropTypes from "prop-types"
import ImPropTypes from "react-immutable-proptypes"
export default class ExamplesSelect extends React.PureComponent {
static propTypes = {
examples: ImPropTypes.map.isRequired,
onSelect: PropTypes.func,
currentExampleKey: PropTypes.string,
isModifiedValueAvailable: PropTypes.bool,
isValueModified: PropTypes.bool,
showLabels: PropTypes.bool,
}
static defaultProps = {
examples: Im.Map({}),
onSelect: (...args) =>
console.log( // eslint-disable-line no-console
// FIXME: remove before merging to master...
`DEBUG: ExamplesSelect was not given an onSelect callback`,
...args
),
currentExampleKey: null,
showLabels: true,
}
_onSelect = (key, { isSyntheticChange = false } = {}) => {
if (typeof this.props.onSelect === "function") {
this.props.onSelect(key, {
isSyntheticChange,
})
}
}
_onDomSelect = e => {
if (typeof this.props.onSelect === "function") {
const element = e.target.selectedOptions[0]
const key = element.getAttribute("value")
this._onSelect(key, {
isSyntheticChange: false,
})
}
}
getCurrentExample = () => {
const { examples, currentExampleKey } = this.props
const currentExamplePerProps = examples.get(currentExampleKey)
const firstExamplesKey = examples.keySeq().first()
const firstExample = examples.get(firstExamplesKey)
return currentExamplePerProps || firstExample || Map({})
}
componentDidMount() {
// this is the not-so-great part of ExamplesSelect... here we're
// artificially kicking off an onSelect event in order to set a default
// value in state. the consumer has the option to avoid this by checking
// `isSyntheticEvent`, but we should really be doing this in a selector.
// TODO: clean this up
// FIXME: should this only trigger if `currentExamplesKey` is nullish?
const { onSelect, examples } = this.props
if (typeof onSelect === "function") {
const firstExample = examples.first()
const firstExampleKey = examples.keyOf(firstExample)
this._onSelect(firstExampleKey, {
isSyntheticChange: true,
})
}
}
componentWillReceiveProps(nextProps) {
const { currentExampleKey, examples } = nextProps
if (examples !== this.props.examples && !examples.has(currentExampleKey)) {
// examples have changed from under us, and the currentExampleKey is no longer
// valid.
const firstExample = examples.first()
const firstExampleKey = examples.keyOf(firstExample)
this._onSelect(firstExampleKey, {
isSyntheticChange: true,
})
}
}
render() {
const {
examples,
currentExampleKey,
isValueModified,
isModifiedValueAvailable,
showLabels,
} = this.props
return (
<div className="examples-select">
{
showLabels ? (
<span className="examples-select__section-label">Examples: </span>
) : null
}
<select
onChange={this._onDomSelect}
value={
isModifiedValueAvailable && isValueModified
? "__MODIFIED__VALUE__"
: (currentExampleKey || "")
}
>
{isModifiedValueAvailable ? (
<option value="__MODIFIED__VALUE__">[Modified value]</option>
) : null}
{examples
.map((example, exampleName) => {
return (
<option
key={exampleName} // for React
value={exampleName} // for matching to select's `value`
>
{example.get("summary") || exampleName}
</option>
)
})
.valueSeq()}
</select>
</div>
)
}
}

View File

@@ -132,7 +132,8 @@ export class Select extends React.Component {
onChange: PropTypes.func,
multiple: PropTypes.bool,
allowEmptyValue: PropTypes.bool,
className: PropTypes.string
className: PropTypes.string,
disabled: PropTypes.bool,
}
static defaultProps = {
@@ -176,12 +177,19 @@ export class Select extends React.Component {
onChange && onChange(value)
}
componentWillReceiveProps(nextProps) {
// TODO: this puts us in a weird area btwn un/controlled selection... review
if(nextProps.value !== this.props.value) {
this.setState({ value: nextProps.value })
}
}
render(){
let { allowedValues, multiple, allowEmptyValue } = this.props
let { allowedValues, multiple, allowEmptyValue, disabled } = this.props
let value = this.state.value.toJS ? this.state.value.toJS() : this.state.value
return (
<select className={this.props.className} multiple={ multiple } value={ value } onChange={ this.onChange } >
<select className={this.props.className} multiple={ multiple } value={value} onChange={ this.onChange } disabled={disabled} >
{ allowEmptyValue ? <option value="">--</option> : null }
{
allowedValues.map(function (item, key) {

View File

@@ -17,11 +17,19 @@ export default class ModelExample extends React.Component {
super(props, context)
let { getConfigs, isExecute } = this.props
let { defaultModelRendering } = getConfigs()
let activeTab = defaultModelRendering
if (defaultModelRendering !== "example" && defaultModelRendering !== "model") {
defaultModelRendering = "example"
activeTab = "example"
}
if(isExecute) {
activeTab = "example"
}
this.state = {
activeTab: isExecute ? "example" : defaultModelRendering
activeTab: activeTab
}
}
@@ -33,8 +41,12 @@ export default class ModelExample extends React.Component {
})
}
componentWillReceiveProps(props) {
if (props.isExecute && props.isExecute !== this.props.isExecute) {
componentWillReceiveProps(nextProps) {
if (
nextProps.isExecute &&
!this.props.isExecute &&
this.props.example
) {
this.setState({ activeTab: "example" })
}
}
@@ -43,10 +55,11 @@ export default class ModelExample extends React.Component {
let { getComponent, specSelectors, schema, example, isExecute, getConfigs, specPath } = this.props
let { defaultModelExpandDepth } = getConfigs()
const ModelWrapper = getComponent("ModelWrapper")
const HighlightCode = getComponent("highlightCode")
let isOAS3 = specSelectors.isOAS3()
return <div>
return <div className="model-example">
<ul className="tab">
<li className={ "tabitem" + ( this.state.activeTab === "example" ? " active" : "") }>
<a className="tablinks" data-name="example" onClick={ this.activeTab }>{isExecute ? "Edit Value" : "Example Value"}</a>
@@ -59,7 +72,11 @@ export default class ModelExample extends React.Component {
</ul>
<div>
{
this.state.activeTab === "example" && example
this.state.activeTab === "example" ? (
example ? example : (
<HighlightCode value="(no example available)" />
)
) : null
}
{
this.state.activeTab === "model" && <ModelWrapper schema={ schema }

View File

@@ -156,6 +156,8 @@ export default class Operation extends PureComponent {
specSelectors={ specSelectors }
pathMethod={ [path, method] }
getConfigs={ getConfigs }
oas3Actions={ oas3Actions }
oas3Selectors={ oas3Selectors }
/>
}
@@ -214,6 +216,7 @@ export default class Operation extends PureComponent {
getConfigs={ getConfigs }
specSelectors={ specSelectors }
oas3Actions={oas3Actions}
oas3Selectors={oas3Selectors}
specActions={ specActions }
produces={specSelectors.producesOptionsFor([path, method]) }
producesValue={ specSelectors.currentProducesFor([path, method]) }

View File

@@ -82,9 +82,8 @@ export default class ParamBody extends PureComponent {
handleOnChange = e => {
const {consumesValue} = this.props
const isJson = /json/i.test(consumesValue)
const isXml = /xml/i.test(consumesValue)
const inputValue = isJson ? e.target.value.trim() : e.target.value
const inputValue = e.target.value
this.onChange(inputValue, {isXml})
}

View File

@@ -1,9 +1,9 @@
import React, { Component } from "react"
import { Map } from "immutable"
import { Map, List } from "immutable"
import PropTypes from "prop-types"
import ImPropTypes from "react-immutable-proptypes"
import win from "core/window"
import { getExtensions, getCommonExtensions, numberToString } from "core/utils"
import { getExtensions, getCommonExtensions, numberToString, stringify } from "core/utils"
export default class ParameterRow extends Component {
static propTypes = {
@@ -18,7 +18,9 @@ export default class ParameterRow extends Component {
specActions: PropTypes.object.isRequired,
pathMethod: PropTypes.array.isRequired,
getConfigs: PropTypes.func.isRequired,
specPath: ImPropTypes.list.isRequired
specPath: ImPropTypes.list.isRequired,
oas3Actions: PropTypes.object.isRequired,
oas3Selectors: PropTypes.object.isRequired,
}
constructor(props, context) {
@@ -29,7 +31,7 @@ export default class ParameterRow extends Component {
componentWillReceiveProps(props) {
let { specSelectors, pathMethod, rawParam } = props
let { isOAS3 } = specSelectors
let isOAS3 = specSelectors.isOAS3()
let parameterWithMeta = specSelectors.parameterWithMetaByIdentity(pathMethod, rawParam) || new Map()
// fallback, if the meta lookup fails
@@ -37,7 +39,7 @@ export default class ParameterRow extends Component {
let enumValue
if(isOAS3()) {
if(isOAS3) {
let schema = parameterWithMeta.get("schema") || Map()
enumValue = schema.get("enum")
} else {
@@ -74,6 +76,15 @@ export default class ParameterRow extends Component {
return onChange(rawParam, valueForUpstream, isXml)
}
_onExampleSelect = (key, /* { isSyntheticChange } = {} */) => {
this.props.oas3Actions.setActiveExamplesMember({
name: key,
pathMethod: this.props.pathMethod,
contextType: "parameters",
contextName: this.getParamKey()
})
}
onChangeIncludeEmpty = (newValue) => {
let { specActions, param, pathMethod } = this.props
const paramName = param.get("name")
@@ -82,10 +93,9 @@ export default class ParameterRow extends Component {
}
setDefaultValue = () => {
let { specSelectors, pathMethod, rawParam } = this.props
let paramWithMeta = specSelectors.parameterWithMetaByIdentity(pathMethod, rawParam)
let { specSelectors, pathMethod, rawParam, oas3Selectors } = this.props
let paramWithMeta = specSelectors.parameterWithMetaByIdentity(pathMethod, rawParam) || Map()
if (!paramWithMeta || paramWithMeta.get("value") !== undefined) {
return
@@ -100,20 +110,32 @@ export default class ParameterRow extends Component {
|| paramWithMeta.getIn(["schema", "example"])
|| paramWithMeta.getIn(["schema", "default"])
} else if (specSelectors.isOAS3()) {
newValue = paramWithMeta.get("example")
const currentExampleKey = oas3Selectors.activeExamplesMember(...pathMethod, "parameters", this.getParamKey())
newValue = paramWithMeta.getIn(["examples", currentExampleKey, "value"])
|| paramWithMeta.get("example")
|| paramWithMeta.getIn(["schema", "example"])
|| paramWithMeta.getIn(["schema", "default"])
}
if(newValue !== undefined) {
this.onChangeWrapper(numberToString(newValue))
this.onChangeWrapper(
List.isList(newValue) ? newValue : stringify(newValue)
)
}
}
}
getParamKey() {
const { param } = this.props
if(!param) return null
return `${param.get("name")}-${param.get("in")}`
}
render() {
let {param, rawParam, getComponent, getConfigs, isExecute, fn, onChangeConsumes, specSelectors, pathMethod, specPath} = this.props
let {param, rawParam, getComponent, getConfigs, isExecute, fn, onChangeConsumes, specSelectors, pathMethod, specPath, oas3Selectors} = this.props
let { isOAS3 } = specSelectors
let isOAS3 = specSelectors.isOAS3()
const { showExtensions, showCommonExtensions } = getConfigs()
@@ -121,6 +143,8 @@ export default class ParameterRow extends Component {
param = rawParam
}
if(!rawParam) return null
// const onChangeWrapper = (value) => onChange(param, value)
const JsonSchemaForm = getComponent("JsonSchemaForm")
const ParamBody = getComponent("ParamBody")
@@ -142,10 +166,12 @@ export default class ParameterRow extends Component {
const Markdown = getComponent("Markdown")
const ParameterExt = getComponent("ParameterExt")
const ParameterIncludeEmpty = getComponent("ParameterIncludeEmpty")
const ExamplesSelectValueRetainer = getComponent("ExamplesSelectValueRetainer")
const Example = getComponent("Example")
let paramWithMeta = specSelectors.parameterWithMetaByIdentity(pathMethod, rawParam)
let paramWithMeta = specSelectors.parameterWithMetaByIdentity(pathMethod, rawParam) || Map()
let format = param.get("format")
let schema = isOAS3 && isOAS3() ? param.get("schema") : param
let schema = isOAS3 ? param.get("schema") : param
let type = schema.get("type")
let isFormData = inType === "formData"
let isFormDataSupported = "FormData" in win
@@ -199,7 +225,7 @@ export default class ParameterRow extends Component {
{ format && <span className="prop-format">(${format})</span>}
</div>
<div className="parameter__deprecated">
{ isOAS3 && isOAS3() && param.get("deprecated") ? "deprecated": null }
{ isOAS3 && param.get("deprecated") ? "deprecated": null }
</div>
<div className="parameter__in">({ param.get("in") })</div>
{ !showCommonExtensions || !commonExt.size ? null : commonExt.map((v, key) => <ParameterExt key={`${key}-${v}`} xKey={key} xVal={v} /> )}
@@ -224,11 +250,28 @@ export default class ParameterRow extends Component {
{(isFormData && !isFormDataSupported) && <div>Error: your browser does not support FormData</div>}
{ bodyParam || !isExecute ? null
{
isOAS3 && param.get("examples") ? (
<section className="parameter-controls">
<ExamplesSelectValueRetainer
examples={param.get("examples")}
onSelect={this._onExampleSelect}
updateValue={this.onChangeWrapper}
getComponent={getComponent}
defaultToFirstExample={true}
currentKey={oas3Selectors.activeExamplesMember(...pathMethod, "parameters", this.getParamKey())}
currentUserInputValue={value}
/>
</section>
) : null
}
{ bodyParam ? null
: <JsonSchemaForm fn={fn}
getComponent={getComponent}
value={ value }
required={ required }
disabled={!isExecute}
description={param.get("description") ? `${param.get("name")} - ${param.get("description")}` : `${param.get("name")}`}
onChange={ this.onChangeWrapper }
errors={ paramWithMeta.get("errors") }
@@ -257,6 +300,18 @@ export default class ParameterRow extends Component {
: null
}
{
isOAS3 && param.get("examples") ? (
<Example
example={param.getIn([
"examples",
oas3Selectors.activeExamplesMember(...pathMethod, "parameters", this.getParamKey())
])}
getComponent={getComponent}
/>
) : null
}
</td>
</tr>
@@ -265,3 +320,4 @@ export default class ParameterRow extends Component {
}
}

View File

@@ -1,123 +0,0 @@
import React, { Component } from "react"
import PropTypes from "prop-types"
import ImPropTypes from "react-immutable-proptypes"
import Im from "immutable"
// More readable, just iterate over maps, only
const eachMap = (iterable, fn) => iterable.valueSeq().filter(Im.Map.isMap).map(fn)
export default class Parameters extends Component {
static propTypes = {
parameters: ImPropTypes.list.isRequired,
specActions: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
fn: PropTypes.object.isRequired,
tryItOutEnabled: PropTypes.bool,
allowTryItOut: PropTypes.bool,
onTryoutClick: PropTypes.func,
onCancelClick: PropTypes.func,
onChangeKey: PropTypes.array,
pathMethod: PropTypes.array.isRequired,
getConfigs: PropTypes.func.isRequired,
specPath: ImPropTypes.list.isRequired,
}
static defaultProps = {
onTryoutClick: Function.prototype,
onCancelClick: Function.prototype,
tryItOutEnabled: false,
allowTryItOut: true,
onChangeKey: [],
specPath: [],
}
onChange = ( param, value, isXml ) => {
let {
specActions: { changeParamByIdentity },
onChangeKey,
} = this.props
changeParamByIdentity(onChangeKey, param, value, isXml)
}
onChangeConsumesWrapper = ( val ) => {
let {
specActions: { changeConsumesValue },
onChangeKey
} = this.props
changeConsumesValue(onChangeKey, val)
}
render(){
let {
onTryoutClick,
onCancelClick,
parameters,
allowTryItOut,
tryItOutEnabled,
specPath,
fn,
getComponent,
getConfigs,
specSelectors,
specActions,
pathMethod
} = this.props
const ParameterRow = getComponent("parameterRow")
const TryItOutButton = getComponent("TryItOutButton")
const isExecute = tryItOutEnabled && allowTryItOut
return (
<div className="opblock-section">
<div className="opblock-section-header">
<div className="tab-header">
<h4 className="opblock-title">Parameters</h4>
</div>
{ allowTryItOut ? (
<TryItOutButton enabled={ tryItOutEnabled } onCancelClick={ onCancelClick } onTryoutClick={ onTryoutClick } />
) : null }
</div>
{ !parameters.count() ? <div className="opblock-description-wrapper"><p>No parameters</p></div> :
<div className="table-container">
<table className="parameters">
<thead>
<tr>
<th className="col col_header parameters-col_name">Name</th>
<th className="col col_header parameters-col_description">Description</th>
</tr>
</thead>
<tbody>
{
eachMap(parameters, (parameter, i) => (
<ParameterRow
fn={ fn }
specPath={specPath.push(i.toString())}
getComponent={ getComponent }
getConfigs={ getConfigs }
rawParam={ parameter }
param={ specSelectors.parameterWithMetaByIdentity(pathMethod, parameter) }
key={ `${parameter.get( "in" )}.${parameter.get("name")}` }
onChange={ this.onChange }
onChangeConsumes={this.onChangeConsumesWrapper}
specSelectors={ specSelectors }
specActions={specActions}
pathMethod={ pathMethod }
isExecute={ isExecute }/>
)).toArray()
}
</tbody>
</table>
</div>
}
</div>
)
}
}

View File

@@ -0,0 +1 @@
export { default as Parameters } from "./parameters"

View File

@@ -2,12 +2,11 @@ import React, { Component } from "react"
import PropTypes from "prop-types"
import Im, { Map, List } from "immutable"
import ImPropTypes from "react-immutable-proptypes"
import { OAS3ComponentWrapFactory } from "../helpers"
// More readable, just iterate over maps, only
const eachMap = (iterable, fn) => iterable.valueSeq().filter(Im.Map.isMap).map(fn)
class Parameters extends Component {
export default class Parameters extends Component {
constructor(props) {
super(props)
@@ -19,21 +18,21 @@ class Parameters extends Component {
static propTypes = {
parameters: ImPropTypes.list.isRequired,
specActions: PropTypes.object.isRequired,
operation: PropTypes.object.isRequired,
specActions: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
getConfigs: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
oas3Actions: PropTypes.object.isRequired,
oas3Selectors: PropTypes.object.isRequired,
fn: PropTypes.object.isRequired,
tryItOutEnabled: PropTypes.bool,
allowTryItOut: PropTypes.bool,
specPath: ImPropTypes.list.isRequired,
onTryoutClick: PropTypes.func,
onCancelClick: PropTypes.func,
onChangeKey: PropTypes.array,
pathMethod: PropTypes.array.isRequired
pathMethod: PropTypes.array.isRequired,
getConfigs: PropTypes.func.isRequired,
specPath: ImPropTypes.list.isRequired,
}
@@ -43,6 +42,7 @@ class Parameters extends Component {
tryItOutEnabled: false,
allowTryItOut: true,
onChangeKey: [],
specPath: [],
}
onChange = ( param, value, isXml ) => {
@@ -51,7 +51,7 @@ class Parameters extends Component {
onChangeKey,
} = this.props
changeParamByIdentity( onChangeKey, param, value, isXml)
changeParamByIdentity(onChangeKey, param, value, isXml)
}
onChangeConsumesWrapper = ( val ) => {
@@ -85,16 +85,16 @@ class Parameters extends Component {
parameters,
allowTryItOut,
tryItOutEnabled,
specPath,
fn,
getComponent,
getConfigs,
specSelectors,
specActions,
pathMethod,
oas3Actions,
oas3Selectors,
pathMethod,
specPath,
operation
} = this.props
@@ -105,14 +105,13 @@ class Parameters extends Component {
const RequestBody = getComponent("RequestBody", true)
const isExecute = tryItOutEnabled && allowTryItOut
const { isOAS3 } = specSelectors
const isOAS3 = specSelectors.isOAS3()
const requestBody = operation.get("requestBody")
const requestBodySpecPath = specPath.slice(0, -1).push("requestBody") // remove the "parameters" part
return (
<div className="opblock-section">
<div className="opblock-section-header">
{ isOAS3 ? (
<div className="tab-header">
<div onClick={() => this.toggleTab("parameters")} className={`tab-item ${this.state.parametersVisible && "active"}`}>
<h4 className="opblock-title"><span>Parameters</span></h4>
@@ -125,6 +124,11 @@ class Parameters extends Component {
) : null
}
</div>
) : (
<div className="tab-header">
<h4 className="opblock-title">Parameters</h4>
</div>
)}
{ allowTryItOut ? (
<TryItOutButton enabled={ tryItOutEnabled } onCancelClick={ onCancelClick } onTryoutClick={ onTryoutClick } />
) : null }
@@ -142,17 +146,20 @@ class Parameters extends Component {
<tbody>
{
eachMap(parameters, (parameter, i) => (
<ParameterRow fn={ fn }
<ParameterRow
fn={ fn }
specPath={specPath.push(i.toString())}
getComponent={ getComponent }
specPath={specPath.push(i)}
getConfigs={ getConfigs }
rawParam={ parameter }
param={ specSelectors.parameterWithMetaByIdentity(pathMethod, parameter) }
key={ parameter.get( "name" ) }
key={ `${parameter.get( "in" )}.${parameter.get("name")}` }
onChange={ this.onChange }
onChangeConsumes={this.onChangeConsumesWrapper}
specSelectors={ specSelectors }
specActions={ specActions }
specActions={specActions}
oas3Actions={oas3Actions}
oas3Selectors={oas3Selectors}
pathMethod={ pathMethod }
isExecute={ isExecute }/>
)).toArray()
@@ -161,16 +168,16 @@ class Parameters extends Component {
</table>
</div>
}
</div> : "" }
</div> : null }
{this.state.callbackVisible ? <div className="callbacks-container opblock-description-wrapper">
<Callbacks
callbacks={Map(operation.get("callbacks"))}
specPath={specPath.slice(0, -1).push("callbacks")}
/>
</div> : "" }
</div> : null }
{
isOAS3() && requestBody && this.state.parametersVisible &&
isOAS3 && requestBody && this.state.parametersVisible &&
<div className="opblock-section opblock-section-request-body">
<div className="opblock-section-header">
<h4 className={`opblock-title parameter__name ${requestBody.get("required") && "required"}`}>Request body</h4>
@@ -186,10 +193,24 @@ class Parameters extends Component {
</div>
<div className="opblock-description-wrapper">
<RequestBody
specPath={requestBodySpecPath}
specPath={specPath.slice(0, -1).push("requestBody")}
requestBody={requestBody}
requestBodyValue={oas3Selectors.requestBodyValue(...pathMethod) || Map()}
requestBodyValue={oas3Selectors.requestBodyValue(...pathMethod)}
isExecute={isExecute}
activeExamplesKey={oas3Selectors.activeExamplesMember(
...pathMethod,
"requestBody",
"requestBody" // RBs are currently not stored per-mediaType
)}
updateActiveExamplesKey={key => {
this.props.oas3Actions.setActiveExamplesMember({
name: key,
pathMethod: this.props.pathMethod,
contextType: "requestBody",
contextName: "requestBody" // RBs are currently not stored per-mediaType
})
}
}
onChange={(value, path) => {
if(path) {
const lastValue = oas3Selectors.requestBodyValue(...pathMethod)
@@ -209,6 +230,3 @@ class Parameters extends Component {
)
}
}
export default OAS3ComponentWrapFactory(Parameters)

View File

@@ -5,19 +5,11 @@ import cx from "classnames"
import { fromJS, Seq, Iterable, List, Map } from "immutable"
import { getSampleSchema, fromJSOrdered, stringify } from "core/utils"
const getExampleComponent = ( sampleResponse, examples, HighlightCode ) => {
if ( examples && examples.size ) {
return examples.entrySeq().map( ([ key, example ]) => {
let exampleValue = stringify(example)
return (<div key={ key }>
<h5>{ key }</h5>
<HighlightCode className="example" value={ exampleValue } />
</div>)
}).toArray()
}
if ( sampleResponse ) { return <div>
const getExampleComponent = ( sampleResponse, HighlightCode ) => {
if (
sampleResponse !== undefined &&
sampleResponse !== null
) { return <div>
<HighlightCode className="example" value={ sampleResponse } />
</div>
}
@@ -29,20 +21,24 @@ export default class Response extends React.Component {
super(props, context)
this.state = {
responseContentType: ""
responseContentType: "",
}
}
static propTypes = {
path: PropTypes.string.isRequired,
method: PropTypes.string.isRequired,
code: PropTypes.string.isRequired,
response: PropTypes.instanceOf(Iterable),
className: PropTypes.string,
getComponent: PropTypes.func.isRequired,
getConfigs: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
oas3Actions: PropTypes.object.isRequired,
specPath: ImPropTypes.list.isRequired,
fn: PropTypes.object.isRequired,
contentType: PropTypes.string,
activeExamplesKey: PropTypes.string,
controlsAcceptHeader: PropTypes.bool,
onContentTypeChange: PropTypes.func
}
@@ -61,8 +57,21 @@ export default class Response extends React.Component {
})
}
getTargetExamplesKey = () => {
const { response, contentType, activeExamplesKey } = this.props
const activeContentType = this.state.responseContentType || contentType
const activeMediaType = response.getIn(["content", activeContentType], Map({}))
const examplesForMediaType = activeMediaType.get("examples", null)
const firstExamplesKey = examplesForMediaType.keySeq().first()
return activeExamplesKey || firstExamplesKey
}
render() {
let {
path,
method,
code,
response,
className,
@@ -72,14 +81,14 @@ export default class Response extends React.Component {
getConfigs,
specSelectors,
contentType,
controlsAcceptHeader
controlsAcceptHeader,
oas3Actions,
} = this.props
let { inferSchema } = fn
let { isOAS3 } = specSelectors
let isOAS3 = specSelectors.isOAS3()
let headers = response.get("headers")
let examples = response.get("examples")
let links = response.get("links")
const Headers = getComponent("headers")
const HighlightCode = getComponent("highlightCode")
@@ -87,44 +96,53 @@ export default class Response extends React.Component {
const Markdown = getComponent( "Markdown" )
const OperationLink = getComponent("operationLink")
const ContentType = getComponent("contentType")
const ExamplesSelect = getComponent("ExamplesSelect")
const Example = getComponent("Example")
var sampleResponse
var sampleSchema
var schema, specPathWithPossibleSchema
const activeContentType = this.state.responseContentType || contentType
const activeMediaType = response.getIn(["content", activeContentType], Map({}))
const examplesForMediaType = activeMediaType.get("examples", null)
if(isOAS3()) {
const mediaType = response.getIn(["content", activeContentType], Map({}))
const oas3SchemaForContentType = mediaType.get("schema", Map({}))
// Goal: find a schema value for `schema`
if(isOAS3) {
const oas3SchemaForContentType = activeMediaType.get("schema", Map({}))
if(mediaType.get("example") !== undefined) {
sampleSchema = stringify(mediaType.get("example"))
} else {
sampleSchema = getSampleSchema(oas3SchemaForContentType.toJS(), this.state.responseContentType, {
includeReadOnly: true
})
}
sampleResponse = oas3SchemaForContentType ? sampleSchema : null
schema = oas3SchemaForContentType ? inferSchema(oas3SchemaForContentType.toJS()) : null
specPathWithPossibleSchema = oas3SchemaForContentType ? List(["content", this.state.responseContentType, "schema"]) : specPath
} else {
schema = inferSchema(response.toJS()) // TODO: don't convert back and forth. Lets just stick with immutable for inferSchema
schema = response.get("schema")
specPathWithPossibleSchema = response.has("schema") ? specPath.push("schema") : specPath
sampleResponse = schema ? getSampleSchema(schema, activeContentType, {
}
// Goal: find an example value for `sampleResponse`
if(isOAS3) {
const oas3SchemaForContentType = activeMediaType.get("schema", Map({}))
if(examplesForMediaType) {
const targetExamplesKey = this.getTargetExamplesKey()
const targetExample = examplesForMediaType.get(targetExamplesKey, Map({}))
sampleResponse = stringify(targetExample.get("value"))
} else if(activeMediaType.get("example") !== undefined) {
// use the example key's value
sampleResponse = stringify(activeMediaType.get("example"))
} else {
// use an example value generated based on the schema
sampleResponse = getSampleSchema(oas3SchemaForContentType.toJS(), this.state.responseContentType, {
includeReadOnly: true
})
}
} else {
sampleResponse = schema ? getSampleSchema(schema.toJS(), activeContentType, {
includeReadOnly: true,
includeWriteOnly: true // writeOnly has no filtering effect in swagger 2.0
}) : null
}
if(examples) {
examples = examples.map(example => {
// Remove unwanted properties from examples
return example.set ? example.set("$$ref", undefined) : example
})
}
let example = getExampleComponent( sampleResponse, examples, HighlightCode )
let example = getExampleComponent( sampleResponse, HighlightCode )
return (
<tr className={ "response " + ( className || "") } data-code={code}>
@@ -137,20 +155,55 @@ export default class Response extends React.Component {
<Markdown source={ response.get( "description" ) } />
</div>
{ isOAS3 ?
<div className={cx("response-content-type", {
"controls-accept-header": controlsAcceptHeader
})}>
{isOAS3 && response.get("content") ? (
<section className="response-controls">
<div
className={cx("response-control-media-type", {
"response-control-media-type--accept-controller": controlsAcceptHeader
})}
>
<small className="response-control-media-type__title">
Media type
</small>
<ContentType
value={this.state.responseContentType}
contentTypes={ response.get("content") ? response.get("content").keySeq() : Seq() }
contentTypes={
response.get("content")
? response.get("content").keySeq()
: Seq()
}
onChange={this._onContentTypeChange}
/>
{ controlsAcceptHeader ? <small>Controls <code>Accept</code> header.</small> : null }
{controlsAcceptHeader ? (
<small className="response-control-media-type__accept-message">
Controls <code>Accept</code> header.
</small>
) : null}
</div>
: null }
{examplesForMediaType ? (
<div className="response-control-examples">
<small className="response-control-examples__title">
Examples
</small>
<ExamplesSelect
examples={examplesForMediaType}
currentExampleKey={this.getTargetExamplesKey()}
onSelect={key =>
oas3Actions.setActiveExamplesMember({
name: key,
pathMethod: [path, method],
contextType: "responses",
contextName: code
})
}
showLabels={false}
/>
</div>
) : null}
</section>
) : null}
{ example ? (
{ example || schema ? (
<ModelExample
specPath={specPathWithPossibleSchema}
getComponent={ getComponent }
@@ -158,6 +211,14 @@ export default class Response extends React.Component {
specSelectors={ specSelectors }
schema={ fromJSOrdered(schema) }
example={ example }/>
) : null }
{ isOAS3 && examplesForMediaType ? (
<Example
example={examplesForMediaType.get(this.getTargetExamplesKey(), Map({}))}
getComponent={getComponent}
omitValue={true}
/>
) : null}
{ headers ? (
@@ -167,9 +228,8 @@ export default class Response extends React.Component {
/>
) : null}
</td>
{specSelectors.isOAS3() ? <td className="col response-col_links">
{isOAS3 ? <td className="col response-col_links">
{ links ?
links.toSeq().map((link, key) => {
return <OperationLink key={key} name={key} link={ link } getComponent={getComponent}/>

View File

@@ -18,6 +18,7 @@ export default class Responses extends React.Component {
specSelectors: PropTypes.object.isRequired,
specActions: PropTypes.object.isRequired,
oas3Actions: PropTypes.object.isRequired,
oas3Selectors: PropTypes.object.isRequired,
specPath: ImPropTypes.list.isRequired,
fn: PropTypes.object.isRequired
}
@@ -28,17 +29,20 @@ export default class Responses extends React.Component {
displayRequestDuration: false
}
shouldComponentUpdate(nextProps) {
// BUG: props.tryItOutResponse is always coming back as a new Immutable instance
let render = this.props.tryItOutResponse !== nextProps.tryItOutResponse
|| this.props.responses !== nextProps.responses
|| this.props.produces !== nextProps.produces
|| this.props.producesValue !== nextProps.producesValue
|| this.props.displayRequestDuration !== nextProps.displayRequestDuration
|| this.props.path !== nextProps.path
|| this.props.method !== nextProps.method
return render
}
// These performance-enhancing checks were disabled as part of Multiple Examples
// because they were causing data-consistency issues
//
// shouldComponentUpdate(nextProps) {
// // BUG: props.tryItOutResponse is always coming back as a new Immutable instance
// let render = this.props.tryItOutResponse !== nextProps.tryItOutResponse
// || this.props.responses !== nextProps.responses
// || this.props.produces !== nextProps.produces
// || this.props.producesValue !== nextProps.producesValue
// || this.props.displayRequestDuration !== nextProps.displayRequestDuration
// || this.props.path !== nextProps.path
// || this.props.method !== nextProps.method
// return render
// }
onChangeProducesWrapper = ( val ) => this.props.specActions.changeProducesValue([this.props.path, this.props.method], val)
@@ -64,6 +68,10 @@ export default class Responses extends React.Component {
producesValue,
displayRequestDuration,
specPath,
path,
method,
oas3Selectors,
oas3Actions,
} = this.props
let defaultCode = defaultStatusCode( responses )
@@ -121,6 +129,8 @@ export default class Responses extends React.Component {
let className = tryItOutResponse && tryItOutResponse.get("status") == code ? "response_current" : ""
return (
<Response key={ code }
path={path}
method={method}
specPath={specPath.push(code)}
isDefault={defaultCode === code}
fn={fn}
@@ -132,6 +142,13 @@ export default class Responses extends React.Component {
onContentTypeChange={this.onResponseContentTypeChange}
contentType={ producesValue }
getConfigs={ getConfigs }
activeExamplesKey={oas3Selectors.activeExamplesMember(
path,
method,
"responses",
code
)}
oas3Actions={oas3Actions}
getComponent={ getComponent }/>
)
}).toArray()

View File

@@ -4,7 +4,6 @@ import { List, fromJS } from "immutable"
import cx from "classnames"
import ImPropTypes from "react-immutable-proptypes"
import DebounceInput from "react-debounce-input"
import { getSampleSchema } from "core/utils"
//import "less/json-schema-form"
const noop = ()=> {}
@@ -18,7 +17,8 @@ const JsonSchemaPropShape = {
errors: ImPropTypes.list,
required: PropTypes.bool,
dispatchInitialValue: PropTypes.bool,
description: PropTypes.any
description: PropTypes.any,
disabled: PropTypes.bool,
}
const JsonSchemaDefaultProps = {
@@ -43,7 +43,7 @@ export class JsonSchemaForm extends Component {
}
render() {
let { schema, errors, value, onChange, getComponent, fn } = this.props
let { schema, errors, value, onChange, getComponent, fn, disabled } = this.props
if(schema.toJS)
schema = schema.toJS()
@@ -51,7 +51,7 @@ export class JsonSchemaForm extends Component {
let { type, format="" } = schema
let Comp = (format ? getComponent(`JsonSchema_${type}_${format}`) : getComponent(`JsonSchema_${type}`)) || getComponent("JsonSchema_string")
return <Comp { ...this.props } errors={errors} fn={fn} getComponent={getComponent} value={value} onChange={onChange} schema={schema}/>
return <Comp { ...this.props } errors={errors} fn={fn} getComponent={getComponent} value={value} onChange={onChange} schema={schema} disabled={disabled}/>
}
}
@@ -65,7 +65,7 @@ export class JsonSchema_string extends Component {
}
onEnumChange = (val) => this.props.onChange(val)
render() {
let { getComponent, value, schema, errors, required, description } = this.props
let { getComponent, value, schema, errors, required, description, disabled } = this.props
let enumValue = schema["enum"]
errors = errors.toJS ? errors.toJS() : []
@@ -80,7 +80,7 @@ export class JsonSchema_string extends Component {
onChange={ this.onEnumChange }/>)
}
const isDisabled = schema["in"] === "formData" && !("FormData" in window)
const isDisabled = disabled || (schema["in"] === "formData" && !("FormData" in window))
const Input = getComponent("Input")
if (schema["type"] === "file") {
return (<Input type="file"
@@ -149,7 +149,7 @@ export class JsonSchema_array extends PureComponent {
}
render() {
let { getComponent, required, schema, errors, fn } = this.props
let { getComponent, required, schema, errors, fn, disabled } = this.props
errors = errors.toJS ? errors.toJS() : []
@@ -167,13 +167,14 @@ export class JsonSchema_array extends PureComponent {
title={ errors.length ? errors : ""}
multiple={ true }
value={ value }
disabled={disabled}
allowedValues={ enumValue }
allowEmptyValue={ !required }
onChange={ this.onEnumChange }/>)
}
return (
<div>
<div className="json-schema-array">
{ !value || !value.count || value.count() < 1 ? null :
value.map( (item,i) => {
let schema = Object.assign({}, itemSchema)
@@ -183,13 +184,32 @@ export class JsonSchema_array extends PureComponent {
}
return (
<div key={i} className="json-schema-form-item">
<JsonSchemaForm fn={fn} getComponent={getComponent} value={item} onChange={(val) => this.onItemChange(val, i)} schema={schema} />
<Button className="btn btn-sm json-schema-form-item-remove" onClick={()=> this.removeItem(i)} > - </Button>
<JsonSchemaForm
fn={fn}
getComponent={getComponent}
value={item}
onChange={(val) => this.onItemChange(val, i)}
schema={schema}
disabled={disabled}
/>
{ !disabled ? (
<Button
className="btn btn-sm json-schema-form-item-remove"
onClick={()=> this.removeItem(i)}
> - </Button>
) : null }
</div>
)
}).toArray()
}
<Button className={`btn btn-sm json-schema-form-item-add ${errors.length ? "invalid" : null}`} onClick={this.addItem}> Add item </Button>
{ !disabled ? (
<Button
className={`btn btn-sm json-schema-form-item-add ${errors.length ? "invalid" : null}`}
onClick={this.addItem}
>
Add item
</Button>
) : null }
</div>
)
}
@@ -201,7 +221,7 @@ export class JsonSchema_boolean extends Component {
onEnumChange = (val) => this.props.onChange(val)
render() {
let { getComponent, value, errors, schema, required } = this.props
let { getComponent, value, errors, schema, required, disabled } = this.props
errors = errors.toJS ? errors.toJS() : []
const Select = getComponent("Select")
@@ -209,6 +229,7 @@ export class JsonSchema_boolean extends Component {
return (<Select className={ errors.length ? "invalid" : ""}
title={ errors.length ? errors : ""}
value={ String(value) }
disabled={disabled}
allowedValues={ fromJS(schema.enum || ["true", "false"]) }
allowEmptyValue={ !schema.enum || !required }
onChange={ this.onEnumChange }/>)
@@ -223,16 +244,6 @@ export class JsonSchema_object extends PureComponent {
static propTypes = JsonSchemaPropShape
static defaultProps = JsonSchemaDefaultProps
componentDidMount() {
if(!this.props.value && this.props.schema) {
this.resetValueToSample()
}
}
resetValueToSample = () => {
this.onChange(getSampleSchema(this.props.schema) )
}
onChange = (value) => {
this.props.onChange(value)
}
@@ -247,7 +258,8 @@ export class JsonSchema_object extends PureComponent {
let {
getComponent,
value,
errors
errors,
disabled
} = this.props
const TextArea = getComponent("TextArea")
@@ -258,6 +270,7 @@ export class JsonSchema_object extends PureComponent {
className={cx({ invalid: errors.size })}
title={ errors.size ? errors.join(", ") : ""}
value={value}
disabled={disabled}
onChange={ this.handleOnChange }/>
</div>
)

View File

@@ -3,6 +3,7 @@
export const UPDATE_SELECTED_SERVER = "oas3_set_servers"
export const UPDATE_REQUEST_BODY_VALUE = "oas3_set_request_body_value"
export const UPDATE_ACTIVE_EXAMPLES_MEMBER = "oas3_set_active_examples_member"
export const UPDATE_REQUEST_CONTENT_TYPE = "oas3_set_request_content_type"
export const UPDATE_RESPONSE_CONTENT_TYPE = "oas3_set_response_content_type"
export const UPDATE_SERVER_VARIABLE_VALUE = "oas3_set_server_variable_value"
@@ -21,6 +22,13 @@ export function setRequestBodyValue ({ value, pathMethod }) {
}
}
export function setActiveExamplesMember ({ name, pathMethod, contextType, contextName }) {
return {
type: UPDATE_ACTIVE_EXAMPLES_MEMBER,
payload: { name, pathMethod, contextType, contextName }
}
}
export function setRequestContentType ({ value, pathMethod }) {
return {
type: UPDATE_REQUEST_CONTENT_TYPE,

View File

@@ -1,24 +1,19 @@
import React, { PureComponent } from "react"
import PropTypes from "prop-types"
import { fromJS } from "immutable"
import { getSampleSchema, stringify } from "core/utils"
import { stringify } from "core/utils"
const NOOP = Function.prototype
export default class RequestBodyEditor extends PureComponent {
static propTypes = {
requestBody: PropTypes.object.isRequired,
mediaType: PropTypes.string.isRequired,
onChange: PropTypes.func,
getComponent: PropTypes.func.isRequired,
isExecute: PropTypes.bool,
specSelectors: PropTypes.object.isRequired,
value: PropTypes.string,
defaultValue: PropTypes.string,
};
static defaultProps = {
mediaType: "application/json",
requestBody: fromJS({}),
onChange: NOOP,
};
@@ -26,108 +21,75 @@ export default class RequestBodyEditor extends PureComponent {
super(props, context)
this.state = {
isEditBox: false,
userDidModify: false,
value: ""
}
value: stringify(props.value) || props.defaultValue
}
componentDidMount() {
this.setValueToSample.call(this)
// this is the glue that makes sure our initial value gets set as the
// current request body value
// TODO: achieve this in a selector instead
props.onChange(props.value)
}
componentWillReceiveProps(nextProps) {
if(this.props.mediaType !== nextProps.mediaType) {
// media type was changed
this.setValueToSample(nextProps.mediaType)
}
applyDefaultValue = (nextProps) => {
const { onChange, defaultValue } = (nextProps ? nextProps : this.props)
if(!this.props.isExecute && nextProps.isExecute) {
// we just entered execute mode,
// so enable editing for convenience
this.setState({ isEditBox: true })
}
}
componentDidUpdate(prevProps) {
if(this.props.requestBody !== prevProps.requestBody) {
// force recalc of value if the request body definition has changed
this.setValueToSample(this.props.mediaType)
}
}
setValueToSample = (explicitMediaType) => {
this.onChange(this.sample(explicitMediaType))
}
resetValueToSample = (explicitMediaType) => {
this.setState({ userDidModify: false })
this.setValueToSample(explicitMediaType)
}
sample = (explicitMediaType) => {
let { requestBody, mediaType } = this.props
let mediaTypeValue = requestBody.getIn(["content", explicitMediaType || mediaType])
let schema = mediaTypeValue.get("schema").toJS()
let mediaTypeExample = mediaTypeValue.get("example") !== undefined ? stringify(mediaTypeValue.get("example")) : null
return mediaTypeExample || getSampleSchema(schema, explicitMediaType || mediaType, {
includeWriteOnly: true
this.setState({
value: defaultValue
})
return onChange(defaultValue)
}
onChange = (value) => {
this.setState({value})
this.props.onChange(value)
this.props.onChange(stringify(value))
}
handleOnChange = e => {
const { mediaType } = this.props
const isJson = /json/i.test(mediaType)
const inputValue = isJson ? e.target.value.trim() : e.target.value
onDomChange = e => {
const inputValue = e.target.value
this.setState({ userDidModify: true })
this.onChange(inputValue)
this.setState({
value: inputValue,
}, () => this.onChange(inputValue))
}
toggleIsEditBox = () => this.setState( state => ({isEditBox: !state.isEditBox}))
componentWillReceiveProps(nextProps) {
if(
this.props.value !== nextProps.value &&
nextProps.value !== this.state.value
) {
this.setState({
value: stringify(nextProps.value)
})
}
if(!nextProps.value && nextProps.defaultValue && !!this.state.value) {
// if new value is falsy, we have a default, AND the falsy value didn't
// come from us originally
this.applyDefaultValue(nextProps)
}
}
render() {
let {
isExecute,
getComponent,
mediaType,
getComponent
} = this.props
const Button = getComponent("Button")
const TextArea = getComponent("TextArea")
const HighlightCode = getComponent("highlightCode")
let {
value
} = this.state
let { value, isEditBox, userDidModify } = this.state
const TextArea = getComponent("TextArea")
return (
<div className="body-param">
{
isEditBox && isExecute
? <TextArea className={"body-param__text"} value={value} onChange={ this.handleOnChange }/>
: (value && <HighlightCode className="body-param__example"
value={ value }/>)
}
<div className="body-param-options">
<div className="body-param-edit">
{
!isExecute ? null
: <Button className={isEditBox ? "btn cancel body-param__example-edit" : "btn edit body-param__example-edit"}
onClick={this.toggleIsEditBox}>{ isEditBox ? "Cancel" : "Edit"}
</Button>
}
{ userDidModify &&
<Button className="btn ml3" onClick={() => { this.resetValueToSample(mediaType) }}>Reset</Button>
}
</div>
</div>
<TextArea
className={"body-param__text"}
value={value}
onChange={ this.onDomChange }
/>
</div>
)

View File

@@ -4,6 +4,36 @@ import ImPropTypes from "react-immutable-proptypes"
import { Map, OrderedMap, List } from "immutable"
import { getCommonExtensions, getSampleSchema, stringify } from "core/utils"
function getDefaultRequestBodyValue(requestBody, mediaType, activeExamplesKey) {
let mediaTypeValue = requestBody.getIn(["content", mediaType])
let schema = mediaTypeValue.get("schema").toJS()
let example =
mediaTypeValue.get("example") !== undefined
? stringify(mediaTypeValue.get("example"))
: null
let currentExamplesValue = mediaTypeValue.getIn([
"examples",
activeExamplesKey,
"value"
])
if (mediaTypeValue.get("examples")) {
// the media type DOES have examples
return stringify(currentExamplesValue) || ""
} else {
// the media type DOES NOT have examples
return stringify(
example ||
getSampleSchema(schema, mediaType, {
includeWriteOnly: true
}) ||
""
)
}
}
const RequestBody = ({
requestBody,
requestBodyValue,
@@ -14,7 +44,9 @@ const RequestBody = ({
contentType,
isExecute,
specPath,
onChange
onChange,
activeExamplesKey,
updateActiveExamplesKey,
}) => {
const handleFile = (e) => {
onChange(e.target.files[0])
@@ -23,6 +55,9 @@ const RequestBody = ({
const Markdown = getComponent("Markdown")
const ModelExample = getComponent("modelExample")
const RequestBodyEditor = getComponent("RequestBodyEditor")
const HighlightCode = getComponent("highlightCode")
const ExamplesSelectValueRetainer = getComponent("ExamplesSelectValueRetainer")
const Example = getComponent("Example")
const { showCommonExtensions } = getConfigs()
@@ -32,6 +67,11 @@ const RequestBody = ({
const mediaTypeValue = requestBodyContent.get(contentType, OrderedMap())
const schemaForMediaType = mediaTypeValue.get("schema", OrderedMap())
const examplesForMediaType = mediaTypeValue.get("examples", OrderedMap())
const handleExamplesSelect = (key /*, { isSyntheticChange } */) => {
updateActiveExamplesKey(key)
}
if(!mediaTypeValue.size) {
return null
@@ -139,6 +179,34 @@ const RequestBody = ({
{ requestBodyDescription &&
<Markdown source={requestBodyDescription} />
}
{
examplesForMediaType ? (
<ExamplesSelectValueRetainer
examples={examplesForMediaType}
currentKey={activeExamplesKey}
currentUserInputValue={requestBodyValue}
onSelect={handleExamplesSelect}
updateValue={onChange}
defaultToFirstExample={true}
getComponent={getComponent}
/>
) : null
}
{
isExecute ? (
<div>
<RequestBodyEditor
value={requestBodyValue}
defaultValue={getDefaultRequestBodyValue(
requestBody,
contentType,
activeExamplesKey,
)}
onChange={onChange}
getComponent={getComponent}
/>
</div>
) : (
<ModelExample
getComponent={ getComponent }
getConfigs={ getConfigs }
@@ -147,15 +215,27 @@ const RequestBody = ({
isExecute={isExecute}
schema={mediaTypeValue.get("schema")}
specPath={specPath.push("content", contentType)}
example={<RequestBodyEditor
requestBody={requestBody}
onChange={onChange}
mediaType={contentType}
getComponent={getComponent}
isExecute={isExecute}
specSelectors={specSelectors}
/>}
example={
<HighlightCode
className="body-param__example"
value={stringify(requestBodyValue) || getDefaultRequestBodyValue(
requestBody,
contentType,
activeExamplesKey,
)}
/>
}
/>
)
}
{
examplesForMediaType ? (
<Example
example={examplesForMediaType.get(activeExamplesKey)}
getComponent={getComponent}
/>
) : null
}
</div>
}
@@ -169,7 +249,9 @@ RequestBody.propTypes = {
contentType: PropTypes.string,
isExecute: PropTypes.bool.isRequired,
onChange: PropTypes.func.isRequired,
specPath: PropTypes.array.isRequired
specPath: PropTypes.array.isRequired,
activeExamplesKey: PropTypes.string,
updateActiveExamplesKey: PropTypes.func,
}
export default RequestBody

View File

@@ -1,6 +1,7 @@
import {
UPDATE_SELECTED_SERVER,
UPDATE_REQUEST_BODY_VALUE,
UPDATE_ACTIVE_EXAMPLES_MEMBER,
UPDATE_REQUEST_CONTENT_TYPE,
UPDATE_SERVER_VARIABLE_VALUE,
UPDATE_RESPONSE_CONTENT_TYPE
@@ -15,6 +16,10 @@ export default {
let [path, method] = pathMethod
return state.setIn( [ "requestData", path, method, "bodyValue" ], value)
},
[UPDATE_ACTIVE_EXAMPLES_MEMBER]: (state, { payload: { name, pathMethod, contextType, contextName } } ) =>{
let [path, method] = pathMethod
return state.setIn( [ "examples", path, method, contextType, contextName, "activeExample" ], name)
},
[UPDATE_REQUEST_CONTENT_TYPE]: (state, { payload: { value, pathMethod } } ) =>{
let [path, method] = pathMethod
return state.setIn( [ "requestData", path, method, "requestContentType" ], value)

View File

@@ -26,6 +26,11 @@ export const requestBodyValue = onlyOAS3((state, path, method) => {
}
)
export const activeExamplesMember = onlyOAS3((state, path, method, type, name) => {
return state.getIn(["examples", path, method, type, name, "activeExample"]) || null
}
)
export const requestContentType = onlyOAS3((state, path, method) => {
return state.getIn(["requestData", path, method, "requestContentType"]) || null
}

View File

@@ -1,6 +1,5 @@
import Markdown from "./markdown"
import AuthItem from "./auth-item"
import parameters from "./parameters"
import VersionStamp from "./version-stamp"
import OnlineValidatorBadge from "./online-validator-badge"
import Model from "./model"
@@ -9,7 +8,6 @@ import JsonSchema_string from "./json-schema-string"
export default {
Markdown,
AuthItem,
parameters,
JsonSchema_string,
VersionStamp,
model: Model,

View File

@@ -25,6 +25,9 @@ import AuthItem from "core/components/auth/auth-item"
import AuthError from "core/components/auth/error"
import ApiKeyAuth from "core/components/auth/api-key-auth"
import BasicAuth from "core/components/auth/basic-auth"
import Example from "core/components/example"
import ExamplesSelect from "core/components/examples-select"
import ExamplesSelectValueRetainer from "core/components/examples-select-value-retainer"
import Oauth2 from "core/components/auth/oauth2"
import Clear from "core/components/clear"
import LiveResponse from "core/components/live-response"
@@ -41,7 +44,7 @@ import HighlightCode from "core/components/highlight-code"
import Responses from "core/components/responses"
import Response from "core/components/response"
import ResponseBody from "core/components/response-body"
import Parameters from "core/components/parameters"
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"
@@ -152,7 +155,10 @@ export default function() {
DeepLink,
InfoUrl,
InfoBasePath,
SvgAssets
SvgAssets,
Example,
ExamplesSelect,
ExamplesSelectValueRetainer,
}
}

View File

@@ -780,7 +780,7 @@ export function stringify(thing) {
return thing
}
if (thing.toJS) {
if (thing && thing.toJS) {
thing = thing.toJS()
}
@@ -793,6 +793,10 @@ export function stringify(thing) {
}
}
if(thing === null || thing === undefined) {
return ""
}
return thing.toString()
}

View File

@@ -70,6 +70,27 @@ textarea
{
@include invalidFormElement();
}
}
input,
textarea,
select {
&[disabled] {
// opacity: 0.85;
background-color: #fafafa;
color: #888;
cursor: not-allowed;
}
}
select[disabled] {
border-color: #888;
}
textarea[disabled] {
background-color: #41444e;
color: #fff;
}
@keyframes shake

View File

@@ -98,6 +98,51 @@
@include text_code();
}
.parameter-controls {
margin-top: 0.75em;
}
.examples {
&__title {
display: block;
font-size: 1.1em;
font-weight: bold;
margin-bottom: 0.75em;
}
&__section {
margin-top: 1.5em;
}
&__section-header {
font-weight: bold;
font-size: .9rem;
margin-bottom: .5rem;
// color: #555;
}
}
.examples-select {
margin-bottom: .75em;
&__section-label {
font-weight: bold;
font-size: .9rem;
margin-right: .5rem;
}
}
.example {
&__section {
margin-top: 1.5em;
}
&__section-header {
font-weight: bold;
font-size: .9rem;
margin-bottom: .5rem;
// color: #555;
}
}
.view-line-link
{
position: relative;
@@ -374,12 +419,14 @@
}
}
.model-example {
margin-top: 1em;
}
.tab
{
display: flex;
margin: 20px 0 10px 0;
padding: 0;
list-style: none;
@@ -538,46 +585,6 @@
}
}
.response-col_description__inner
{
div.markdown, div.renderedMarkdown
{
font-size: 12px;
font-style: italic;
display: block;
margin: 0;
padding: 10px;
border-radius: 4px;
background: $response-col-description-inner-markdown-background-color;
@include text_code($response-col-description-inner-markdown-font-color);
p
{
margin: 0;
@include text_code($response-col-description-inner-markdown-font-color);
}
a
{
@include text_code($response-col-description-inner-markdown-link-font-color);
text-decoration: underline;
&:hover {
color: $response-col-description-inner-markdown-link-font-color-hover;
}
}
th
{
@include text_code($response-col-description-inner-markdown-font-color);
border-bottom: 1px solid $response-col-description-inner-markdown-font-color;
}
}
}
.opblock-body
{
.opblock-loading-animation
@@ -744,18 +751,37 @@
}
}
.response-content-type {
.response-controls {
padding-top: 1em;
display: flex;
}
&.controls-accept-header {
.response-control-media-type {
margin-right: 1em;
&--accept-controller {
select {
border-color: $response-content-type-controls-accept-header-select-border-color;
}
}
small {
&__accept-message {
color: $response-content-type-controls-accept-header-small-font-color;
font-size: .7em;
}
&__title {
display: block;
margin-bottom: 0.2em;
font-size: .7em;
}
}
.response-control-examples {
&__title {
display: block;
margin-bottom: 0.2em;
font-size: .7em;
}
}

View File

@@ -84,6 +84,8 @@ table
.parameters-col_description
{
width: 99%; // forces other columns to shrink to their content widths
margin-bottom: 2em;
input[type=text]
{
width: 100%;
@@ -100,6 +102,10 @@ table
font-size: 16px;
font-weight: normal;
// hack to give breathing room to the name column
// TODO: refactor all of this to flexbox
margin-right: .75em;
@include text_headline();
&.required
@@ -158,3 +164,12 @@ table
{
padding: 20px;
}
.response-col_description {
width: 99%; // forces other columns to shrink to their content widths
}
.response-col_links {
min-width: 6em;
}

View File

@@ -127,12 +127,6 @@ $response-col-status-undocumented-font-color: $gray-300 !default;
$response-col-links-font-color: $gray-300 !default;
$response-col-description-inner-markdown-font-color: $white !default;
$response-col-description-inner-markdown-background-color: $gray-custom-1 !default;
$response-col-description-inner-markdown-link-font-color: $color-primary !default;
$response-col-description-inner-markdown-link-font-color-hover: $color-primary-hover !default;
$opblock-body-background-color: $gray-custom-1 !default;
$opblock-body-font-color: $white !default;

View File

@@ -0,0 +1,679 @@
module.exports = {
ParameterPrimitiveTestCases,
RequestBodyPrimitiveTestCases,
ResponsePrimitiveTestCases,
}
function ParameterPrimitiveTestCases({
operationDomId,
parameterName,
exampleA, // { value, key }
exampleB, // { value, key }
exampleC,
customUserInput,
customExpectedUrlSubstring,
}) {
it("should render examples options without Modified Value by default", () => {
cy.visit("/?url=/documents/features/multiple-examples-core.openapi.yaml")
.get(operationDomId)
.click()
.get(`tr[data-param-name="${parameterName}"]`)
.find(".examples-select option")
.should("have.length", exampleC ? 3 : 2)
// Ensure the relevant input is disabled
.get(
`tr[data-param-name="${parameterName}"] input, tr[data-param-name="${parameterName}"] textarea`
)
.should("have.attr", "disabled")
// Switch to Try-It-Out
.get(".try-out__btn")
.click()
.get(`.opblock-section-request-body`)
.find(".examples-select option")
.should("have.length", exampleC ? 3 : 2)
})
it("should set default static and Try-It-Out values based on the first member", () => {
cy.visit("/?url=/documents/features/multiple-examples-core.openapi.yaml")
// Expand the operation
.get(operationDomId)
.click()
// Assert on the static docs value
.get(
`tr[data-param-name="${parameterName}"] input,tr[data-param-name="${parameterName}"] textarea`
)
.should("have.value", exampleA.value)
// Switch to Try-It-Out
.get(".try-out__btn")
.click()
// Assert on the Try-It-Out value
.get(
`tr[data-param-name="${parameterName}"] input,tr[data-param-name="${parameterName}"] textarea`
)
.should("have.value", exampleA.value)
// Execute the operation
.get(".execute")
.click()
// Assert on the request URL
.get(".request-url")
.contains(
exampleA.serializedValue || `?message=${escape(exampleA.value)}`
)
})
it("should set static and Try-It-Out values based on the second member", () => {
cy.visit("/?url=/documents/features/multiple-examples-core.openapi.yaml")
// Expand the operation
.get(operationDomId)
.click()
// Choose the second example
.get("table.parameters .examples-select > select")
.select(exampleB.key)
// Assert on the static docs value
.get(
`tr[data-param-name="${parameterName}"] input,tr[data-param-name="${parameterName}"] textarea`
)
.should("have.value", exampleB.value)
// Switch to Try-It-Out
.get(".try-out__btn")
.click()
// Assert on the Try-It-Out value
.get(
`tr[data-param-name="${parameterName}"] input,tr[data-param-name="${parameterName}"] textarea`
)
.should("have.value", exampleB.value)
// Execute the operation
.get(".execute")
.click()
// Assert on the request URL
.get(".request-url")
.contains(
exampleB.serializedValue
? `?${exampleB.serializedValue}`
: `?message=${escape(exampleB.value)}`
)
})
it("should handle user-entered values correctly", () => {
cy.visit("/?url=/documents/features/multiple-examples-core.openapi.yaml")
// Expand the operation
.get(operationDomId)
.click()
// Switch to Try-It-Out
.get(".try-out__btn")
.click()
// Modify the input value
.get(
`tr[data-param-name="${parameterName}"] input,tr[data-param-name="${parameterName}"] textarea`
)
.clear()
.type(customUserInput)
// Assert on the active select menu item
.get("table.parameters .examples-select > select")
.find(":selected")
.should("have.text", "[Modified value]")
// Execute the operation
.get(".execute")
.click()
// Assert on the request URL
.get(".request-url")
.contains(
customExpectedUrlSubstring || `?message=${escape(customUserInput)}`
)
})
it("should retain user-entered values correctly", () => {
cy.visit("/?url=/documents/features/multiple-examples-core.openapi.yaml")
// Expand the operation
.get(operationDomId)
.click()
// Switch to Try-It-Out
.get(".try-out__btn")
.click()
// Modify the input value
.get(
`tr[data-param-name="${parameterName}"] input,tr[data-param-name="${parameterName}"] textarea`
)
.clear()
.type(customUserInput)
// Select the first example
.get("table.parameters .examples-select > select")
.select(exampleA.key)
// Execute the operation
.get(".execute")
.click()
// Assert on the request URL
.get(".request-url")
.contains(
exampleA.serializedValue
? `?${exampleA.serializedValue}`
: `?message=${escape(exampleA.value)}`
)
// Select the modified value
.get("table.parameters .examples-select > select")
.select("__MODIFIED__VALUE__")
// Execute the operation
.get(".execute")
.click()
// Assert on the request URL
.get(".request-url")
.contains(
customExpectedUrlSubstring || `?message=${escape(customUserInput)}`
)
})
}
function RequestBodyPrimitiveTestCases({
operationDomId,
exampleA, // { value, key, summary }
exampleB, // { value, key, summary }
exampleC,
customUserInput,
customUserInputExpectedCurlSubstring,
primaryMediaType = "text/plain",
secondaryMediaType = "text/plain+other",
}) {
it("should render examples options without Modified Value by default", () => {
cy.visit("/?url=/documents/features/multiple-examples-core.openapi.yaml")
.get(operationDomId)
.click()
.get(`.opblock-section-request-body`)
.find(".examples-select option")
.should("have.length", exampleC ? 3 : 2)
// Switch to Try-It-Out
.get(".try-out__btn")
.click()
.get(`.opblock-section-request-body`)
.find(".examples-select option")
.should("have.length", exampleC ? 3 : 2)
})
it("should set default static and Try-It-Out values based on the first member", () => {
cy.visit("/?url=/documents/features/multiple-examples-core.openapi.yaml")
// Expand the operation
.get(operationDomId)
.click()
// Assert on the static docs value
.get(`.opblock-section-request-body .microlight`)
.should("have.text", exampleA.value)
// Switch to Try-It-Out
.get(".try-out__btn")
.click()
// Assert on the Try-It-Out value
.get(`.opblock-section-request-body textarea`)
.should("have.value", exampleA.value)
// Execute the operation
.get(".execute")
.click()
// Assert on the curl body
// TODO: use an interceptor instead of curl
.get(".curl")
.contains(`-d "${exampleA.serializedValue || exampleA.value}"`)
})
it("should set default static and Try-It-Out values based on choosing the second member in static mode", () => {
cy.visit("/?url=/documents/features/multiple-examples-core.openapi.yaml")
// Expand the operation
.get(operationDomId)
.click()
// Choose the second example
.get(".opblock-section-request-body .examples-select > select")
.select(exampleB.key)
// Assert on the static docs value
.get(`.opblock-section-request-body .microlight`)
.should("have.text", exampleB.value)
// Switch to Try-It-Out
.get(".try-out__btn")
.click()
// Assert on the Try-It-Out value
.get(`.opblock-section-request-body textarea`)
.should("have.value", exampleB.value)
// Execute the operation
.get(".execute")
.click()
// Assert on the request URL
// TODO: use an interceptor instead of curl
.get(".curl")
.contains(`-d "${exampleB.serializedValue || exampleB.value}"`)
})
it("should set default static and Try-It-Out values based on choosing the second member in Try-It-Out mode", () => {
cy.visit("/?url=/documents/features/multiple-examples-core.openapi.yaml")
// Expand the operation
.get(operationDomId)
.click()
// Switch to Try-It-Out
.get(".try-out__btn")
.click()
// Choose the second example
.get(".opblock-section-request-body .examples-select > select")
.select(exampleB.key)
// Assert on the Try-It-Out value
.get(`.opblock-section-request-body textarea`)
.should("have.value", exampleB.value)
// Execute the operation
.get(".execute")
.click()
// Assert on the request URL
// TODO: use an interceptor instead of curl
.get(".curl")
.contains(`-d "${exampleB.serializedValue || exampleB.value}"`)
// Switch to static docs
.get(".try-out__btn")
.click()
// Assert on the static docs value
.get(`.opblock-section-request-body .microlight`)
.should("have.text", exampleB.value)
})
it("should return the dropdown entry for an example when manually returning to its value", () => {
cy.visit("/?url=/documents/features/multiple-examples-core.openapi.yaml")
// Expand the operation
.get(operationDomId)
.click()
// Assert on the static docs value
.get(`.opblock-section-request-body .microlight`)
.should("have.text", exampleA.value)
// Switch to Try-It-Out
.get(".try-out__btn")
.click()
// Assert on the Try-It-Out value
.get(`.opblock-section-request-body textarea`)
.should("have.value", exampleA.value)
// Clear the Try-It-Out value, replace it with custom value
.clear()
.type(customUserInput)
// Assert on the dropdown value
.get(".opblock-section-request-body .examples-select > select")
.find(":selected")
.should("have.text", "[Modified value]")
// Modify the value again, going back to the example value
.get(`.opblock-section-request-body textarea`)
.clear()
.type(exampleA.value)
// Assert on the dropdown value returning to the example value
.get(".opblock-section-request-body .examples-select > select")
.find(":selected")
.should("have.text", exampleA.summary)
})
it("should retain choosing a member in static docs when changing the media type", () => {
cy.visit("/?url=/documents/features/multiple-examples-core.openapi.yaml")
// Expand the operation
.get(operationDomId)
.click()
// Choose the second example
.get(".opblock-section-request-body .examples-select > select")
.select(exampleB.key)
// Change the media type
.get(".opblock-section-request-body .content-type")
.select(secondaryMediaType)
// Assert on the static docs value
.get(`.opblock-section-request-body .microlight`)
.should("have.text", exampleB.value)
// Switch to Try-It-Out
.get(".try-out__btn")
.click()
// Assert on the Try-It-Out value
.get(`.opblock-section-request-body textarea`)
.should("have.value", exampleB.value)
// Execute the operation
.get(".execute")
.click()
// Assert on the request URL
// TODO: use an interceptor instead of curl
.get(".curl")
.contains(`-d "${exampleB.serializedValue || exampleB.value}"`)
})
it("should use the first example for the media type when changing the media type without prior interactions with the value", () => {
cy.visit("/?url=/documents/features/multiple-examples-core.openapi.yaml")
// Expand the operation
.get(operationDomId)
.click()
// Change the media type
.get(".opblock-section-request-body .content-type")
.select(secondaryMediaType)
// Assert on the static docs value
.get(`.opblock-section-request-body .microlight`)
.should("have.text", exampleA.value)
// Switch to Try-It-Out
.get(".try-out__btn")
.click()
// Assert on the Try-It-Out value
.get(`.opblock-section-request-body textarea`)
.should("have.value", exampleA.value)
// Execute the operation
.get(".execute")
.click()
// Assert on the request URL
// TODO: use an interceptor instead of curl
.get(".curl")
.contains(`-d "${exampleA.serializedValue || exampleA.value}"`)
})
it("static mode toggling: mediaType -> example -> mediaType -> example", () => {
cy.visit("/?url=/documents/features/multiple-examples-core.openapi.yaml")
// Expand the operation
.get(operationDomId)
.click()
// Change the media type
.get(".opblock-section-request-body .content-type")
.select(secondaryMediaType)
// Assert on the static docs value
.get(`.opblock-section-request-body .microlight`)
.should("have.text", exampleA.value)
// Assert on the dropdown value
.get(".opblock-section-request-body .examples-select > select")
.find(":selected")
.should("have.text", exampleA.summary)
// Choose exampleB
.get(".opblock-section-request-body .examples-select > select")
.select(exampleB.key)
// Assert on the static docs value
.get(`.opblock-section-request-body .microlight`)
.should("have.text", exampleB.value)
// Assert on the dropdown value
.get(".opblock-section-request-body .examples-select > select")
.find(":selected")
.should("have.text", exampleB.summary)
// Change the media type
.get(".opblock-section-request-body .content-type")
.select(primaryMediaType)
// Assert that the static docs value didn't change
.get(`.opblock-section-request-body .microlight`)
.should("have.text", exampleB.value)
// Assert that the dropdown value didn't change
.get(".opblock-section-request-body .examples-select > select")
.find(":selected")
.should("have.text", exampleB.summary)
// Choose exampleA
.get(".opblock-section-request-body .examples-select > select")
.select(exampleA.key)
// Assert on the static docs value
.get(`.opblock-section-request-body .microlight`)
.should("have.text", exampleA.value)
// Assert on the dropdown value
.get(".opblock-section-request-body .examples-select > select")
.find(":selected")
.should("have.text", exampleA.summary)
})
it("Try-It-Out toggling: mediaType -> example -> mediaType -> example", () => {
cy.visit("/?url=/documents/features/multiple-examples-core.openapi.yaml")
// Expand the operation
.get(operationDomId)
.click()
// Switch to Try-It-Out
.get(".try-out__btn")
.click()
// Change the media type
.get(".opblock-section-request-body .content-type")
.select(secondaryMediaType)
// Assert on the static docs value
.get(`.opblock-section-request-body textarea`)
.should("have.text", exampleA.value)
// Assert on the dropdown value
.get(".opblock-section-request-body .examples-select > select")
.find(":selected")
.should("have.text", exampleA.summary)
// Choose exampleB
.get(".opblock-section-request-body .examples-select > select")
.select(exampleB.key)
// Assert on the static docs value
.get(`.opblock-section-request-body textarea`)
.should("have.text", exampleB.value)
// Assert on the dropdown value
.get(".opblock-section-request-body .examples-select > select")
.find(":selected")
.should("have.text", exampleB.summary)
// Change the media type
.get(".opblock-section-request-body .content-type")
.select(primaryMediaType)
// Assert that the static docs value didn't change
.get(`.opblock-section-request-body textarea`)
.should("have.text", exampleB.value)
// Assert that the dropdown value didn't change
.get(".opblock-section-request-body .examples-select > select")
.find(":selected")
.should("have.text", exampleB.summary)
// Choose exampleA
.get(".opblock-section-request-body .examples-select > select")
.select(exampleA.key)
// Assert on the static docs value
.get(`.opblock-section-request-body textarea`)
.should("have.text", exampleA.value)
// Assert on the dropdown value
.get(".opblock-section-request-body .examples-select > select")
.find(":selected")
.should("have.text", exampleA.summary)
})
it("Try-It-Out toggling and execution with modified values: mediaType -> modified value -> example -> mediaType -> example", () => {
cy.visit("/?url=/documents/features/multiple-examples-core.openapi.yaml")
// Expand the operation
.get(operationDomId)
.click()
// Switch to Try-It-Out
.get(".try-out__btn")
.click()
// Change the media type
.get(".opblock-section-request-body .content-type")
.select(secondaryMediaType)
// Assert on the static docs value
.get(`.opblock-section-request-body textarea`)
.should("have.text", exampleA.value)
// Assert on the dropdown value
.get(".opblock-section-request-body .examples-select > select")
.find(":selected")
.should("have.text", exampleA.summary)
// Modify the value
.get(`.opblock-section-request-body textarea`)
.clear()
.type(customUserInput)
// Assert on the dropdown value
.get(".opblock-section-request-body .examples-select > select")
.find(":selected")
.should("have.text", "[Modified value]")
// Fire the operation
.get(".execute")
.click()
// Assert on the curl body
// TODO: use an interceptor instead of curl
.get(".curl")
.contains(
`-d "${customUserInputExpectedCurlSubstring || customUserInput}"`
)
// Choose exampleB
.get(".opblock-section-request-body .examples-select > select")
.select(exampleB.key)
// Assert on the static docs value
.get(`.opblock-section-request-body textarea`)
.should("have.text", exampleB.value)
// Assert on the dropdown value
.get(".opblock-section-request-body .examples-select > select")
.find(":selected")
.should("have.text", exampleB.summary)
// Fire the operation
.get(".execute")
.click()
// Assert on the curl body
// TODO: use an interceptor instead of curl
.get(".curl")
.contains(`-d "${exampleB.serializedValue || exampleB.value}"`)
// Ensure the modified value is still accessible
.get(".opblock-section-request-body .examples-select > select")
.contains("[Modified value]")
// Change the media type to text/plain
.get(".opblock-section-request-body .content-type")
.select(primaryMediaType)
// Assert that the static docs value didn't change
.get(`.opblock-section-request-body textarea`)
.should("have.text", exampleB.value)
// Assert that the dropdown value didn't change
.get(".opblock-section-request-body .examples-select > select")
.find(":selected")
.should("have.text", exampleB.summary)
// Fire the operation
.get(".execute")
.click()
// Assert on the curl body
// TODO: use an interceptor instead of curl
.get(".curl")
.contains(`-d "${exampleB.serializedValue || exampleB.value}"`)
// Ensure the modified value is still accessible
.get(".opblock-section-request-body .examples-select > select")
.contains("[Modified value]")
// Choose exampleA
.get(".opblock-section-request-body .examples-select > select")
.select(exampleA.key)
// Assert on the static docs value
.get(`.opblock-section-request-body textarea`)
.should("have.text", exampleA.value)
// Assert on the dropdown value
.get(".opblock-section-request-body .examples-select > select")
.find(":selected")
.should("have.text", exampleA.summary)
// Fire the operation
.get(".execute")
.click()
// Assert on the curl body
// TODO: use an interceptor instead of curl
.get(".curl")
.contains(`-d "${exampleA.serializedValue || exampleA.value}"`)
// Ensure the modified value is still the same value
.get(".opblock-section-request-body .examples-select > select")
.select("__MODIFIED__VALUE__")
// Assert on the static docs value
.get(`.opblock-section-request-body textarea`)
.should("have.text", customUserInput.replace(/{{}/g, "{"))
// Assert on the dropdown value
.get(".opblock-section-request-body .examples-select > select")
.find(":selected")
.should("have.text", "[Modified value]")
// Fire the operation
.get(".execute")
.click()
// Assert on the curl body
// TODO: use an interceptor instead of curl
.get(".curl")
.contains(
`-d "${customUserInputExpectedCurlSubstring || customUserInput}"`
)
})
// TODO: Try-It-Out + Try-It-Out media type changes
}
function ResponsePrimitiveTestCases({
operationDomId,
exampleA, // { value, key, summary }
exampleB, // { value, key, summary }
exampleC, // { value, key, summary }
}) {
it("should render the first example by default", () => {
cy.visit("/?url=/documents/features/multiple-examples-core.openapi.yaml")
.get(operationDomId)
.click()
.get(".responses-wrapper")
.within(() => {
cy.get(".examples-select > select")
.find(":selected")
.should("have.text", exampleA.summary)
.get(".microlight")
.should("have.text", exampleA.value)
})
})
it("should render the second example", () => {
cy.visit("/?url=/documents/features/multiple-examples-core.openapi.yaml")
.get(operationDomId)
.click()
.get(".responses-wrapper")
.within(() => {
cy.get(".examples-select > select")
.select(exampleB.key)
.find(":selected")
.should("have.text", exampleB.summary)
.get(".microlight")
.should("have.text", exampleB.value)
})
})
it("should retain an example choice across media types if they share the same example", () => {
cy.visit("/?url=/documents/features/multiple-examples-core.openapi.yaml")
.get(operationDomId)
.click()
.get(".responses-wrapper")
.within(() => {
cy
// Change examples
.get(".examples-select > select")
.select(exampleB.key)
// Assert against dropdown value
.find(":selected")
.should("have.text", exampleB.summary)
// Assert against example value
.get(".microlight")
.should("have.text", exampleB.value)
// Change media types
.get(".content-type")
.select("text/plain+other")
// Assert against dropdown value
.get(".examples-select > select")
.find(":selected")
.should("have.text", exampleB.summary)
// Assert against example value
.get(".microlight")
.should("have.text", exampleB.value)
})
})
;(exampleC ? it : it.skip)(
"should reset to the first example if the new media type lacks the current example",
() => {
cy.visit("/?url=/documents/features/multiple-examples-core.openapi.yaml")
.get(operationDomId)
.click()
.get(".responses-wrapper")
.within(() => {
cy
// Change media types
.get(".content-type")
.select("text/plain+other")
// Change examples
.get(".examples-select > select")
.select(exampleC.key)
// Assert against dropdown value
.find(":selected")
.should("have.text", exampleC.summary || exampleC.key)
// Assert against example value
.get(".microlight")
.should("have.text", exampleC.value)
// Change media types
.get(".content-type")
.select("text/plain")
// Assert against dropdown value
.get(".examples-select > select")
.find(":selected")
.should("have.text", exampleA.summary)
// Assert against example value
.get(".microlight")
.should("have.text", exampleA.value)
})
}
)
}

View File

@@ -0,0 +1,400 @@
openapi: 3.0.0
info:
title: "Multiple Examples: Core Document"
description: |
This document has examples for straightforward usage of `examples` in...
* Parameter Object positions
* Response Object positions
* Request Body Object positions
It includes:
* cases for each JSON Schema type as an example value (except null) in each position
* variously-sized `examples` objects
* multi-paragraph descriptions within each example
It **does not** include the following out-of-scope items:
* usage of `examples` within `Parameter.content` or `Response.content`
* `externalValue` (might change)
It also lacks edge cases, which will be covered in the "Corner" Document:
* `examples` n=1, which presents an interesting UI problem w/ the dropdown
* `example` and `examples` both present
* example item value that doesn't match the input type
* e.g., `Parameter.type === "number"`, but `Parameter.examples.[key].value` is an object
* `null` as an example value
version: "1.0.2"
paths:
/String:
post:
summary: "Bonus: contains two requestBody media types"
parameters:
- in: query
name: message
required: true
description: This parameter just so happens to have a one-line description.
schema:
type: string
examples:
StringExampleA:
$ref: '#/components/examples/StringExampleA'
StringExampleB:
$ref: '#/components/examples/StringExampleB'
requestBody:
description: the wonderful payload of my request
content:
text/plain:
schema:
type: string
examples:
StringExampleA:
$ref: '#/components/examples/StringExampleA'
StringExampleB:
$ref: '#/components/examples/StringExampleB'
text/plain+other:
schema:
type: string
examples:
StringExampleA:
$ref: '#/components/examples/StringExampleA'
StringExampleB:
$ref: '#/components/examples/StringExampleB'
responses:
200:
description: has two media types; the second has a third example!
content:
text/plain:
schema:
type: string
examples:
StringExampleA:
$ref: '#/components/examples/StringExampleA'
StringExampleB:
$ref: '#/components/examples/StringExampleB'
text/plain+other:
schema:
type: string
examples:
StringExampleA:
$ref: '#/components/examples/StringExampleA'
StringExampleB:
$ref: '#/components/examples/StringExampleB'
StringExampleC:
$ref: '#/components/examples/StringExampleC'
/Number:
post:
parameters:
- in: query
name: message
required: true
schema:
type: number
examples:
NumberExampleA:
$ref: '#/components/examples/NumberExampleA'
NumberExampleB:
$ref: '#/components/examples/NumberExampleB'
NumberExampleC:
$ref: '#/components/examples/NumberExampleC'
requestBody:
description: the wonderful payload of my request
content:
text/plain:
schema:
type: number
examples:
NumberExampleA:
$ref: '#/components/examples/NumberExampleA'
NumberExampleB:
$ref: '#/components/examples/NumberExampleB'
NumberExampleC:
$ref: '#/components/examples/NumberExampleC'
text/plain+other:
schema:
type: number
examples:
NumberExampleA:
$ref: '#/components/examples/NumberExampleA'
NumberExampleB:
$ref: '#/components/examples/NumberExampleB'
NumberExampleC:
$ref: '#/components/examples/NumberExampleC'
responses:
200:
description: OK!
content:
text/plain:
schema:
type: number
examples:
NumberExampleA:
$ref: '#/components/examples/NumberExampleA'
NumberExampleB:
$ref: '#/components/examples/NumberExampleB'
text/plain+other:
schema:
type: number
examples:
NumberExampleA:
$ref: '#/components/examples/NumberExampleA'
NumberExampleB:
$ref: '#/components/examples/NumberExampleB'
NumberExampleC:
$ref: '#/components/examples/NumberExampleC'
/Boolean:
post:
parameters:
- in: query
name: message
required: true
schema:
type: boolean
examples:
BooleanExampleA:
$ref: '#/components/examples/BooleanExampleA'
BooleanExampleB:
$ref: '#/components/examples/BooleanExampleB'
requestBody:
description: the wonderful payload of my request
content:
text/plain:
schema:
type: boolean
examples:
BooleanExampleA:
$ref: '#/components/examples/BooleanExampleA'
BooleanExampleB:
$ref: '#/components/examples/BooleanExampleB'
text/plain+other:
schema:
type: boolean
examples:
BooleanExampleA:
$ref: '#/components/examples/BooleanExampleA'
BooleanExampleB:
$ref: '#/components/examples/BooleanExampleB'
responses:
200:
description: OK!
content:
text/plain:
schema:
type: boolean
examples:
BooleanExampleA:
$ref: '#/components/examples/BooleanExampleA'
BooleanExampleB:
$ref: '#/components/examples/BooleanExampleB'
text/plain+other:
schema:
type: boolean
examples:
BooleanExampleA:
$ref: '#/components/examples/BooleanExampleA'
BooleanExampleB:
$ref: '#/components/examples/BooleanExampleB'
/Array:
post:
parameters:
- in: query
name: message
required: true
schema:
type: array
items: {} # intentionally empty; don't want to assert on the items
examples:
ArrayExampleA:
$ref: '#/components/examples/ArrayExampleA'
ArrayExampleB:
$ref: '#/components/examples/ArrayExampleB'
ArrayExampleC:
$ref: '#/components/examples/ArrayExampleC'
requestBody:
description: the wonderful payload of my request
content:
application/json:
schema:
type: array
items: {} # intentionally empty; don't want to assert on the items
examples:
ArrayExampleA:
$ref: '#/components/examples/ArrayExampleA'
ArrayExampleB:
$ref: '#/components/examples/ArrayExampleB'
ArrayExampleC:
$ref: '#/components/examples/ArrayExampleC'
responses:
200:
description: OK!
content:
application/json:
schema:
type: array
items: {} # intentionally empty; don't want to assert on the items
examples:
ArrayExampleA:
$ref: '#/components/examples/ArrayExampleA'
ArrayExampleB:
$ref: '#/components/examples/ArrayExampleB'
ArrayExampleC:
$ref: '#/components/examples/ArrayExampleC'
/Object:
post:
parameters:
- in: query
name: data
required: true
schema:
type: object
examples:
ObjectExampleA:
$ref: '#/components/examples/ObjectExampleA'
ObjectExampleB:
$ref: '#/components/examples/ObjectExampleB'
requestBody:
description: the wonderful payload of my request
content:
application/json:
schema:
type: object
examples:
ObjectExampleA:
$ref: '#/components/examples/ObjectExampleA'
ObjectExampleB:
$ref: '#/components/examples/ObjectExampleB'
text/plain+other:
schema:
type: object
examples:
ObjectExampleA:
$ref: '#/components/examples/ObjectExampleA'
ObjectExampleB:
$ref: '#/components/examples/ObjectExampleB'
responses:
200:
description: OK!
content:
application/json:
schema:
type: object
examples:
ObjectExampleA:
$ref: '#/components/examples/ObjectExampleA'
ObjectExampleB:
$ref: '#/components/examples/ObjectExampleB'
text/plain+other:
schema:
type: object
examples:
ObjectExampleA:
$ref: '#/components/examples/ObjectExampleA'
ObjectExampleB:
$ref: '#/components/examples/ObjectExampleB'
components:
examples:
StringExampleA:
value: "hello world"
summary: Don't just string me along...
description: |
A string in C is actually a character array. As an individual character variable can store only one character, we need an array of characters to store strings. Thus, in C string is stored in an array of characters. Each character in a string occupies one location in an array. The null character \0 is put after the last character. This is done so that program can tell when the end of the string has been reached.
For example, the string “Hello” is stored as follows...
![](http://www.tutorialspoint.com/computer_programming/images/string_representation.jpg)
Since the string contains 5 characters. it requires a character array of size 6 to store it. the last character in a string is always a NULL('\0') character. Always keep in mind that the '\0' is not included in the length if the string, so here the length of the string is 5 only. Notice above that the indexes of the string starts from 0 not one so don't confuse yourself with index and length of string.
Thus, in C, a string is a one-dimensional array of characters terminated a null character. The terminating null character is important. In fact, a string not terminated by \0 is not really a string, but merely a collection of characters.
StringExampleB:
value: "The quick brown fox jumps over the lazy dog"
summary: "I'm a pangram!"
description: |
A pangram (Greek: παν γράμμα, pan gramma, "every letter") or holoalphabetic sentence is a sentence using every letter of a given alphabet at least once. Pangrams have been used to display typefaces, test equipment, and develop skills in handwriting, calligraphy, and keyboarding.
The best-known English pangram is "The quick brown fox jumps over the lazy dog". It has been used since at least the late 19th century, was utilized by Western Union to test Telex / TWX data communication equipment for accuracy and reliability, and is now used by a number of computer programs (most notably the font viewer built into Microsoft Windows) to display computer fonts.
Pangrams exist in practically every alphabet-based language. An example from German is _Victor jagt zwölf Boxkämpfer quer über den großen Sylter Deich_, which contains all letters, including every umlaut (ä, ö, ü) plus the ß. It has been used since before 1800.
In a sense, the pangram is the opposite of the lipogram, in which the aim is to omit one or more letters.
A perfect pangram contains every letter of the alphabet only once and
can be considered an anagram of the alphabet. The only perfect pangrams
that are known either use abbreviations, such as "Mr Jock, TV quiz PhD,
bags few lynx", or use words so obscure that the phrase is hard to
understand, such as "Cwm fjord bank glyphs vext quiz", where cwm is a
loan word from the Welsh language meaning a steep-sided valley, and vext
is an uncommon way to spell vexed.
StringExampleC:
value: "JavaScript rules"
summary: "A third example, for use in special places..."
NumberExampleA:
value: 7710263025
summary: "World population"
description: |
In demographics, the world population is the total number of humans currently living, and was estimated to have reached 7.7 billion people as of April 2019. It took over 200,000 years of human history for the world's population to reach 1 billion; and only 200 years more to reach 7 billion.
World population has experienced continuous growth since the end of the Great Famine of 13151317 and the Black Death in 1350, when it was near 370 million. The highest population growth rates global population increases above 1.8% per year occurred between 1955 and 1975, peaking to 2.1% between 1965 and 1970. The growth rate has declined to 1.2% between 2010 and 2015 and is projected to decline further in the course of the 21st century. However, the global population is still growing and is projected to reach about 10 billion in 2050 and more than 11 billion in 2100.
Total annual births were highest in the late 1980s at about 139 million, and as of 2011 were expected to remain essentially constant at a level of 135 million, while deaths numbered 56 million per year and were expected to increase to 80 million per year by 2040. The median age of the world's population was estimated to be 30.4 years in 2018.
NumberExampleB:
value: 9007199254740991
summary: "Number.MAX_SAFE_INTEGER"
description: |
The `MAX_SAFE_INTEGER` constant has a value of `9007199254740991` (9,007,199,254,740,991 or ~9 quadrillion). The reasoning behind that number is that JavaScript uses double-precision floating-point format numbers as specified in IEEE 754 and can only safely represent numbers between `-(2^53 - 1)` and `2^53 - 1`.
Safe in this context refers to the ability to represent integers exactly and to correctly compare them. For example, `Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2` will evaluate to `true`, which is mathematically incorrect. See `Number.isSafeInteger()` for more information.
Because `MAX_SAFE_INTEGER` is a static property of `Number`, you always use it as `Number.MAX_SAFE_INTEGER`, rather than as a property of a `Number` object you created.
NumberExampleC:
# `description` and `summary` intentionally omitted
value: 0
BooleanExampleA:
value: true
summary: The truth will set you free
description: |
In some programming languages, any expression can be evaluated in a context that expects a Boolean data type. Typically (though this varies by programming language) expressions like the number zero, the empty string, empty lists, and null evaluate to false, and strings with content (like "abc"), other numbers, and objects evaluate to true. Sometimes these classes of expressions are called "truthy" and "falsey".
BooleanExampleB:
# `description` intentionally omitted
value: false
summary: Friends don't lie to friends
ArrayExampleA:
value: [a, b, c]
summary: A lowly array of strings
description: |
In computer science, a list or sequence is an abstract data type that represents a countable number of ordered values, where the same value may occur more than once. An instance of a list is a computer representation of the mathematical concept of a finite sequence; the (potentially) infinite analog of a list is a stream.[1]:§3.5 Lists are a basic example of containers, as they contain other values. If the same value occurs multiple times, each occurrence is considered a distinct item.
ArrayExampleB:
value: [1, 2, 3, 4]
summary: A lowly array of numbers
description: |
Many programming languages provide support for list data types, and have special syntax and semantics for lists and list operations. A list can often be constructed by writing the items in sequence, separated by commas, semicolons, and/or spaces, within a pair of delimiters such as parentheses '()', brackets '[]', braces '{}', or angle brackets '<>'. Some languages may allow list types to be indexed or sliced like array types, in which case the data type is more accurately described as an array. In object-oriented programming languages, lists are usually provided as instances of subclasses of a generic "list" class, and traversed via separate iterators. List data types are often implemented using array data structures or linked lists of some sort, but other data structures may be more appropriate for some applications. In some contexts, such as in Lisp programming, the term list may refer specifically to a linked list rather than an array.
In type theory and functional programming, abstract lists are usually defined inductively by two operations: nil that yields the empty list, and cons, which adds an item at the beginning of a list.
ArrayExampleC:
# `summary` intentionally omitted
value: []
description: An empty array value should clear the current value
ObjectExampleA:
value:
firstName: Kyle
lastName: Shockey
email: kyle.shockey@smartbear.com
summary: A user's contact info
description: Who is this guy, anyways?
ObjectExampleB:
value:
name: Abbey
type: kitten
color: calico
gender: female
age: 11 weeks
summary: A wonderful kitten's info
description: |
Todays domestic cats are physically very similar to their wild
ancestors. “Domestic cats and wildcats share a majority of their
characteristics,” Lyons says, but there are a few key differences:
wildcats were and are typically larger than their domestic kin, with
brown, tabby-like fur. “Wildcats have to have camouflage thats going to
keep them very inconspicuous in the wild,” Lyons says. “So you cant
have cats with orange and white running around—theyre going to be
snatched up by their predators.” As cats were domesticated, they began
to be selected and bred for more interesting colorations, thus giving us
todays range of beautiful cat breeds.

View File

@@ -0,0 +1,642 @@
/**
* @prettier
*/
const {
ParameterPrimitiveTestCases,
RequestBodyPrimitiveTestCases,
ResponsePrimitiveTestCases,
} = require("../../helpers/multiple-examples")
describe("OpenAPI 3.0 Multiple Examples - core features", () => {
describe("/String", () => {
describe("in a parameter", () => {
ParameterPrimitiveTestCases({
operationDomId: "#operations-default-post_String",
parameterName: "message",
exampleA: {
key: "StringExampleA",
value: "hello world",
},
exampleB: {
key: "StringExampleB",
value: "The quick brown fox jumps over the lazy dog",
},
customUserInput: "OpenAPIs.org <3",
})
})
describe("in a Request Body", () => {
RequestBodyPrimitiveTestCases({
operationDomId: "#operations-default-post_String",
exampleA: {
key: "StringExampleA",
value: "hello world",
serializedValue: "hello world",
summary: "Don't just string me along...",
},
exampleB: {
key: "StringExampleB",
value: "The quick brown fox jumps over the lazy dog",
serializedValue: "The quick brown fox jumps over the lazy dog",
summary: "I'm a pangram!",
},
customUserInput: "OpenAPIs.org <3",
})
})
describe("in a Response", () => {
ResponsePrimitiveTestCases({
operationDomId: "#operations-default-post_String",
exampleA: {
key: "StringExampleA",
value: "hello world",
summary: "Don't just string me along...",
},
exampleB: {
key: "StringExampleB",
value: "The quick brown fox jumps over the lazy dog",
summary: "I'm a pangram!",
},
exampleC: {
key: "StringExampleC",
value: "JavaScript rules",
summary: "A third example, for use in special places...",
},
})
})
})
describe("/Number", () => {
describe("in a parameter", () => {
ParameterPrimitiveTestCases({
operationDomId: "#operations-default-post_Number",
parameterName: "message",
exampleA: {
key: "NumberExampleA",
value: "7710263025",
},
exampleB: {
key: "NumberExampleB",
value: "9007199254740991",
},
exampleC: {
key: "NumberExampleC",
value: "0",
},
customUserInput: "9001",
})
})
describe("in a Request Body", () => {
RequestBodyPrimitiveTestCases({
operationDomId: "#operations-default-post_Number",
exampleA: {
key: "NumberExampleA",
value: "7710263025",
summary: "World population",
},
exampleB: {
key: "NumberExampleB",
value: "9007199254740991",
summary: "Number.MAX_SAFE_INTEGER",
},
exampleC: {
key: "NumberExampleC",
value: "0",
},
customUserInput: "1337",
})
})
describe("in a Response", () => {
ResponsePrimitiveTestCases({
operationDomId: "#operations-default-post_Number",
exampleA: {
key: "NumberExampleA",
value: "7710263025",
summary: "World population",
},
exampleB: {
key: "NumberExampleB",
value: "9007199254740991",
summary: "Number.MAX_SAFE_INTEGER",
},
exampleC: {
key: "NumberExampleC",
value: "0",
},
})
})
})
describe("/Boolean", () => {
describe("in a parameter", () => {
it("should render and apply the first example and value by default", () => {
cy.visit(
"/?url=/documents/features/multiple-examples-core.openapi.yaml"
)
.get("#operations-default-post_Boolean")
.click()
// Assert on the initial dropdown value
.get("table.parameters .examples-select > select")
.find(":selected")
.should("have.text", "The truth will set you free")
// Assert on the initial JsonSchemaForm value
.get(".parameters-col_description > select")
.should("have.attr", "disabled")
.get(".parameters-col_description > select")
.find(":selected")
.should("have.text", "true")
// Execute
.get(".try-out__btn")
.click()
.get(".execute")
.click()
// Assert on the request URL
.get(".request-url")
.contains(`?message=true`)
})
it("should render and apply the second value when chosen", () => {
cy.visit(
"/?url=/documents/features/multiple-examples-core.openapi.yaml"
)
.get("#operations-default-post_Boolean")
.click()
// Set the dropdown value, then assert on it
.get("table.parameters .examples-select > select")
.select("BooleanExampleB")
.find(":selected")
.should("have.text", "Friends don't lie to friends")
// Set the JsonSchemaForm value, then assert on it
.get(".parameters-col_description > select")
.find(":selected")
.should("have.text", "false")
// Execute
.get(".try-out__btn")
.click()
.get(".execute")
.click()
// Assert on the request URL
.get(".request-url")
.contains(`?message=false`)
})
it("should track value changes against valid examples", () => {
cy.visit(
"/?url=/documents/features/multiple-examples-core.openapi.yaml"
)
.get("#operations-default-post_Boolean")
.click()
.get(".try-out__btn")
.click()
// Set the JsonSchemaForm value, then assert on it
.get(".parameters-col_description > select")
.select("false")
.find(":selected")
.should("have.text", "false")
// Assert on the dropdown value
.get("table.parameters .examples-select > select")
.find(":selected")
.should("have.text", "Friends don't lie to friends")
// Execute
.get(".execute")
.click()
// Assert on the request URL
.get(".request-url")
.contains(`?message=false`)
})
})
describe("in a Request Body", () => {
RequestBodyPrimitiveTestCases({
operationDomId: "#operations-default-post_Boolean",
exampleA: {
key: "BooleanExampleA",
value: "true",
summary: "The truth will set you free",
},
exampleB: {
key: "BooleanExampleB",
value: "false",
summary: "Friends don't lie to friends",
},
customUserInput: "tralse",
})
})
describe("in a Response", () => {
it("should render and apply the first example and value by default", () => {
cy.visit(
"/?url=/documents/features/multiple-examples-core.openapi.yaml"
)
.get("#operations-default-post_Boolean")
.click()
// Assert on the initial dropdown value
.get(".responses-wrapper .examples-select > select")
.find(":selected")
.should("have.text", "The truth will set you free")
// Assert on the example value
.get(".example.microlight")
.should("have.text", "true")
})
it("should render and apply the second value when chosen", () => {
cy.visit(
"/?url=/documents/features/multiple-examples-core.openapi.yaml"
)
.get("#operations-default-post_Boolean")
.click()
// Set the dropdown value, then assert on it
.get(".responses-wrapper .examples-select > select")
.select("BooleanExampleB")
.find(":selected")
.should("have.text", "Friends don't lie to friends")
// Assert on the example value
.get(".example.microlight")
.should("have.text", "false")
})
})
})
describe("/Array", () => {
describe("in a Parameter", () => {
it("should have the first example's array entries by default", () => {
cy.visit(
"/?url=/documents/features/multiple-examples-core.openapi.yaml"
)
.get("#operations-default-post_Array")
.click()
.get(".json-schema-form-item > input")
.then(inputs => {
expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([
"a",
"b",
"c",
])
})
.get(".parameters-col_description .examples-select > select")
.find(":selected")
.should("have.text", "A lowly array of strings")
})
it("should switch to the second array's entries via dropdown", () => {
cy.visit(
"/?url=/documents/features/multiple-examples-core.openapi.yaml"
)
.get("#operations-default-post_Array")
.click()
.get(".parameters-col_description .examples-select > select")
.select("ArrayExampleB")
.get(".json-schema-form-item > input")
.then(inputs => {
expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([
"1",
"2",
"3",
"4",
])
})
.get(".parameters-col_description .examples-select > select")
.find(":selected")
.should("have.text", "A lowly array of numbers")
})
it("should not allow modification of values in static mode", () => {
cy.visit(
"/?url=/documents/features/multiple-examples-core.openapi.yaml"
)
.get("#operations-default-post_Array")
.click()
.get(".parameters-col_description .examples-select > select")
.select("ArrayExampleB")
// Add a new item
.get(".json-schema-form-item > input")
.should("have.attr", "disabled")
})
it("should allow modification of values in Try-It-Out", () => {
cy.visit(
"/?url=/documents/features/multiple-examples-core.openapi.yaml"
)
.get("#operations-default-post_Array")
.click()
.get(".try-out__btn")
.click()
.get(".parameters-col_description .examples-select > select")
.select("ArrayExampleB")
// Add a new item
.get(".json-schema-form-item-add")
.click()
.get(".json-schema-form-item:last-of-type > input")
.type("5")
// Assert against the input fields
.get(".json-schema-form-item > input")
.then(inputs => {
expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([
"1",
"2",
"3",
"4",
"5",
])
})
.get(".parameters-col_description .examples-select > select")
.find(":selected")
.should("have.text", "[Modified value]")
})
it("should retain a modified value, and support returning to it", () => {
cy.visit(
"/?url=/documents/features/multiple-examples-core.openapi.yaml"
)
.get("#operations-default-post_Array")
.click()
.get(".try-out__btn")
.click()
.get(".parameters-col_description .examples-select > select")
.select("ArrayExampleB")
// Add a new item
.get(".json-schema-form-item-add")
.click()
.get(".json-schema-form-item:last-of-type > input")
.type("5")
// Reset to an example
.get(".parameters-col_description .examples-select > select")
.select("ArrayExampleB")
// Assert against the input fields
.get(".json-schema-form-item > input")
.then(inputs => {
expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([
"1",
"2",
"3",
"4",
])
})
.get(".parameters-col_description .examples-select > select")
.find(":selected")
.should("have.text", "A lowly array of numbers")
// Return to the modified value
.get(".parameters-col_description .examples-select > select")
.select("__MODIFIED__VALUE__")
// Assert that our modified value is back
.get(".json-schema-form-item > input")
.then(inputs => {
expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([
"1",
"2",
"3",
"4",
"5",
])
})
.get(".parameters-col_description .examples-select > select")
.find(":selected")
.should("have.text", "[Modified value]")
})
})
describe("in a Request Body", () => {
it("should have the first example's array entries by default", () => {
cy.visit(
"/?url=/documents/features/multiple-examples-core.openapi.yaml"
)
.get("#operations-default-post_Array")
.click()
// Check HighlightCode value
.get(".opblock-section-request-body .highlight-code")
.should("have.text", JSON.stringify(["a", "b", "c"], null, 2))
// Check dropdown value
.get(".opblock-section-request-body .examples-select > select")
.find(":selected")
.should("have.text", "A lowly array of strings")
// Switch to Try-It-Out
.get(".try-out__btn")
.click()
// Check textarea value
.get(".opblock-section-request-body textarea")
.should("have.value", JSON.stringify(["a", "b", "c"], null, 2))
// Check dropdown value
.get(".opblock-section-request-body .examples-select > select")
.find(":selected")
.should("have.text", "A lowly array of strings")
})
it("should switch to the second array's entries via dropdown", () => {
cy.visit(
"/?url=/documents/features/multiple-examples-core.openapi.yaml"
)
.get("#operations-default-post_Array")
.click()
.get(".opblock-section-request-body .examples-select > select")
.select("ArrayExampleB")
.get(".opblock-section-request-body .highlight-code")
.should("have.text", JSON.stringify([1, 2, 3, 4], null, 2))
.get(".opblock-section-request-body .examples-select > select")
.find(":selected")
.should("have.text", "A lowly array of numbers")
// Switch to Try-It-Out
.get(".try-out__btn")
.click()
// Check textarea value
.get(".opblock-section-request-body textarea")
.should("have.text", JSON.stringify([1, 2, 3, 4], null, 2))
// Check dropdown value
.get(".opblock-section-request-body .examples-select > select")
.find(":selected")
.should("have.text", "A lowly array of numbers")
})
it("should allow modification of values", () => {
cy.visit(
"/?url=/documents/features/multiple-examples-core.openapi.yaml"
)
.get("#operations-default-post_Array")
.click()
// Switch to Try-It-Out
.get(".try-out__btn")
.click()
// Choose the second example
.get(".opblock-section-request-body .examples-select > select")
.select("ArrayExampleB")
// Change the value
.get(".opblock-section-request-body textarea")
.type(`{leftarrow}{leftarrow},{enter} 5`)
// Check that [Modified value] is displayed in dropdown
.get(".opblock-section-request-body .examples-select > select")
.find(":selected")
.should("have.text", "[Modified value]")
// Check textarea value
.get(".opblock-section-request-body textarea")
.should("have.text", JSON.stringify([1, 2, 3, 4, 5], null, 2))
})
it("should retain a modified value, and support returning to it", () => {
cy.visit(
"/?url=/documents/features/multiple-examples-core.openapi.yaml"
)
.get("#operations-default-post_Array")
.click()
// Switch to Try-It-Out
.get(".try-out__btn")
.click()
// Choose the second example as the example to start with
.get(".opblock-section-request-body .examples-select > select")
.select("ArrayExampleB")
// Change the value
.get(".opblock-section-request-body textarea")
.type(`{leftarrow}{leftarrow},{enter} 5`)
// Check that [Modified value] is displayed in dropdown
.get(".opblock-section-request-body .examples-select > select")
.find(":selected")
.should("have.text", "[Modified value]")
// Check textarea value
.get(".opblock-section-request-body textarea")
.should("have.text", JSON.stringify([1, 2, 3, 4, 5], null, 2))
// Choose the second example
.get(".opblock-section-request-body .examples-select > select")
.select("ArrayExampleB")
// Check that the example is displayed in dropdown
.get(".opblock-section-request-body .examples-select > select")
.find(":selected")
.should("have.text", "A lowly array of numbers")
// Check textarea value
.get(".opblock-section-request-body textarea")
.should("have.text", JSON.stringify([1, 2, 3, 4], null, 2))
// Switch back to the modified value
.get(".opblock-section-request-body .examples-select > select")
.select("__MODIFIED__VALUE__")
// Check textarea value
.get(".opblock-section-request-body textarea")
.should("have.text", JSON.stringify([1, 2, 3, 4, 5], null, 2))
})
})
describe("in a Response", () => {
it("should render and apply the first example and value by default", () => {
cy.visit(
"/?url=/documents/features/multiple-examples-core.openapi.yaml"
)
.get("#operations-default-post_Array")
.click()
// Assert on the initial dropdown value
.get(".responses-wrapper .examples-select > select")
.find(":selected")
.should("have.text", "A lowly array of strings")
// Assert on the example value
.get(".example.microlight")
.should("have.text", JSON.stringify(["a", "b", "c"], null, 2))
})
it("should render and apply the second value when chosen", () => {
cy.visit(
"/?url=/documents/features/multiple-examples-core.openapi.yaml"
)
.get("#operations-default-post_Array")
.click()
// Set the dropdown value, then assert on it
.get(".responses-wrapper .examples-select > select")
.select("ArrayExampleB")
.find(":selected")
.should("have.text", "A lowly array of numbers")
// Assert on the example value
.get(".example.microlight")
.should("have.text", JSON.stringify([1, 2, 3, 4], null, 2))
})
})
})
describe("/Object", () => {
describe("in a Parameter", () => {
ParameterPrimitiveTestCases({
operationDomId: "#operations-default-post_Object",
parameterName: "data",
customUserInput: `{{} "openapiIsCool": true }`,
customExpectedUrlSubstring: "?openapiIsCool=true",
exampleA: {
key: "ObjectExampleA",
serializedValue:
"firstName=Kyle&lastName=Shockey&email=kyle.shockey%40smartbear.com",
value: JSON.stringify(
{
firstName: "Kyle",
lastName: "Shockey",
email: "kyle.shockey@smartbear.com",
},
null,
2
),
},
exampleB: {
key: "ObjectExampleB",
serializedValue:
"name=Abbey&type=kitten&color=calico&gender=female&age=11%20weeks",
value: JSON.stringify(
{
name: "Abbey",
type: "kitten",
color: "calico",
gender: "female",
age: "11 weeks",
},
null,
2
),
},
})
})
describe("in a Request Body", () => {
RequestBodyPrimitiveTestCases({
operationDomId: "#operations-default-post_Object",
primaryMediaType: "application/json",
// ↓ not a typo, Cypress requires escaping { when using `cy.type`
customUserInput: `{{} "openapiIsCool": true }`,
customExpectedUrlSubstring: "?openapiIsCool=true",
customUserInputExpectedCurlSubstring: `{\\"openapiIsCool\\":true}`,
exampleA: {
key: "ObjectExampleA",
serializedValue: `{\\"firstName\\":\\"Kyle\\",\\"lastName\\":\\"Shockey\\",\\"email\\":\\"kyle.shockey@smartbear.com\\"}`,
value: JSON.stringify(
{
firstName: "Kyle",
lastName: "Shockey",
email: "kyle.shockey@smartbear.com",
},
null,
2
),
summary: "A user's contact info",
},
exampleB: {
key: "ObjectExampleB",
serializedValue: `{\\"name\\":\\"Abbey\\",\\"type\\":\\"kitten\\",\\"color\\":\\"calico\\",\\"gender\\":\\"female\\",\\"age\\":\\"11 weeks\\"}`,
value: JSON.stringify(
{
name: "Abbey",
type: "kitten",
color: "calico",
gender: "female",
age: "11 weeks",
},
null,
2
),
summary: "A wonderful kitten's info",
},
})
})
describe("in a Response", () => {
ResponsePrimitiveTestCases({
operationDomId: "#operations-default-post_Object",
exampleA: {
key: "ObjectExampleA",
value: JSON.stringify(
{
firstName: "Kyle",
lastName: "Shockey",
email: "kyle.shockey@smartbear.com",
},
null,
2
),
summary: "A user's contact info",
},
exampleB: {
key: "ObjectExampleB",
value: JSON.stringify(
{
name: "Abbey",
type: "kitten",
color: "calico",
gender: "female",
age: "11 weeks",
},
null,
2
),
summary: "A wonderful kitten's info",
},
})
})
})
})

View File

@@ -41,8 +41,8 @@ const rules = [
module.exports = require("./make-webpack-config")(rules, {
_special: {
separateStylesheets: false,
sourcemaps: true,
},
devtool: "eval-source-map",
entry: {
"swagger-ui-bundle": [
"./src/polyfills",