mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-09-19 02:48:30 +08:00
* prototyping
* render histos on open gene set
* prototyping
* render histos on open gene set
* factor out add genes to own component
* remove unused import
* mock reducer
* color by geneset stub
* menus and buttons
* geneset dialogue stub
* remove heatmap mock
* componetize histogram
* reenable add genes
* re-add isuserdefined
* test data
* remove have fetched
* add isExpanded state to gene, and pass to histogram
* expand button
* toggleable
* mini
* bump number of genes to 50
* don't clear diffexp on subset
* move create category to top
* render diffexp as geneset
* geneset show mean expression
* gene set reducer
* add geneset UI reducer
* wire e2e gene set loading prototype
* fix sniffing bug
* fix typo
* add gene modals
* client/src/actions/
* add autosave
* rename data-dir cli param
* add geneset, add gene, delete set
* prototype: remove csv upload placeholder
* handle delete gene from set
* prepopulate geneset with genes from modal
* add geneset: rename action
* icons, language consistency
* chevron after
* handle empty string case on genes for create geneset
* edit geneset
* fix language on create
* copy correction
* add popper2
upgrade react popper
upgrade react popper
adding popover2 package
* truncate uses tooltip2
* gene set button text typo
* remove logging
* moving server over
* remove test imports
* don't try to destructure map, use array.from
* fix add gene map datastructure error
* Revert "fix add gene map datastructure error"
This reverts commit b0eed45952.
* name --> genesetName, genes --> geneSymbols
* add gene to geneset, temporary format
* handle empty case, clear form input
* lint -- genesets wasn't passed via props
* userinfo
* move genes string to object conversion to action
* remove tmp gene description
* emptystring default for description
* remove empty string
* remove top level package json
* remove package lock as well
* remove flag for feature toggle
* remove comments in geneset
* comment cleanup
* remove comment
* revert diffexp genes to 10
* color by gene set
* disable color by gene set
* Gene menus are now inline, remove dead prototype code
* remove todo, magic number to variable
* remove jshint in rightsidebar
Co-authored-by: Severiano Badajoz <sbadajoz@chanzuckerberg.com>
* remove unused geneset validation code
* tmp format pending geneset description
* move magic number into variable
* reorganize genesetsUI reducer pending tests
* rewire edit given new action name
* add basic validation and feedback for geneset name uniqueness
* mv annoDialog
* mv label, repair paths
* Update client/src/components/brushableHistogram/header.js
Co-authored-by: Severiano Badajoz <sbadajoz@chanzuckerberg.com>
* add imports for icon in histo
* update jest snapshots given blueprint/tooltip2 usage of index -1
* ensure no empty paragraph
* intent from blueprint
* remove remainder of jshint references
* do not push undo when autosave fires
* fix autosave bugs
* remove todos
* clamp to util
* scient to util
* revert clearing diffexp
* rename value to be more specific stacked bar
* clean up logging and commetns
* remove gene entry tests pending rewrite
* tab index -1
* update jest snapshot, blueprint tooltip 2
* caret margin
* snapshot update
* ensure histogram is centered
* add geneset actions to config
* comment maybeScientific
* comment clamp
* comment ui reducer
* remove prototype code
* remove error log
* remove references to bl.ocks
* componetize parseBulkGeneString
* catch case where geneset rename same name
* genesetui reducer tests
* add geneset ui to index reducer config
Co-authored-by: bkmartinjr <bruce@chanzuckerberg.com>
Co-authored-by: Severiano Badajoz <sbadajoz@chanzuckerberg.com>
384 lines
10 KiB
JavaScript
384 lines
10 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
|
|
*/
|
|
|
|
/* eslint-disable no-await-in-loop -- await in loop is needed to emulate sequential user actions */
|
|
import { appUrlBase, DATASET } from "./config";
|
|
|
|
import { datasets } from "./data";
|
|
|
|
import {
|
|
clickOn,
|
|
getAllByClass,
|
|
getElementCoordinates,
|
|
getOneElementInnerHTML,
|
|
getTestId,
|
|
goToPage,
|
|
waitByID,
|
|
clickOnUntil,
|
|
} from "./puppeteerUtils";
|
|
|
|
import {
|
|
calcDragCoordinates,
|
|
clip,
|
|
drag,
|
|
getAllCategoriesAndCounts,
|
|
getCellSetCount,
|
|
selectCategory,
|
|
login,
|
|
logout,
|
|
} from "./cellxgeneActions";
|
|
|
|
const data = datasets[DATASET];
|
|
|
|
describe("did launch", () => {
|
|
test("page launched", async () => {
|
|
await goToPage(appUrlBase);
|
|
|
|
const element = await getOneElementInnerHTML(getTestId("header"));
|
|
|
|
expect(element).toMatchSnapshot();
|
|
});
|
|
});
|
|
|
|
describe("metadata loads", () => {
|
|
test("categories and values from dataset appear", async () => {
|
|
await goToPage(appUrlBase);
|
|
|
|
for (const label of Object.keys(data.categorical)) {
|
|
const element = await getOneElementInnerHTML(
|
|
getTestId(`category-${label}`)
|
|
);
|
|
|
|
expect(element).toMatchSnapshot();
|
|
|
|
await clickOn(`${label}:category-expand`);
|
|
|
|
const categories = await 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 () => {
|
|
await goToPage(appUrlBase);
|
|
|
|
for (const label of Object.keys(data.continuous)) {
|
|
await waitByID(`histogram-${label}`);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("cell selection", () => {
|
|
test("selects all cells cellset 1", async () => {
|
|
await goToPage(appUrlBase);
|
|
|
|
const cellCount = await getCellSetCount(1);
|
|
expect(cellCount).toBe(data.dataframe.nObs);
|
|
});
|
|
|
|
test("selects all cells cellset 2", async () => {
|
|
await goToPage(appUrlBase);
|
|
|
|
const cellCount = await getCellSetCount(2);
|
|
expect(cellCount).toBe(data.dataframe.nObs);
|
|
});
|
|
|
|
test("selects cells via lasso", async () => {
|
|
await goToPage(appUrlBase);
|
|
|
|
for (const cellset of data.cellsets.lasso) {
|
|
const cellset1 = await calcDragCoordinates(
|
|
"layout-graph",
|
|
cellset["coordinates-as-percent"]
|
|
);
|
|
|
|
await drag("layout-graph", cellset1.start, cellset1.end, true);
|
|
const cellCount = await getCellSetCount(1);
|
|
expect(cellCount).toBe(cellset.count);
|
|
}
|
|
});
|
|
|
|
test("selects cells via categorical", async () => {
|
|
await goToPage(appUrlBase);
|
|
|
|
for (const cellset of data.cellsets.categorical) {
|
|
await clickOn(`${cellset.metadata}:category-expand`);
|
|
await clickOn(`${cellset.metadata}:category-select`);
|
|
|
|
for (const value of cellset.values) {
|
|
await clickOn(`categorical-value-select-${cellset.metadata}-${value}`);
|
|
}
|
|
|
|
const cellCount = await getCellSetCount(1);
|
|
|
|
expect(cellCount).toBe(cellset.count);
|
|
}
|
|
});
|
|
|
|
test("selects cells via continuous", async () => {
|
|
await goToPage(appUrlBase);
|
|
|
|
for (const cellset of data.cellsets.continuous) {
|
|
const histBrushableAreaId = `histogram-${cellset.metadata}-plot-brushable-area`;
|
|
|
|
const coords = await calcDragCoordinates(
|
|
histBrushableAreaId,
|
|
cellset["coordinates-as-percent"]
|
|
);
|
|
|
|
await drag(histBrushableAreaId, coords.start, coords.end);
|
|
|
|
const cellCount = await getCellSetCount(1);
|
|
|
|
expect(cellCount).toBe(cellset.count);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("subset", () => {
|
|
test("subset - cell count matches", async () => {
|
|
await goToPage(appUrlBase);
|
|
|
|
for (const select of data.subset.cellset1) {
|
|
if (select.kind === "categorical") {
|
|
await selectCategory(select.metadata, select.values, true);
|
|
}
|
|
}
|
|
|
|
await clickOn("subset-button");
|
|
|
|
for (const label of Object.keys(data.subset.categorical)) {
|
|
const categories = await 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("lasso after subset", async () => {
|
|
await goToPage(appUrlBase);
|
|
|
|
for (const select of data.subset.cellset1) {
|
|
if (select.kind === "categorical") {
|
|
await selectCategory(select.metadata, select.values, true);
|
|
}
|
|
}
|
|
|
|
await clickOn("subset-button");
|
|
|
|
const lassoSelection = await calcDragCoordinates(
|
|
"layout-graph",
|
|
data.subset.lasso["coordinates-as-percent"]
|
|
);
|
|
|
|
await drag("layout-graph", lassoSelection.start, lassoSelection.end, true);
|
|
|
|
const cellCount = await getCellSetCount(1);
|
|
expect(cellCount).toBe(data.subset.lasso.count);
|
|
});
|
|
});
|
|
|
|
describe("clipping", () => {
|
|
test("clip continuous", async () => {
|
|
await goToPage(appUrlBase);
|
|
|
|
await clip(data.clip.min, data.clip.max);
|
|
const histBrushableAreaId = `histogram-${data.clip.metadata}-plot-brushable-area`;
|
|
const coords = await calcDragCoordinates(
|
|
histBrushableAreaId,
|
|
data.clip["coordinates-as-percent"]
|
|
);
|
|
await drag(histBrushableAreaId, coords.start, coords.end);
|
|
const cellCount = await getCellSetCount(1);
|
|
expect(cellCount).toBe(data.clip.count);
|
|
});
|
|
});
|
|
|
|
// interact with UI elements just that they do not break
|
|
describe("ui elements don't error", () => {
|
|
test("color by", async () => {
|
|
await goToPage(appUrlBase);
|
|
|
|
const allLabels = [
|
|
...Object.keys(data.categorical),
|
|
...Object.keys(data.continuous),
|
|
];
|
|
|
|
for (const label of allLabels) {
|
|
await clickOn(`colorby-${label}`);
|
|
}
|
|
});
|
|
|
|
test("pan and zoom", async () => {
|
|
await goToPage(appUrlBase);
|
|
|
|
await clickOn("mode-pan-zoom");
|
|
const panCoords = await calcDragCoordinates(
|
|
"layout-graph",
|
|
data.pan["coordinates-as-percent"]
|
|
);
|
|
|
|
await drag("layout-graph", panCoords.start, panCoords.end, false);
|
|
|
|
await page.evaluate("window.scrollBy(0, 1000);");
|
|
});
|
|
});
|
|
|
|
describe("centroid labels", () => {
|
|
test("labels are created", async () => {
|
|
await goToPage(appUrlBase);
|
|
|
|
const labels = Object.keys(data.categorical);
|
|
|
|
await clickOn(`colorby-${labels[0]}`);
|
|
await clickOn("centroid-label-toggle");
|
|
|
|
// Toggle colorby for each category and check to see if labels are generated
|
|
for (let i = 0, { length } = labels; i < length; i += 1) {
|
|
const label = labels[i];
|
|
// first label is already enabled
|
|
if (i !== 0) await clickOn(`colorby-${label}`);
|
|
const generatedLabels = await getAllByClass("centroid-label");
|
|
// Number of labels generated should be equal to size of the object
|
|
expect(generatedLabels).toHaveLength(
|
|
Object.keys(data.categorical[label]).length
|
|
);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("graph overlay", () => {
|
|
test("transform centroids correctly", async () => {
|
|
await goToPage(appUrlBase);
|
|
|
|
const category = Object.keys(data.categorical)[0];
|
|
|
|
await clickOn(`colorby-${category}`);
|
|
await clickOn("centroid-label-toggle");
|
|
await clickOn("mode-pan-zoom");
|
|
|
|
const panCoords = await calcDragCoordinates(
|
|
"layout-graph",
|
|
data.pan["coordinates-as-percent"]
|
|
);
|
|
|
|
const categoryValue = Object.keys(data.categorical[category])[0];
|
|
const initialCoordinates = await getElementCoordinates(
|
|
`${categoryValue}-centroid-label`
|
|
);
|
|
|
|
await drag("layout-graph", panCoords.start, panCoords.end, false);
|
|
const terminalCoordinates = await getElementCoordinates(
|
|
`${categoryValue}-centroid-label`
|
|
);
|
|
|
|
expect(terminalCoordinates[0] - initialCoordinates[0]).toBeCloseTo(
|
|
panCoords.end.x - panCoords.start.x
|
|
);
|
|
expect(terminalCoordinates[1] - initialCoordinates[1]).toBeCloseTo(
|
|
panCoords.end.y - panCoords.start.y
|
|
);
|
|
});
|
|
});
|
|
|
|
test("pan zoom mode resets lasso selection", async () => {
|
|
await goToPage(appUrlBase);
|
|
|
|
const panzoomLasso = data.features.panzoom.lasso;
|
|
|
|
const lassoSelection = await calcDragCoordinates(
|
|
"layout-graph",
|
|
panzoomLasso["coordinates-as-percent"]
|
|
);
|
|
|
|
await drag("layout-graph", lassoSelection.start, lassoSelection.end, true);
|
|
await waitByID("lasso-element", { visible: true });
|
|
|
|
const initialCount = await getCellSetCount(1);
|
|
|
|
expect(initialCount).toBe(panzoomLasso.count);
|
|
|
|
await clickOn("mode-pan-zoom");
|
|
await clickOn("mode-lasso");
|
|
|
|
const modeSwitchCount = await getCellSetCount(1);
|
|
|
|
expect(modeSwitchCount).toBe(initialCount);
|
|
});
|
|
|
|
test("lasso moves after pan", async () => {
|
|
await goToPage(appUrlBase);
|
|
|
|
const panzoomLasso = data.features.panzoom.lasso;
|
|
const coordinatesAsPercent = panzoomLasso["coordinates-as-percent"];
|
|
|
|
const lassoSelection = await calcDragCoordinates(
|
|
"layout-graph",
|
|
coordinatesAsPercent
|
|
);
|
|
|
|
await drag("layout-graph", lassoSelection.start, lassoSelection.end, true);
|
|
await waitByID("lasso-element", { visible: true });
|
|
|
|
const initialCount = await getCellSetCount(1);
|
|
|
|
expect(initialCount).toBe(panzoomLasso.count);
|
|
|
|
await clickOn("mode-pan-zoom");
|
|
|
|
const panCoords = await calcDragCoordinates(
|
|
"layout-graph",
|
|
coordinatesAsPercent
|
|
);
|
|
|
|
await drag("layout-graph", panCoords.start, panCoords.end, false);
|
|
await clickOn("mode-lasso");
|
|
|
|
const panCount = await getCellSetCount(2);
|
|
|
|
expect(panCount).toBe(initialCount);
|
|
});
|
|
|
|
const describeIfCalledByMakeFileTarget =
|
|
process.env.CXG_AUTH_TYPE?.toLowerCase() === "test"
|
|
? describe
|
|
: describe.skip;
|
|
|
|
describeIfCalledByMakeFileTarget("auth buttons", () => {
|
|
test("login then logout", async () => {
|
|
await goToPage(appUrlBase);
|
|
await clickOnUntil("log-in", async () => {
|
|
await page.waitForNavigation({ waitUntil: "networkidle0" });
|
|
await waitByID("user-info");
|
|
});
|
|
await logout();
|
|
});
|
|
});
|
|
|
|
const conditionalDescribe = describe.skip;
|
|
|
|
conditionalDescribe("AuthN Integration", () => {
|
|
it("logs in", async () => {
|
|
await login();
|
|
});
|
|
|
|
it("logs out", async () => {
|
|
await login();
|
|
await logout();
|
|
});
|
|
});
|
|
/* eslint-enable no-await-in-loop -- await in loop is needed to emulate sequential user actions */
|