From 280a75d39ca310a5aae5d91540388ea79920b96c Mon Sep 17 00:00:00 2001 From: "yan.nan" Date: Fri, 15 Nov 2019 08:55:11 +0800 Subject: [PATCH] fix: Fixed method sortByRequired --- src/services/models/Operation.ts | 5 +- src/services/models/Schema.ts | 6 +- src/utils/__tests__/openapi.test.ts | 367 ++++++++++++++++++++++++++++ src/utils/openapi.ts | 33 +-- 4 files changed, 391 insertions(+), 20 deletions(-) diff --git a/src/services/models/Operation.ts b/src/services/models/Operation.ts index 88640dc563..bdfbc5c7c6 100644 --- a/src/services/models/Operation.ts +++ b/src/services/models/Operation.ts @@ -154,12 +154,11 @@ export class OperationModel implements IMenuItem { ).map(paramOrRef => new FieldModel(this.parser, paramOrRef, this.pointer, this.options)); if (this.options.sortPropsAlphabetically) { - sortByField(_parameters, 'name'); + return sortByField(_parameters, 'name'); } if (this.options.requiredPropsFirst) { - sortByRequired(_parameters); + return sortByRequired(_parameters); } - return _parameters; } @memoize diff --git a/src/services/models/Schema.ts b/src/services/models/Schema.ts index 6b22871142..7970be03dc 100644 --- a/src/services/models/Schema.ts +++ b/src/services/models/Schema.ts @@ -251,7 +251,7 @@ function buildFields( const props = schema.properties || {}; const additionalProps = schema.additionalProperties; const defaults = schema.default || {}; - const fields = Object.keys(props || []).map(fieldName => { + let fields = Object.keys(props || []).map(fieldName => { let field = props[fieldName]; if (!field) { @@ -280,11 +280,11 @@ function buildFields( }); if (options.sortPropsAlphabetically) { - sortByField(fields, 'name'); + fields = sortByField(fields, 'name'); } if (options.requiredPropsFirst) { // if not sort alphabetically sort in the order from required keyword - sortByRequired(fields, !options.sortPropsAlphabetically ? schema.required : undefined); + fields = sortByRequired(fields, !options.sortPropsAlphabetically ? schema.required : undefined); } if (typeof additionalProps === 'object' || additionalProps === true) { diff --git a/src/utils/__tests__/openapi.test.ts b/src/utils/__tests__/openapi.test.ts index 465c6e6a7c..ab22268278 100644 --- a/src/utils/__tests__/openapi.test.ts +++ b/src/utils/__tests__/openapi.test.ts @@ -9,6 +9,7 @@ import { normalizeServers, pluralizeType, serializeParameterValue, + sortByRequired, } from '../'; import { FieldModel, OpenAPIParser, RedocNormalizedOptions } from '../../services'; @@ -636,4 +637,370 @@ describe('Utils', () => { }); }); }); + + describe('OpenAPI sortByRequired', () => { + it('should equal to the old data when all items have no required props', () => { + let fields = [ + { + name: 'loginName', + required: false, + }, + { + name: 'displayName', + required: false, + }, + { + name: 'email', + required: false, + }, + { + name: 'space', + required: false, + }, + { + name: 'type', + required: false, + }, + { + name: 'depIds', + required: false, + }, + { + name: 'depNames', + required: false, + }, + { + name: 'password', + required: false, + }, + { + name: 'pwdControl', + required: false, + }, + { + name: 'csfLevel', + required: false, + }, + { + name: 'priority', + required: false, + }, + { + name: 'siteId', + required: false, + }, + ]; + expect(sortByRequired(fields as FieldModel[])).toEqual(fields); + }); + + it('other item should be the same order when some of items are required', () => { + let fields = [ + { + name: 'loginName', + required: true, + }, + { + name: 'displayName', + required: false, + }, + { + name: 'email', + required: true, + }, + { + name: 'space', + required: false, + }, + { + name: 'type', + required: false, + }, + { + name: 'depIds', + required: false, + }, + { + name: 'depNames', + required: false, + }, + { + name: 'password', + required: false, + }, + { + name: 'pwdControl', + required: false, + }, + { + name: 'csfLevel', + required: false, + }, + { + name: 'priority', + required: false, + }, + { + name: 'siteId', + required: false, + }, + ]; + let sortedFields = [ + { + name: 'loginName', + required: true, + }, + { + name: 'email', + required: true, + }, + { + name: 'displayName', + required: false, + }, + { + name: 'space', + required: false, + }, + { + name: 'type', + required: false, + }, + { + name: 'depIds', + required: false, + }, + { + name: 'depNames', + required: false, + }, + { + name: 'password', + required: false, + }, + { + name: 'pwdControl', + required: false, + }, + { + name: 'csfLevel', + required: false, + }, + { + name: 'priority', + required: false, + }, + { + name: 'siteId', + required: false, + }, + ]; + expect(sortByRequired(fields as FieldModel[])).toEqual(sortedFields); + }); + + it('should the order of required items is as same as the order parameter ', () => { + let fields = [ + { + name: 'loginName', + required: true, + }, + { + name: 'displayName', + required: true, + }, + { + name: 'email', + required: true, + }, + { + name: 'space', + required: false, + }, + { + name: 'type', + required: false, + }, + { + name: 'depIds', + required: false, + }, + { + name: 'depNames', + required: false, + }, + { + name: 'password', + required: false, + }, + { + name: 'pwdControl', + required: false, + }, + { + name: 'csfLevel', + required: false, + }, + { + name: 'priority', + required: false, + }, + { + name: 'siteId', + required: false, + }, + ]; + expect( + sortByRequired(fields as FieldModel[], ['siteId', 'displayName', 'loginName', 'email']), + ).toEqual([ + { + name: 'displayName', + required: true, + }, + { + name: 'loginName', + required: true, + }, + { + name: 'email', + required: true, + }, + { + name: 'space', + required: false, + }, + { + name: 'type', + required: false, + }, + { + name: 'depIds', + required: false, + }, + { + name: 'depNames', + required: false, + }, + { + name: 'password', + required: false, + }, + { + name: 'pwdControl', + required: false, + }, + { + name: 'csfLevel', + required: false, + }, + { + name: 'priority', + required: false, + }, + { + name: 'siteId', + required: false, + }, + ]); + expect(sortByRequired(fields as FieldModel[], ['email', 'displayName'])).toEqual([ + { + name: 'email', + required: true, + }, + { + name: 'displayName', + required: true, + }, + { + name: 'loginName', + required: true, + }, + { + name: 'space', + required: false, + }, + { + name: 'type', + required: false, + }, + { + name: 'depIds', + required: false, + }, + { + name: 'depNames', + required: false, + }, + { + name: 'password', + required: false, + }, + { + name: 'pwdControl', + required: false, + }, + { + name: 'csfLevel', + required: false, + }, + { + name: 'priority', + required: false, + }, + { + name: 'siteId', + required: false, + }, + ]); + + expect(sortByRequired(fields as FieldModel[], ['displayName'])).toEqual([ + { + name: 'displayName', + required: true, + }, + { + name: 'loginName', + required: true, + }, + { + name: 'email', + required: true, + }, + { + name: 'space', + required: false, + }, + { + name: 'type', + required: false, + }, + { + name: 'depIds', + required: false, + }, + { + name: 'depNames', + required: false, + }, + { + name: 'password', + required: false, + }, + { + name: 'pwdControl', + required: false, + }, + { + name: 'csfLevel', + required: false, + }, + { + name: 'priority', + required: false, + }, + { + name: 'siteId', + required: false, + }, + ]); + }); + }); }); diff --git a/src/utils/openapi.ts b/src/utils/openapi.ts index 30b2bb20eb..a518ee5a1b 100644 --- a/src/utils/openapi.ts +++ b/src/utils/openapi.ts @@ -1,6 +1,7 @@ import { dirname } from 'path'; const URLtemplate = require('url-template'); +import { FieldModel } from '../services/models'; import { OpenAPIParser } from '../services/OpenAPIParser'; import { OpenAPIEncoding, @@ -444,25 +445,29 @@ export function humanizeConstraints(schema: OpenAPISchema): string[] { return res; } -export function sortByRequired( - fields: Array<{ required: boolean; name: string }>, - order: string[] = [], -) { - fields.sort((a, b) => { - if (!a.required && b.required) { - return 1; - } else if (a.required && !b.required) { - return -1; - } else if (a.required && b.required) { - return order.indexOf(a.name) - order.indexOf(b.name); +export function sortByRequired(fields: FieldModel[], order: string[] = []) { + const unrequiredFields: FieldModel[] = []; + const orderedFields: FieldModel[] = []; + const unorderedFields: FieldModel[] = []; + + fields.forEach(field => { + if (field.required) { + order.includes(field.name) ? orderedFields.push(field) : unorderedFields.push(field); } else { - return 0; + unrequiredFields.push(field); } }); + + orderedFields.sort((a, b) => order.indexOf(a.name) - order.indexOf(b.name)); + + return [...orderedFields, ...unorderedFields, ...unrequiredFields]; } -export function sortByField(fields: Array<{ [P in T]: string }>, param: T) { - fields.sort((a, b) => { +export function sortByField( + fields: FieldModel[], + param: keyof Pick, +) { + return [...fields].sort((a, b) => { return a[param].localeCompare(b[param]); }); }