From fd8274b3538bdbd9c4306c260589beac3892b0bd Mon Sep 17 00:00:00 2001 From: kyle Date: Fri, 30 Mar 2018 18:02:32 -0700 Subject: [PATCH] Filter `$$ref` from examples (#4392) * fix(dev-server): don't open localhost in a browser * tests: refactor model-example enzyme tests to be more isolated * tests: add failing sampleFromSchema tests for $$ref keys * tests: add additional test for user-created $$ref values * fix: create deeplyStripKey; use it to filter $$refs out of examples * tests: add cases for deeplyStripKey --- src/core/plugins/samples/fn.js | 13 ++-- src/core/utils.js | 22 ++++++ test/components/model-example.js | 40 ++++++---- test/core/plugins/samples/fn.js | 130 +++++++++++++++++++++++++------ test/core/utils.js | 55 ++++++++++++- 5 files changed, 214 insertions(+), 46 deletions(-) diff --git a/src/core/plugins/samples/fn.js b/src/core/plugins/samples/fn.js index 30e74132..d7fa8cad 100644 --- a/src/core/plugins/samples/fn.js +++ b/src/core/plugins/samples/fn.js @@ -1,4 +1,4 @@ -import { objectify, isFunc, normalizeArray } from "core/utils" +import { objectify, isFunc, normalizeArray, deeplyStripKey } from "core/utils" import XML from "xml" import memoizee from "memoizee" @@ -29,13 +29,14 @@ export const sampleFromSchema = (schema, config={}) => { let { type, example, properties, additionalProperties, items } = objectify(schema) let { includeReadOnly, includeWriteOnly } = config - if(example && example.$$ref) { - delete example.$$ref + if(example !== undefined) { + return deeplyStripKey(example, "$$ref", (val) => { + // do a couple of quick sanity tests to ensure the value + // looks like a $$ref that swagger-client generates. + return typeof val === "string" && val.indexOf("#") > -1 + }) } - if(example !== undefined) - return example - if(!type) { if(properties) { type = "object" diff --git a/src/core/utils.js b/src/core/utils.js index 08450e73..913801ce 100644 --- a/src/core/utils.js +++ b/src/core/utils.js @@ -712,3 +712,25 @@ export const createDeepLinkPath = (str) => typeof str == "string" || str instanc export const escapeDeepLinkPath = (str) => cssEscape( createDeepLinkPath(str) ) export const getExtensions = (defObj) => defObj.filter((v, k) => /^x-/.test(k)) + +// Deeply strips a specific key from an object. +// +// `predicate` can be used to discriminate the stripping further, +// by preserving the key's place in the object based on its value. +export function deeplyStripKey(input, keyToStrip, predicate = () => true) { + if(typeof input !== "object" || Array.isArray(input) || !keyToStrip) { + return input + } + + const obj = Object.assign({}, input) + + Object.keys(obj).forEach(k => { + if(k === keyToStrip && predicate(obj[k], k)) { + delete obj[k] + return + } + obj[k] = deeplyStripKey(obj[k], keyToStrip, predicate) + }) + + return obj +} diff --git a/test/components/model-example.js b/test/components/model-example.js index f4225c30..eb8c8a27 100644 --- a/test/components/model-example.js +++ b/test/components/model-example.js @@ -6,23 +6,8 @@ import ModelExample from "components/model-example" import ModelComponent from "components/model-wrapper" describe("", function(){ - // Given - let components = { - ModelWrapper: ModelComponent - } - let props = { - getComponent: (c) => { - return components[c] - }, - specSelectors: {}, - schema: {}, - example: "{\"example\": \"value\"}", - isExecute: false, - getConfigs: () => ({ - defaultModelRendering: "model", - defaultModelExpandDepth: 1 - }) - } + let components, props + let exampleSelectedTestInputs = [ { defaultModelRendering: "model", isExecute: true }, { defaultModelRendering: "example", isExecute: true }, @@ -30,10 +15,31 @@ describe("", function(){ { defaultModelRendering: "othervalue", isExecute: true }, { defaultModelRendering: "othervalue", isExecute: false } ] + let modelSelectedTestInputs = [ { defaultModelRendering: "model", isExecute: false } ] + beforeEach(() => { + components = { + ModelWrapper: ModelComponent + } + + props = { + getComponent: (c) => { + return components[c] + }, + specSelectors: {}, + schema: {}, + example: "{\"example\": \"value\"}", + isExecute: false, + getConfigs: () => ({ + defaultModelRendering: "model", + defaultModelExpandDepth: 1 + }) + } + }) + it("renders model and example tabs", function(){ // When diff --git a/test/core/plugins/samples/fn.js b/test/core/plugins/samples/fn.js index 5a969d9c..65fdd141 100644 --- a/test/core/plugins/samples/fn.js +++ b/test/core/plugins/samples/fn.js @@ -100,6 +100,92 @@ describe("sampleFromSchema", function() { expect(sampleFromSchema(definition, { includeWriteOnly: true })).toEqual(expected) }) + it("returns object without any $$ref fields at the root schema level", function () { + var definition = { + type: "object", + properties: { + message: { + type: "string" + } + }, + example: { + value: { + message: "Hello, World!" + }, + $$ref: "#/components/examples/WelcomeExample" + }, + $$ref: "#/components/schemas/Welcome" + } + + var expected = { + "value": { + "message": "Hello, World!" + } + } + + expect(sampleFromSchema(definition, { includeWriteOnly: true })).toEqual(expected) + }) + + it("returns object without any $$ref fields at nested schema levels", function () { + var definition = { + type: "object", + properties: { + message: { + type: "string" + } + }, + example: { + a: { + value: { + message: "Hello, World!" + }, + $$ref: "#/components/examples/WelcomeExample" + } + }, + $$ref: "#/components/schemas/Welcome" + } + + var expected = { + a: { + "value": { + "message": "Hello, World!" + } + } + } + + expect(sampleFromSchema(definition, { includeWriteOnly: true })).toEqual(expected) + }) + + it("returns object with any $$ref fields that appear to be user-created", function () { + var definition = { + type: "object", + properties: { + message: { + type: "string" + } + }, + example: { + $$ref: { + value: { + message: "Hello, World!" + }, + $$ref: "#/components/examples/WelcomeExample" + } + }, + $$ref: "#/components/schemas/Welcome" + } + + var expected = { + $$ref: { + "value": { + "message": "Hello, World!" + } + } + } + + expect(sampleFromSchema(definition, { includeWriteOnly: true })).toEqual(expected) + }) + describe("for array type", function() { it("returns array with sample of array type", function() { var definition = { @@ -108,12 +194,12 @@ describe("sampleFromSchema", function() { type: "integer" } } - + var expected = [ 0 ] - + expect(sampleFromSchema(definition)).toEqual(expected) }) - + it("returns array of examples for array that has example", function() { var definition = { type: "array", @@ -122,9 +208,9 @@ describe("sampleFromSchema", function() { }, example: "dog" } - + var expected = [ "dog" ] - + expect(sampleFromSchema(definition)).toEqual(expected) }) @@ -132,16 +218,16 @@ describe("sampleFromSchema", function() { var definition = { type: "array", items: { - type: "string", + type: "string", }, example: [ "dog", "cat" ] } - + var expected = [ "dog", "cat" ] - + expect(sampleFromSchema(definition)).toEqual(expected) }) - + it("returns array of samples for oneOf type", function() { var definition = { type: "array", @@ -154,9 +240,9 @@ describe("sampleFromSchema", function() { ] } } - + var expected = [ 0 ] - + expect(sampleFromSchema(definition)).toEqual(expected) }) @@ -175,9 +261,9 @@ describe("sampleFromSchema", function() { ] } } - + var expected = [ "string", 0 ] - + expect(sampleFromSchema(definition)).toEqual(expected) }) @@ -198,12 +284,12 @@ describe("sampleFromSchema", function() { ] } } - + var expected = [ "dog", 1 ] - + expect(sampleFromSchema(definition)).toEqual(expected) }) - + it("returns array of samples for anyOf type", function() { var definition = { type: "array", @@ -216,9 +302,9 @@ describe("sampleFromSchema", function() { ] } } - + var expected = [ 0 ] - + expect(sampleFromSchema(definition)).toEqual(expected) }) @@ -237,9 +323,9 @@ describe("sampleFromSchema", function() { ] } } - + var expected = [ "string", 0 ] - + expect(sampleFromSchema(definition)).toEqual(expected) }) @@ -260,9 +346,9 @@ describe("sampleFromSchema", function() { ] } } - + var expected = [ "dog", 1 ] - + expect(sampleFromSchema(definition)).toEqual(expected) }) }) diff --git a/test/core/utils.js b/test/core/utils.js index 99ee60d4..1c6b2beb 100644 --- a/test/core/utils.js +++ b/test/core/utils.js @@ -21,7 +21,8 @@ import { createDeepLinkPath, escapeDeepLinkPath, sanitizeUrl, - extractFileNameFromContentDispositionHeader + extractFileNameFromContentDispositionHeader, + deeplyStripKey } from "core/utils" import win from "core/window" @@ -942,6 +943,58 @@ describe("utils", function() { }) }) + describe("deeplyStripKey", function() { + it("should filter out a specified key", function() { + const input = { + $$ref: "#/this/is/my/ref", + a: { + $$ref: "#/this/is/my/other/ref", + value: 12345 + } + } + const result = deeplyStripKey(input, "$$ref") + expect(result).toEqual({ + a: { + value: 12345 + } + }) + }) + + it("should filter out a specified key by predicate", function() { + const input = { + $$ref: "#/this/is/my/ref", + a: { + $$ref: "#/keep/this/one", + value: 12345 + } + } + const result = deeplyStripKey(input, "$$ref", (v) => v !== "#/keep/this/one") + expect(result).toEqual({ + a: { + value: 12345, + $$ref: "#/keep/this/one" + } + }) + }) + + it("should only call the predicate when the key matches", function() { + const input = { + $$ref: "#/this/is/my/ref", + a: { + $$ref: "#/this/is/my/other/ref", + value: 12345 + } + } + let count = 0 + + const result = deeplyStripKey(input, "$$ref", () => { + count++ + return true + }) + expect(count).toEqual(2) + }) + }) + describe("parse and serialize search", function() { afterEach(function() { win.location.search = ""