Add smoke test for annotations features

This commit is contained in:
Matt Weiden
2020-02-07 15:17:27 -08:00
parent ac13b31e13
commit ab141f2a7b
21 changed files with 2937 additions and 76 deletions

View File

@@ -23,6 +23,9 @@ jobs:
install: skip
python: "3.6"
script: docker build .
- name: "Smoke Tests"
- name: "Smoke Tests (with Annotations)"
python: "3.6"
script: make smoke-test
script: cd client && make smoke-test
- name: "Smoke Tests (without Annotations)"
python: "3.6"
script: cd client && make smoke-test-annotations

View File

@@ -1,9 +1,9 @@
include ../common.mk
DATASET := $(if $(DATASET),$(DATASET),../example-dataset/pbmc3k.h5ad)
ANNOTATIONS := $(if $(ANNOTATIONS),$(ANNOTATIONS),../example-dataset/pbmc3k-annotations.csv)
# Packaging
.PHONY: clean
clean:
rm -rf node_modules
@@ -52,6 +52,10 @@ backend-dev-anno-ontology: server-requirements
CXG_OPTIONS='--experimental-annotations --experimental-annotations-ontology' \
$(MAKE) backend-dev
.PHONY: test
test:
node node_modules/jest/bin/jest.js
.PHONY: e2e
e2e:
node node_modules/jest/bin/jest.js \
@@ -59,14 +63,24 @@ e2e:
--config __tests__/e2e/e2eJestConfig.json \
e2e/e2e.test.js
.PHONY: test
test:
node node_modules/jest/bin/jest.js
.PHONY: e2e-annotations
e2e-annotations:
node node_modules/jest/bin/jest.js \
--verbose false \
--config __tests__/e2e/e2eJestConfig.json \
e2e/e2eAnnotations.test.js
.PHONY: smoke-test
smoke-test:
start_server_and_test '$(MAKE) start-server' $(CXG_SERVER_PORT) '$(MAKE) e2e'
.PHONY: smoke-test-annotations
smoke-test-annotations:
start_server_and_test \
'CXG_OPTIONS="--experimental-annotations --experimental-annotations-file $(ANNOTATIONS)" $(MAKE) start-server' \
$(CXG_SERVER_PORT) \
'$(MAKE) e2e-annotations'
.PHONY: unit-test
unit-test:
node node_modules/jest/bin/jest.js --testPathIgnorePatterns e2e

View File

@@ -4,3 +4,6 @@ export const appUrlBase = `http://localhost:${appPort}`;
export const DEV = jest_env === "dev";
export const DEBUG = jest_env === "debug";
export const DATASET = "pbmc3k";
if (DEBUG) jest.setTimeout(100000);
if (DEV) jest.setTimeout(10000);

View File

@@ -115,6 +115,18 @@ export const datasets = {
}
}
},
categoryLabel: {
lasso: {
"coordinates-as-percent": { x1: 0.3, y1: 0.3, x2: 0.5, y2: 0.5 },
count: "38"
},
newCount: {
bySubsetConfig: {
false: "199",
true: "193"
}
}
},
clip: {
min: "30",
max: "70",

View File

@@ -3,45 +3,16 @@ Smoke test suite that will be run in Travis CI
Tests included in this file are expected to be relatively stable and test core features
*/
import puppeteer from "puppeteer";
import { appUrlBase, DEBUG, DEV, DATASET } from "./config";
import { puppeteerUtils, cellxgeneActions } from "./puppeteerUtils";
import { appUrlBase, DEBUG, DATASET } from "./config";
import { setupTestBrowser } from "./puppeteerUtils";
import { datasets } from "./data";
let browser, page, utils, cxgActions, spy;
const browserViewport = { width: 1280, height: 960 };
let data = datasets[DATASET];
if (DEBUG) jest.setTimeout(100000);
if (DEV) jest.setTimeout(10000);
beforeAll(async () => {
const browserParams = DEV
? { headless: false, slowMo: 5 }
: DEBUG
? { headless: false, slowMo: 100, devtools: true }
: {};
browser = await puppeteer.launch(browserParams);
page = await browser.newPage();
await page.setViewport(browserViewport);
if (DEV || DEBUG) {
page.on("console", async msg => {
// If there is a console.error but an error is not thrown, this will ensure the test fails
if (msg.type() === "error") {
const errorMsgText = await Promise.all(
// TODO can we do this without internal properties?
msg.args().map(arg => arg._remoteObject.description)
);
throw new Error(`Console error: ${errorMsgText}`);
}
console.log(`PAGE LOG: ${msg.text()}`);
});
}
page.on("pageerror", err => {
throw new Error(`Console error: ${err}`);
});
utils = puppeteerUtils(page);
cxgActions = cellxgeneActions(page);
[browser, page, utils, cxgActions] = await setupTestBrowser(browserViewport);
});
beforeEach(async () => {
@@ -69,7 +40,7 @@ describe("metadata loads", () => {
`[data-testid="category-${label}"]`
);
expect(categoryName).toMatch(label);
await utils.clickOn(`category-expand-${label}`);
await utils.clickOn(`${label}:category-expand`);
const categories = await cxgActions.getAllCategoriesAndCounts(label);
expect(Object.keys(categories)).toMatchObject(
Object.keys(data.categorical[label])
@@ -112,8 +83,8 @@ describe("cell selection", () => {
test("selects cells via categorical", async () => {
for (const cellset of data.cellsets.categorical) {
await utils.clickOn(`category-expand-${cellset.metadata}`);
await utils.clickOn(`category-select-${cellset.metadata}`);
await utils.clickOn(`${cellset.metadata}:category-expand`);
await utils.clickOn(`${cellset.metadata}:category-select`);
for (const val of cellset.values) {
await utils.clickOn(
`categorical-value-select-${cellset.metadata}-${val}`

View File

@@ -0,0 +1,140 @@
/*
Tests included in this file are specific to annotation features
*/
import {appUrlBase, DEBUG, DEV, DATASET} from "./config";
import {setupTestBrowser} from "./puppeteerUtils";
import {datasets} from "./data";
let browser, page, utils, cxgActions;
const browserViewport = {width: 1280, height: 960};
const data = datasets[DATASET];
beforeAll(async () => {
[browser, page, utils, cxgActions] = await setupTestBrowser(browserViewport);
});
beforeEach(async () => {
await page.goto(appUrlBase);
});
afterAll(() => {
if (!DEBUG) browser.close();
});
describe("did launch", () => {
test("page launched", async () => {
let el = await utils.getOneElementInnerHTML("[data-testid='header']");
expect(el).toBe(data.title);
});
});
describe.each([
{withSubset: true},
{withSubset: false}
])("annotations", (config) => {
async function subset() {
const lassoSelection = await cxgActions.calcDragCoordinates(
"layout-graph",
{ x1: 0.10, y1: 0.10, x2: 0.80, y2: 0.80 }
);
await cxgActions.drag(
"layout-graph",
lassoSelection.start,
lassoSelection.end,
true
);
await utils.clickOn("subset-button");
const coordinate = await cxgActions.calcCoordinate("layout-graph", 0.9, 0.9);
await cxgActions.clickOnCoordinate("layout-graph", coordinate);
}
beforeEach(async () => {
if (config.withSubset) await subset();
});
test("create a category", async () => {
await utils.clickOn("open-annotation-dialog");
await utils.typeInto("new-category-name", "test-category-name");
await utils.clickOn("submit-category");
const result = await utils.waitByID("test-category-name:category-expand");
expect(await result.evaluate(node => node.innerText)).toBe("test-category-name");
});
test("delete a category", async () => {
const previous = await utils.waitByID("cluster-test:category-expand");
expect(await previous.evaluate(node => node.innerText)).toBe("cluster-test");
await utils.hoverOn("cluster-test:see-actions");
await utils.clickOn("cluster-test:delete-category");
const result = await page.$("[data-testid='cluster-test:category-expand']");
expect(result).toBeNull();
});
test("rename a category", async () => {
const previous = await utils.waitByID("cluster-test:category-expand");
expect(await previous.evaluate(node => node.innerText)).toBe("cluster-test");
await utils.hoverOn("cluster-test:see-actions");
await utils.clickOn("cluster-test:edit-category-mode");
await utils.typeInto("cluster-test:edit-category-name-text", "-renamed");
await utils.clickOn("cluster-test:submit-category-edit");
const result = await utils.waitByID("cluster-test-renamed:category-expand");
expect(await result.evaluate(node => node.innerText)).toBe("cluster-test-renamed");
});
test("create a label", async () => {
await utils.hoverOn("cluster-test:see-actions");
await utils.clickOn("cluster-test:add-new-label-to-category");
await utils.typeInto("cluster-test:new-label-name", "test-label-name");
await utils.clickOn("cluster-test:submit-label");
await cxgActions.expandCategory("cluster-test");
const result = await utils.waitByID("categorical-value-cluster-test-test-label-name");
expect(await result.evaluate(node => node.innerText)).toBe("test-label-name");
});
test("delete a label", async () => {
await cxgActions.expandCategory("cluster-test");
const previous = await utils.waitByID("categorical-value-cluster-test-three");
expect(await previous.evaluate(node => node.innerText)).toBe("three");
await utils.hoverOn("cluster-test:three:see-actions");
await utils.clickOn("cluster-test:three:delete-label");
const result = await page.$("[data-testid='categorical-value-cluster-test-three']");
expect(result).toBeNull();
});
test("rename a label", async () => {
await cxgActions.expandCategory("cluster-test");
const previous = await utils.waitByID("categorical-value-cluster-test-four");
expect(await previous.evaluate(node => node.innerText)).toBe("four");
await utils.hoverOn("cluster-test:four:see-actions");
await utils.clickOn("cluster-test:four:edit-label");
await utils.typeInto("cluster-test:four:edit-label-name", ".");
await utils.clickOn("cluster-test:four:submit-label-edit");
const result = await utils.waitByID("categorical-value-cluster-test-four.");
expect(await result.evaluate(node => node.innerText)).toBe("four.");
});
test("assign cells to a label", async () => {
await cxgActions.expandCategory("cluster-test");
const lassoSelection = await cxgActions.calcDragCoordinates(
"layout-graph",
data.categoryLabel.lasso["coordinates-as-percent"]
);
await cxgActions.drag(
"layout-graph",
lassoSelection.start,
lassoSelection.end,
true
);
await utils.waitByID("lasso-element", {visible: true});
const initialCount = await cxgActions.cellSet(1);
expect(initialCount).toBe(data.categoryLabel.lasso.count);
await utils.hoverOn("cluster-test:one:see-actions");
await utils.clickOn("cluster-test:one:add-current-selection-to-this-label");
const result = await utils.waitByID("categorical-value-count-cluster-test-one");
expect(await result.evaluate(node => node.innerText)).toBe(
data.categoryLabel.newCount.bySubsetConfig[config.withSubset]
);
});
});

View File

@@ -1,3 +1,6 @@
import {DEBUG, DEV} from "./config";
import puppeteer from "puppeteer";
export const puppeteerUtils = puppeteerPage => ({
async waitByID(testid, props = {}) {
return await puppeteerPage.waitForSelector(
@@ -63,6 +66,12 @@ export const puppeteerUtils = puppeteerPage => ({
await puppeteerPage.waitFor(50);
},
async hoverOn(testid) {
await this.waitByID(testid);
await puppeteerPage.hover(`[data-testid='${testid}']`);
await puppeteerPage.waitFor(50);
},
async getOneElementInnerHTML(selector) {
await puppeteerPage.waitForSelector(selector);
let text = await puppeteerPage.$eval(selector, el => el.innerHTML);
@@ -97,6 +106,14 @@ export const cellxgeneActions = puppeteerPage => ({
await puppeteerPage.mouse.up();
},
async clickOnCoordinate(testid, coord) {
const layout = await puppeteerUtils(puppeteerPage).waitByID(testid);
const elBox = await layout.boxModel();
const x = elBox.content[0].x + coord.x;
const y = elBox.content[0].y + coord.y;
await puppeteerPage.mouse.click(x, y);
},
async getAllHistograms(testclass, testids) {
const histTestIds = testids.map(tid => `histogram-${tid}`);
// these load asynchronously, so we need to wait for each histogram individually
@@ -137,7 +154,7 @@ export const cellxgeneActions = puppeteerPage => ({
},
async resetCategory(category) {
const checkboxId = `category-select-${category}`;
const checkboxId = `${category}:category-select`;
await puppeteerUtils(puppeteerPage).waitByID(checkboxId);
const checkedPseudoclass = await puppeteerPage.$eval(
`[data-testid='${checkboxId}']`,
@@ -150,39 +167,40 @@ export const cellxgeneActions = puppeteerPage => ({
}
try {
const categoryRow = await puppeteerUtils(puppeteerPage).waitByID(
`category-expand-${category}`
`${category}:category-expand`
);
const isExpanded = await categoryRow.$(
"[data-testclass='category-expand-is-expanded']"
);
if (isExpanded) {
await puppeteerUtils(puppeteerPage).clickOn(
`category-expand-${category}`
`${category}:category-expand`
);
}
} catch {}
},
async calcDragCoordinates(testid, coordinateAsPercent) {
async calcCoordinate(testid, xAsPercent, yAsPercent) {
const el = await puppeteerUtils(puppeteerPage).waitByID(testid);
const size = await el.boxModel();
return {
x: Math.floor(size.width * xAsPercent),
y: Math.floor(size.height * yAsPercent)
}
},
async calcDragCoordinates(testid, coordinateAsPercent) {
const coords = {
start: {
x: Math.floor(size.width * coordinateAsPercent.x1),
y: Math.floor(size.height * coordinateAsPercent.y1)
},
end: {
x: Math.floor(size.width * coordinateAsPercent.x2),
y: Math.floor(size.height * coordinateAsPercent.y2)
}
start: await this.calcCoordinate(testid, coordinateAsPercent.x1, coordinateAsPercent.y1),
end: await this.calcCoordinate(testid, coordinateAsPercent.x2, coordinateAsPercent.y2)
};
return coords;
},
async selectCategory(category, values, reset = true) {
if (reset) await this.resetCategory(category);
await puppeteerUtils(puppeteerPage).clickOn(`category-expand-${category}`);
await puppeteerUtils(puppeteerPage).clickOn(`category-select-${category}`);
await puppeteerUtils(puppeteerPage).clickOn(`${category}:category-expand`);
await puppeteerUtils(puppeteerPage).clickOn(`${category}:category-select`);
for (const val of values) {
await puppeteerUtils(puppeteerPage).clickOn(
`categorical-value-select-${category}-${val}`
@@ -190,6 +208,12 @@ export const cellxgeneActions = puppeteerPage => ({
}
},
async expandCategory(category) {
const expand = await puppeteerUtils(puppeteerPage).waitByID(`${category}:category-expand`);
const expandArrow = await expand.$("[data-testclass='category-expand-is-not-expanded']");
await expandArrow.click();
},
async reset() {
await puppeteerUtils(puppeteerPage).clickOn("reset");
// loading state never actually happens, reset is too fast
@@ -209,3 +233,33 @@ export const cellxgeneActions = puppeteerPage => ({
await puppeteerUtils(puppeteerPage).clickOn("clip-commit");
}
});
export async function setupTestBrowser(browserViewport) {
const browserParams = DEV
? { headless: false, slowMo: 5 }
: DEBUG
? { headless: false, slowMo: 100, devtools: true }
: {};
const browser = await puppeteer.launch(browserParams);
const page = await browser.newPage();
await page.setViewport(browserViewport);
if (DEV || DEBUG) {
page.on("console", async msg => {
// If there is a console.error but an error is not thrown, this will ensure the test fails
if (msg.type() === "error") {
const errorMsgText = await Promise.all(
// TODO can we do this without internal properties?
msg.args().map(arg => arg._remoteObject.description)
);
throw new Error(`Console error: ${errorMsgText}`);
}
console.log(`PAGE LOG: ${msg.text()}`);
});
}
page.on("pageerror", err => {
throw new Error(`Console error: ${err}`);
});
const utils = puppeteerUtils(page);
const cxgActions = cellxgeneActions(page);
return [browser, page, utils, cxgActions];
}

View File

@@ -32,7 +32,8 @@ class AnnoDialog extends React.Component {
handleSubmit,
primaryButtonText,
secondaryButtonText,
handleSecondaryButtonSubmit
handleSecondaryButtonSubmit,
primaryButtonProps
} = this.props;
return (
@@ -75,6 +76,7 @@ class AnnoDialog extends React.Component {
</Button>
) : null}
<Button
{...primaryButtonProps}
onClick={handleSubmit}
disabled={!text || validationError}
intent="primary"

View File

@@ -101,6 +101,8 @@ class Category extends React.Component {
annotations.isAddingNewLabel &&
annotations.categoryAddingNewLabel === metadataField
}
inputProps={{ "data-testid": `${metadataField}:create-label-dialog` }}
primaryButtonProps={{ "data-testid": `${metadataField}:submit-label` }}
title="Add new label to category"
instruction="New, unique label name:"
cancelTooltipContent="Close this dialog without adding a label."
@@ -116,6 +118,7 @@ class Category extends React.Component {
<AnnoInputs
useSuggest={ontologyEnabled}
text={newLabelText}
inputProps={{ "data-testid": `${metadataField}:new-label-name` }}
handleCreateArbitraryLabel={this.handleCreateArbitraryLabel}
handleItemChange={this.handleSuggestActiveItemChange}
handleChoice={this.handleChoice}

View File

@@ -121,6 +121,8 @@ class AnnoDialogEditCategoryName extends React.Component {
annotations.isEditingCategoryName &&
annotations.categoryBeingEdited === metadataField
}
inputProps={{ "data-testid": `${metadataField}:edit-category-name-dialog` }}
primaryButtonProps={{ "data-testid": `${metadataField}:submit-category-edit` }}
title="Edit category name"
instruction="New, unique category name:"
cancelTooltipContent="Close this dialog without editing this category."
@@ -132,6 +134,7 @@ class AnnoDialogEditCategoryName extends React.Component {
handleCancel={this.disableEditCategoryMode}
annoInput={
<AnnoInputs
inputProps={{ "data-testid": `${metadataField}:edit-category-name-text`}}
useSuggest={false}
text={newCategoryText}
handleTextChange={this.handleCategoryEditTextChange}

View File

@@ -3,9 +3,10 @@ import { connect } from "react-redux";
import { InputGroup } from "@blueprintjs/core";
const VanillaInput = props => {
const { text, handleTextChange } = props;
const { text, handleTextChange, inputProps } = props;
return (
<InputGroup
{...inputProps}
autoFocus
value={text}
intent="none"
@@ -31,10 +32,14 @@ class AnnoInputs extends React.Component {
}
render() {
const { handleTextChange, text } = this.props;
const { handleTextChange, text, ...restProps } = this.props;
return (
<div>
<VanillaInput text={text} handleTextChange={handleTextChange} />
<VanillaInput
{...restProps}
text={text}
handleTextChange={handleTextChange}
/>
</div>
);
}

View File

@@ -76,7 +76,7 @@ class AnnoMenuCategory extends React.Component {
<MenuItem
icon="tag"
data-testclass="handleAddNewLabelToCategory"
data-testid={`handleAddNewLabelToCategory-${metadataField}`}
data-testid={`${metadataField}:add-new-label-to-category`}
onClick={this.activateAddNewLabelMode}
text={createText}
/>
@@ -84,7 +84,7 @@ class AnnoMenuCategory extends React.Component {
<MenuItem
icon="book"
data-testclass="activateAddNewOntologyLabelMode"
data-testid={`activateAddNewOntologyLabelMode-${metadataField}`}
data-testid={`${metadataField}:add-new-ontology-label-mode`}
onClick={this.activateAddNewOntologyLabelMode}
text={createFromOntologyText}
/>
@@ -93,7 +93,7 @@ class AnnoMenuCategory extends React.Component {
icon="edit"
disabled={annotations.isEditingCategoryName}
data-testclass="activateEditCategoryMode"
data-testid={`activateEditCategoryMode-${metadataField}`}
data-testid={`${metadataField}:edit-category-mode`}
onClick={this.activateEditCategoryMode}
text={editText}
/>
@@ -101,7 +101,7 @@ class AnnoMenuCategory extends React.Component {
icon="delete"
intent="danger"
data-testclass="handleDeleteCategory"
data-testid={`handleDeleteCategory-${metadataField}`}
data-testid={`${metadataField}:delete-category`}
onClick={this.handleDeleteCategory}
text={deleteText}
/>
@@ -111,7 +111,7 @@ class AnnoMenuCategory extends React.Component {
<Button
style={{ marginLeft: 0 }}
data-testclass="seeActions"
data-testid={`seeActions-${metadataField}`}
data-testid={`${metadataField}:see-actions`}
icon="more"
minimal
/>

View File

@@ -145,6 +145,7 @@ class Categories extends React.Component {
instruction="New, unique category name:"
cancelTooltipContent="Close this dialog without creating a category."
primaryButtonText="Create new category"
primaryButtonProps={{ "data-testid": "submit-category" }}
text={newCategoryText}
validationError={this.categoryNameError(newCategoryText)}
errorMessage={this.categoryNameErrorMessage(newCategoryText)}
@@ -153,6 +154,7 @@ class Categories extends React.Component {
annoInput={
<AnnoInputs
text={newCategoryText}
inputProps={{ "data-testid": "new-category-name" }}
handleItemChange={this.handleSuggestActiveItemChange}
handleChoice={this.handleChoice}
handleTextChange={this.handleNewCategoryText}
@@ -194,7 +196,11 @@ class Categories extends React.Component {
)}
{writableCategoriesEnabled ? (
<div>
<Button onClick={this.handleEnableAnnoMode} intent="primary">
<Button
data-testid="open-annotation-dialog"
onClick={this.handleEnableAnnoMode}
intent="primary"
>
Create new category
</Button>
</div>

View File

@@ -175,7 +175,7 @@ class Category extends React.Component {
<label className="bp3-control bp3-checkbox">
<input
data-testclass="category-select"
data-testid={`category-select-${metadataField}`}
data-testid={`${metadataField}:category-select`}
onChange={this.handleToggleAllClick.bind(this)}
ref={el => {
this.checkbox = el;
@@ -187,7 +187,7 @@ class Category extends React.Component {
<span className="bp3-control-indicator" />
</label>
<span
data-testid={`category-expand-${metadataField}`}
data-testid={`${metadataField}:category-expand`}
style={{
cursor: "pointer",
display: "inline-block"

View File

@@ -433,6 +433,8 @@ class CategoryValue extends React.Component {
<div>
<AnnoDialog
isActive={editModeActive}
inputProps={{ "data-testid": `${metadataField}:edit-label-name-dialog` }}
primaryButtonProps={{ "data-testid": `${metadataField}:${displayString}:submit-label-edit` }}
title="Edit label"
instruction={`New label text must be unique within category ${metadataField}:`}
cancelTooltipContent="Close this dialog without editing label text."
@@ -447,6 +449,7 @@ class CategoryValue extends React.Component {
<AnnoInputs
useSuggest={ontologyEnabled}
text={editedLabelText}
inputProps={{ "data-testid": `${metadataField}:${displayString}:edit-label-name` }}
handleCreateArbitraryLabel={
this.handleCreateArbitraryLabel
}
@@ -513,7 +516,7 @@ class CategoryValue extends React.Component {
<MenuItem
icon="plus"
data-testclass="handleAddCurrentSelectionToThisLabel"
data-testid={`handleAddCurrentSelectionToThisLabel-${metadataField}`}
data-testid={`${metadataField}:${displayString}:add-current-selection-to-this-label`}
onClick={this.handleAddCurrentSelectionToThisLabel}
text={
<span>
@@ -541,7 +544,7 @@ class CategoryValue extends React.Component {
icon="edit"
text="Edit this label's name"
data-testclass="handleEditValue"
data-testid={`handleEditValue-${metadataField}`}
data-testid={`${metadataField}:${displayString}:edit-label`}
onClick={this.activateEditLabelMode}
disabled={annotations.isEditingLabelName}
/>
@@ -551,7 +554,7 @@ class CategoryValue extends React.Component {
icon="delete"
intent="danger"
data-testclass="handleDeleteValue"
data-testid={`handleDeleteValue-${metadataField}`}
data-testid={`${metadataField}:${displayString}:delete-label`}
onClick={this.handleDeleteValue}
text={`Delete this label, and reassign all cells to type '${globals.unassignedCategoryLabel}'`}
/>
@@ -567,7 +570,7 @@ class CategoryValue extends React.Component {
minHeight: 16
}}
data-testclass="seeActions"
data-testid={`seeActions-${metadataField}`}
data-testid={`${metadataField}:${displayString}:see-actions`}
icon="more"
small
minimal

View File

@@ -1,5 +1,5 @@
/*
Helper functions for user-editable nnotations state management.
Helper functions for user-editable annotations state management.
See also reducers/annotations.js
*/
import { unassignedCategoryLabel } from "../../globals";

View File

@@ -21,6 +21,7 @@ Client and server tests run on Travis CI for every push, PR, and commit to maste
### Unit tests
Steps to run the all unit tests:
1. Start in the project root directory
1. [Load an environment](#Environment)
1. `make dev-env`
1. `make unit-test`

View File

@@ -134,4 +134,3 @@ You can also use `CXG_OPTIONS` to pass options to the `cellxgene launch` command
**About** Runs backend tests without starting the server. You will need to start the rest api separately with the pbmc3k.h5ad file. Note you can use the `JEST_ENV` environment variable to change how JEST runs in the browser.
**Usage** `make e2e`

File diff suppressed because it is too large Load Diff

View File

@@ -14,6 +14,8 @@ START_SERVER_SCRIPT="$1"
PORT="$2"
TEST_SCRIPT="$3"
await_port --await-free "$PORT"
eval "$START_SERVER_SCRIPT &"
SERVER_PID=$!

View File

@@ -38,7 +38,6 @@ class WritableAnnotationTest(unittest.TestCase):
def test_error_checks(self):
# verify that the expected errors are generated
n_rows = self.data.data.obs.shape[0]
fbs_bad = self.make_fbs({"louvain": pd.Series(["undefined" for l in range(0, n_rows)], dtype="category")})
@@ -64,7 +63,7 @@ class WritableAnnotationTest(unittest.TestCase):
self.assertTrue(path.exists(self.annotations_file))
df = pd.read_csv(self.annotations_file, index_col=0, header=0, comment="#")
self.assertEqual(df.shape, (n_rows, 2))
self.assertEqual(set(df.columns), set(["cat_A", "cat_B"]))
self.assertEqual(set(df.columns), {"cat_A", "cat_B"})
self.assertTrue(self.data.original_obs_index.equals(df.index))
self.assertTrue(np.all(df["cat_A"] == ["label_A" for l in range(0, n_rows)]))
self.assertTrue(np.all(df["cat_B"] == ["label_B" for l in range(0, n_rows)]))
@@ -80,7 +79,7 @@ class WritableAnnotationTest(unittest.TestCase):
self.assertEqual(res, json.dumps({"status": "OK"}))
self.assertTrue(path.exists(self.annotations_file))
df = pd.read_csv(self.annotations_file, index_col=0, header=0, comment="#")
self.assertEqual(set(df.columns), set(["cat_A", "cat_C"]))
self.assertEqual(set(df.columns), {"cat_A", "cat_C"})
self.assertTrue(np.all(df["cat_A"] == ["label_A1" for l in range(0, n_rows)]))
self.assertTrue(np.all(df["cat_C"] == ["label_C" for l in range(0, n_rows)]))