mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-09-18 18:38:11 +08:00
* menubar 1 * zoom switching * centering, pixel perfect canvas * remove dead args and code * clipping * remove log * if * connect props * lint * undo * logo left, componetize * graph back to full height * shadow to top * do not prematurely call event handlers during render * change test to deal with async histogram creation * left section padding * lint * adjust graph to account for top bar, * lasso tests * refine histogram tests * remove testing (onlys)
344 lines
11 KiB
JavaScript
344 lines
11 KiB
JavaScript
/*
|
|
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 { 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);
|
|
});
|
|
|
|
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("metadata loads", () => {
|
|
test("categories and values from dataset appear", async () => {
|
|
for (const label in data.categorical) {
|
|
await utils.waitByID(`category-${label}`);
|
|
const categoryName = await utils.getOneElementInnerText(
|
|
`[data-testid="category-${label}"]`
|
|
);
|
|
expect(categoryName).toMatch(label);
|
|
await utils.clickOn(`category-expand-${label}`);
|
|
const categories = await cxgActions.getAllCategoriesAndCounts(label);
|
|
expect(Object.keys(categories)).toMatchObject(
|
|
Object.keys(data.categorical[label])
|
|
);
|
|
expect(Object.values(categories)).toMatchObject(
|
|
Object.values(data.categorical[label])
|
|
);
|
|
}
|
|
});
|
|
|
|
test("continuous data appears", async () => {
|
|
for (const label in data.continuous) {
|
|
await utils.waitByID(`histogram-${label}`);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("cell selection", () => {
|
|
test("selects all cells cellset 1", async () => {
|
|
const cellCount = await cxgActions.cellSet(1);
|
|
expect(cellCount).toBe(data.dataframe.nObs);
|
|
});
|
|
|
|
test("selects all cells cellset 2", async () => {
|
|
const cellCount = await cxgActions.cellSet(2);
|
|
expect(cellCount).toBe(data.dataframe.nObs);
|
|
});
|
|
|
|
test("selects cells via lasso", async () => {
|
|
for (const cellset of data.cellsets.lasso) {
|
|
const cellset1 = await cxgActions.calcDragCoordinates(
|
|
"layout-graph",
|
|
cellset["coordinates-as-percent"]
|
|
);
|
|
await cxgActions.drag("layout-graph", cellset1.start, cellset1.end, true);
|
|
const cellCount = await cxgActions.cellSet(1);
|
|
expect(cellCount).toBe(cellset.count);
|
|
}
|
|
});
|
|
|
|
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}`);
|
|
for (const val of cellset.values) {
|
|
await utils.clickOn(
|
|
`categorical-value-select-${cellset.metadata}-${val}`
|
|
);
|
|
}
|
|
const cellCount = await cxgActions.cellSet(1);
|
|
expect(cellCount).toBe(cellset.count);
|
|
}
|
|
});
|
|
|
|
test("selects cells via continuous", async () => {
|
|
for (const cellset of data.cellsets.continuous) {
|
|
const histId = `histogram-${cellset.metadata}-plot-brush`;
|
|
const coords = await cxgActions.calcDragCoordinates(
|
|
histId,
|
|
cellset["coordinates-as-percent"]
|
|
);
|
|
await cxgActions.drag(histId, coords.start, coords.end);
|
|
const cellCount = await cxgActions.cellSet(1);
|
|
expect(cellCount).toBe(cellset.count);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("gene entry", () => {
|
|
test("search for single gene", async () => {
|
|
// blueprint's typeahead is treating typing weird, clicking & waiting first solves this
|
|
await utils.typeInto("gene-search", data.genes.search);
|
|
await page.keyboard.press("Enter");
|
|
await page.waitForSelector(
|
|
`[data-testid='histogram-${data.genes.search}']`
|
|
);
|
|
});
|
|
|
|
test("bulk add genes", async () => {
|
|
await cxgActions.reset();
|
|
const testGenes = data.genes.bulkadd;
|
|
await utils.clickOn("section-bulk-add");
|
|
await utils.typeInto("input-bulk-add", testGenes.join(","));
|
|
await page.keyboard.press("Enter");
|
|
|
|
const allHistograms = await cxgActions.getAllHistograms(
|
|
"histogram-user-gene",
|
|
testGenes
|
|
);
|
|
expect(allHistograms).toEqual(expect.arrayContaining(testGenes));
|
|
expect(allHistograms.length).toEqual(testGenes.length);
|
|
});
|
|
});
|
|
|
|
describe("diffexp", () => {
|
|
test("selects cells, saves them and performs diffexp", async () => {
|
|
for (const select of data.diffexp.cellset1) {
|
|
if (select.kind === "categorical") {
|
|
await cxgActions.selectCategory(select.metadata, select.values, true);
|
|
}
|
|
}
|
|
await cxgActions.cellSet(1);
|
|
for (const select of data.diffexp.cellset2) {
|
|
if (select.kind === "categorical") {
|
|
await cxgActions.selectCategory(select.metadata, select.values, true);
|
|
}
|
|
}
|
|
await cxgActions.cellSet(2);
|
|
await utils.clickOn("diffexp-button");
|
|
const allHistograms = await cxgActions.getAllHistograms(
|
|
"histogram-diffexp",
|
|
data.diffexp["gene-results"]
|
|
);
|
|
expect(allHistograms).toEqual(
|
|
expect.arrayContaining(data.diffexp["gene-results"])
|
|
);
|
|
expect(allHistograms.length).toEqual(data.diffexp["gene-results"].length);
|
|
});
|
|
});
|
|
|
|
describe("subset/reset", () => {
|
|
test("subset - cell count matches", async () => {
|
|
for (const select of data.subset.cellset1) {
|
|
if (select.kind === "categorical") {
|
|
await cxgActions.selectCategory(select.metadata, select.values, true);
|
|
}
|
|
}
|
|
await utils.clickOn("subset-button");
|
|
for (const label in data.subset.categorical) {
|
|
const categories = await cxgActions.getAllCategoriesAndCounts(label);
|
|
expect(Object.keys(categories)).toMatchObject(
|
|
Object.keys(data.subset.categorical[label])
|
|
);
|
|
expect(Object.values(categories)).toMatchObject(
|
|
Object.values(data.subset.categorical[label])
|
|
);
|
|
}
|
|
});
|
|
|
|
test("reset after subset", async () => {
|
|
for (const select of data.subset.cellset1) {
|
|
if (select.kind === "categorical") {
|
|
await cxgActions.selectCategory(select.metadata, select.values, true);
|
|
}
|
|
}
|
|
await utils.clickOn("subset-button");
|
|
for (const label in data.subset.categorical) {
|
|
const categories = await cxgActions.getAllCategoriesAndCounts(label);
|
|
expect(Object.keys(categories)).toMatchObject(
|
|
Object.keys(data.subset.categorical[label])
|
|
);
|
|
expect(Object.values(categories)).toMatchObject(
|
|
Object.values(data.subset.categorical[label])
|
|
);
|
|
}
|
|
await cxgActions.reset();
|
|
for (const label in data.categorical) {
|
|
await utils.waitByID(`category-${label}`);
|
|
const categoryName = await utils.getOneElementInnerText(
|
|
`[data-testid="category-${label}"]`
|
|
);
|
|
expect(categoryName).toMatch(label);
|
|
const categories = await cxgActions.getAllCategoriesAndCounts(label);
|
|
expect(Object.keys(categories)).toMatchObject(
|
|
Object.keys(data.categorical[label])
|
|
);
|
|
expect(Object.values(categories)).toMatchObject(
|
|
Object.values(data.categorical[label])
|
|
);
|
|
}
|
|
});
|
|
|
|
test("lasso after subset", async () => {
|
|
for (const select of data.subset.cellset1) {
|
|
if (select.kind === "categorical") {
|
|
await cxgActions.selectCategory(select.metadata, select.values, true);
|
|
}
|
|
}
|
|
await utils.clickOn("subset-button");
|
|
const lassoSelection = await cxgActions.calcDragCoordinates(
|
|
"layout-graph",
|
|
data.subset.lasso["coordinates-as-percent"]
|
|
);
|
|
await cxgActions.drag(
|
|
"layout-graph",
|
|
lassoSelection.start,
|
|
lassoSelection.end,
|
|
true
|
|
);
|
|
const cellCount = await cxgActions.cellSet(1);
|
|
expect(cellCount).toBe(data.subset.lasso.count);
|
|
});
|
|
});
|
|
|
|
describe("scatter plot", () => {
|
|
test("scatter plot appears", async () => {
|
|
await cxgActions.reset();
|
|
const testGenes = data.scatter.genes;
|
|
await utils.clickOn("section-bulk-add");
|
|
await utils.typeInto("input-bulk-add", Object.values(testGenes).join(","));
|
|
await page.keyboard.press("Enter");
|
|
await utils.clickOn(`plot-x-${data.scatter.genes.x}`);
|
|
await utils.clickOn(`plot-y-${data.scatter.genes.y}`);
|
|
await utils.waitByID("scatterplot");
|
|
});
|
|
});
|
|
|
|
describe("clipping", () => {
|
|
test("clip continuous", async () => {
|
|
await cxgActions.clip(data.clip.min, data.clip.max);
|
|
const histId = `histogram-${data.clip.metadata}-plot-brush`;
|
|
const coords = await cxgActions.calcDragCoordinates(
|
|
histId,
|
|
data.clip["coordinates-as-percent"]
|
|
);
|
|
await cxgActions.drag(histId, coords.start, coords.end);
|
|
const cellCount = await cxgActions.cellSet(1);
|
|
expect(cellCount).toBe(data.clip.count);
|
|
});
|
|
|
|
test("clip gene", async () => {
|
|
await utils.typeInto("gene-search", data.clip.gene);
|
|
await page.keyboard.press("Enter");
|
|
await page.waitForSelector(`[data-testid='histogram-${data.clip.gene}']`);
|
|
await cxgActions.clip(data.clip.min, data.clip.max);
|
|
const histId = `histogram-${data.clip.gene}-plot-brush`;
|
|
const coords = await cxgActions.calcDragCoordinates(
|
|
histId,
|
|
data.clip["coordinates-as-percent"]
|
|
);
|
|
await cxgActions.drag(histId, coords.start, coords.end);
|
|
const cellCount = await cxgActions.cellSet(1);
|
|
expect(cellCount).toBe(data.clip["gene-cell-count"]);
|
|
});
|
|
});
|
|
|
|
// interact with UI elements just that they do not break
|
|
describe("ui elements don't error", () => {
|
|
test("color by", async () => {
|
|
for (const label in data.categorical) {
|
|
await utils.clickOn(`colorby-${label}`);
|
|
}
|
|
for (const label in data.continuous) {
|
|
await utils.clickOn(`colorby-${label}`);
|
|
}
|
|
});
|
|
|
|
test("color by for gene", async () => {
|
|
await utils.typeInto("gene-search", data.genes.search);
|
|
await page.keyboard.press("Enter");
|
|
await page.waitForSelector(
|
|
`[data-testid='histogram-${data.genes.search}']`
|
|
);
|
|
await utils.clickOn(`colorby-${data.genes.search}`);
|
|
});
|
|
|
|
test("pan and zoom", async () => {
|
|
await utils.clickOn("mode-pan-zoom");
|
|
const panCoords = await cxgActions.calcDragCoordinates(
|
|
"layout-graph",
|
|
data.pan["coordinates-as-percent"]
|
|
);
|
|
await cxgActions.drag(
|
|
"layout-graph",
|
|
panCoords.start,
|
|
panCoords.end,
|
|
false
|
|
);
|
|
await page.evaluate(`window.scrollBy(0, 1000);`);
|
|
});
|
|
});
|