diff --git a/src/schema/data/actions.js b/src/schema/data/actions.js
index e54d68a..001d39b 100644
--- a/src/schema/data/actions.js
+++ b/src/schema/data/actions.js
@@ -4,12 +4,8 @@
*/
export const SET_SCHEMA_CURRENT_DEFINITION_NAME = 'SET_SCHEMA_CURRENT_DEFINITION_NAME';
-export const UPDATE_SCHEMA_STATE = 'UPDATE_SCHEMA_STATE';
export function setSchemaCurrentDefinitionName( currentSchemaDefinitionName ) {
return { type: SET_SCHEMA_CURRENT_DEFINITION_NAME, currentSchemaDefinitionName };
}
-export function updateSchemaState() {
- return { type: UPDATE_SCHEMA_STATE };
-}
diff --git a/src/schema/data/reducer.js b/src/schema/data/reducer.js
index 9a7cf3d..0573992 100644
--- a/src/schema/data/reducer.js
+++ b/src/schema/data/reducer.js
@@ -4,8 +4,7 @@
*/
import {
- SET_SCHEMA_CURRENT_DEFINITION_NAME,
- UPDATE_SCHEMA_STATE
+ SET_SCHEMA_CURRENT_DEFINITION_NAME
} from './actions';
import {
@@ -42,7 +41,6 @@ export default function schemaReducer( globalState, schemaState, action ) {
// if we're back in the commands tab.
// * UPDATE_MODEL_STATE – An action called by the editorEventObserver for the model document change.
case SET_ACTIVE_INSPECTOR_TAB:
- case UPDATE_SCHEMA_STATE:
return {
...schemaState,
diff --git a/src/schema/pane.js b/src/schema/pane.js
index 44aa360..b67ce54 100644
--- a/src/schema/pane.js
+++ b/src/schema/pane.js
@@ -5,7 +5,6 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
-import { updateSchemaState } from './data/actions';
import Pane from '../components/pane';
import Tabs from '../components/tabs';
@@ -37,4 +36,4 @@ const mapStateToProps = ( { currentEditorName } ) => {
return { currentEditorName };
};
-export default connect( mapStateToProps, { updateSchemaState } )( SchemaPane );
+export default connect( mapStateToProps )( SchemaPane );
diff --git a/tests/inspector/schema/data/actions.js b/tests/inspector/schema/data/actions.js
new file mode 100644
index 0000000..619905d
--- /dev/null
+++ b/tests/inspector/schema/data/actions.js
@@ -0,0 +1,18 @@
+/**
+ * @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+import {
+ setSchemaCurrentDefinitionName,
+ SET_SCHEMA_CURRENT_DEFINITION_NAME
+} from '../../../../src/schema/data/actions';
+
+describe( 'schema data store actions', () => {
+ it( 'should export setSchemaCurrentDefinitionName()', () => {
+ expect( setSchemaCurrentDefinitionName( 'foo' ) ).to.deep.equal( {
+ type: SET_SCHEMA_CURRENT_DEFINITION_NAME,
+ currentSchemaDefinitionName: 'foo'
+ } );
+ } );
+} );
diff --git a/tests/inspector/schema/data/reducer.js b/tests/inspector/schema/data/reducer.js
new file mode 100644
index 0000000..853aa56
--- /dev/null
+++ b/tests/inspector/schema/data/reducer.js
@@ -0,0 +1,180 @@
+/**
+ * @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* global document, window */
+
+import TestEditor from '../../../utils/testeditor';
+import schemaReducer from '../../../../src/schema/data/reducer';
+
+import {
+ setSchemaCurrentDefinitionName
+} from '../../../../src/schema/data/actions';
+
+import {
+ setActiveTab,
+ setEditors,
+ setCurrentEditorName
+} from '../../../../src/data/actions';
+
+import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
+
+describe( 'schema data store reducer', () => {
+ let editorA, editorB;
+ let elementA, elementB;
+ let globalState, schemaState;
+
+ beforeEach( async () => {
+ window.localStorage.clear();
+
+ elementA = document.createElement( 'div' );
+ elementB = document.createElement( 'div' );
+ document.body.appendChild( elementA );
+ document.body.appendChild( elementB );
+
+ editorA = await TestEditor.create( elementA, {
+ plugins: [ Paragraph ],
+ initialData: '
foo
'
+ } );
+
+ editorB = await TestEditor.create( elementB );
+
+ globalState = {
+ currentEditorName: 'a',
+ editors: new Map( [
+ [ 'a', editorA ],
+ [ 'b', editorB ]
+ ] ),
+ ui: {
+ activeTab: 'Schema'
+ }
+ };
+
+ schemaState = schemaReducer( globalState, null, {} );
+ } );
+
+ afterEach( async () => {
+ await editorA.destroy();
+ await editorB.destroy();
+ } );
+
+ it( 'should not create a state if ui#activeTab is different than "Schema"', () => {
+ globalState.ui.activeTab = 'Model';
+
+ schemaState = schemaReducer( globalState, null, {} );
+
+ expect( schemaState ).to.be.null;
+ } );
+
+ it( 'should create a default state if no schema state was passed to the reducer', () => {
+ schemaState = schemaReducer( globalState, null, {} );
+
+ expect( schemaState ).to.have.property( 'treeDefinition' );
+ expect( schemaState ).to.have.property( 'currentSchemaDefinitionName' );
+ expect( schemaState ).to.have.property( 'currentSchemaDefinition' );
+ } );
+
+ it( 'should pass through when no action was passed to the reducer', () => {
+ schemaState = schemaReducer( globalState, {
+ treeDefinition: [ 'foo' ],
+ currentSchemaDefinitionName: 'bar',
+ currentSchemaDefinition: 'baz'
+ }, {} );
+
+ expect( schemaState.treeDefinition ).to.deep.equal( [ 'foo' ] );
+ expect( schemaState.currentSchemaDefinitionName ).to.equal( 'bar' );
+ expect( schemaState.currentSchemaDefinition ).to.equal( 'baz' );
+ } );
+
+ describe( 'application state', () => {
+ describe( '#currentSchemaDefinitionName', () => {
+ it( 'should be reset on setEditors() action', () => {
+ schemaState.currentSchemaDefinitionName = 'paragraph';
+ schemaState = schemaReducer( globalState, schemaState, setEditors( new Map( [ [ 'b', editorB ] ] ) ) );
+
+ expect( schemaState.currentSchemaDefinitionName ).to.be.null;
+ } );
+
+ it( 'should be reset on setCurrentEditorName() action', () => {
+ schemaState.currentSchemaDefinitionName = null;
+ schemaState = schemaReducer( globalState, schemaState, setCurrentEditorName( 'b' ) );
+
+ expect( schemaState.currentSchemaDefinitionName ).to.be.null;
+ } );
+
+ it( 'should be set on setSchemaCurrentDefinitionName() action', () => {
+ schemaState.currentSchemaDefinitionName = null;
+ schemaState = schemaReducer( globalState, schemaState, setSchemaCurrentDefinitionName( 'paragraph' ) );
+
+ expect( schemaState.currentSchemaDefinitionName ).to.equal( 'paragraph' );
+ } );
+ } );
+
+ describe( '#currentSchemaDefinition', () => {
+ it( 'should be reset on setEditors() action', () => {
+ schemaState.currentSchemaDefinition = null;
+ schemaState = schemaReducer( globalState, schemaState, setEditors( new Map( [ [ 'b', editorB ] ] ) ) );
+
+ expect( schemaState.currentSchemaDefinition ).to.be.null;
+ } );
+
+ it( 'should be reset on setCurrentEditorName() action', () => {
+ schemaState.currentSchemaDefinition = null;
+ schemaState = schemaReducer( globalState, schemaState, setCurrentEditorName( 'b' ) );
+
+ expect( schemaState.currentSchemaDefinition ).to.be.null;
+ } );
+
+ it( 'should be set on setSchemaCurrentDefinitionName() action', () => {
+ schemaState.currentSchemaDefinition = null;
+ schemaState = schemaReducer( globalState, schemaState, setSchemaCurrentDefinitionName( 'paragraph' ) );
+
+ expect( schemaState.currentSchemaDefinition ).to.be.an( 'object' );
+ } );
+
+ it( 'should be set on setActiveTab() action', () => {
+ schemaState.currentSchemaDefinitionName = 'paragraph';
+ schemaState.currentSchemaDefinition = null;
+ schemaState = schemaReducer( globalState, schemaState, setActiveTab( 'Schema' ) );
+
+ expect( schemaState.currentSchemaDefinition ).to.be.an( 'object' );
+ } );
+ } );
+
+ describe( '#treeDefinition', () => {
+ it( 'should be empty if there are no editors', () => {
+ schemaState.treeDefinition = null;
+
+ globalState.editors = new Map();
+ globalState.currentEditorName = null;
+
+ schemaState = schemaReducer( globalState, schemaState, setEditors( new Map() ) );
+
+ expect( schemaState.treeDefinition ).to.be.an( 'array' );
+ expect( schemaState.treeDefinition ).to.have.length( 0 );
+ } );
+
+ it( 'should be set on setEditors() action', () => {
+ schemaState.treeDefinition = null;
+ schemaState = schemaReducer( globalState, schemaState, setEditors( new Map( [ [ 'b', editorB ] ] ) ) );
+
+ expect( schemaState.treeDefinition ).to.be.an( 'array' );
+ } );
+
+ it( 'should be set on setCurrentEditorName() action', () => {
+ schemaState.treeDefinition = null;
+ schemaState = schemaReducer( globalState, schemaState, setCurrentEditorName( 'b' ) );
+
+ expect( schemaState.treeDefinition ).to.be.an( 'array' );
+ } );
+
+ it( 'should be set on setActiveTab() action', () => {
+ schemaState.treeDefinition = null;
+ schemaState = schemaReducer( globalState, schemaState, setActiveTab( 'Commands' ) );
+
+ expect( schemaState.treeDefinition ).to.be.an( 'array' );
+ } );
+ } );
+ } );
+} );
diff --git a/tests/inspector/schema/pane.js b/tests/inspector/schema/pane.js
new file mode 100644
index 0000000..118b9c7
--- /dev/null
+++ b/tests/inspector/schema/pane.js
@@ -0,0 +1,81 @@
+/**
+ * @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* global document, window */
+
+import React from 'react';
+import TestEditor from '../../utils/testeditor';
+import { createStore } from 'redux';
+import { Provider } from 'react-redux';
+
+import SchemaPane from '../../../src/schema/pane';
+import SchemaTree from '../../../src/schema/tree';
+import SchemaDefinitionInspector from '../../../src/schema/schemadefinitioninspector';
+
+describe( '', () => {
+ let editor, wrapper, element, store;
+
+ beforeEach( () => {
+ window.localStorage.clear();
+
+ element = document.createElement( 'div' );
+ document.body.appendChild( element );
+
+ return TestEditor.create( element ).then( newEditor => {
+ editor = newEditor;
+
+ store = createStore( state => state, {
+ editors: new Map( [ [ 'test-editor', editor ] ] ),
+ currentEditorName: 'test-editor',
+ ui: {
+ activeTab: 'Schema'
+ },
+ schema: {
+ }
+ } );
+
+ wrapper = mount( );
+ } );
+ } );
+
+ afterEach( () => {
+ wrapper.unmount();
+ element.remove();
+
+ return editor.destroy();
+ } );
+
+ describe( 'render()', () => {
+ it( 'renders a placeholder when no props#currentEditorName', () => {
+ store = createStore( state => state, {
+ currentEditorName: null,
+ model: {
+ ui: {}
+ }
+ } );
+
+ const wrapper = mount( );
+
+ expect( wrapper.text() ).to.match( /^Nothing to show/ );
+
+ wrapper.unmount();
+ } );
+
+ it( 'should render ', () => {
+ const tabs = wrapper.find( 'Tabs' );
+
+ expect( tabs ).to.have.length( 1 );
+ expect( tabs.props().activeTab ).to.equal( 'Inspect' );
+ } );
+
+ it( 'should render a ', () => {
+ expect( wrapper.find( SchemaTree ) ).to.have.length( 1 );
+ } );
+
+ it( 'should render a ', () => {
+ expect( wrapper.find( SchemaDefinitionInspector ) ).to.have.length( 1 );
+ } );
+ } );
+} );
diff --git a/tests/inspector/schema/schemadefinitioninspector.js b/tests/inspector/schema/schemadefinitioninspector.js
new file mode 100644
index 0000000..7df9e32
--- /dev/null
+++ b/tests/inspector/schema/schemadefinitioninspector.js
@@ -0,0 +1,186 @@
+/**
+ * @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* global document */
+
+import React from 'react';
+import TestEditor from '../../utils/testeditor';
+import { createStore } from 'redux';
+import { Provider } from 'react-redux';
+
+import { getSchemaDefinition } from '../../../src/schema/data/utils';
+
+import { reducer } from '../../../src/data/reducer';
+import ObjectInspector from '../../../src/components/objectinspector';
+import SchemaDefinitionInspector from '../../../src/schema/schemadefinitioninspector';
+
+import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
+
+describe( '', () => {
+ let editor, wrapper, element, store;
+
+ beforeEach( async () => {
+ element = document.createElement( 'div' );
+ document.body.appendChild( element );
+
+ editor = await TestEditor.create( element, {
+ plugins: [
+ Paragraph,
+ function( editor ) {
+ this.afterInit = () => {
+ editor.model.schema.extend( 'paragraph', {
+ allowAttributes: [ 'foo' ]
+ } );
+
+ editor.model.schema.setAttributeProperties( 'foo', {
+ someProperty: 123
+ } );
+ };
+ }
+ ]
+ } );
+
+ const editors = new Map( [ [ 'test-editor', editor ] ] );
+ const currentEditorName = 'test-editor';
+
+ store = createStore( reducer, {
+ editors,
+ currentEditorName,
+ ui: {
+ activeTab: 'Schema'
+ },
+ schema: {
+ currentSchemaDefinitionName: 'paragraph',
+ currentSchemaDefinition: getSchemaDefinition( { editors, currentEditorName }, 'paragraph' ),
+ treeDefinition: null
+ }
+ } );
+
+ wrapper = mount( );
+ } );
+
+ afterEach( async () => {
+ wrapper.unmount();
+ element.remove();
+ sinon.restore();
+
+ await editor.destroy();
+ } );
+
+ describe( 'render()', () => {
+ it( 'should render a placeholder when no props#currentSchemaDefinition', () => {
+ const store = createStore( state => state, {
+ editors: new Map( [ [ 'test-editor', editor ] ] ),
+ currentEditorName: 'test-editor',
+ schema: {
+ currentSchemaDefinition: null
+ }
+ } );
+
+ const wrapper = mount( );
+
+ expect( wrapper.childAt( 0 ).text() ).to.match( /^Select a schema definition/ );
+
+ wrapper.unmount();
+ } );
+
+ it( 'should render an object inspector when there is props#currentSchemaDefinition', () => {
+ expect( wrapper.find( ObjectInspector ) ).to.have.length( 1 );
+ } );
+
+ describe( 'scheme definition info', () => {
+ it( 'should render schema definition properties', () => {
+ wrapper.setProps( { currentSchemaDefinitionName: 'paragraph' } );
+
+ const inspector = wrapper.find( ObjectInspector );
+ const lists = inspector.props().lists;
+
+ expect( lists[ 0 ].name ).to.equal( 'Properties' );
+ expect( lists[ 0 ].url ).to.match( /^https:\/\/ckeditor.com\/docs\// );
+ expect( lists[ 0 ].itemDefinitions ).to.deep.equal( {
+ isBlock: { value: 'true' }
+ } );
+ } );
+
+ it( 'should render schema definition allowed attributes', () => {
+ wrapper.setProps( { currentSchemaDefinitionName: 'paragraph' } );
+
+ const inspector = wrapper.find( ObjectInspector );
+ const lists = inspector.props().lists;
+
+ expect( lists[ 1 ].name ).to.equal( 'Allowed attributes' );
+ expect( lists[ 1 ].url ).to.match( /^https:\/\/ckeditor.com\/docs\// );
+ expect( lists[ 1 ].itemDefinitions ).to.deep.equal( {
+ foo: {
+ subProperties: {
+ someProperty: {
+ value: '123'
+ }
+ },
+ value: 'true'
+ }
+ } );
+ } );
+
+ it( 'should render schema definition allowed children', () => {
+ wrapper.setProps( { currentSchemaDefinitionName: 'paragraph' } );
+
+ const inspector = wrapper.find( ObjectInspector );
+ const lists = inspector.props().lists;
+
+ expect( lists[ 2 ].name ).to.equal( 'Allowed children' );
+ expect( lists[ 2 ].url ).to.match( /^https:\/\/ckeditor.com\/docs\// );
+ expect( lists[ 2 ].itemDefinitions ).to.deep.equal( {
+ $text: { value: 'true' }
+ } );
+ } );
+
+ it( 'should render schema definition allowed in', () => {
+ wrapper.setProps( { currentSchemaDefinitionName: 'paragraph' } );
+
+ const inspector = wrapper.find( ObjectInspector );
+ const lists = inspector.props().lists;
+
+ expect( lists[ 3 ].name ).to.equal( 'Allowed in' );
+ expect( lists[ 3 ].url ).to.match( /^https:\/\/ckeditor.com\/docs\// );
+ expect( lists[ 3 ].itemDefinitions ).to.deep.equal( {
+ $clipboardHolder: { value: 'true' },
+ $documentFragment: { value: 'true' },
+ $root: { value: 'true' }
+ } );
+ } );
+ } );
+
+ it( 'should navigate to another definition upon clicking a name in "allowed children"', () => {
+ wrapper.setProps( { currentSchemaDefinitionName: 'paragraph' } );
+
+ const inspector = wrapper.find( ObjectInspector );
+ const propertyTitleLabel = inspector
+ .find( 'PropertyTitle' )
+ .filter( { name: '$text' } )
+ .first()
+ .find( 'label' );
+
+ propertyTitleLabel.simulate( 'click' );
+
+ expect( store.getState().schema.currentSchemaDefinitionName ).to.equal( '$text' );
+ } );
+
+ it( 'should navigate to another definition upon clicking a name in "allowed in"', () => {
+ wrapper.setProps( { currentSchemaDefinitionName: 'paragraph' } );
+
+ const inspector = wrapper.find( ObjectInspector );
+ const propertyTitleLabel = inspector
+ .find( 'PropertyTitle' )
+ .filter( { name: '$root' } )
+ .first()
+ .find( 'label' );
+
+ propertyTitleLabel.simulate( 'click' );
+
+ expect( store.getState().schema.currentSchemaDefinitionName ).to.equal( '$root' );
+ } );
+ } );
+} );
diff --git a/tests/inspector/schema/tree.js b/tests/inspector/schema/tree.js
new file mode 100644
index 0000000..e1241de
--- /dev/null
+++ b/tests/inspector/schema/tree.js
@@ -0,0 +1,119 @@
+/**
+ * @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* global document */
+
+import React from 'react';
+import TestEditor from '../../utils/testeditor';
+import { createStore } from 'redux';
+import { Provider } from 'react-redux';
+
+import { getSchemaTreeDefinition } from '../../../src/schema/data/utils';
+
+import { reducer } from '../../../src/data/reducer';
+import Tree from '../../../src/components/tree/tree.js';
+import SchemaTree from '../../../src/schema/tree';
+
+import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
+
+describe( '', () => {
+ let editor, wrapper, element, store;
+
+ beforeEach( () => {
+ element = document.createElement( 'div' );
+ document.body.appendChild( element );
+
+ return TestEditor.create( element, {
+ plugins: [ Paragraph ]
+ } ).then( newEditor => {
+ editor = newEditor;
+
+ const editors = new Map( [ [ 'test-editor', editor ] ] );
+ const currentEditorName = 'test-editor';
+
+ store = createStore( reducer, {
+ editors,
+ currentEditorName,
+ ui: {
+ activeTab: 'Schema'
+ },
+ schema: {
+ currentSchemaDefinitionName: 'paragraph',
+ treeDefinition: getSchemaTreeDefinition( { editors, currentEditorName } )
+ }
+ } );
+
+ wrapper = mount( );
+ } );
+ } );
+
+ afterEach( () => {
+ wrapper.unmount();
+ element.remove();
+
+ return editor.destroy();
+ } );
+
+ describe( 'render()', () => {
+ it( 'should use a component', () => {
+ const tree = wrapper.find( Tree );
+ const schemaTree = wrapper.find( 'SchemaTree' );
+
+ expect( tree.props().definition ).to.equal( schemaTree.props().treeDefinition );
+ expect( tree.props().onClick ).to.equal( schemaTree.instance().handleTreeClick );
+ expect( tree.props().activeNode ).to.equal( 'paragraph' );
+ } );
+
+ it( 'should render a with schema items in alphabetical order', () => {
+ const tree = wrapper.find( Tree );
+
+ // Note: Asserting just a few. There are plenty of them and they will change as the editor develops.
+ expect( tree.props().definition ).to.include.deep.members( [
+ {
+ attributes: [],
+ children: [],
+ name: '$root',
+ node: '$root',
+ presentation: {
+ cssClass: 'ck-inspector-tree-node_tagless',
+ isEmpty: true
+ },
+ type: 'element'
+ },
+ {
+ attributes: [],
+ children: [],
+ name: '$text',
+ node: '$text',
+ presentation: {
+ cssClass: 'ck-inspector-tree-node_tagless',
+ isEmpty: true
+ },
+ type: 'element'
+ },
+ {
+ attributes: [],
+ children: [],
+ name: 'paragraph',
+ node: 'paragraph',
+ presentation: {
+ cssClass: 'ck-inspector-tree-node_tagless',
+ isEmpty: true
+ },
+ type: 'element'
+ }
+ ] );
+ } );
+
+ it( 'should start inspecting a schema definition when an item was clicked', () => {
+ const tree = wrapper.find( Tree );
+ const element = tree.find( 'TreeElement' ).first();
+
+ element.simulate( 'click' );
+
+ expect( store.getState().schema.currentSchemaDefinitionName ).to.equal( '$block' );
+ } );
+ } );
+} );