Skip to content

Commit

Permalink
Add jest tests to pipeline editor (elyra-ai#818)
Browse files Browse the repository at this point in the history
Adds basic jest tests to the pipeline editor which tests:
- Create a `PipelineEditorFactory`
- Create a `PipelineEditorWidget`
- Create a `PipelineEditor`
  • Loading branch information
Martha Cryan authored and lresende committed Aug 18, 2020
1 parent d5ac043 commit 340b709
Show file tree
Hide file tree
Showing 17 changed files with 438 additions and 24 deletions.
13 changes: 9 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -115,11 +115,16 @@ watch: ## Watch packages. For use alongside jupyter lab --watch
test-server: install-server ## Run unit tests
pytest -v elyra

test-ui: lint-ui ## Run frontend tests
npm test
test-ui: lint-ui test-ui-unit test-ui-integration ## Run frontend tests

test-ui-debug: lint-ui
npm run test-debug
test-ui-integration: ## Run frontend cypress integration tests
npm run test:integration

test-ui-unit: ## Run frontend jest unit tests
npm run test:unit

test-ui-debug: ## Open cypress integration test debugger
npm run test:integration:debug

test: test-server test-ui ## Run all tests

Expand Down
46 changes: 46 additions & 0 deletions docs/source/developer_guide/testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Contributing to frontend tests
Elyra uses two types of frontend tests: integration tests (which use [cypress](https://docs.cypress.io/)) and unit tests (which use [jest](https://jestjs.io/docs/en/getting-started)).
## Integration tests
There are two ways to run the integration tests: to only see the output logs from all of the integration tests, run `make test-ui-integration` from the root directory. To debug tests that are going wrong or develop new tests, run `make test-ui-debug` - this will open an interactive tool for writing and debugging tests.

Elyra's integration tests automatically start JupyterLab and visit / interact with pages through cypress API calls. The tests use the cypress API to check for the existence of various buttons and visual elements. Refer to the [cypress API](https://docs.cypress.io/api/api/table-of-contents.html) for more details.

New integration tests can be added to `tests/integration`.

## Unit tests
To run all of the unit tests, use `make test-ui-unit` from the root directory. To run the unit tests for a specific Elyra package, simply run `jest` from that package's directory (under `packages/`). For writing tests, `jest` has a watch mode option: just run `jest --watch`.

Elyra's unit tests test the various classes and objects used by Elyra extensions. Refer to the [jest API](https://jestjs.io/docs/en/getting-started) for more details.

To add unit tests for a package that doesn't have tests set up, some configuration files are required. In the directory for the package being tested, add a file titled `jest.config.js` that contains the following:
```
module.exports = require('../../testutils/jest.config');
```
Then, in the `package.json`, add the following under `'scripts'`:
```
"test": "jest",
"build:test": "tsc --build tsconfig.test.json",
```
And the following under `'dev_dependencies'`:
```
"@jupyterlab/testutils": "^1.0.0",
"@types/enzyme": "^3.10.5",
"@types/enzyme-adapter-react-16": "^1.0.6",
"@types/jest": "^23.3.11",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.3",
"install": "^0.13.0",
"jest": "^24.7.1",
"jest-raw-loader": "^1.0.1",
"ts-jest": "^24.0.2",
```
Create a file `tsconfig.test.json` that contains:
```
{
"extends": "../../tsconfigbase.test",
"include": ["src/*", "test/*"],
"references": []
}
```

Finally, create a folder called `test` in the `src` directory of the package being tested, and add tests using the file extension `.spec.ts`.
1 change: 1 addition & 0 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,4 @@ Elyra is a set of AI-centric extensions to JupyterLab Notebooks.
developer_guide/metadata
developer_guide/pipelines
developer_guide/trackers
developer_guide/testing
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
"prettier": "prettier --ignore-path .gitignore --write \"**/*{.ts,.tsx,.js,.jsx,.css,.json}\"",
"prettier:check": "prettier --ignore-path .gitignore --check \"**/*{.ts,.tsx,.js,.jsx,.css,.json}\"",
"start-jupyter": "mkdir -p build/cypress-tests && jupyter lab --config=./tests/test-config.py",
"test": "start-server-and-test start-jupyter http-get://localhost:8888?token=test cy:run",
"test-debug": "start-server-and-test start-jupyter http-get://localhost:8888?token=test cy:open"
"test:integration": "start-server-and-test start-jupyter http-get://localhost:8888?token=test cy:run",
"test:integration:debug": "start-server-and-test start-jupyter http-get://localhost:8888?token=test cy:open",
"test:unit": "lerna run test --scope \"@elyra/*\" --concurrency 1 --stream",
"test": "npm run test:unit && npm run test:integration"
},
"husky": {
"hooks": {
Expand All @@ -26,6 +28,7 @@
"devDependencies": {
"@4tw/cypress-drag-drop": "^1.3.1",
"@cypress/webpack-preprocessor": "^4.1.5",
"@types/jest": "^26.0.9",
"@typescript-eslint/eslint-plugin": "~2.23.0",
"@typescript-eslint/parser": "~2.23.0",
"cypress": "^4.4.1",
Expand Down
17 changes: 17 additions & 0 deletions packages/pipeline-editor/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* Copyright 2018-2020 IBM Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* global module, require */
module.exports = require('../../testutils/jest.config');
13 changes: 12 additions & 1 deletion packages/pipeline-editor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@
"style": "style/index.css",
"scripts": {
"build": "tsc",
"build:test": "tsc --build tsconfig.test.json",
"clean": "rimraf lib",
"dist": "npm pack .",
"test": "jest",
"prepare": "npm run build ",
"watch": "tsc -w"
},
Expand All @@ -52,7 +54,6 @@
"@material-ui/core": "^4.1.2",
"@material-ui/icons": "^4.2.1",
"@material-ui/lab": "^4.0.0-alpha.18",
"@types/jest": "24.0.15",
"@types/node": "^12.0.10",
"@types/uuid": "^3.4.7",
"autoprefixer": "^9.6.0",
Expand All @@ -66,11 +67,21 @@
"uuid": "^3.4.0"
},
"devDependencies": {
"@jupyterlab/testutils": "^1.0.0",
"@types/enzyme": "^3.10.5",
"@types/enzyme-adapter-react-16": "^1.0.6",
"@types/jest": "^23.3.11",
"@types/react": "^16.8.23",
"@types/react-dom": "^16.8.5",
"@types/react-intl": "^2.3.0",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.3",
"install": "^0.13.0",
"jest": "^24.7.1",
"jest-raw-loader": "^1.0.1",
"rimraf": "~3.0.0",
"source-map-loader": "^0.2.4",
"ts-jest": "^24.0.2",
"ts-loader": "^6.2.1",
"typescript": "~3.7.3"
},
Expand Down
47 changes: 33 additions & 14 deletions packages/pipeline-editor/src/PipelineEditorWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,11 @@ import {
} from '@jupyterlab/docregistry';
import { IFileBrowserFactory } from '@jupyterlab/filebrowser';
import { NotebookPanel } from '@jupyterlab/notebook';
import { ServiceManager } from '@jupyterlab/services';
import { notebookIcon } from '@jupyterlab/ui-components';

import { toArray } from '@lumino/algorithm';
import { CommandRegistry } from '@lumino/commands';
import { IDragEvent } from '@lumino/dragdrop';
import { Collapse, IconButton } from '@material-ui/core';
import CloseIcon from '@material-ui/icons/Close';
Expand Down Expand Up @@ -121,23 +123,29 @@ export const commandIDs = {
* Wrapper Class for Common Canvas React Component
*/
export class PipelineEditorWidget extends ReactWidget {
app: JupyterFrontEnd;
shell: JupyterFrontEnd.IShell;
commands: CommandRegistry;
browserFactory: IFileBrowserFactory;
context: DocumentRegistry.Context;
serviceManager: ServiceManager;

constructor(props: any) {
super(props);
this.app = props.app;
this.shell = props.shell;
this.commands = props.commands;
this.browserFactory = props.browserFactory;
this.context = props.context;
this.serviceManager = props.serviceManager;
}

render(): React.ReactElement {
return (
<PipelineEditor
app={this.app}
shell={this.shell}
commands={this.commands}
browserFactory={this.browserFactory}
widgetContext={this.context}
serviceManager={this.serviceManager}
/>
);
}
Expand All @@ -151,9 +159,11 @@ export namespace PipelineEditor {
* The props for PipelineEditor.
*/
export interface IProps {
app: JupyterFrontEnd;
shell: JupyterFrontEnd.IShell;
commands: CommandRegistry;
browserFactory: IFileBrowserFactory;
widgetContext: DocumentRegistry.Context;
serviceManager: ServiceManager;
}

/**
Expand Down Expand Up @@ -194,8 +204,10 @@ export class PipelineEditor extends React.Component<
PipelineEditor.IProps,
PipelineEditor.IState
> {
app: JupyterFrontEnd;
shell: JupyterFrontEnd.IShell;
commands: CommandRegistry;
browserFactory: IFileBrowserFactory;
serviceManager: ServiceManager;
canvasController: any;
widgetContext: DocumentRegistry.Context;
position = 10;
Expand All @@ -205,8 +217,10 @@ export class PipelineEditor extends React.Component<

constructor(props: any) {
super(props);
this.app = props.app;
this.shell = props.shell;
this.commands = props.commands;
this.browserFactory = props.browserFactory;
this.serviceManager = props.serviceManager;
this.canvasController = new CanvasController();
this.canvasController.setPipelineFlowPalette(palette);
this.widgetContext = props.widgetContext;
Expand Down Expand Up @@ -761,7 +775,7 @@ export class PipelineEditor extends React.Component<
for (let i = 0; i < selectedNodes.length; i++) {
const path = this.canvasController.getNode(selectedNodes[i]).app_data
.filename;
this.app.commands.execute(commandIDs.openDocManager, { path });
this.commands.execute(commandIDs.openDocManager, { path });
}
}

Expand Down Expand Up @@ -997,7 +1011,7 @@ export class PipelineEditor extends React.Component<
*/
async validateProperties(node: any): Promise<string> {
const validationErrors: string[] = [];
const notebookValidationErr = await this.app.serviceManager.contents
const notebookValidationErr = await this.serviceManager.contents
.get(node.app_data.filename)
.then((result: any): any => {
return null;
Expand Down Expand Up @@ -1174,14 +1188,14 @@ export class PipelineEditor extends React.Component<
}

handleOpenRuntimes(): void {
this.app.shell.activateById(
this.shell.activateById(
`elyra-metadata:${RUNTIMES_NAMESPACE}:${KFP_SCHEMA}`
);
}

handleClosePipeline(): void {
if (this.app.shell.currentWidget) {
this.app.shell.currentWidget.close();
if (this.shell.currentWidget) {
this.shell.currentWidget.close();
}
}

Expand Down Expand Up @@ -1243,19 +1257,24 @@ export class PipelineEditor extends React.Component<
}

export class PipelineEditorFactory extends ABCWidgetFactory<DocumentWidget> {
app: JupyterFrontEnd;
shell: JupyterFrontEnd.IShell;
commands: CommandRegistry;
browserFactory: IFileBrowserFactory;
serviceManager: ServiceManager;

constructor(options: any) {
super(options);
this.app = options.app;
this.shell = options.shell;
this.commands = options.commands;
this.browserFactory = options.browserFactory;
this.serviceManager = options.serviceManager;
}

protected createNewWidget(context: DocumentRegistry.Context): DocumentWidget {
// Creates a blank widget with a DocumentWidget wrapper
const props = {
app: this.app,
shell: this.shell,
commands: this.commands,
browserFactory: this.browserFactory,
context: context
};
Expand Down
6 changes: 4 additions & 2 deletions packages/pipeline-editor/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,10 @@ const extension: JupyterFrontEndPlugin<void> = {
name: PIPELINE_FACTORY,
fileTypes: [PIPELINE],
defaultFor: [PIPELINE],
app: app,
browserFactory: browserFactory
shell: app.shell,
commands: app.commands,
browserFactory: browserFactory,
serviceManager: app.serviceManager
});

// Add the default behavior of opening the widget for .pipeline files
Expand Down
21 changes: 21 additions & 0 deletions packages/pipeline-editor/src/test/__mocks__/PipelineService.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright 2018-2020 IBM Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export const mockGetRuntimeImages = jest.fn();
const mock = jest.fn().mockImplementation(() => {
return { getRuntimeImages: mockGetRuntimeImages };
});

export default mock;
Loading

0 comments on commit 340b709

Please sign in to comment.