diff --git a/front-end/__tests__/components/API.test.js b/front-end/__tests__/components/API.test.js new file mode 100644 index 0000000..fb3d7cb --- /dev/null +++ b/front-end/__tests__/components/API.test.js @@ -0,0 +1,180 @@ +import React from 'react' +import { render } from '@testing-library/react' +import * as API from '../../src/API'; +import VPLinkModel from '../../src/components/VPLink/VPLinkModel'; +import CustomNodeModel from '../../src/components/CustomNode/CustomNodeModel'; +import VPPortModel from '../../src/components/VPPort/VPPortModel' + +global.console = {log: jest.fn()} +global.URL.createObjectURL = jest.fn(() => 'http://localhost:8080/'); +global.URL.revokeObjectURL = jest.fn(); + +describe('Validates API calls', () => { + + beforeEach(() => { + global.fetch = jest.fn(() => Promise.resolve({ + ok: true, + data: [], + json: jest.fn(() => []), + text: jest.fn(() => Promise.resolve({})), + headers:{ + get: (s)=>{ + if (s === "content-type") { + return "text"; + } + + if (s === "Content-Disposition") { + return "filenameToDownload"; + } + } + } + })); + }); + + it('Validates executionOrder', () => { + API.executionOrder(); + expect(global.fetch.mock.calls.length).toBe(1); + expect(global.fetch.mock.calls[0][1]).toStrictEqual({}); + expect(global.fetch.mock.calls[0][0]).toBe("/workflow/execute"); + }); + + it('Validates execute', () => { + const node = { + options: { + id: 'nodeId' + } + }; + + API.execute(node); + expect(global.fetch.mock.calls.length).toBe(1); + expect(global.fetch.mock.calls[0][1]).toStrictEqual({}); + expect(global.fetch.mock.calls[0][0]).toBe("/node/nodeId/execute"); + }); + + it('Validates retrieveData', () => { + API.retrieveData("nodeId"); + expect(global.fetch.mock.calls.length).toBe(1); + expect(global.fetch.mock.calls[0][1]).toStrictEqual({}); + expect(global.fetch.mock.calls[0][0]).toBe("/node/nodeId/retrieve_data"); + }); + + it('Validates downloadDataFile', () => { + const node = { + options: { + id: 'nodeId' + }, + config: {} + }; + + API.downloadDataFile(node); + expect(global.fetch.mock.calls.length).toBe(1); + expect(global.fetch.mock.calls[0][0]).toBe("/workflow/download"); + }); + + it('Validates uploadDataFile', () => { + const formData = { data: {}}; + const options = {method: "POST", body: formData}; + API.uploadDataFile(formData); + expect(global.fetch.mock.calls.length).toBe(1); + expect(global.fetch.mock.calls[0][1]).toStrictEqual(options); + expect(global.fetch.mock.calls[0][0]).toBe("/workflow/upload"); + }); + + it('Validates deleteEdge', () => { + const sourceModel = new CustomNodeModel({id: "myId1", num_in: 2, num_out: 1}); + const targetModel = new CustomNodeModel({id: "myId2", num_in: 2, num_out: 1}); + + const sourcePort = new VPPortModel({name: 'source-port-name'}); + sourcePort.setParent(sourceModel); + const targetPort = new VPPortModel({name: 'target-port-name'}); + targetPort.setParent(targetModel); + const linkModel = new VPLinkModel(); + linkModel.setSourcePort(sourcePort); + linkModel.setTargetPort(targetPort); + + const options = {method: "POST"}; + API.deleteEdge(linkModel); + + expect(global.fetch.mock.calls.length).toBe(2); + expect(global.fetch.mock.calls[0][1]).toStrictEqual(options); + expect(global.fetch.mock.calls[0][0]).toBe("/node/edge/myId1/myId2"); + + }); + + it('Validates addEdge', () => { + const sourceModel = new CustomNodeModel({id: "myId1", num_in: 2, num_out: 1}); + const targetModel = new CustomNodeModel({id: "myId2", num_in: 2, num_out: 1}); + + const sourcePort = new VPPortModel({name: 'source-port-name', in: true}); + sourcePort.setParent(sourceModel); + const targetPort = new VPPortModel({name: 'target-port-name'}); + targetPort.setParent(targetModel); + const linkModel = new VPLinkModel(); + linkModel.setSourcePort(sourcePort); + linkModel.setTargetPort(targetPort); + + const options = {method: "POST"}; + API.addEdge(linkModel); + + expect(global.fetch.mock.calls.length).toBe(2); + expect(global.fetch.mock.calls[0][1]).toStrictEqual(options); + expect(global.fetch.mock.calls[0][0]).toBe("/node/edge/myId2/myId1"); + + }); + + it('Validates uploadWorkflow', () => { + const formData = { data: {}}; + const options = {method: "POST", body: formData}; + API.uploadWorkflow(formData); + expect(global.fetch.mock.calls.length).toBe(1); + expect(global.fetch.mock.calls[0][1]).toStrictEqual(options); + expect(global.fetch.mock.calls[0][0]).toBe("/workflow/open"); + }); + + it('Validates initWorkflow', () => { + const sourceModel = new CustomNodeModel({id: "myId1", num_in: 2, num_out: 1}); + API.initWorkflow(sourceModel); + expect(global.fetch.mock.calls.length).toBe(1); + expect(global.fetch.mock.calls[0][0]).toBe("/workflow/new"); + }); + + it('Validates getGlobalVars', () => { + API.getGlobalVars(); + expect(global.fetch.mock.calls.length).toBe(1); + expect(global.fetch.mock.calls[0][0]).toBe("/workflow/globals"); + }); + + it('Validates getNodes', () => { + API.getNodes(); + expect(global.fetch.mock.calls.length).toBe(1); + expect(global.fetch.mock.calls[0][0]).toBe("/workflow/nodes"); + }); + + it('Validates updateNode', () => { + const sourceModel = new CustomNodeModel({id: "myId1", num_in: 2, num_out: 1}); + API.updateNode(sourceModel); + expect(global.fetch.mock.calls.length).toBe(1); + expect(global.fetch.mock.calls[0][0]).toBe("/node/myId1"); + }); + + it('Validates deleteNode', () => { + const sourceModel = new CustomNodeModel({id: "myId1", num_in: 2, num_out: 1}); + API.deleteNode(sourceModel); + expect(global.fetch.mock.calls.length).toBe(1); + expect(global.fetch.mock.calls[0][0]).toBe("/node/myId1"); + }); + + it('Validates addNode', () => { + const sourceModel = new CustomNodeModel({id: "myId1", num_in: 2, num_out: 1}); + API.addNode(sourceModel); + expect(global.fetch.mock.calls.length).toBe(1); + expect(global.fetch.mock.calls[0][0]).toBe("/node/"); + }); + + it('Validates getNode', () => { + API.getNode("myId1"); + expect(global.fetch.mock.calls.length).toBe(1); + expect(global.fetch.mock.calls[0][0]).toBe("/node/myId1"); + }); + +}); diff --git a/front-end/__tests__/components/About.test.js b/front-end/__tests__/components/About.test.js new file mode 100644 index 0000000..1e025c7 --- /dev/null +++ b/front-end/__tests__/components/About.test.js @@ -0,0 +1,42 @@ +import React from 'react' +import { render } from '@testing-library/react' +import ReactDOM from 'react-dom'; +import About from '../../src/components/About'; + +global.console = {error: jest.fn()} + +global.fetch = jest.fn(() => Promise.resolve({ + data: [], + json: jest.fn(() => []) +})); + +describe('Validates About', () => { + it('Does not display About info', () => { + const div = React.createElement('div'); + const app = render(, div); + expect(app).toMatchSnapshot(); + }); + + it('Displays About info', () => { + const div = React.createElement('div'); + const app = render(, div); + expect(app).toMatchSnapshot(); + }); + + it('Validates closing', () => { + const props = { + show: true + }; + + const event = { + preventDefault: jest.fn(() => []) + }; + + const about = new About(props); + about.componentDidMount(); + about.handleShow(event); + about.handleClose(); + + expect(event.preventDefault.mock.calls.length).toBe(1); + }); +}); diff --git a/front-end/__tests__/components/App.test.js b/front-end/__tests__/components/App.test.js new file mode 100644 index 0000000..56d1cba --- /dev/null +++ b/front-end/__tests__/components/App.test.js @@ -0,0 +1,17 @@ +import React from 'react' +import { render } from '@testing-library/react' +import App from '../../src/components/App'; + +global.console = {log: jest.fn()} + +global.fetch = jest.fn(() => Promise.resolve({ + data: [], + json: jest.fn(() => []) +})); + +describe('Validates App initialization', () => { + it('Creates App', () => { + const app = render(); + expect(app).toMatchSnapshot(); + }); +}); diff --git a/front-end/__tests__/components/CustomNode/CustomNodeFactory.test.js b/front-end/__tests__/components/CustomNode/CustomNodeFactory.test.js new file mode 100644 index 0000000..4fd3dc9 --- /dev/null +++ b/front-end/__tests__/components/CustomNode/CustomNodeFactory.test.js @@ -0,0 +1,28 @@ +import React from 'react' +import { render } from '@testing-library/react' +import CustomNodeFactory from '../../../src/components/CustomNode/CustomNodeFactory'; +import CustomNodeModel from '../../../src/components/CustomNode/CustomNodeModel'; +import CustomNodeWidget from '../../../src/components/CustomNode/CustomNodeWidget'; + +describe('Validate CustomNodeFactory', () => { + it('CustomNodeFactory generates CustomNodeWidget', () => { + const customNodeFactory = new CustomNodeFactory(); + const node = new CustomNodeModel({id: "myId"}); + const model = { + node: node, + }; + const event = { + model: model, + initialConfig: { + options: { id: "modelId"}, + config: {} + } + }; + const widget = customNodeFactory.generateReactWidget(event); + expect(React.isValidElement(widget)).toBe(true); + + const nodeModel = customNodeFactory.generateModel(event); + expect(nodeModel instanceof CustomNodeModel).toBe(true); + expect(nodeModel.getNodeId()).toBe("modelId"); + }); +}) diff --git a/front-end/__tests__/components/CustomNode/CustomNodeModel.test.js b/front-end/__tests__/components/CustomNode/CustomNodeModel.test.js new file mode 100644 index 0000000..7935cdd --- /dev/null +++ b/front-end/__tests__/components/CustomNode/CustomNodeModel.test.js @@ -0,0 +1,27 @@ +import React from 'react' +import CustomNodeModel from '../../../src/components/CustomNode/CustomNodeModel'; +import CustomNodeFactory from '../../../src/components/CustomNode/CustomNodeFactory'; +import VPPortFactory from '../../../src/components/VPPort/VPPortFactory'; +import createEngine from '@projectstorm/react-diagrams'; + + +describe('Validates CustomNodeModel', () => { + it('Validates serialization/deserialization', () => { + const node = new CustomNodeModel({id: "myId", num_in: 2, num_out: 1}); + node.setStatus("Complete"); + const engine = createEngine(); + engine.getNodeFactories().registerFactory(new CustomNodeFactory()); + engine.getPortFactories().registerFactory(new VPPortFactory()); + + const serializedModel = node.serialize(); + const event = { + data: serializedModel, + engine: engine, + registerModel: jest.fn(() => []) + }; + const otherNode = new CustomNodeModel(); + otherNode.deserialize(event, engine); + + expect(event.registerModel.mock.calls.length).toBe(4); + }); +}); diff --git a/front-end/__tests__/components/CustomNode/CustomNodeWidget.test.js b/front-end/__tests__/components/CustomNode/CustomNodeWidget.test.js new file mode 100644 index 0000000..6345c17 --- /dev/null +++ b/front-end/__tests__/components/CustomNode/CustomNodeWidget.test.js @@ -0,0 +1,106 @@ +import React from 'react' +import createEngine from '@projectstorm/react-diagrams'; +import { render } from '@testing-library/react' +import { shallow, mount } from 'enzyme'; +import CustomNodeWidget from '../../../src/components/CustomNode/CustomNodeWidget'; +import CustomNodeModel from '../../../src/components/CustomNode/CustomNodeModel'; + +global.console = {log: jest.fn(), error: jest.fn()} + + +describe('Validate CustomNodeWidget', () => { + + beforeEach(() => { + global.fetch = jest.fn(() => Promise.resolve({ + ok: true, + data: [], + json: jest.fn(() => []), + text: jest.fn(() => Promise.resolve({})), + headers:{ + get: (s)=>{ + if (s === "content-type") { + return "text"; + } + + if (s === "Content-Disposition") { + return "filenameToDownload"; + } + } + } + })); + + const createElement = document.createElement.bind(document); +document.createElement = (tagName) => { + if (tagName === 'canvas') { + return { + getContext: () => ({}), + measureText: () => ({}) + }; + } + return createElement(tagName); +}; + + }); + + it('Display CustomNodeWidget', () => { + const node = new CustomNodeModel({id: "myId"}); + const model = { + node: node, + globals: {} + }; + const engine = createEngine(); + engine.setModel(model); + + const customNodeWidget = render( + + ); + expect(customNodeWidget).toMatchSnapshot(); + }); + + it('Validates CustomNodeWidget', () => { + const node = new CustomNodeModel({id: "myId"}); + const model = { + node: node, + globals: {} + }; + const engine = createEngine(); + engine.setModel(model); + + const nodeWidget = shallow(); + + expect(nodeWidget.state('showConfig')).toBe(false); + nodeWidget.find({ className: 'custom-node-configure' }).simulate('click'); + + expect(nodeWidget.state('showConfig')).toBe(true); + }); + + it('Mounts CustomNodeWidget', () => { + const node = new CustomNodeModel({id: "myId"}); + const model = { + node: node, + globals: {} + }; + const engine = createEngine(); + engine.setModel(model); + + const nodeWidget = mount(); + + expect(nodeWidget.state('showConfig')).toBe(false); + expect(nodeWidget.state('showGraph')).toBe(false); + }); + + it('Accepts configuration', () => { + const node = new CustomNodeModel({id: "myId"}); + const repaintCanvas = jest.fn(() => []) + const engine = { + repaintCanvas: repaintCanvas + } + const props = { node: node, engine: engine}; + const nodeWidget = new CustomNodeWidget(props); + nodeWidget.acceptConfiguration({}, {}); + }); +}) diff --git a/front-end/__tests__/components/CustomNode/GraphView.test.js b/front-end/__tests__/components/CustomNode/GraphView.test.js index 228ba46..88f19db 100644 --- a/front-end/__tests__/components/CustomNode/GraphView.test.js +++ b/front-end/__tests__/components/CustomNode/GraphView.test.js @@ -1,8 +1,23 @@ import React from 'react' import { render } from '@testing-library/react' +import { shallow, mount } from 'enzyme'; +import { VariableSizeGrid as Grid } from 'react-window'; import CustomNodeModel from '../../../src/components/CustomNode/CustomNodeModel'; import GraphView from '../../../src/components/CustomNode/GraphView'; +global.console = {log: jest.fn()} + +const data = {"AAPL_x": {"0": "2014-01-02", "1": "2014-01-03", "2": "2014-01-06", "3": "2014-01-07", "4": "2014-01-08", "5": "2014-01-09", "6": "2014-01-10", "7": "2014-01-13", "8": "2014-01-14", "9": "2014-01-15", "10": "2014-01-16", "11": "2014-01-17", "12": "2014-01-21", "13": "2014-01-22", "14": "2014-01-23"}, "AAPL_y": {"0": 77.44539475, "1": 77.04557544, "2": 74.89697204, "3": 75.856461, "4": 75.09194679, "5": 76.20263178, "6": 75.2301837, "7": 73.84891755, "8": 75.0113527, "9": 77.14481412, "10": 77.33058367, "11": 76.85652616, "12": 75.39394758, "13": 76.7763823, "14": 76.64038513}}; + +const response = { + json: jest.fn(() => { + return data; + }), + ok: true +}; + +global.fetch = jest.fn(() => Promise.resolve(response)); + describe('Validate Graph Modal', () => { it('Display warning message', () => { const node = new CustomNodeModel({id: "myId"}); @@ -15,4 +30,25 @@ describe('Validate Graph Modal', () => { ); expect(graphView).toMatchSnapshot(); }); + + it('Display data', (done) => { + const node = new CustomNodeModel({id: "myId", options: + { node_type: "Read CSV", + status: "complete"}}); + const graphView = shallow( {}} + onDelete={() => {}} + onSubmit={() => {}} />); + + expect(graphView.state('loading')).toBe(false); + graphView.find('Button').at(1).simulate('click'); + expect(graphView.state('loading')).toBe(true); + setTimeout(()=>{ + graphView.update(); + expect(graphView.state('loading')).toBe(false); + done(); + }, 1000); + }); }); diff --git a/front-end/__tests__/components/CustomNode/Input/BooleanInput.test.js b/front-end/__tests__/components/CustomNode/Input/BooleanInput.test.js new file mode 100644 index 0000000..07a4cba --- /dev/null +++ b/front-end/__tests__/components/CustomNode/Input/BooleanInput.test.js @@ -0,0 +1,39 @@ +import React from 'react' +import { shallow, mount } from 'enzyme'; +import BooleanInput from '../../../../src/components/CustomNode/Input/BooleanInput'; + +describe('Validates BooleanInpupt', () => { + it('Display BooleanInput', () => { + const booleanInput = shallow(); + expect(booleanInput).toMatchSnapshot(); + }); + + it('Checked BooleanInput', () => { + + const onChange = jest.fn(() => []); + const booleanInput = shallow(); + + expect(booleanInput.find({name: "booleanSelector"}).prop('checked')).toBe(false); + + booleanInput.find({name: "booleanSelector"}).simulate('change', {target: {checked: true}}) + + booleanInput.update(); + expect(booleanInput.find({name: "booleanSelector"}).prop('checked')).toBe(true); + }); + + it('Checked BooleanInput onChange', () => { + + const onChange = jest.fn(() => []); + const booleanInput = mount(); + + booleanInput.find({keyName: "booleanSelector"}).simulate('change', {target: {checked: true}}) + + expect(onChange.mock.calls.length).toBe(1); + }); +}); diff --git a/front-end/__tests__/components/CustomNode/Input/FileUploadInput.test.js b/front-end/__tests__/components/CustomNode/Input/FileUploadInput.test.js new file mode 100644 index 0000000..e790e37 --- /dev/null +++ b/front-end/__tests__/components/CustomNode/Input/FileUploadInput.test.js @@ -0,0 +1,61 @@ +import React from 'react' +import { shallow, mount } from 'enzyme'; +import FileUploadInput from '../../../../src/components/CustomNode/Input/FileUploadInput'; +import CustomNodeModel from '../../../../src/components/CustomNode/CustomNodeModel'; + +describe('Validates FileUploadInput', () => { + + beforeEach(() => { + global.console = {log: jest.fn(() => []), error: jest.fn(() => [])}; + + global.fetch = jest.fn(() => Promise.resolve({ + ok: true, + data: [], + json: jest.fn(() => []), + text: jest.fn(() => Promise.resolve({})), + headers:{ + get: (s)=>{ + if (s === "content-type") { + return "text"; + } + + if (s === "Content-Disposition") { + return "filenameToDownload"; + } + } + } + })); + }); + + + it('Display FileUploadInput', () => { + const node = new CustomNodeModel({id: "myId", num_in: 2, num_out: 1, is_global: false}); + const onChange = jest.fn(() => []); + const disableFunc = jest.fn(() => []); + const input = mount(); + expect(input).toMatchSnapshot(); + }); + + it('FileUploadInput selects a file', () => { + const node = new CustomNodeModel({id: "myId", num_in: 2, num_out: 1, is_global: false}); + const onChange = jest.fn(() => []); + const disableFunc = jest.fn(() => []); + const input = mount(); + + const event = { preventDefault: jest.fn(() => [])}; + input.find({keyName: "uploadFile"}).simulate('change', event); + expect(event.preventDefault.mock.calls.length).toBe(1); + + }); + +}); diff --git a/front-end/__tests__/components/CustomNode/Input/FlowVariableOverride.test.js b/front-end/__tests__/components/CustomNode/Input/FlowVariableOverride.test.js new file mode 100644 index 0000000..b167cc0 --- /dev/null +++ b/front-end/__tests__/components/CustomNode/Input/FlowVariableOverride.test.js @@ -0,0 +1,39 @@ +import React from 'react' +import { shallow, mount } from 'enzyme'; +import { Form } from 'react-bootstrap'; +import FlowVariableOverride from '../../../../src/components/CustomNode/Input/FlowVariableOverride'; + +const nodes = {"Visualization": [{"name": "Graph Node", "node_key": "GraphNode", "node_type": "visualization", "num_in": 1, "num_out": 0, "color": "red", "filename": "graph", "doc": "Displays a pandas DataFrame in a visual graph.\n\n Raises:\n NodeException: any error generating Altair Chart.\n ", "options": {"graph_type": "bar", "mark_options": false, "width": 10, "height": 10, "encode_options": true, "x_axis": "a", "y_axis": "average(b)"}, "option_types": {"graph_type": {"type": "select", "label": "Graph Type", "value": "bar", "docstring": "Graph viz type", "options": ["area", "bar", "line", "point"]}, "mark_options": {"type": "boolean", "label": "Specify mark options", "value": false, "docstring": "Specify mark options"}, "width": {"type": "int", "label": "Mark width", "value": 10, "docstring": "Width of marks"}, "height": {"type": "int", "label": "Mark height", "value": 10, "docstring": "Height of marks"}, "encode_options": {"type": "boolean", "label": "Specify encoding options", "value": true, "docstring": "Specify encoding options"}, "x_axis": {"type": "string", "label": "X-Axis", "value": "a", "docstring": "X-axis values"}, "y_axis": {"type": "string", "label": "Y-Axis", "value": "average(b)", "docstring": "Y-axis values"}}, "download_result": false}], "Flow Control": +[{"node_id":"nodeId01", "name": "Integer Input", "node_key": "IntegerNode", "node_type": "flow_control", "num_in": 1, "num_out": 1, "color": "purple", "filename": "integer_input", "doc": "StringNode object\n\n Allows for Strings to replace 'string' fields in Nodes\n ", "options": {"default_value": null, "var_name": "my_var"}, "option_types": {"default_value": {"type": "int", "label": "Default Value", "value": null, "docstring": "Value this node will pass as a flow variable"}, "var_name": {"type": "string", "label": "Variable Name", "value": "my_var", "docstring": "Name of the variable to use in another Node"}}, "download_result": false}, +{ "node_id":"nodeId02", "name": "String Input", "node_key": "StringNode", "node_type": "flow_control", "num_in": 1, "num_out": 1, "color": "purple", "filename": "string_input", "doc": "StringNode object\n\n Allows for Strings to replace 'string' fields in Nodes\n ", "options": {"default_value": null, "var_name": "my_var"}, "option_types": {"default_value": {"type": "string", "label": "Default Value", "value": null, "docstring": "Value this node will pass as a flow variable"}, "var_name": {"type": "string", "label": "Variable Name", "value": "my_var", "docstring": "Name of the variable to use in another Node"}}, "download_result": false}], "Manipulation": [{"name": "Joiner", "node_key": "JoinNode", "node_type": "manipulation", "num_in": 2, "num_out": 1, "color": "goldenrod", "filename": "join", "doc": null, "options": {"on": null}, "option_types": {"on": {"type": "string", "label": "Join Column", "value": null, "docstring": "Name of column to join on"}}, "download_result": false}, +{ "node_id":"nodeId03", "name": "Filter", "node_key": "FilterNode", "node_type": "manipulation", "num_in": 1, "num_out": 1, "color": "goldenrod", "filename": "filter", "doc": null, "options": {"items": null, "like": null, "regex": null, "axis": null}, "option_types": {"items": {"type": "string", "label": "Items", "value": null, "docstring": "Keep labels from axis which are in items"}, "like": {"type": "string", "label": "Like", "value": null, "docstring": "Keep labels from axis for which like in label == True."}, "regex": {"type": "string", "label": "Regex", "value": null, "docstring": "Keep labels from axis for which re.search(regex, label) == True."}, "axis": {"type": "string", "label": "Axis", "value": null, "docstring": "The axis to filter on."}}, "download_result": false}, +{ "node_id":"nodeId04", "name": "Pivoting", "node_key": "PivotNode", "node_type": "manipulation", "num_in": 1, "num_out": 3, "color": "goldenrod", "filename": "pivot", "doc": null, "options": {"index": null, "values": null, "columns": null, "aggfunc": "mean", "fill_value": null, "margins": false, "dropna": true, "margins_name": "All", "observed": false}, "option_types": {"index": {"type": "string", "label": "Index", "value": null, "docstring": "Column to aggregate (column, grouper, array or list)"}, "values": {"type": "string", "label": "Values", "value": null, "docstring": "Column name to use to populate new frame's values (column, grouper, array or list)"}, "columns": {"type": "string", "label": "Column Name Row", "value": null, "docstring": "Column(s) to use for populating new frame values. (column, grouper, array or list)"}, "aggfunc": {"type": "string", "label": "Aggregation function", "value": "mean", "docstring": "Function used for aggregation (function, list of functions, dict, default numpy.mean)"}, "fill_value": {"type": "string", "label": "Fill value", "value": null, "docstring": "Value to replace missing values with (scalar)"}, "margins": {"type": "boolean", "label": "Margins name", "value": false, "docstring": "Add all rows/columns"}, "dropna": {"type": "boolean", "label": "Drop NaN columns", "value": true, "docstring": "Ignore columns with all NaN entries"}, "margins_name": {"type": "string", "label": "Margins name", "value": "All", "docstring": "Name of the row/column that will contain the totals when margins is True"}, "observed": {"type": "boolean", "label": "Column Name Row", "value": false, "docstring": "Row number with column names (0-indexed) or \"infer\""}}, "download_result": false}], "I/O": [{"name": "Read CSV", "node_key": "ReadCsvNode", "node_type": "io", "num_in": 0, "num_out": 1, "color": "green", "filename": "read_csv", "doc": "ReadCsvNode\n\n Reads a CSV file into a pandas DataFrame.\n\n Raises:\n NodeException: any error reading CSV file, converting\n to DataFrame.\n ", "options": {"file": null, "sep": ",", "header": "infer"}, "option_types": {"file": {"type": "file", "label": "File", "value": null, "docstring": "CSV File"}, "sep": {"type": "string", "label": "Delimiter", "value": ",", "docstring": "Column delimiter"}, "header": {"type": "string", "label": "Header Row", "value": "infer", "docstring": "Row number containing column names (0-indexed)"}}, "download_result": false}, {"name": "Write CSV", "node_key": "WriteCsvNode", "node_type": "io", "num_in": 1, "num_out": 0, "color": "green", "filename": "write_csv", "doc": "WriteCsvNode\n\n Writes the current DataFrame to a CSV file.\n\n Raises:\n NodeException: any error writing CSV file, converting\n from DataFrame.\n ", "options": {"file": null, "sep": ",", "index": true}, "option_types": {"file": {"type": "string", "label": "Filename", "value": null, "docstring": "CSV file to write"}, "sep": {"type": "string", "label": "Delimiter", "value": ",", "docstring": "Column delimiter"}, "index": {"type": "boolean", "label": "Write Index", "value": true, "docstring": "Write index as column?"}}, "download_result": true}], "Custom Nodes": [{"name": "Table Creator", "node_key": "TableCreatorNode", "node_type": "custom_nodes", "num_in": 0, "num_out": 1, "color": "green", "filename": "table_creator", "doc": "Accepts raw-text CSV input to create data tables.\n\n Raises:\n NodeException: any error reading CSV file, converting\n to DataFrame.\n ", "options": {"input": "", "sep": ",", "header": "infer"}, "option_types": {"input": {"type": "text", "label": "Input", "value": "", "docstring": "Text input"}, "sep": {"type": "string", "label": "Delimiter", "value": ",", "docstring": "Column delimiter"}, "header": {"type": "string", "label": "Header Row", "value": "infer", "docstring": "Row number containing column names (0-indexed)"}}, "download_result": false}]}; + + + +describe('Validates FlowVariableOverride', () => { + it('Display FlowVariableOverride', () => { + const override = shallow(); + expect(override).toMatchSnapshot(); + }); + + it('Validates FlowVariableOverride behavior', () => { + const onChange = jest.fn(() => []); + const onFlowCheck = jest.fn(() => []); + const override = shallow(); + + override.find(Form.Check).simulate('change', {target: {checked: true}}) + + expect(onFlowCheck.mock.calls.length).toBe(1); + + override.find(Form.Control).simulate('change', {target: {value: "nodeId01"}}) + + expect(onChange.mock.calls.length).toBe(1); + }); +}); diff --git a/front-end/__tests__/components/CustomNode/Input/OptionInput.test.js b/front-end/__tests__/components/CustomNode/Input/OptionInput.test.js new file mode 100644 index 0000000..37e01d5 --- /dev/null +++ b/front-end/__tests__/components/CustomNode/Input/OptionInput.test.js @@ -0,0 +1,77 @@ +import React from 'react' +import { shallow, mount } from 'enzyme'; +import CustomNodeModel from '../../../../src/components/CustomNode/CustomNodeModel'; +import OptionInput from '../../../../src/components/CustomNode/Input/OptionInput'; + +const nodes = {"Visualization": [{"name": "Graph Node", "node_key": "GraphNode", "node_type": "visualization", "num_in": 1, "num_out": 0, "color": "red", "filename": "graph", "doc": "Displays a pandas DataFrame in a visual graph.\n\n Raises:\n NodeException: any error generating Altair Chart.\n ", "options": {"graph_type": "bar", "mark_options": false, "width": 10, "height": 10, "encode_options": true, "x_axis": "a", "y_axis": "average(b)"}, "option_types": {"graph_type": {"type": "select", "label": "Graph Type", "value": "bar", "docstring": "Graph viz type", "options": ["area", "bar", "line", "point"]}, "mark_options": {"type": "boolean", "label": "Specify mark options", "value": false, "docstring": "Specify mark options"}, "width": {"type": "int", "label": "Mark width", "value": 10, "docstring": "Width of marks"}, "height": {"type": "int", "label": "Mark height", "value": 10, "docstring": "Height of marks"}, "encode_options": {"type": "boolean", "label": "Specify encoding options", "value": true, "docstring": "Specify encoding options"}, "x_axis": {"type": "string", "label": "X-Axis", "value": "a", "docstring": "X-axis values"}, "y_axis": {"type": "string", "label": "Y-Axis", "value": "average(b)", "docstring": "Y-axis values"}}, "download_result": false}], "Flow Control": +[{"node_id":"nodeId01", "name": "Integer Input", "node_key": "IntegerNode", "node_type": "flow_control", "num_in": 1, "num_out": 1, "color": "purple", "filename": "integer_input", "doc": "StringNode object\n\n Allows for Strings to replace 'string' fields in Nodes\n ", "options": {"default_value": null, "var_name": "my_var"}, "option_types": {"default_value": {"type": "int", "label": "Default Value", "value": null, "docstring": "Value this node will pass as a flow variable"}, "var_name": {"type": "string", "label": "Variable Name", "value": "my_var", "docstring": "Name of the variable to use in another Node"}}, "download_result": false}, +{ "node_id":"nodeId02", "name": "String Input", "node_key": "StringNode", "node_type": "flow_control", "num_in": 1, "num_out": 1, "color": "purple", "filename": "string_input", "doc": "StringNode object\n\n Allows for Strings to replace 'string' fields in Nodes\n ", "options": {"default_value": null, "var_name": "my_var"}, "option_types": {"default_value": {"type": "string", "label": "Default Value", "value": null, "docstring": "Value this node will pass as a flow variable"}, "var_name": {"type": "string", "label": "Variable Name", "value": "my_var", "docstring": "Name of the variable to use in another Node"}}, "download_result": false}], "Manipulation": [{"name": "Joiner", "node_key": "JoinNode", "node_type": "manipulation", "num_in": 2, "num_out": 1, "color": "goldenrod", "filename": "join", "doc": null, "options": {"on": null}, "option_types": {"on": {"type": "string", "label": "Join Column", "value": null, "docstring": "Name of column to join on"}}, "download_result": false}, +{ "node_id":"nodeId03", "name": "Filter", "node_key": "FilterNode", "node_type": "manipulation", "num_in": 1, "num_out": 1, "color": "goldenrod", "filename": "filter", "doc": null, "options": {"items": null, "like": null, "regex": null, "axis": null}, "option_types": {"items": {"type": "string", "label": "Items", "value": null, "docstring": "Keep labels from axis which are in items"}, "like": {"type": "string", "label": "Like", "value": null, "docstring": "Keep labels from axis for which like in label == True."}, "regex": {"type": "string", "label": "Regex", "value": null, "docstring": "Keep labels from axis for which re.search(regex, label) == True."}, "axis": {"type": "string", "label": "Axis", "value": null, "docstring": "The axis to filter on."}}, "download_result": false}, +{ "node_id":"nodeId04", "name": "Pivoting", "node_key": "PivotNode", "node_type": "manipulation", "num_in": 1, "num_out": 3, "color": "goldenrod", "filename": "pivot", "doc": null, "options": {"index": null, "values": null, "columns": null, "aggfunc": "mean", "fill_value": null, "margins": false, "dropna": true, "margins_name": "All", "observed": false}, "option_types": {"index": {"type": "string", "label": "Index", "value": null, "docstring": "Column to aggregate (column, grouper, array or list)"}, "values": {"type": "string", "label": "Values", "value": null, "docstring": "Column name to use to populate new frame's values (column, grouper, array or list)"}, "columns": {"type": "string", "label": "Column Name Row", "value": null, "docstring": "Column(s) to use for populating new frame values. (column, grouper, array or list)"}, "aggfunc": {"type": "string", "label": "Aggregation function", "value": "mean", "docstring": "Function used for aggregation (function, list of functions, dict, default numpy.mean)"}, "fill_value": {"type": "string", "label": "Fill value", "value": null, "docstring": "Value to replace missing values with (scalar)"}, "margins": {"type": "boolean", "label": "Margins name", "value": false, "docstring": "Add all rows/columns"}, "dropna": {"type": "boolean", "label": "Drop NaN columns", "value": true, "docstring": "Ignore columns with all NaN entries"}, "margins_name": {"type": "string", "label": "Margins name", "value": "All", "docstring": "Name of the row/column that will contain the totals when margins is True"}, "observed": {"type": "boolean", "label": "Column Name Row", "value": false, "docstring": "Row number with column names (0-indexed) or \"infer\""}}, "download_result": false}], "I/O": [{"name": "Read CSV", "node_key": "ReadCsvNode", "node_type": "io", "num_in": 0, "num_out": 1, "color": "green", "filename": "read_csv", "doc": "ReadCsvNode\n\n Reads a CSV file into a pandas DataFrame.\n\n Raises:\n NodeException: any error reading CSV file, converting\n to DataFrame.\n ", "options": {"file": null, "sep": ",", "header": "infer"}, "option_types": {"file": {"type": "file", "label": "File", "value": null, "docstring": "CSV File"}, "sep": {"type": "string", "label": "Delimiter", "value": ",", "docstring": "Column delimiter"}, "header": {"type": "string", "label": "Header Row", "value": "infer", "docstring": "Row number containing column names (0-indexed)"}}, "download_result": false}, {"name": "Write CSV", "node_key": "WriteCsvNode", "node_type": "io", "num_in": 1, "num_out": 0, "color": "green", "filename": "write_csv", "doc": "WriteCsvNode\n\n Writes the current DataFrame to a CSV file.\n\n Raises:\n NodeException: any error writing CSV file, converting\n from DataFrame.\n ", "options": {"file": null, "sep": ",", "index": true}, "option_types": {"file": {"type": "string", "label": "Filename", "value": null, "docstring": "CSV file to write"}, "sep": {"type": "string", "label": "Delimiter", "value": ",", "docstring": "Column delimiter"}, "index": {"type": "boolean", "label": "Write Index", "value": true, "docstring": "Write index as column?"}}, "download_result": true}], "Custom Nodes": [{"name": "Table Creator", "node_key": "TableCreatorNode", "node_type": "custom_nodes", "num_in": 0, "num_out": 1, "color": "green", "filename": "table_creator", "doc": "Accepts raw-text CSV input to create data tables.\n\n Raises:\n NodeException: any error reading CSV file, converting\n to DataFrame.\n ", "options": {"input": "", "sep": ",", "header": "infer"}, "option_types": {"input": {"type": "text", "label": "Input", "value": "", "docstring": "Text input"}, "sep": {"type": "string", "label": "Delimiter", "value": ",", "docstring": "Column delimiter"}, "header": {"type": "string", "label": "Header Row", "value": "infer", "docstring": "Row number containing column names (0-indexed)"}}, "download_result": false}]}; + +describe('Validates OptionInput', () => { + it('Display OptionInput', () => { + const optionInput = shallow(); + expect(optionInput).toMatchSnapshot(); + }); + + it('Display OptionInput as FileUploadInput', () => { + const node = new CustomNodeModel({id: "myId", num_in: 2, num_out: 1, is_global: false}); + const optionInput = shallow(); + expect(optionInput).toMatchSnapshot(); + }); + + it('Display OptionInput as SimpleInput', () => { + const node = new CustomNodeModel({id: "myId", num_in: 2, num_out: 1, is_global: false}); + const optionInput = shallow(); + expect(optionInput).toMatchSnapshot(); + }); + + it('Display OptionInput as text', () => { + const node = new CustomNodeModel({id: "myId", num_in: 2, num_out: 1, is_global: false}); + const optionInput = shallow(); + expect(optionInput).toMatchSnapshot(); + }); + + it('Display OptionInput as int', () => { + const node = new CustomNodeModel({id: "myId", num_in: 2, num_out: 1, is_global: false}); + const optionInput = shallow(); + expect(optionInput).toMatchSnapshot(); + }); + + it('Display OptionInput as BooleanInput', () => { + const node = new CustomNodeModel({id: "myId", num_in: 2, num_out: 1, is_global: false}); + const optionInput = shallow(); + expect(optionInput).toMatchSnapshot(); + }); + + it('Display OptionInput as SelectInput', () => { + const node = new CustomNodeModel({id: "myId", num_in: 2, num_out: 1, is_global: false}); + const optionInput = shallow(); + expect(optionInput).toMatchSnapshot(); + }); +}); diff --git a/front-end/__tests__/components/CustomNode/Input/SelectInput.test.js b/front-end/__tests__/components/CustomNode/Input/SelectInput.test.js new file mode 100644 index 0000000..eb039f1 --- /dev/null +++ b/front-end/__tests__/components/CustomNode/Input/SelectInput.test.js @@ -0,0 +1,29 @@ +import React from 'react' +import { shallow, mount } from 'enzyme'; +import SelectInput from '../../../../src/components/CustomNode/Input/SelectInput'; + +describe('Validates SelectInput', () => { + it('Display SelectInput', () => { + const options =["area", "bar", "line", "point"]; + const input = shallow(); + expect(input).toMatchSnapshot(); + }); + + it('SelectInput onChange', () => { + const onChange = jest.fn(() => []); + const options =["area", "bar", "line", "point"]; + const input = mount(); + + expect(onChange.mock.calls.length).toBe(1); + input.find({keyName: "selectInputPlot"}).simulate('change', {target: {value: "bar"}}) + + expect(onChange.mock.calls.length).toBe(2); + }); +}); diff --git a/front-end/__tests__/components/CustomNode/Input/SimpleInput.test.js b/front-end/__tests__/components/CustomNode/Input/SimpleInput.test.js new file mode 100644 index 0000000..6895a2f --- /dev/null +++ b/front-end/__tests__/components/CustomNode/Input/SimpleInput.test.js @@ -0,0 +1,24 @@ +import React from 'react' +import { shallow, mount } from 'enzyme'; +import SimpleInput from '../../../../src/components/CustomNode/Input/SimpleInput'; + +describe('Validates SimpleInput', () => { + it('Display SimpleInput', () => { + const input = shallow(); + expect(input).toMatchSnapshot(); + }); + + it('Display SimpleInput as number', () => { + const changeFn = jest.fn(() => []); + const input = mount(); + + expect(changeFn.mock.calls.length).toBe(1); + input.find({keyName: "numberInput"}).simulate('change', {target: {value: 654321}}) + expect(changeFn.mock.calls.length).toBe(2); + }); +}); diff --git a/front-end/__tests__/components/CustomNode/Input/__snapshots__/BooleanInput.test.js.snap b/front-end/__tests__/components/CustomNode/Input/__snapshots__/BooleanInput.test.js.snap new file mode 100644 index 0000000..dfec036 --- /dev/null +++ b/front-end/__tests__/components/CustomNode/Input/__snapshots__/BooleanInput.test.js.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Validates BooleanInpupt Display BooleanInput 1`] = `ShallowWrapper {}`; diff --git a/front-end/__tests__/components/CustomNode/Input/__snapshots__/FileUploadInput.test.js.snap b/front-end/__tests__/components/CustomNode/Input/__snapshots__/FileUploadInput.test.js.snap new file mode 100644 index 0000000..fa9d1ae --- /dev/null +++ b/front-end/__tests__/components/CustomNode/Input/__snapshots__/FileUploadInput.test.js.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Validates FileUploadInput Display FileUploadInput 1`] = `ReactWrapper {}`; diff --git a/front-end/__tests__/components/CustomNode/Input/__snapshots__/FlowVariableOverride.test.js.snap b/front-end/__tests__/components/CustomNode/Input/__snapshots__/FlowVariableOverride.test.js.snap new file mode 100644 index 0000000..e12dcfc --- /dev/null +++ b/front-end/__tests__/components/CustomNode/Input/__snapshots__/FlowVariableOverride.test.js.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Validates FlowVariableOverride Display FlowVariableOverride 1`] = `ShallowWrapper {}`; diff --git a/front-end/__tests__/components/CustomNode/Input/__snapshots__/OptionInput.test.js.snap b/front-end/__tests__/components/CustomNode/Input/__snapshots__/OptionInput.test.js.snap new file mode 100644 index 0000000..6d2bc10 --- /dev/null +++ b/front-end/__tests__/components/CustomNode/Input/__snapshots__/OptionInput.test.js.snap @@ -0,0 +1,15 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Validates OptionInput Display OptionInput 1`] = `ShallowWrapper {}`; + +exports[`Validates OptionInput Display OptionInput as BooleanInput 1`] = `ShallowWrapper {}`; + +exports[`Validates OptionInput Display OptionInput as FileUploadInput 1`] = `ShallowWrapper {}`; + +exports[`Validates OptionInput Display OptionInput as SelectInput 1`] = `ShallowWrapper {}`; + +exports[`Validates OptionInput Display OptionInput as SimpleInput 1`] = `ShallowWrapper {}`; + +exports[`Validates OptionInput Display OptionInput as int 1`] = `ShallowWrapper {}`; + +exports[`Validates OptionInput Display OptionInput as text 1`] = `ShallowWrapper {}`; diff --git a/front-end/__tests__/components/CustomNode/Input/__snapshots__/SelectInput.test.js.snap b/front-end/__tests__/components/CustomNode/Input/__snapshots__/SelectInput.test.js.snap new file mode 100644 index 0000000..b96f413 --- /dev/null +++ b/front-end/__tests__/components/CustomNode/Input/__snapshots__/SelectInput.test.js.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Validates SelectInput Display SelectInput 1`] = `ShallowWrapper {}`; diff --git a/front-end/__tests__/components/CustomNode/Input/__snapshots__/SimpleInput.test.js.snap b/front-end/__tests__/components/CustomNode/Input/__snapshots__/SimpleInput.test.js.snap new file mode 100644 index 0000000..c1effb7 --- /dev/null +++ b/front-end/__tests__/components/CustomNode/Input/__snapshots__/SimpleInput.test.js.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Validates SimpleInput Display SimpleInput 1`] = `ShallowWrapper {}`; diff --git a/front-end/__tests__/components/CustomNode/NodeConfig.test.js b/front-end/__tests__/components/CustomNode/NodeConfig.test.js index d3751fe..0e45cde 100644 --- a/front-end/__tests__/components/CustomNode/NodeConfig.test.js +++ b/front-end/__tests__/components/CustomNode/NodeConfig.test.js @@ -1,8 +1,24 @@ import React from 'react' +import { Col, Modal, Button, Form } from 'react-bootstrap'; import { render } from '@testing-library/react' +import { shallow, mount } from 'enzyme'; import CustomNodeModel from '../../../src/components/CustomNode/CustomNodeModel'; import NodeConfig from '../../../src/components/CustomNode/NodeConfig'; +import OptionInput from '../../../src/components/CustomNode/NodeConfig'; +import FileUploadInput from '../../../src/components/CustomNode/NodeConfig'; +import SimpleInput from '../../../src/components/CustomNode/NodeConfig'; +import BooleanInput from '../../../src/components/CustomNode/NodeConfig'; +import FlowVariableOverride from '../../../src/components/CustomNode/NodeConfig'; +import SelectInput from '../../../src/components/CustomNode/NodeConfig'; +global.console = {log: jest.fn()} + +global.fetch = jest.fn(() => Promise.resolve({ + data: [], + json: jest.fn(() => []) +})); + +global.confirm = (s) => true; describe('Validate NodeConfig Modal', () => { it('Display configuration', () => { @@ -16,4 +32,47 @@ describe('Validate NodeConfig Modal', () => { ); expect(nodeConfig).toMatchSnapshot(); }); + + it('Validates handleDelete',() => { + const props = { + onDelete: jest.fn(() => []), + toggleShow: jest.fn(() => []) + }; + + const nodeConfig = new NodeConfig(props); + nodeConfig.handleDelete(); + + expect(props.onDelete.mock.calls.length).toBe(1); + expect(props.toggleShow.mock.calls.length).toBe(1); + }); + + it('Validates handleSubmit', () => { + const props = { + onSubmit: jest.fn(() => []), + toggleShow: jest.fn(() => []) + }; + + const event = { + preventDefault: jest.fn(() => []) + }; + + const nodeConfig = new NodeConfig(props); + nodeConfig.handleSubmit(event); + + expect(event.preventDefault.mock.calls.length).toBe(1); + expect(props.onSubmit.mock.calls.length).toBe(1); + expect(props.toggleShow.mock.calls.length).toBe(1); + }); + + it('Validates OptionInput with properties', () => { + const optionInputRendered = render(); + + expect(optionInputRendered).toMatchSnapshot(); + }); + + }) diff --git a/front-end/__tests__/components/CustomNode/__snapshots__/CustomNodeWidget.test.js.snap b/front-end/__tests__/components/CustomNode/__snapshots__/CustomNodeWidget.test.js.snap new file mode 100644 index 0000000..8459425 --- /dev/null +++ b/front-end/__tests__/components/CustomNode/__snapshots__/CustomNodeWidget.test.js.snap @@ -0,0 +1,232 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Validate CustomNodeWidget Display CustomNodeWidget 1`] = ` +Object { + "asFragment": [Function], + "baseElement": +
+
+
+
+
+
+ ⚙ +
+
+ Tabular +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ , + "container":
+
+
+
+
+
+ ⚙ +
+
+ Tabular +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
, + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} +`; diff --git a/front-end/__tests__/components/CustomNode/__snapshots__/GraphView.test.js.snap b/front-end/__tests__/components/CustomNode/__snapshots__/GraphView.test.js.snap index 7294c1f..5689ab7 100644 --- a/front-end/__tests__/components/CustomNode/__snapshots__/GraphView.test.js.snap +++ b/front-end/__tests__/components/CustomNode/__snapshots__/GraphView.test.js.snap @@ -21,7 +21,7 @@ Object { tabindex="-1" > + +
+
+
+ , + "container": , + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} +`; + +exports[`Validates About Does not display About info 1`] = ` +Object { + "asFragment": [Function], + "baseElement": +
+
+ + About + +
+
+ , + "container":
+
+ + About + +
+
, + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} +`; diff --git a/front-end/__tests__/components/__snapshots__/App.test.js.snap b/front-end/__tests__/components/__snapshots__/App.test.js.snap new file mode 100644 index 0000000..77dbb21 --- /dev/null +++ b/front-end/__tests__/components/__snapshots__/App.test.js.snap @@ -0,0 +1,390 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Validates App initialization Creates App 1`] = ` +Object { + "asFragment": [Function], + "baseElement": +
+
+
+
+

+ Visual Programming Workspace +

+
+
+ + About + +
+
+
+
+
+ + + + + + + + +
+
+
+
+
+

+ Flow Variables +

+ + + + + + + + + +
+ Name + + Type + + Value + + +
+ +
+
+

+ Node Menu +

+
+ Drag-and-drop nodes to build a workflow. +
+
+ + +
+
+
+
+
+ +
+
+
+
+
+
+
+ , + "container":
+
+
+
+

+ Visual Programming Workspace +

+
+
+ + About + +
+
+
+
+
+ + + + + + + + +
+
+
+
+
+

+ Flow Variables +

+ + + + + + + + + +
+ Name + + Type + + Value + + +
+ +
+
+

+ Node Menu +

+
+ Drag-and-drop nodes to build a workflow. +
+
+ + +
+
+
+
+
+ +
+
+
+
+
+
+
, + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} +`; diff --git a/front-end/__tests__/components/__snapshots__/CustomNodeUpload.test.js.snap b/front-end/__tests__/components/__snapshots__/CustomNodeUpload.test.js.snap new file mode 100644 index 0000000..76383de --- /dev/null +++ b/front-end/__tests__/components/__snapshots__/CustomNodeUpload.test.js.snap @@ -0,0 +1,84 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Validates CustomNodeUpload Displays CustomNodeUpload 1`] = ` +Object { + "asFragment": [Function], + "baseElement": +
+ + +
+ , + "container":
+ + +
, + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} +`; diff --git a/front-end/__tests__/components/__snapshots__/FileUpload.test.js.snap b/front-end/__tests__/components/__snapshots__/FileUpload.test.js.snap new file mode 100644 index 0000000..1834539 --- /dev/null +++ b/front-end/__tests__/components/__snapshots__/FileUpload.test.js.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Validates FileUpload Displays FileUpload 1`] = `ShallowWrapper {}`; diff --git a/front-end/__tests__/components/__snapshots__/GlobalFlowMenu.test.js.snap b/front-end/__tests__/components/__snapshots__/GlobalFlowMenu.test.js.snap new file mode 100644 index 0000000..24e1f0d --- /dev/null +++ b/front-end/__tests__/components/__snapshots__/GlobalFlowMenu.test.js.snap @@ -0,0 +1,146 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Validates GlobalFlowMenu Display of GlobalFlowMenu with empty list 1`] = ` +Object { + "asFragment": [Function], + "baseElement": +
+
+

+ Flow Variables +

+ + + + + + + + + +
+ Name + + Type + + Value + + +
+ +
+
+ , + "container":
+
+

+ Flow Variables +

+ + + + + + + + + +
+ Name + + Type + + Value + + +
+ +
+
, + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} +`; diff --git a/front-end/__tests__/components/__snapshots__/NodeMenu.test.js.snap b/front-end/__tests__/components/__snapshots__/NodeMenu.test.js.snap new file mode 100644 index 0000000..b119d82 --- /dev/null +++ b/front-end/__tests__/components/__snapshots__/NodeMenu.test.js.snap @@ -0,0 +1,635 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Validate NodeMenu Display NodeMenu 1`] = ` +Object { + "asFragment": [Function], + "baseElement": +
+
+

+ Node Menu +

+
+ Drag-and-drop nodes to build a workflow. +
+
+
+ + Visualization + +
    +
  • + graph +
  • +
+
+
+ + Flow Control + +
    +
  • + Integer Input +
  • +
  • + String Input +
  • +
+
+
+ + Manipulation + +
    +
  • + Joiner +
  • +
  • + Filter +
  • +
  • + Pivoting +
  • +
+
+
+ + I/O + +
    +
  • + Read CSV +
  • +
  • + Write CSV +
  • +
+
+
+ + Custom Nodes + +
    +
  • + Table Creator +
  • +
+
+ + +
+
+ , + "container":
+
+

+ Node Menu +

+
+ Drag-and-drop nodes to build a workflow. +
+
+
+ + Visualization + +
    +
  • + graph +
  • +
+
+
+ + Flow Control + +
    +
  • + Integer Input +
  • +
  • + String Input +
  • +
+
+
+ + Manipulation + +
    +
  • + Joiner +
  • +
  • + Filter +
  • +
  • + Pivoting +
  • +
+
+
+ + I/O + +
    +
  • + Read CSV +
  • +
  • + Write CSV +
  • +
+
+
+ + Custom Nodes + +
    +
  • + Table Creator +
  • +
+
+ + +
+
, + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} +`; + +exports[`Validate NodeMenu Display NodeMenu with missing packages 1`] = ` +Object { + "asFragment": [Function], + "baseElement": +
+
+

+ Node Menu +

+
+ Drag-and-drop nodes to build a workflow. +
+
+
+ + Visualization + +
    +
  • + graph +
  • +
+
+
+ + Flow Control + +
    +
  • + Integer Input +
  • +
  • + String Input +
  • +
+
+
+ + Manipulation + +
    +
  • + Joiner +
  • +
  • + Filter +
  • +
  • + Pivoting +
  • +
+
+
+ + I/O + +
    +
  • + Read CSV +
  • +
  • + Write CSV +
  • +
+
+
+ + Custom Nodes + +
    +
  • + Table Creator +
  • +
+
+ + +
+
+ , + "container":
+
+

+ Node Menu +

+
+ Drag-and-drop nodes to build a workflow. +
+
+
+ + Visualization + +
    +
  • + graph +
  • +
+
+
+ + Flow Control + +
    +
  • + Integer Input +
  • +
  • + String Input +
  • +
+
+
+ + Manipulation + +
    +
  • + Joiner +
  • +
  • + Filter +
  • +
  • + Pivoting +
  • +
+
+
+ + I/O + +
    +
  • + Read CSV +
  • +
  • + Write CSV +
  • +
+
+
+ + Custom Nodes + +
    +
  • + Table Creator +
  • +
+
+ + +
+
, + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} +`; diff --git a/front-end/__tests__/components/__snapshots__/Workspace.test.js.snap b/front-end/__tests__/components/__snapshots__/Workspace.test.js.snap new file mode 100644 index 0000000..3e9e8a0 --- /dev/null +++ b/front-end/__tests__/components/__snapshots__/Workspace.test.js.snap @@ -0,0 +1,302 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Validates Workspace initialization Creates Workspace 1`] = ` +Object { + "asFragment": [Function], + "baseElement": +
+
+
+ + + + + + + + +
+
+
+
+
+

+ Flow Variables +

+ + + + + + + + + +
+ Name + + Type + + Value + + +
+ +
+
+

+ Node Menu +

+
+ Drag-and-drop nodes to build a workflow. +
+
+ + +
+
+
+
+
+
+
+
+
+ , + "container":
+
+
+ + + + + + + + +
+
+
+
+
+

+ Flow Variables +

+ + + + + + + + + +
+ Name + + Type + + Value + + +
+ +
+
+

+ Node Menu +

+
+ Drag-and-drop nodes to build a workflow. +
+
+ + +
+
+
+
+
+
+
+
+
, + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} +`; diff --git a/front-end/babel.config.js b/front-end/babel.config.js index 099fe78..22b18d1 100644 --- a/front-end/babel.config.js +++ b/front-end/babel.config.js @@ -1,6 +1,8 @@ module.exports = { - presets: ['@babel/preset-env', - '@babel/preset-react', + presets: [ + ['@babel/preset-env', {targets: {node: 'current'}}], + '@babel/preset-typescript', + '@babel/preset-react', ], plugins: [ "@babel/plugin-syntax-dynamic-import", diff --git a/front-end/jest.config.js b/front-end/jest.config.js new file mode 100644 index 0000000..dcda260 --- /dev/null +++ b/front-end/jest.config.js @@ -0,0 +1,31 @@ +module.exports = { + collectCoverage: true, + collectCoverageFrom: ['src/**/*.{js,jsx}'], + coveragePathIgnorePatterns: [ + "src/index.js", + "src/serviceWorker.js" + ], + coverageThreshold: { + "global": { + "branches": 60, + "functions": 70, + "lines": 70, + "statements": 80 + } + }, + moduleNameMapper: { + "\\.(css|less)$": "/__mocks__/css/styleMock.js" + }, + setupFiles: [ + 'jest-canvas-mock' + ], + setupFilesAfterEnv: [ + "./setupTests.js" + ], + testPathIgnorePatterns: [ + ], + transform: { + '^.+\\.(js|jsx)?$': 'babel-jest' + }, + +}; diff --git a/front-end/package-lock.json b/front-end/package-lock.json index 7b30060..245a7dd 100644 --- a/front-end/package-lock.json +++ b/front-end/package-lock.json @@ -886,12 +886,12 @@ } }, "@babel/preset-env": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.9.5.tgz", - "integrity": "sha512-eWGYeADTlPJH+wq1F0wNfPbVS1w1wtmMJiYk55Td5Yu28AsdR9AsC97sZ0Qq8fHqQuslVSIYSGJMcblr345GfQ==", + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.9.6.tgz", + "integrity": "sha512-0gQJ9RTzO0heXOhzftog+a/WyOuqMrAIugVYxMYf83gh1CQaQDjMtsOpqOwXyDL/5JcWsrCm8l4ju8QC97O7EQ==", "requires": { - "@babel/compat-data": "^7.9.0", - "@babel/helper-compilation-targets": "^7.8.7", + "@babel/compat-data": "^7.9.6", + "@babel/helper-compilation-targets": "^7.9.6", "@babel/helper-module-imports": "^7.8.3", "@babel/helper-plugin-utils": "^7.8.3", "@babel/plugin-proposal-async-generator-functions": "^7.8.3", @@ -899,7 +899,7 @@ "@babel/plugin-proposal-json-strings": "^7.8.3", "@babel/plugin-proposal-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-proposal-numeric-separator": "^7.8.3", - "@babel/plugin-proposal-object-rest-spread": "^7.9.5", + "@babel/plugin-proposal-object-rest-spread": "^7.9.6", "@babel/plugin-proposal-optional-catch-binding": "^7.8.3", "@babel/plugin-proposal-optional-chaining": "^7.9.0", "@babel/plugin-proposal-unicode-property-regex": "^7.8.3", @@ -926,9 +926,9 @@ "@babel/plugin-transform-function-name": "^7.8.3", "@babel/plugin-transform-literals": "^7.8.3", "@babel/plugin-transform-member-expression-literals": "^7.8.3", - "@babel/plugin-transform-modules-amd": "^7.9.0", - "@babel/plugin-transform-modules-commonjs": "^7.9.0", - "@babel/plugin-transform-modules-systemjs": "^7.9.0", + "@babel/plugin-transform-modules-amd": "^7.9.6", + "@babel/plugin-transform-modules-commonjs": "^7.9.6", + "@babel/plugin-transform-modules-systemjs": "^7.9.6", "@babel/plugin-transform-modules-umd": "^7.9.0", "@babel/plugin-transform-named-capturing-groups-regex": "^7.8.3", "@babel/plugin-transform-new-target": "^7.8.3", @@ -944,12 +944,88 @@ "@babel/plugin-transform-typeof-symbol": "^7.8.4", "@babel/plugin-transform-unicode-regex": "^7.8.3", "@babel/preset-modules": "^0.1.3", - "@babel/types": "^7.9.5", - "browserslist": "^4.9.1", + "@babel/types": "^7.9.6", + "browserslist": "^4.11.1", "core-js-compat": "^3.6.2", "invariant": "^2.2.2", "levenary": "^1.1.1", "semver": "^5.5.0" + }, + "dependencies": { + "@babel/compat-data": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.9.6.tgz", + "integrity": "sha512-5QPTrNen2bm7RBc7dsOmcA5hbrS4O2Vhmk5XOL4zWW/zD/hV0iinpefDlkm+tBBy8kDtFaaeEvmAqt+nURAV2g==", + "requires": { + "browserslist": "^4.11.1", + "invariant": "^2.2.4", + "semver": "^5.5.0" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.9.6.tgz", + "integrity": "sha512-x2Nvu0igO0ejXzx09B/1fGBxY9NXQlBW2kZsSxCJft+KHN8t9XWzIvFxtPHnBOAXpVsdxZKZFbRUC8TsNKajMw==", + "requires": { + "@babel/compat-data": "^7.9.6", + "browserslist": "^4.11.1", + "invariant": "^2.2.4", + "levenary": "^1.1.1", + "semver": "^5.5.0" + } + }, + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.9.6.tgz", + "integrity": "sha512-Ga6/fhGqA9Hj+y6whNpPv8psyaK5xzrQwSPsGPloVkvmH+PqW1ixdnfJ9uIO06OjQNYol3PMnfmJ8vfZtkzF+A==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.0", + "@babel/plugin-transform-parameters": "^7.9.5" + } + }, + "@babel/plugin-transform-modules-amd": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.9.6.tgz", + "integrity": "sha512-zoT0kgC3EixAyIAU+9vfaUVKTv9IxBDSabgHoUCBP6FqEJ+iNiN7ip7NBKcYqbfUDfuC2mFCbM7vbu4qJgOnDw==", + "requires": { + "@babel/helper-module-transforms": "^7.9.0", + "@babel/helper-plugin-utils": "^7.8.3", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.9.6.tgz", + "integrity": "sha512-7H25fSlLcn+iYimmsNe3uK1at79IE6SKW9q0/QeEHTMC9MdOZ+4bA+T1VFB5fgOqBWoqlifXRzYD0JPdmIrgSQ==", + "requires": { + "@babel/helper-module-transforms": "^7.9.0", + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-simple-access": "^7.8.3", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/plugin-transform-modules-systemjs": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.9.6.tgz", + "integrity": "sha512-NW5XQuW3N2tTHim8e1b7qGy7s0kZ2OH3m5octc49K1SdAKGxYxeIx7hiIz05kS1R2R+hOWcsr1eYwcGhrdHsrg==", + "requires": { + "@babel/helper-hoist-variables": "^7.8.3", + "@babel/helper-module-transforms": "^7.9.0", + "@babel/helper-plugin-utils": "^7.8.3", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/types": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.6.tgz", + "integrity": "sha512-qxXzvBO//jO9ZnoasKF1uJzHd2+M6Q2ZPIVfnFps8JJvXy0ZBbwbNOmE6SGIY5XOY6d1Bo5lb9d9RJ8nv3WSeA==", + "requires": { + "@babel/helper-validator-identifier": "^7.9.5", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/preset-modules": { @@ -1769,13 +1845,23 @@ } }, "@testing-library/react": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-10.0.3.tgz", - "integrity": "sha512-EVmd3ghDFBEjOSLnISUdi7OcLdP6tsqjmTprpMaBz5TreoM8jnxGKIPkLD579CE0LYGqK01iffQiy6wwW/RUig==", + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-10.0.4.tgz", + "integrity": "sha512-2e1B5debfuiIGbvUuiSXybskuh7ZTVJDDvG/IxlzLOY9Co/mKFj9hIklAe2nGZYcOUxFaiqWrRZ9vCVGzJfRlQ==", "requires": { - "@babel/runtime": "^7.9.2", - "@testing-library/dom": "^7.1.0", - "@types/testing-library__react": "^10.0.0" + "@babel/runtime": "^7.9.6", + "@testing-library/dom": "^7.2.2", + "@types/testing-library__react": "^10.0.1" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.6.tgz", + "integrity": "sha512-64AF1xY3OAkFHqOb9s4jpgk1Mm5vDZ4L3acHvAml+53nO1XbXLuDodsVpO4OIUsmemlUHMxNdYMNJmsvOwLrvQ==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + } } }, "@testing-library/user-event": { @@ -1877,6 +1963,131 @@ "@types/istanbul-lib-report": "*" } }, + "@types/jest": { + "version": "25.2.1", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-25.2.1.tgz", + "integrity": "sha512-msra1bCaAeEdkSyA0CZ6gW1ukMIvZ5YoJkdXw/qhQdsuuDlFTcEUrUw8CLCPt2rVRUfXlClVvK2gvPs9IokZaA==", + "dev": true, + "requires": { + "jest-diff": "^25.2.1", + "pretty-format": "^25.2.1" + }, + "dependencies": { + "@jest/types": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0" + } + }, + "@types/yargs": { + "version": "15.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.4.tgz", + "integrity": "sha512-9T1auFmbPZoxHz0enUFlUuKRy3it01R+hlggyVUMtnCTQRunsQYifnSGb8hET4Xo8yiC0o0r1paW3ud5+rbURg==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "diff-sequences": { + "version": "25.2.6", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-25.2.6.tgz", + "integrity": "sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "jest-diff": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-25.5.0.tgz", + "integrity": "sha512-z1kygetuPiREYdNIumRpAHY6RXiGmp70YHptjdaxTWGmA085W3iCnXNx0DhflK3vwrKmrRWyY1wUpkPMVxMK7A==", + "dev": true, + "requires": { + "chalk": "^3.0.0", + "diff-sequences": "^25.2.6", + "jest-get-type": "^25.2.6", + "pretty-format": "^25.5.0" + } + }, + "jest-get-type": { + "version": "25.2.6", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.2.6.tgz", + "integrity": "sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig==", + "dev": true + }, + "pretty-format": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.5.0.tgz", + "integrity": "sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ==", + "dev": true, + "requires": { + "@jest/types": "^25.5.0", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^16.12.0" + } + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "@types/json-schema": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.4.tgz", @@ -2367,6 +2578,11 @@ "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.3.tgz", "integrity": "sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg==" }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, "accepts": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", @@ -2463,6 +2679,24 @@ "indent-string": "^4.0.0" } }, + "airbnb-prop-types": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/airbnb-prop-types/-/airbnb-prop-types-2.15.0.tgz", + "integrity": "sha512-jUh2/hfKsRjNFC4XONQrxo/n/3GG4Tn6Hl0WlFQN5PY9OMC9loSCoAYKnZsWaP8wEfd5xcrPloK0Zg6iS1xwVA==", + "dev": true, + "requires": { + "array.prototype.find": "^2.1.0", + "function.prototype.name": "^1.1.1", + "has": "^1.0.3", + "is-regex": "^1.0.4", + "object-is": "^1.0.1", + "object.assign": "^4.1.0", + "object.entries": "^1.1.0", + "prop-types": "^15.7.2", + "prop-types-exact": "^1.2.0", + "react-is": "^16.9.0" + } + }, "ajv": { "version": "6.12.2", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", @@ -2531,6 +2765,39 @@ "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" }, + "are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -2573,6 +2840,12 @@ "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=" }, + "array-filter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-1.0.0.tgz", + "integrity": "sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=", + "dev": true + }, "array-flat-polyfill": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/array-flat-polyfill/-/array-flat-polyfill-1.0.1.tgz", @@ -2611,6 +2884,16 @@ "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" }, + "array.prototype.find": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/array.prototype.find/-/array.prototype.find-2.1.1.tgz", + "integrity": "sha512-mi+MYNJYLTx2eNYy+Yh6raoQacCsNeeMUaspFPh9Y141lFSsWxxB8V9mM2ye+eqiRs917J6/pJ4M9ZPzenWckA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.4" + } + }, "array.prototype.flat": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz", @@ -3360,6 +3643,15 @@ "pkg-up": "^2.0.0" } }, + "bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "requires": { + "fast-json-stable-stringify": "2.x" + } + }, "bser": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", @@ -3507,6 +3799,16 @@ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001048.tgz", "integrity": "sha512-g1iSHKVxornw0K8LG9LLdf+Fxnv7T1Z+mMsf0/YYLclQX4Cd522Ap0Lrw6NFqHgezit78dtyWxzlV2Xfc7vgRg==" }, + "canvas": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.6.1.tgz", + "integrity": "sha512-S98rKsPcuhfTcYbtF53UIJhcbgIAK533d1kJKMwsMwAIFgfd58MOyxRud3kktlzWiEkFliaJtvyZCBtud/XVEA==", + "requires": { + "nan": "^2.14.0", + "node-pre-gyp": "^0.11.0", + "simple-get": "^3.0.3" + } + }, "capture-exit": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", @@ -3540,6 +3842,75 @@ "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" }, + "cheerio": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.3.tgz", + "integrity": "sha512-0td5ijfUPuubwLUu0OBoe98gZj8C/AA+RW3v67GPlGOrvxWjZmBXiBCRU+I8VEiNyJzjth40POfHiz2RB3gImA==", + "dev": true, + "requires": { + "css-select": "~1.2.0", + "dom-serializer": "~0.1.1", + "entities": "~1.1.1", + "htmlparser2": "^3.9.1", + "lodash": "^4.15.0", + "parse5": "^3.0.1" + }, + "dependencies": { + "css-select": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "dev": true, + "requires": { + "boolbase": "~1.0.0", + "css-what": "2.1", + "domutils": "1.5.1", + "nth-check": "~1.0.1" + } + }, + "css-what": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", + "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==", + "dev": true + }, + "dom-serializer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", + "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", + "dev": true, + "requires": { + "domelementtype": "^1.3.0", + "entities": "^1.1.1" + } + }, + "domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "dev": true, + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "dev": true + }, + "parse5": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz", + "integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==", + "dev": true, + "requires": { + "@types/node": "*" + } + } + } + }, "chokidar": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.0.tgz", @@ -3915,6 +4286,11 @@ "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==" }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + }, "constants-browserify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", @@ -4233,6 +4609,11 @@ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==" }, + "cssfontparser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/cssfontparser/-/cssfontparser-1.2.1.tgz", + "integrity": "sha1-9AIvyPlwDGgCnVQghK+69CWj8+M=" + }, "cssnano": { "version": "4.1.10", "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.10.tgz", @@ -4610,6 +4991,14 @@ "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" }, + "decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "requires": { + "mimic-response": "^2.0.0" + } + }, "deep-equal": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", @@ -4623,6 +5012,11 @@ "regexp.prototype.flags": "^1.2.0" } }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" + }, "deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", @@ -4737,6 +5131,11 @@ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + }, "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -4756,6 +5155,11 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" + }, "detect-newline": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", @@ -4809,6 +5213,12 @@ } } }, + "discontinuous-range": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz", + "integrity": "sha1-44Mx8IRLukm5qctxx3FYWqsbxlo=", + "dev": true + }, "dns-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", @@ -5083,6 +5493,77 @@ "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz", "integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==" }, + "enzyme": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/enzyme/-/enzyme-3.11.0.tgz", + "integrity": "sha512-Dw8/Gs4vRjxY6/6i9wU0V+utmQO9kvh9XLnz3LIudviOnVYDEe2ec+0k+NQoMamn1VrjKgCUOWj5jG/5M5M0Qw==", + "dev": true, + "requires": { + "array.prototype.flat": "^1.2.3", + "cheerio": "^1.0.0-rc.3", + "enzyme-shallow-equal": "^1.0.1", + "function.prototype.name": "^1.1.2", + "has": "^1.0.3", + "html-element-map": "^1.2.0", + "is-boolean-object": "^1.0.1", + "is-callable": "^1.1.5", + "is-number-object": "^1.0.4", + "is-regex": "^1.0.5", + "is-string": "^1.0.5", + "is-subset": "^0.1.1", + "lodash.escape": "^4.0.1", + "lodash.isequal": "^4.5.0", + "object-inspect": "^1.7.0", + "object-is": "^1.0.2", + "object.assign": "^4.1.0", + "object.entries": "^1.1.1", + "object.values": "^1.1.1", + "raf": "^3.4.1", + "rst-selector-parser": "^2.2.3", + "string.prototype.trim": "^1.2.1" + } + }, + "enzyme-adapter-react-16": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.15.2.tgz", + "integrity": "sha512-SkvDrb8xU3lSxID8Qic9rB8pvevDbLybxPK6D/vW7PrT0s2Cl/zJYuXvsd1EBTz0q4o3iqG3FJhpYz3nUNpM2Q==", + "dev": true, + "requires": { + "enzyme-adapter-utils": "^1.13.0", + "enzyme-shallow-equal": "^1.0.1", + "has": "^1.0.3", + "object.assign": "^4.1.0", + "object.values": "^1.1.1", + "prop-types": "^15.7.2", + "react-is": "^16.12.0", + "react-test-renderer": "^16.0.0-0", + "semver": "^5.7.0" + } + }, + "enzyme-adapter-utils": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/enzyme-adapter-utils/-/enzyme-adapter-utils-1.13.0.tgz", + "integrity": "sha512-YuEtfQp76Lj5TG1NvtP2eGJnFKogk/zT70fyYHXK2j3v6CtuHqc8YmgH/vaiBfL8K1SgVVbQXtTcgQZFwzTVyQ==", + "dev": true, + "requires": { + "airbnb-prop-types": "^2.15.0", + "function.prototype.name": "^1.1.2", + "object.assign": "^4.1.0", + "object.fromentries": "^2.0.2", + "prop-types": "^15.7.2", + "semver": "^5.7.1" + } + }, + "enzyme-shallow-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/enzyme-shallow-equal/-/enzyme-shallow-equal-1.0.1.tgz", + "integrity": "sha512-hGA3i1so8OrYOZSM9whlkNmVHOicJpsjgTzC+wn2JMJXhq1oO4kA4bJ5MsfzSIcC71aLDKzJ6gZpIxrqt3QTAQ==", + "dev": true, + "requires": { + "has": "^1.0.3", + "object-is": "^1.0.2" + } + }, "errno": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", @@ -6796,11 +7277,76 @@ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, + "function.prototype.name": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.2.tgz", + "integrity": "sha512-C8A+LlHBJjB2AdcRPorc5JvJ5VUoWlXdEHLOJdCI7kjHEtGTpHQUiqMvCIKUwIsGwZX2jZJy761AXsn356bJQg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "functions-have-names": "^1.2.0" + } + }, "functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=" }, + "functions-have-names": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.1.tgz", + "integrity": "sha512-j48B/ZI7VKs3sgeI2cZp7WXWmZXu7Iq5pl5/vptV5N2mq+DGFuS/ulaDjtaoLpYzuD6u8UgrUKHfgo7fDTSiBA==", + "dev": true + }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, "gensync": { "version": "1.0.0-beta.1", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", @@ -7003,6 +7549,11 @@ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + }, "has-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", @@ -7130,6 +7681,15 @@ "resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.2.tgz", "integrity": "sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==" }, + "html-element-map": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/html-element-map/-/html-element-map-1.2.0.tgz", + "integrity": "sha512-0uXq8HsuG1v2TmQ8QkIhzbrqeskE4kn52Q18QJ9iAA/SnHoEKXWiUxHQtclRsCFWEUD2So34X+0+pZZu862nnw==", + "dev": true, + "requires": { + "array-filter": "^1.0.0" + } + }, "html-encoding-sniffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", @@ -7317,6 +7877,14 @@ "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==" }, + "ignore-walk": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", + "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", + "requires": { + "minimatch": "^3.0.4" + } + }, "immer": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/immer/-/immer-1.10.0.tgz", @@ -7602,6 +8170,12 @@ "binary-extensions": "^2.0.0" } }, + "is-boolean-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.0.1.tgz", + "integrity": "sha512-TqZuVwa/sppcrhUCAYkGBk7w0yxfQQnxq28fjkO53tnK9FQXmdwz2JS5+GjsWQ6RByES1K40nI+yDic5c9/aAQ==", + "dev": true + }, "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", @@ -7729,6 +8303,12 @@ } } }, + "is-number-object": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz", + "integrity": "sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==", + "dev": true + }, "is-obj": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", @@ -7801,6 +8381,12 @@ "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==" }, + "is-subset": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz", + "integrity": "sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY=", + "dev": true + }, "is-svg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-3.0.0.tgz", @@ -7974,6 +8560,15 @@ } } }, + "jest-canvas-mock": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/jest-canvas-mock/-/jest-canvas-mock-2.2.0.tgz", + "integrity": "sha512-DcJdchb7eWFZkt6pvyceWWnu3lsp5QWbUeXiKgEMhwB3sMm5qHM1GQhDajvJgBeiYpgKcojbzZ53d/nz6tXvJw==", + "requires": { + "cssfontparser": "^1.2.1", + "parse-color": "^1.0.0" + } + }, "jest-changed-files": { "version": "24.9.0", "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-24.9.0.tgz", @@ -8235,6 +8830,15 @@ "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.1.tgz", "integrity": "sha512-pgFw2tm54fzgYvc/OHrnysABEObZCUNFnhjoRjaVOCN8NYc032/gVjPaHD4Aq6ApkSieWtfKAFQtmDKAmhupnQ==" }, + "jest-react-hooks-shallow": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/jest-react-hooks-shallow/-/jest-react-hooks-shallow-1.3.0.tgz", + "integrity": "sha512-seERyGrtUNUt4Pgd8ST2alIYUK29GOB6taxNeSvRjGLL9LdpCmmSYWVMUuDpeJO5Abo5gb0dtsB1AK0m/5tQYQ==", + "dev": true, + "requires": { + "react": "^16.8.0" + } + }, "jest-regex-util": { "version": "24.9.0", "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.9.0.tgz", @@ -8466,9 +9070,9 @@ } }, "jquery": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.5.0.tgz", - "integrity": "sha512-Xb7SVYMvygPxbFMpTFQiHh1J7HClEaThguL15N/Gg37Lri/qKyhRGZYzHRyLH8Stq3Aow0LsHO2O2ci86fCrNQ==" + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.5.1.tgz", + "integrity": "sha512-XwIBPqcMn57FxfT+Go5pzySnm4KWkT1Tv7gjrpT1srtf8Weynl6R273VJ5GjkRb51IzMp5nbaPjJXMWeju2MKg==" }, "js-tokens": { "version": "4.0.0", @@ -8797,6 +9401,24 @@ "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=" }, + "lodash.escape": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.1.tgz", + "integrity": "sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg=", + "dev": true + }, + "lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", + "dev": true + }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=", + "dev": true + }, "lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -8881,6 +9503,12 @@ } } }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, "makeerror": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", @@ -8921,9 +9549,9 @@ "integrity": "sha1-HfUmIkOuNBwaCATdMCBIJnrHE7s=" }, "mathjs": { - "version": "6.6.4", - "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-6.6.4.tgz", - "integrity": "sha512-fvmP89ujJbDAC8ths7FZh7PWdA71dfA5WJVAzJbQhSDCHK1aBk8WRf1XcTw51ERs+sKx9nYBGsRshqmb/oe8Ag==", + "version": "6.6.5", + "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-6.6.5.tgz", + "integrity": "sha512-jvRqk7eoEHBcx/lskmy05m+8M7xDHAJcJzRJoqIsqExtlTHPDQO0Zv85g5F0rasDAXF+DLog/70hcqCJijSzPQ==", "requires": { "complex.js": "^2.0.11", "decimal.js": "^10.2.0", @@ -9100,6 +9728,11 @@ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" }, + "mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==" + }, "min-indent": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.0.tgz", @@ -9183,6 +9816,30 @@ "minipass": "^3.0.0" } }, + "minizlib": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", + "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", + "requires": { + "minipass": "^2.9.0" + }, + "dependencies": { + "minipass": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", + "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + } + } + }, "mississippi": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", @@ -9243,6 +9900,12 @@ "minimist": "^1.2.5" } }, + "moo": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.1.tgz", + "integrity": "sha512-I1mnb5xn4fO80BH9BLcF0yLypy2UKl+Cb01Fu0hJRkJjlCRtxZMWkTdAtDd5ZqCOxtCkhmRwyI57vWT+1iZ67w==", + "dev": true + }, "move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", @@ -9283,8 +9946,7 @@ "nan": { "version": "2.14.1", "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz", - "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==", - "optional": true + "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==" }, "nanomatch": { "version": "1.2.13", @@ -9309,6 +9971,44 @@ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=" }, + "nearley": { + "version": "2.19.3", + "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.19.3.tgz", + "integrity": "sha512-FpAy1PmTsUpOtgxr23g4jRNvJHYzZEW2PixXeSzksLR/ykPfwKhAodc2+9wQhY+JneWLcvkDw6q7FJIsIdF/aQ==", + "dev": true, + "requires": { + "commander": "^2.19.0", + "moo": "^0.5.0", + "railroad-diagrams": "^1.0.0", + "randexp": "0.4.6", + "semver": "^5.4.1" + } + }, + "needle": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.4.1.tgz", + "integrity": "sha512-x/gi6ijr4B7fwl6WYL9FwlCvRQKGlUNvnceho8wxkwXqN8jvVmmmATTmZPRRG7b/yC1eode26C2HO9jl78Du9g==", + "requires": { + "debug": "^3.2.6", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, "negotiator": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", @@ -9446,11 +10146,37 @@ "which": "^1.3.0" } }, + "node-pre-gyp": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz", + "integrity": "sha512-TwWAOZb0j7e9eGaf9esRx3ZcLaE5tQ2lvYy1pb5IAaG1a2e2Kv5Lms1Y4hpj+ciXJRofIxxlt5haeQ/2ANeE0Q==", + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, "node-releases": { "version": "1.1.53", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.53.tgz", "integrity": "sha512-wp8zyQVwef2hpZ/dJH7SfSrIPD6YoJz6BDQDpGEkcA0s3LpAQoxBIYmfIq6QAhC1DhwsyCgTaTTcONwX8qzCuQ==" }, + "nopt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", + "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, "normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", @@ -9486,6 +10212,29 @@ "sort-keys": "^1.0.0" } }, + "npm-bundled": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz", + "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", + "requires": { + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npm-normalize-package-bin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", + "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==" + }, + "npm-packlist": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz", + "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==", + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1", + "npm-normalize-package-bin": "^1.0.1" + } + }, "npm-run-path": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", @@ -9494,6 +10243,17 @@ "path-key": "^2.0.0" } }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, "nth-check": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", @@ -9746,6 +10506,11 @@ "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=" }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + }, "os-locale": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", @@ -9761,6 +10526,15 @@ "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, "p-defer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", @@ -9895,6 +10669,21 @@ "safe-buffer": "^5.1.1" } }, + "parse-color": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-color/-/parse-color-1.0.0.tgz", + "integrity": "sha1-e3SLlag/A/FqlPU15S1/PZRlhhk=", + "requires": { + "color-convert": "~0.5.0" + }, + "dependencies": { + "color-convert": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-0.5.3.tgz", + "integrity": "sha1-vbbGnOZg+t/+CwAHzER+G59ygr0=" + } + } + }, "parse-json": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", @@ -11183,6 +11972,17 @@ "react-is": "^16.8.1" } }, + "prop-types-exact": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/prop-types-exact/-/prop-types-exact-1.2.0.tgz", + "integrity": "sha512-K+Tk3Kd9V0odiXFP9fwDHUYRyvK3Nun3GVyPapSIs5OBkITAm15W0CPFD/YKTkMUAbc0b9CUwRQp2ybiBIq+eA==", + "dev": true, + "requires": { + "has": "^1.0.3", + "object.assign": "^4.1.0", + "reflect.ownkeys": "^0.2.0" + } + }, "prop-types-extra": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz", @@ -11301,6 +12101,22 @@ "performance-now": "^2.1.0" } }, + "railroad-diagrams": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz", + "integrity": "sha1-635iZ1SN3t+4mcG5Dlc3RVnN234=", + "dev": true + }, + "randexp": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz", + "integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==", + "dev": true, + "requires": { + "discontinuous-range": "1.0.0", + "ret": "~0.1.10" + } + }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -11341,6 +12157,24 @@ } } }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + } + } + }, "react": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react/-/react-16.13.1.tgz", @@ -11867,6 +12701,12 @@ "strip-indent": "^3.0.0" } }, + "reflect.ownkeys": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz", + "integrity": "sha1-dJrO7H8/34tj+SegSAnpDFwLNGA=", + "dev": true + }, "regenerate": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", @@ -12257,6 +13097,16 @@ "inherits": "^2.0.1" } }, + "rst-selector-parser": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz", + "integrity": "sha1-gbIw6i/MYGbInjRy3nlChdmwPZE=", + "dev": true, + "requires": { + "lodash.flattendeep": "^4.4.0", + "nearley": "^2.7.10" + } + }, "rsvp": { "version": "4.8.5", "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", @@ -12611,6 +13461,21 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" }, + "simple-concat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.0.tgz", + "integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY=" + }, + "simple-get": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.0.tgz", + "integrity": "sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==", + "requires": { + "decompress-response": "^4.2.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "simple-swizzle": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", @@ -13134,6 +13999,17 @@ "side-channel": "^1.0.2" } }, + "string.prototype.trim": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.1.tgz", + "integrity": "sha512-MjGFEeqixw47dAMFMtgUro/I0+wNqZB5GKXGt1fFr24u3TzDXCPu7J9Buppzoe3r/LqkSDLDDJzE15RGWDGAVw==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1" + } + }, "string.prototype.trimend": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", @@ -13341,6 +14217,44 @@ "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==" }, + "tar": { + "version": "4.4.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", + "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.8.6", + "minizlib": "^1.2.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.3" + }, + "dependencies": { + "fs-minipass": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", + "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", + "requires": { + "minipass": "^2.6.0" + } + }, + "minipass": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", + "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + } + } + }, "terser": { "version": "4.6.12", "resolved": "https://registry.npmjs.org/terser/-/terser-4.6.12.tgz", @@ -13637,6 +14551,92 @@ "punycode": "^2.1.0" } }, + "ts-jest": { + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-25.4.0.tgz", + "integrity": "sha512-+0ZrksdaquxGUBwSdTIcdX7VXdwLIlSRsyjivVA9gcO+Cvr6ByqDhu/mi5+HCcb6cMkiQp5xZ8qRO7/eCqLeyw==", + "dev": true, + "requires": { + "bs-logger": "0.x", + "buffer-from": "1.x", + "fast-json-stable-stringify": "2.x", + "json5": "2.x", + "lodash.memoize": "4.x", + "make-error": "1.x", + "micromatch": "4.x", + "mkdirp": "1.x", + "resolve": "1.x", + "semver": "6.x", + "yargs-parser": "18.x" + }, + "dependencies": { + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, "ts-pnp": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.1.6.tgz", @@ -15160,6 +16160,38 @@ "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "requires": { + "string-width": "^1.0.2 || 2" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, "word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", diff --git a/front-end/package.json b/front-end/package.json index 64507d6..97541a0 100644 --- a/front-end/package.json +++ b/front-end/package.json @@ -7,14 +7,16 @@ "@emotion/styled": "^10.0.27", "@projectstorm/react-diagrams": "^6.0.2", "@testing-library/jest-dom": "^4.2.4", - "@testing-library/react": "^10.0.3", + "@testing-library/react": "^10.0.4", "@testing-library/user-event": "^7.2.1", "bootstrap": "^4.4.1", + "canvas": "^2.6.1", "closest": "0.0.1", "dagre": "^0.8.5", "jest": "24.9.0", - "jquery": "^3.4.1", - "mathjs": "^6.6.1", + "jest-canvas-mock": "^2.2.0", + "jquery": "^3.5.1", + "mathjs": "^6.6.5", "pathfinding": "^0.4.18", "paths-js": "^0.4.10", "prop-types": "^15.7.2", @@ -33,25 +35,9 @@ "scripts": { "start": "react-scripts start", "build": "react-scripts build", - "test": "jest --collect-coverage --passWithNoTests", + "test": "jest --passWithNoTests --no-cache", "eject": "react-scripts eject" }, - "jest": { - "coverageThreshold": { - "global": { - "branches": 80, - "functions": 80, - "lines": 80, - "statements": 80 - } - }, - "moduleNameMapper": { - "\\.(css|less)$": "/__mocks__/css/styleMock.js" - }, - "setupFilesAfterEnv": [ - "./setupTests.js" - ] - }, "eslintConfig": { "extends": "react-app" }, @@ -67,10 +53,15 @@ "last 1 safari version" ] }, - "proxy": "http://back-end:8000", + "proxy": "http://localhost:8000", "devDependencies": { - "@babel/preset-env": "^7.9.5", + "@babel/preset-env": "^7.9.6", "@babel/preset-react": "^7.9.4", - "react-test-renderer": "^16.13.1" + "@types/jest": "^25.2.1", + "enzyme": "^3.11.0", + "enzyme-adapter-react-16": "^1.15.2", + "jest-react-hooks-shallow": "^1.3.0", + "react-test-renderer": "^16.13.1", + "ts-jest": "^25.4.0" } } diff --git a/front-end/setupTests.js b/front-end/setupTests.js index ec9a12c..eb36e93 100644 --- a/front-end/setupTests.js +++ b/front-end/setupTests.js @@ -1,2 +1,7 @@ - +import 'jest-canvas-mock'; import 'regenerator-runtime/runtime' + +import { configure } from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; + +configure({ adapter: new Adapter() }); diff --git a/front-end/src/components/About.js b/front-end/src/components/About.js index 2d2c062..8cba47b 100644 --- a/front-end/src/components/About.js +++ b/front-end/src/components/About.js @@ -1,37 +1,49 @@ -import React, { useState } from 'react'; +import React from 'react'; import { Modal } from 'react-bootstrap'; -function About(props) { - const [show, setShow] = useState(props.show); - const [info, setInfo] = useState(); +export default class About extends React.Component { - async function fetchInfo() { + constructor(props) { + super(props); + this.state = { + show: props.show, + info: {}}; + } + + componentDidMount() { + + } + + fetchInfo = async () => { const resp = await fetch("/info"); const data = await resp.json(); - setInfo(data); + this.setState({info: data}); } - const handleClose = () => setShow(false); - const handleShow = (e) => { + handleClose = () => { + this.setState({show: false}); + } + + handleShow = (e) => { e.preventDefault(); - fetchInfo() - setShow(true); + this.fetchInfo() + this.setState({show: true}); } - return ( - <> -
About
- - - - About Visual Programming - - - {JSON.stringify(info)} - - - - ); -} + render() { + return ( + <> +
About
-export default About; + + + About Visual Programming + + + {JSON.stringify(this.state.info)} + + + + ); + } +} diff --git a/front-end/src/components/CustomAction/CustomDeleteItemsAction.js b/front-end/src/components/CustomAction/CustomDeleteItemsAction.js deleted file mode 100644 index c5bb80b..0000000 --- a/front-end/src/components/CustomAction/CustomDeleteItemsAction.js +++ /dev/null @@ -1,39 +0,0 @@ -import * as _ from 'lodash' -import { Action, InputType } from '@projectstorm/react-canvas-core'; - -export interface CustomDeleteItemsActionOptions { - keyCodes?: number[]; -} - -/** - * Deletes all selected items, but asks for confirmation first - */ -export class CustomDeleteItemsAction extends Action { - constructor(options: CustomDeleteItemsActionOptions = {}) { - options = { - keyCodes: [46, 8], - ...options - }; - super({ - type: InputType.KEY_DOWN, - fire: (event: ActionEvent) => { - if (options.keyCodes.indexOf(event.event.keyCode) !== -1) { - const selectedEntities = this.engine.getModel().getSelectedEntities(); - if (selectedEntities.length > 0) { - const confirm = window.confirm('Are you sure you want to delete?'); - - if (confirm) { - _.forEach(selectedEntities, model => { - // only delete items which are not locked - if (!model.isLocked()) { - model.remove(); - } - }); - this.engine.repaintCanvas(); - } - } - } - } - }); - } -} diff --git a/front-end/src/components/CustomNode/CustomNodeWidget.js b/front-end/src/components/CustomNode/CustomNodeWidget.js index 6bc38eb..0ab2b5d 100644 --- a/front-end/src/components/CustomNode/CustomNodeWidget.js +++ b/front-end/src/components/CustomNode/CustomNodeWidget.js @@ -12,25 +12,28 @@ export default class CustomNodeWidget extends React.Component { constructor(props) { super(props); this.state = {showConfig: false, showGraph: false}; - this.toggleConfig = this.toggleConfig.bind(this); - this.toggleGraph = this.toggleGraph.bind(this); - this.handleDelete = this.handleDelete.bind(this); - this.acceptConfiguration = this.acceptConfiguration.bind(this); this.icon = '9881'; } + componentDidMount() { + this.toggleConfig = this.toggleConfig.bind(this); + this.toggleGraph = this.toggleGraph.bind(this); + this.handleDelete = this.handleDelete.bind(this); + this.acceptConfiguration = this.acceptConfiguration.bind(this); + } + // show/hide node configuration modal - toggleConfig() { + toggleConfig = () => { this.setState({showConfig: !this.state.showConfig}); } // show/hide node graph modal - toggleGraph() { + toggleGraph = () => { this.setState({showGraph: !this.state.showGraph}); } // delete node from diagram model and redraw diagram - handleDelete() { + handleDelete = () => { API.deleteNode(this.props.node).then(() => { this.props.node.remove(); this.props.engine.repaintCanvas(); @@ -41,6 +44,7 @@ export default class CustomNodeWidget extends React.Component { API.updateNode(this.props.node, optionsData, flowData).then(() => { this.props.node.setStatus("configured"); this.forceUpdate(); + console.log("Node updated"); this.props.engine.repaintCanvas(); }).catch(err => console.log(err)); } diff --git a/front-end/src/components/CustomNode/GraphView.js b/front-end/src/components/CustomNode/GraphView.js index e872a86..966de12 100644 --- a/front-end/src/components/CustomNode/GraphView.js +++ b/front-end/src/components/CustomNode/GraphView.js @@ -1,6 +1,6 @@ import React from 'react'; import { VegaLite } from 'react-vega'; -import { Roller } from 'react-spinners-css'; +import {Spinner} from "react-bootstrap"; import { Modal, Button } from 'react-bootstrap'; import propTypes from 'prop-types'; import { VariableSizeGrid as Grid } from 'react-window'; @@ -119,7 +119,7 @@ export default class GraphView extends React.Component { if (this.state.loading) { // Print loading spinner - body = (); + body = (); } else if (this.state.data.length < 1) { // Print message to load respective table/graph if (this.props.node.options.node_type === "visualization") { diff --git a/front-end/src/components/CustomNode/Input/BooleanInput.js b/front-end/src/components/CustomNode/Input/BooleanInput.js new file mode 100644 index 0000000..1d2eba8 --- /dev/null +++ b/front-end/src/components/CustomNode/Input/BooleanInput.js @@ -0,0 +1,24 @@ +import React, { useState, useEffect } from 'react'; +import { Form } from 'react-bootstrap'; + +export default function BooleanInput(props) { + + const [value, setValue] = useState(props.value); + const handleChange = (event) => { + setValue(event.target.checked); + }; + + const {keyName, onChange} = props; + // whenever value changes, fire callback to update config form + useEffect(() => { + onChange(keyName, value); + }, + [value, keyName, onChange]); + + return ( + + ) +} diff --git a/front-end/src/components/CustomNode/Input/FileUploadInput.js b/front-end/src/components/CustomNode/Input/FileUploadInput.js new file mode 100644 index 0000000..28d0e3b --- /dev/null +++ b/front-end/src/components/CustomNode/Input/FileUploadInput.js @@ -0,0 +1,72 @@ +import React, { useRef, useState, useEffect } from 'react'; +import { Button } from 'react-bootstrap'; +import * as API from '../../../API'; + +/** + * Component representing a file parameter. + * Uploads selected file to server upon selection, and passes + * the filename from the server response to the form callback. + */ +export default function FileUploadInput(props) { + + const input = useRef(null); + const [fileName, setFileName] = useState(props.value || ""); + const [status, setStatus] = useState(props.value ? "ready" : "unconfigured"); + + const {keyName, onChange} = props; + // fire callback on mount to update node config state + useEffect(() => { + onChange(keyName, fileName); + }, + [fileName, keyName, onChange]); + + const uploadFile = async file => { + props.disableFunc(true); + setStatus("loading"); + const fd = new FormData(); + fd.append("file", file); + fd.append("nodeId", props.node.options.id); + API.uploadDataFile(fd) + .then(resp => { + setFileName(resp.filename); + setStatus("ready"); + props.disableFunc(false); + setStatus("ready"); + }).catch(() => { + setStatus("failed"); + }); + input.current.value = null; + }; + const onFileSelect = e => { + e.preventDefault(); + if (!input.current.files) { + return; + } + + uploadFile(input.current.files[0]); + }; + + if (status === "loading") return (
Uploading file...
); + const btnText = status === "ready" ? "Choose Different File" : "Choose File"; + let content; + if (status === "ready") { + const rxp = new RegExp(props.node.options.id + '-'); + content = ( +
+ File loaded:  + {fileName.replace(rxp, '')} +
+ ) + } else if (status === "failed") { + content = (
Upload failed. Try a new file.
); + } + return ( + <> + + + + {content} + + ) +} diff --git a/front-end/src/components/CustomNode/Input/FlowVariableOverride.js b/front-end/src/components/CustomNode/Input/FlowVariableOverride.js new file mode 100644 index 0000000..a074156 --- /dev/null +++ b/front-end/src/components/CustomNode/Input/FlowVariableOverride.js @@ -0,0 +1,36 @@ +import React from 'react'; +import { Col, Form } from 'react-bootstrap'; + +export default function FlowVariableOverride(props) { + + const handleSelect = (event) => { + const uuid = event.target.value; + const flow = props.flowNodes.find(d => d.node_id === uuid); + const obj = { + node_id: uuid, + is_global: flow.is_global + }; + props.onChange(obj); + }; + const handleCheck = (event) => { props.onFlowCheck(event.target.checked) }; + + return ( + + + {props.checked ? + + + )} + + : null + } + + ) +} diff --git a/front-end/src/components/CustomNode/Input/OptionInput.js b/front-end/src/components/CustomNode/Input/OptionInput.js new file mode 100644 index 0000000..37f0c10 --- /dev/null +++ b/front-end/src/components/CustomNode/Input/OptionInput.js @@ -0,0 +1,64 @@ +import React, { useState } from 'react'; +import { Col, Form } from 'react-bootstrap'; +import BooleanInput from './BooleanInput' +import FileUploadInput from './FileUploadInput' +import FlowVariableOverride from './FlowVariableOverride' +import SelectInput from './SelectInput' +import SimpleInput from './SimpleInput' + + +/** + * Wrapper component to render form groups in the node config form. + */ +export default function OptionInput(props) { + + const [isFlow, setIsFlow] = useState(props.flowValue ? true : false); + + const handleFlowCheck = (bool) => { + // if un-checking, fire callback with null so no stale value is in `option_replace` + if (!bool) props.onChange(props.keyName, null, true); + setIsFlow(bool); + }; + + // fire callback to update `option_replace` with flow node info + const handleFlowVariable = (value) => { + props.onChange(props.keyName, value, true); + }; + + let inputComp; + if (props.type === "file") { + inputComp = + } else if (props.type === "string") { + inputComp = + } else if (props.type === "text") { + inputComp = + } else if (props.type === "int") { + inputComp = + } else if (props.type === "boolean") { + inputComp = + } else if (props.type === "select") { + inputComp = + } else { + return (<>) + } + + const hideFlow = props.node.options.is_global + || props.type === "file" || props.flowNodes.length === 0; + return ( + + {props.label} +
{props.docstring}
+ + { inputComp } + {hideFlow ? null : + + } + +
+ ) +} diff --git a/front-end/src/components/CustomNode/Input/SelectInput.js b/front-end/src/components/CustomNode/Input/SelectInput.js new file mode 100644 index 0000000..e85b1d1 --- /dev/null +++ b/front-end/src/components/CustomNode/Input/SelectInput.js @@ -0,0 +1,27 @@ +import React, { useState, useEffect } from 'react'; +import { Form } from 'react-bootstrap'; + +export default function SelectInput(props) { + + const [value, setValue] = useState(props.value); + const handleChange = (event) => { + setValue(event.target.value); + }; + + const {keyName, onChange} = props; + // whenever value changes, fire callback to update config form + useEffect(() => { + onChange(keyName, value); + }, + [value, keyName, onChange]); + + return ( + + {props.options.map(opt => + + )} + + ) +} diff --git a/front-end/src/components/CustomNode/Input/SimpleInput.js b/front-end/src/components/CustomNode/Input/SimpleInput.js new file mode 100644 index 0000000..3e8bcaf --- /dev/null +++ b/front-end/src/components/CustomNode/Input/SimpleInput.js @@ -0,0 +1,28 @@ +import React, { useState, useEffect } from 'react'; +import { Form } from 'react-bootstrap'; + +export default function SimpleInput(props) { + + const [value, setValue] = useState(props.value); + const handleChange = (event) => { + setValue(event.target.value); + }; + + const {keyName, onChange, type} = props; + // whenever value changes, fire callback to update config form + useEffect(() => { + const formValue = type === "number" ? Number(value) : value; + onChange(keyName, formValue); + }, + [value, keyName, onChange, type]); + + const extraProps = props.type === "textarea" + ? {as: "textarea", rows: props.rows || 7} + : {type: props.type}; + return ( + + ) +} diff --git a/front-end/src/components/CustomNode/NodeConfig.js b/front-end/src/components/CustomNode/NodeConfig.js index a30df87..1c003b2 100644 --- a/front-end/src/components/CustomNode/NodeConfig.js +++ b/front-end/src/components/CustomNode/NodeConfig.js @@ -1,9 +1,10 @@ -import React, { useRef, useState, useEffect } from 'react'; -import { Col, Modal, Button, Form } from 'react-bootstrap'; +import React from 'react'; +import { Modal, Button, Form } from 'react-bootstrap'; import propTypes from 'prop-types'; import * as _ from 'lodash'; import * as API from '../../API'; import '../../styles/NodeConfig.css'; +import OptionInput from './Input/OptionInput' export default class NodeConfig extends React.Component { @@ -77,7 +78,10 @@ export default class NodeConfig extends React.Component { }; render() { - if (!this.props.node) return null; + if (!this.props.node) { + return null; + } + return ( { - // if un-checking, fire callback with null so no stale value is in `option_replace` - if (!bool) props.onChange(props.keyName, null, true); - setIsFlow(bool); - }; - - // fire callback to update `option_replace` with flow node info - const handleFlowVariable = (value) => { - props.onChange(props.keyName, value, true); - }; - - let inputComp; - if (props.type === "file") { - inputComp = - } else if (props.type === "string") { - inputComp = - } else if (props.type === "text") { - inputComp = - } else if (props.type === "int") { - inputComp = - } else if (props.type === "boolean") { - inputComp = - } else if (props.type === "select") { - inputComp = - } else { - return (<>) - } - - const hideFlow = props.node.options.is_global - || props.type === "file" || props.flowNodes.length === 0; - return ( - - {props.label} -
{props.docstring}
- - { inputComp } - {hideFlow ? null : - - } - -
- ) -} - - -/** - * Component representing a file parameter. - * Uploads selected file to server upon selection, and passes - * the filename from the server response to the form callback. - */ -function FileUploadInput(props) { - - const input = useRef(null); - const [fileName, setFileName] = useState(props.value || ""); - const [status, setStatus] = useState(props.value ? "ready" : "unconfigured"); - - const {keyName, onChange} = props; - // fire callback on mount to update node config state - useEffect(() => { - onChange(keyName, fileName); - }, - [fileName, keyName, onChange]); - - const uploadFile = async file => { - props.disableFunc(true); - setStatus("loading"); - const fd = new FormData(); - fd.append("file", file); - fd.append("nodeId", props.node.options.id); - API.uploadDataFile(fd) - .then(resp => { - setFileName(resp.filename); - setStatus("ready"); - props.disableFunc(false); - setStatus("ready"); - }).catch(() => { - setStatus("failed"); - }); - input.current.value = null; - }; - const onFileSelect = e => { - e.preventDefault(); - if (!input.current.files) return; - uploadFile(input.current.files[0]); - }; - - if (status === "loading") return (
Uploading file...
); - const btnText = status === "ready" ? "Choose Different File" : "Choose File"; - let content; - if (status === "ready") { - const rxp = new RegExp(props.node.options.id + '-'); - content = ( -
- File loaded:  - {fileName.replace(rxp, '')} -
- ) - } else if (status === "failed") { - content = (
Upload failed. Try a new file.
); - } - return ( - <> - - - - {content} - - ) -} - -function SimpleInput(props) { - - const [value, setValue] = useState(props.value); - const handleChange = (event) => { - setValue(event.target.value); - }; - - const {keyName, onChange, type} = props; - // whenever value changes, fire callback to update config form - useEffect(() => { - const formValue = type === "number" ? Number(value) : value; - onChange(keyName, formValue); - }, - [value, keyName, onChange, type]); - - const extraProps = props.type === "textarea" - ? {as: "textarea", rows: props.rows || 7} - : {type: props.type}; - return ( - - ) -} - - -function BooleanInput(props) { - - const [value, setValue] = useState(props.value); - const handleChange = (event) => { - setValue(event.target.checked); - }; - - const {keyName, onChange} = props; - // whenever value changes, fire callback to update config form - useEffect(() => { - onChange(keyName, value); - }, - [value, keyName, onChange]); - - return ( - - ) -} - - -function FlowVariableOverride(props) { - - const handleSelect = (event) => { - const uuid = event.target.value; - const flow = props.flowNodes.find(d => d.node_id === uuid); - const obj = { - node_id: uuid, - is_global: flow.is_global - }; - props.onChange(obj); - }; - const handleCheck = (event) => { props.onFlowCheck(event.target.checked) }; - - return ( - - - {props.checked ? - - - )} - - : null - } - - ) -} - - -function SelectInput(props) { - - const [value, setValue] = useState(props.value); - const handleChange = (event) => { - setValue(event.target.value); - }; - - const {keyName, onChange} = props; - // whenever value changes, fire callback to update config form - useEffect(() => { - onChange(keyName, value); - }, - [value, keyName, onChange]); - - return ( - - {props.options.map(opt => - - )} - - ) -} diff --git a/front-end/src/components/CustomNodeUpload.js b/front-end/src/components/CustomNodeUpload.js index f9a744b..4af42a6 100644 --- a/front-end/src/components/CustomNodeUpload.js +++ b/front-end/src/components/CustomNodeUpload.js @@ -1,48 +1,61 @@ -import React, {useRef, useState} from "react"; +import React from "react"; import * as API from "../API"; import {Button} from "react-bootstrap"; +export default class CustomNodeUpload extends React.Component { -export default function CustomNodeUpload({ onUpload }) { + constructor(props) { + super(props); + this.state = { + status: "ready", + input: React.createRef() + } - const input = useRef(null); - const [status, setStatus] = useState("ready"); + this.onUpload = props.onUpload; + } - const uploadFile = async file => { - setStatus("loading"); + uploadFile = async file => { + this.setState({status: "loading"}); const fd = new FormData(); fd.append("file", file); API.uploadDataFile(fd) .then(resp => { - onUpload(); - setStatus("ready"); + this.onUpload(); + this.setState({status: "ready"}); }).catch(() => { - setStatus("failed"); + this.setState({status: "failed"}); }); - input.current.value = null; + + this.setState({input: React.createRef()}); }; - const onFileSelect = e => { + + onFileSelect = e => { e.preventDefault(); - if (!input.current.files) return; - uploadFile(input.current.files[0]); + if (!this.state.input.current || !this.state.input.current.files) { + return; + } + + this.uploadFile(this.state.input.current.files[0]); }; - let content; - if (status === "loading") { - content =
Uploading file...
; - } else if (status === "failed") { - content = (
Upload failed. Try a new file.
); + render() { + let content; + if (this.state.status === "loading") { + content =
Uploading file...
; + } else if (this.state.status === "failed") { + content = (
Upload failed. Try a new file.
); + } + return ( + <> + + + {content} + + ) } - return ( - <> - - - {content} - - ) } diff --git a/front-end/src/components/FileUpload.js b/front-end/src/components/FileUpload.js new file mode 100644 index 0000000..7aab8f2 --- /dev/null +++ b/front-end/src/components/FileUpload.js @@ -0,0 +1,43 @@ +import React from 'react'; +import { Button } from 'react-bootstrap'; +import * as API from '../API'; + +export default class FileUpload extends React.Component { + + constructor(props) { + super(props); + this.state = { + input: React.createRef() + } + } + + uploadFile = file => { + const form = new FormData(); + form.append("file", file); + API.uploadWorkflow(form).then(json => { + this.props.handleData(json); + }).catch(err => { + console.log(err); + }); + this.setState({input: React.createRef()}); + }; + + onFileSelect = e => { + e.preventDefault(); + if (!this.state.input.current || !this.state.input.current.files) { + return; + } + + this.uploadFile(this.state.input.current.files[0]); + }; + + render() { + return ( + <> + + + + ) + } +} diff --git a/front-end/src/components/GlobalFlowMenu.js b/front-end/src/components/GlobalFlowMenu.js index 9c27a41..e832db7 100644 --- a/front-end/src/components/GlobalFlowMenu.js +++ b/front-end/src/components/GlobalFlowMenu.js @@ -1,119 +1,130 @@ -import React, { useState } from 'react'; +import React from 'react'; import { Dropdown, ButtonGroup, Table } from 'react-bootstrap'; import CustomNodeModel from './CustomNode/CustomNodeModel'; import NodeConfig from './CustomNode/NodeConfig'; import * as API from '../API'; -export default function GlobalFlowMenu(props) { - const [show, setShow] = useState(false); - const [activeNode, setActiveNode] = useState(); - const [creating, setCreating] = useState(false); - const toggleShow = () => setShow(!show); +export default class GlobalFlowMenu extends React.Component { + + constructor(props) { + super(props); + this.state = { + show: false, + activeNode: null, + creating: false + } + }; + + toggleShow = () => { + this.setState({ show: !this.state.show}); + }; // Create CustomNodeModel from JSON data, // whether from menu item or global flow variable - function nodeFromData(data) { + nodeFromData = (data) => { const info = {...data, is_global: true}; const config = info.options; delete info.options; if (!info.option_types) { - info.option_types = lookupOptionTypes(info.node_key); + info.option_types = this.lookupOptionTypes(info.node_key); } + const node = new CustomNodeModel(info, config); return node; - } + }; // Look up option types from appropriate menu item. // The option types aren't included in the global flow // serialization from the server. - function lookupOptionTypes(nodeKey) { - const keyMatches = props.menuItems.filter(d => d.node_key === nodeKey); - if (!keyMatches.length) return {}; + lookupOptionTypes = (nodeKey) => { + const keyMatches = this.props.menuItems.filter(d => d.node_key === nodeKey); + if (!keyMatches.length) { + return {}; + } + return keyMatches[0].option_types || {}; - } + }; - const handleEdit = (data, create = false) => { - setCreating(create); - const node = nodeFromData(data); - setActiveNode(node); - setShow(true); + handleEdit = (data, create = false) => { + const node = this.nodeFromData(data); + this.setState({ creating: create, show: true, activeNode: node}); }; - const handleSubmit = (data) => { - const node = activeNode; - if (creating) { + handleSubmit = (data) => { + const node = this.state.activeNode; + if (this.state.creating) { node.config = data; API.addNode(node) - .then(() => props.onUpdate()) + .then(() => this.props.onUpdate()) .catch(err => console.log(err)); } else { API.updateNode(node, data) - .then(() => props.onUpdate()) + .then(() => this.props.onUpdate()) .catch(err => console.log(err)); } }; - const handleDelete = (data) => { + handleDelete = (data) => { const msg = "Are you sure you want to delete the global flow variable?"; if (window.confirm(msg)) { - const node = nodeFromData(data) + const node = this.nodeFromData(data) API.deleteNode(node) - .then(() => props.onUpdate()) + .then(() => this.props.onUpdate()) .catch(err => console.log(err)); } }; - return ( -
-

Flow Variables

- - - - - - - - - - - - {props.nodes.map(node => - - - - - - - - )} + render() { + return ( +
+

Flow Variables

+
NameTypeValue
{node.options.var_name}{node.name}{node.options.default_value} handleEdit(node, false)}> - ✎ - handleDelete(node)}> - x -
+ + + + + + + + + + + {this.props.nodes.map(node => + + + + + + + + )} - -
NameTypeValue
{node.options.var_name}{node.name}{node.options.default_value} this.handleEdit(node, false)}> + ✎ + this.handleDelete(node)}> + x +
- - - Add Global Flow Variable  - - - {props.menuItems.map((node, i) => - handleEdit(node, true)}> - {node.name} - - )} - - - -
- ) + + + + + Add Global Flow Variable  + + + {this.props.menuItems.map((node, i) => + this.handleEdit(node, true)}> + {node.name} + + )} + + + +
+ ); + }; } - - diff --git a/front-end/src/components/NodeMenu.js b/front-end/src/components/NodeMenu.js index 7563771..de9f95e 100644 --- a/front-end/src/components/NodeMenu.js +++ b/front-end/src/components/NodeMenu.js @@ -96,4 +96,3 @@ const NodeTooltip = React.forwardRef((props, ref) => { ) }); - diff --git a/front-end/src/components/VPLink/VPLinkWidget.js b/front-end/src/components/VPLink/VPLinkWidget.js deleted file mode 100644 index f441160..0000000 --- a/front-end/src/components/VPLink/VPLinkWidget.js +++ /dev/null @@ -1,5 +0,0 @@ -import { DefaultLinkWidget } from '@projectstorm/react-diagrams'; - -export default class VPLinkWidget extends DefaultLinkWidget { - -} diff --git a/front-end/src/components/VPPort/VPPortModel.js b/front-end/src/components/VPPort/VPPortModel.js index c72778d..bb1554f 100644 --- a/front-end/src/components/VPPort/VPPortModel.js +++ b/front-end/src/components/VPPort/VPPortModel.js @@ -9,6 +9,10 @@ export default class VPPortModel extends DefaultPortModel { } canLinkToPort(port) { + if (port == null) { + return false; + } + // if connecting to flow port, make sure this is a flow port // and opposite of other's direction if (port.options.name.includes("flow")) { diff --git a/front-end/src/components/Workspace.js b/front-end/src/components/Workspace.js index 1d06abb..560e53f 100644 --- a/front-end/src/components/Workspace.js +++ b/front-end/src/components/Workspace.js @@ -1,4 +1,4 @@ -import React, { useRef } from 'react'; +import React from 'react'; import { Row, Col, Button } from 'react-bootstrap'; import createEngine, { DiagramModel } from '@projectstorm/react-diagrams'; import { CanvasWidget } from '@projectstorm/react-canvas-core'; @@ -10,8 +10,9 @@ import * as API from '../API'; import NodeMenu from './NodeMenu'; import '../styles/Workspace.css'; import GlobalFlowMenu from "./GlobalFlowMenu"; +import FileUpload from "./FileUpload" -class Workspace extends React.Component { +export default class Workspace extends React.Component { constructor(props) { super(props); @@ -151,32 +152,3 @@ class Workspace extends React.Component { ); } } - - -function FileUpload(props) { - const input = useRef(null); - const uploadFile = file => { - const form = new FormData(); - form.append("file", file); - API.uploadWorkflow(form).then(json => { - props.handleData(json); - }).catch(err => { - console.log(err); - }); - input.current.value = null; - }; - const onFileSelect = e => { - e.preventDefault(); - if (!input.current.files) return; - uploadFile(input.current.files[0]); - }; - return ( - <> - - - - ) -} - -export default Workspace;