Skip to content

Commit

Permalink
fix: step-in
Browse files Browse the repository at this point in the history
  • Loading branch information
P0lip committed Feb 26, 2020
1 parent 670f0e5 commit e59a5f2
Show file tree
Hide file tree
Showing 10 changed files with 199 additions and 60 deletions.
24 changes: 12 additions & 12 deletions src/components/__tests__/Property.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { JSONSchema4 } from 'json-schema';
import * as React from 'react';
import { SchemaTree } from '../../tree';
import { metadataStore } from '../../tree/metadata';
import { walk } from '../../tree/walk';
import { walk } from '../../tree/utils/walk';
import { SchemaTreeListNode } from '../../types';
import { Property, Types } from '../shared';

Expand All @@ -25,7 +25,7 @@ describe('Property component', () => {
};

metadataStore.set(treeNode, {
schemaNode: walk(schema).next().value,
schemaNode: walk(schema).next().value.node,
path: [],
schema,
});
Expand All @@ -49,7 +49,7 @@ describe('Property component', () => {
};

metadataStore.set(treeNode, {
schemaNode: walk(schema).next().value,
schemaNode: walk(schema).next().value.node,
path: [],
schema,
});
Expand All @@ -70,7 +70,7 @@ describe('Property component', () => {
};

metadataStore.set(treeNode, {
schemaNode: walk(schema).next().value,
schemaNode: walk(schema).next().value.node,
path: [],
schema,
});
Expand All @@ -92,7 +92,7 @@ describe('Property component', () => {
};

metadataStore.set(treeNode, {
schemaNode: walk(schema).next().value,
schemaNode: walk(schema).next().value.node,
path: [],
schema,
});
Expand All @@ -114,7 +114,7 @@ describe('Property component', () => {
};

metadataStore.set(treeNode, {
schemaNode: walk(schema).next().value,
schemaNode: walk(schema).next().value.node,
path: [],
schema,
});
Expand All @@ -136,7 +136,7 @@ describe('Property component', () => {
};

metadataStore.set(treeNode, {
schemaNode: walk(schema).next().value,
schemaNode: walk(schema).next().value.node,
path: [],
schema,
});
Expand Down Expand Up @@ -164,7 +164,7 @@ describe('Property component', () => {

tree.populate();

const wrapper = shallow(<Property node={Array.from(tree)[1]} />);
const wrapper = shallow(<Property node={tree.itemAt(1)!} />);
expect(wrapper.find('div').first()).toHaveText('foo');
});

Expand All @@ -188,7 +188,7 @@ describe('Property component', () => {

tree.populate();

const wrapper = shallow(<Property node={Array.from(tree)[1]} />);
const wrapper = shallow(<Property node={tree.itemAt(1)!} />);
expect(wrapper.find('div').first()).toHaveText('foo');
});

Expand All @@ -213,11 +213,11 @@ describe('Property component', () => {
tree.populate();
tree.unwrap(Array.from(tree)[1] as TreeListParentNode);

const wrapper = shallow(<Property node={Array.from(tree)[2]} />);
const wrapper = shallow(<Property node={tree.itemAt(2)!} />);
expect(wrapper.find('div').first()).not.toExist();
});

xtest('given a ref pointing at complex type, should not display property name', () => {
test('given a ref pointing at complex type, should not display property name', () => {
const schema: JSONSchema4 = {
properties: {
foo: {
Expand All @@ -238,7 +238,7 @@ describe('Property component', () => {
tree.populate();
tree.unwrap(Array.from(tree)[1] as TreeListParentNode);

const wrapper = shallow(<Property node={Array.from(tree)[2]} />);
const wrapper = shallow(<Property node={tree.itemAt(2)!} />);
expect(wrapper.find('div').first()).not.toExist();
});
});
Expand Down
10 changes: 5 additions & 5 deletions src/components/__tests__/SchemaRow.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,28 +81,28 @@ describe('SchemaRow component', () => {
});

tree.populate();
tree.unwrap(Array.from(tree)[1] as TreeListParentNode);
tree.unwrap(tree.itemAt(1) as TreeListParentNode);
});

test('should preserve the required validation', () => {
const wrapper = shallow(<SchemaRow node={Array.from(tree)[5]} rowOptions={{}} />);
const wrapper = shallow(<SchemaRow node={tree.itemAt(6)!} rowOptions={{}} />);
expect(wrapper.find(Validations)).toHaveProp('required', true);
});

test('should preserve the optional validation', () => {
const wrapper = shallow(<SchemaRow node={Array.from(tree)[6]} rowOptions={{}} />);
const wrapper = shallow(<SchemaRow node={tree.itemAt(7)!} rowOptions={{}} />);
expect(wrapper.find(Validations)).toHaveProp('required', false);
});

describe('given a referenced object', () => {
test('should preserve the required validation', () => {
const wrapper = shallow(<SchemaRow node={Array.from(tree)[2]} rowOptions={{}} />);
const wrapper = shallow(<SchemaRow node={tree.itemAt(3)!} rowOptions={{}} />);

expect(wrapper.find(Validations)).toHaveProp('required', true);
});

test('should preserve the optional validation', () => {
const wrapper = shallow(<SchemaRow node={Array.from(tree)[3]} rowOptions={{}} />);
const wrapper = shallow(<SchemaRow node={tree.itemAt(4)!} rowOptions={{}} />);
expect(wrapper.find(Validations)).toHaveProp('required', false);
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import { shallow } from 'enzyme';
import 'jest-enzyme';
import { JSONSchema4 } from 'json-schema';
import * as React from 'react';
import { SchemaTree } from '../components';
import { useMetadata } from '../hooks/useMetadata';
import { useMetadata } from '../../hooks/useMetadata';
import { SchemaTree } from '../index';

jest.mock('mobx-react-lite', () => ({
observer: (children: any) => children,
}));
jest.mock('../hooks/useMetadata');
jest.mock('../../hooks/useMetadata');

const schema: JSONSchema4 = {
type: 'object',
Expand Down
2 changes: 1 addition & 1 deletion src/components/shared/Property.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ function count(obj: Optional<JSONSchema4 | null>): number | null {
function shouldShowPropertyName(treeNode: SchemaTreeListNode) {
if (treeNode.parent === null) return false;
try {
const schema = getNodeMetadata(treeNode.parent).schema;
const { schema } = getNodeMetadata(treeNode.parent);
let type = getPrimaryType(schema);

if (type === SchemaKind.Array && schema.items) {
Expand Down
2 changes: 1 addition & 1 deletion src/tree/__tests__/populateTree.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Tree } from '@stoplight/tree-list';
import * as fs from 'fs';
import * as path from 'path';
import { generateId } from '../../utils/generateId';
import { populateTree } from '../populateTree';
import { populateTree } from '../utils/populateTree';

const BASE_PATH = path.resolve(__dirname, '../../__fixtures__/');

Expand Down
110 changes: 110 additions & 0 deletions src/tree/__tests__/tree.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { TreeListParentNode } from '@stoplight/tree-list';
import { JSONSchema4 } from 'json-schema';
import { getNodeMetadata } from '../metadata';
import { SchemaTree, SchemaTreeState } from '../tree';

describe('SchemaTree', () => {
describe('expanding', () => {
describe('oneOf combiner', () => {
let tree: SchemaTree;
let schema: JSONSchema4;

beforeEach(() => {
schema = {
type: 'object',
properties: {
user: {
$ref: '#/properties/id',
},
id: {
oneOf: [
{
type: 'object',
required: ['foo'],
properties: {
foo: {
type: 'string',
},
},
},
],
},
},
};

tree = new SchemaTree(schema, new SchemaTreeState(), {
expandedDepth: 0,
mergeAllOf: false,
resolveRef: void 0,
});

tree.populate();
});

test('upon expanded $ref, should insert only oneOf combiner', () => {
expect(tree.count).toEqual(3);

tree.unwrap(tree.itemAt(1) as TreeListParentNode);

expect(tree.count).toEqual(4);
expect(getNodeMetadata(tree.itemAt(2)!)).toHaveProperty(
'schema',
expect.objectContaining({
oneOf: [
{
type: 'object',
required: ['foo'],
properties: {
foo: {
type: 'string',
},
},
},
],
}),
);
});

test('upon expanded $ref and expanded oneOf combiner, should insert object', () => {
expect(tree.count).toEqual(3);

tree.unwrap(tree.itemAt(1) as TreeListParentNode);
tree.unwrap(tree.itemAt(2) as TreeListParentNode);

expect(tree.count).toEqual(5);
expect(getNodeMetadata(tree.itemAt(3)!)).toHaveProperty(
'schema',
expect.objectContaining({
type: 'object',
required: ['foo'],
properties: {
foo: {
type: 'string',
},
},
}),
);
});

test('upon expanded id property, should insert object', () => {
expect(tree.count).toEqual(3);

tree.unwrap(tree.itemAt(2) as TreeListParentNode);

expect(tree.count).toEqual(4);
expect(getNodeMetadata(tree.itemAt(3)!)).toHaveProperty(
'schema',
expect.objectContaining({
type: 'object',
required: ['foo'],
properties: {
foo: {
type: 'string',
},
},
}),
);
});
});
});
});
33 changes: 20 additions & 13 deletions src/tree/tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { JSONSchema4 } from 'json-schema';
import { get as _get, isEqual as _isEqual, isObject as _isObject } from 'lodash';
import { isRefNode } from '../utils/guards';
import { getNodeMetadata, metadataStore } from './metadata';
import { populateTree } from './populateTree';
import { canStepIn } from './utils/canStepIn';
import { populateTree } from './utils/populateTree';

export type SchemaTreeRefDereferenceFn = (path: JsonPath, schema: JSONSchema4) => Optional<JSONSchema4>;

Expand Down Expand Up @@ -36,7 +37,7 @@ export class SchemaTree extends Tree {
const expanded = {};
populateTree(this.schema, this.root, 0, [], {
mergeAllOf: this.mergeAllOf,
onNode: (node, parentTreeNode, level): boolean => {
onNode: (fragment, node, parentTreeNode, level): boolean => {
if (isRefNode(node) && node.$ref !== null && isLocalRef(node.$ref)) {
expanded[node.id] = false;
}
Expand All @@ -51,27 +52,34 @@ export class SchemaTree extends Tree {
this.invalidate();
}

public populateTreeFragment(parent: TreeListParentNode, schema: JSONSchema4, path: JsonPath) {
public populateTreeFragment(parent: TreeListParentNode, schema: JSONSchema4, path: JsonPath, stepIn: boolean) {
const initialLevel = Tree.getLevel(parent);
const artificialRoot = Tree.createArtificialRoot();
populateTree(schema, artificialRoot, initialLevel, path, {
mergeAllOf: this.mergeAllOf,
onNode: (node, parentTreeNode, level) => level <= this.expandedDepth + 1 || level <= initialLevel + 1,
onNode: (fragment, node, parentTreeNode, level) => {
if (level <= this.expandedDepth || level <= initialLevel) return true;
return stepIn && level <= initialLevel + 1 && canStepIn(getNodeMetadata(parentTreeNode).schema);
},
});

if (artificialRoot.children.length === 0) {
throw new Error(`Could not expand node ${path.join('.')}`);
}

// todo: improve walk, i.e. add stepIn so that this is not required
this.insertTreeFragment(stepIn ? this.stepIn(artificialRoot, parent) : artificialRoot.children, parent);
}

protected stepIn(root: TreeListParentNode, parent: TreeListParentNode) {
if (
'children' in artificialRoot.children[0] &&
_isEqual(getNodeMetadata(parent).path, getNodeMetadata(artificialRoot.children[0]).path)
root.children.length > 0 &&
'children' in root.children[0] &&
_isEqual(getNodeMetadata(parent).path, getNodeMetadata(root.children[0]).path)
) {
this.insertTreeFragment(artificialRoot.children[0].children, parent);
} else {
this.insertTreeFragment(artificialRoot.children, parent);
return root.children[0].children;
}

return root.children;
}

public unwrap(node: TreeListParentNode) {
Expand All @@ -82,16 +90,15 @@ export class SchemaTree extends Tree {
const metadata = getNodeMetadata(node);
const { path, schemaNode, schema } = metadata;
if (!isRefNode(schemaNode)) {
this.populateTreeFragment(node, schema, path);
this.populateTreeFragment(node, schema, path, true);
} else if (schemaNode.$ref !== null) {
const refPath = pointerToPath(schemaNode.$ref);
const schemaFragment = this.resolveRef ? this.resolveRef(refPath, this.schema) : _get(this.schema, refPath);
if (!_isObject(schemaFragment)) {
throw new ReferenceError(`Could not dereference ${refPath.join('.')}`);
}

this.populateTreeFragment(node, schemaFragment, path);
metadata.schema = schemaFragment;
this.populateTreeFragment(node, schemaFragment, path, false);
} else {
throw new Error(`I do know not how not expand node ${path.join('.')}`);
}
Expand Down
13 changes: 13 additions & 0 deletions src/tree/utils/canStepIn.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { JSONSchema4 } from 'json-schema';
import { SchemaKind } from '../../types';
import { getCombiner } from '../../utils/getCombiner';
import { getPrimaryType } from '../../utils/getPrimaryType';

export const canStepIn = (fragment: JSONSchema4) => {
if (getCombiner(fragment)) {
return true;
}

const type = getPrimaryType(fragment);
return type === SchemaKind.Array || type === SchemaKind.Object;
};
Loading

0 comments on commit e59a5f2

Please sign in to comment.