diff --git a/.travis.yml b/.travis.yml index ceb2399f..fd83efb7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,10 +12,18 @@ install: - make install - pip install -r server/requirements-dev.txt - docker build . -script: - - set -eo pipefail - - flake8 server - - black --check - - npm run --prefix client/ build - - npm run --prefix client/ test - - pytest -s server/test + +jobs: + include: + - name: "Branch Tests" + script: + - set -eo pipefail + - flake8 server + - black --check + - npm run --prefix client/ build + - npm run --prefix client/ unit-test + - pytest -s server/test + - name: "Smoke Tests" + if: branch = master AND type = cron + script: + - npm run --prefix client/ smoke-test diff --git a/client/__tests__/e2e/e2e.test.js b/client/__tests__/e2e/e2e.test.js new file mode 100644 index 00000000..514fe163 --- /dev/null +++ b/client/__tests__/e2e/e2e.test.js @@ -0,0 +1,185 @@ +import puppeteer from "puppeteer"; + +const jest_env = process.env.JEST_ENV || "dev"; +const appPort = process.env.JEST_CXG_PORT || 3000; +const appUrlBase = `http://localhost:${appPort}`; +const DEV = jest_env === "dev"; + +let browser; +let page; +const browserViewport = { width: 1280, height: 960 }; + +beforeAll(async () => { + const browser_params = DEV + ? { headless: false, slowMo: 100, devtools: true } + : {}; + browser = await puppeteer.launch(browser_params); + page = await browser.newPage(); + page.setViewport(browserViewport); + if (DEV) page.on("console", msg => console.log("PAGE LOG:", msg.text())); +}); + +afterAll(() => { + if (!DEV) { + browser.close(); + } +}); + +const getOneElementInnerHTML = async function(selector) { + let text = await page.$eval(selector, el => el.innerHTML); + return text; +}; + +const drag = async function(el_box, start, end, lasso = false) { + const x1 = el_box.content[0].x + start.x; + const x2 = el_box.content[0].x + end.x; + const y1 = el_box.content[0].y + start.y; + const y2 = el_box.content[0].y + end.y; + await page.mouse.move(x1, y1); + await page.mouse.down(); + if (lasso) { + await page.mouse.move(x2, y1); + await page.mouse.move(x2, y2); + await page.mouse.move(x1, y2); + await page.mouse.move(x1, y1); + } else { + await page.mouse.move(x2, y2); + } + await page.mouse.up(); +}; + +describe("did launch", () => { + test("page launched", async () => { + await page.goto(appUrlBase); + let el = await getOneElementInnerHTML("[data-testid='header']"); + expect(el).toBe("cellxgene: pbmc3k"); + }); +}); + +describe("search for genes", () => { + test("search for known gene and add to metadata", async () => { + await page.goto(appUrlBase); + await page.waitForSelector("[ data-testid='gene-search']"); + // blueprint's typeahead is treating typing weird, clicking & waiting first solves this + await page.click("[data-testid='gene-search']"); + await page.waitFor(200); + await page.type("[data-testid='gene-search']", "ACD"); + await page.keyboard.press("Enter"); + await page.waitForSelector("[data-testid='histogram-ACD']"); + }); +}); + +describe("select cells and diffexp", () => { + test("selects cells from layout and adds to cell set 1", async () => { + await page.goto(appUrlBase); + const layout = await page.waitForSelector("[data-testid='layout']"); + const size = await layout.boxModel(); + const cellset1 = { + start: { + x: Math.floor(size.width * 0.25), + y: Math.floor(size.height * 0.25) + }, + end: { + x: Math.floor(size.width * 0.35), + y: Math.floor(size.height * 0.35) + } + }; + await drag(size, cellset1.start, cellset1.end, true); + await page.click("[data-testid='cellset-button-1"); + let button = await getOneElementInnerHTML("[data-testid='cellset-button-1"); + expect(button).toMatch(/26 cells/); + }); + + test("selects cells from layout and adds to cell set 2", async () => { + await page.goto(appUrlBase); + const layout = await page.waitForSelector("[data-testid='layout']"); + const size = await layout.boxModel(); + const cellset2 = { + start: { + x: Math.floor(size.width * 0.45), + y: Math.floor(size.height * 0.45) + }, + end: { + x: Math.floor(size.width * 0.55), + y: Math.floor(size.height * 0.55) + } + }; + await drag(size, cellset2.start, cellset2.end, true); + await page.click("[data-testid='cellset-button-2"); + let button = await getOneElementInnerHTML("[data-testid='cellset-button-2"); + expect(button).toMatch(/49 cells/); + }); + + test("selects cells, saves them and performs diffexp", async () => { + await page.goto(appUrlBase); + const layout = await page.waitForSelector("[data-testid='layout']"); + const size = await layout.boxModel(); + const cellset1 = { + start: { + x: Math.floor(size.width * 0.25), + y: Math.floor(size.height * 0.25) + }, + end: { + x: Math.floor(size.width * 0.35), + y: Math.floor(size.height * 0.35) + } + }; + await drag(size, cellset1.start, cellset1.end, true); + await page.click("[data-testid='cellset-button-1"); + const cellset2 = { + start: { + x: Math.floor(size.width * 0.45), + y: Math.floor(size.height * 0.45) + }, + end: { + x: Math.floor(size.width * 0.55), + y: Math.floor(size.height * 0.55) + } + }; + await drag(size, cellset2.start, cellset2.end, true); + await page.click("[data-testid='cellset-button-2"); + await page.click("[data-testid='diffexp-button"); + await page.waitForSelector("[data-testclass='histogram-diffexp']"); + const diffexps = await page.$$eval( + "[data-testclass='histogram-diffexp']", + divs => { + return divs.map(div => + div.id.substring("histogram-".length, div.id.length) + ); + } + ); + expect(diffexps).toMatchObject([ + "HLA-DPA1", + "HLA-DQA1", + "HLA-DRB1", + "HLA-DMA", + "CST3", + "HLA-DPB1", + "HLA-DQB1", + "LGALS2", + "FCER1A", + "LTB" + ]); + }); +}); + +describe("brushable histogram", () => { + test("can brush historgram", async () => { + await page.goto(appUrlBase); + const hist = await page.waitForSelector( + "[data-testid='histogram_n_genes_svg-brush'] > .overlay" + ); + const hist_size = await hist.boxModel(); + const draghist = { + start: { + x: Math.floor(hist_size.width * 0.25), + y: Math.floor(hist_size.height * 0.5) + }, + end: { + x: Math.floor(hist_size.width * 0.55), + y: Math.floor(hist_size.height * 0.5) + } + }; + await drag(hist_size, draghist.start, draghist.end); + }); +}); diff --git a/client/package-lock.json b/client/package-lock.json index 4dab4c57..cd365c89 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1296,6 +1296,15 @@ "integrity": "sha512-OtUw6JUTgxA2QoqqmrmQ7F2NYqiBPi/L2jqHyFtllhOUvXYQXf0Z1CYUinIfyT4bTCGmrA7gX9FvHA81uzCoVw==", "dev": true }, + "agent-base": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", + "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==", + "dev": true, + "requires": { + "es6-promisify": "^5.0.0" + } + }, "ajv": { "version": "6.5.4", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.4.tgz", @@ -2807,6 +2816,12 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, + "check-more-types": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz", + "integrity": "sha1-FCD/sQ/URNz8ebQ4kbv//TKoRgA=", + "dev": true + }, "chokidar": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", @@ -4118,6 +4133,12 @@ "is-obj": "^1.0.0" } }, + "duplexer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", + "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", + "dev": true + }, "duplexer3": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", @@ -4278,6 +4299,15 @@ "integrity": "sha512-n6wvpdE43VFtJq+lUDYDBFUwV8TZbuGXLV4D6wKafg13ldznKsyEvatubnmUe31zcvelSzOHF+XbaT+Bl9ObDg==", "dev": true }, + "es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "dev": true, + "requires": { + "es6-promise": "^4.0.3" + } + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -4767,6 +4797,21 @@ "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", "dev": true }, + "event-stream": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", + "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", + "dev": true, + "requires": { + "duplexer": "~0.1.1", + "from": "~0", + "map-stream": "~0.1.0", + "pause-stream": "0.0.11", + "split": "0.3", + "stream-combiner": "~0.0.4", + "through": "~2.3.1" + } + }, "events": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", @@ -4963,6 +5008,35 @@ "is-extglob": "^1.0.0" } }, + "extract-zip": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.7.tgz", + "integrity": "sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k=", + "dev": true, + "requires": { + "concat-stream": "1.6.2", + "debug": "2.6.9", + "mkdirp": "0.5.1", + "yauzl": "2.4.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", @@ -5023,6 +5097,15 @@ } } }, + "fd-slicer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", + "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", + "dev": true, + "requires": { + "pend": "~1.2.0" + } + }, "figures": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", @@ -5229,6 +5312,12 @@ "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", "dev": true }, + "from": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", + "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", + "dev": true + }, "from2": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", @@ -6127,6 +6216,12 @@ "minimalistic-crypto-utils": "^1.0.1" } }, + "hoek": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-5.0.4.tgz", + "integrity": "sha512-Alr4ZQgoMlnere5FZJsIyfIjORBqZll5POhDsF4q64dPuJR6rNxXdDxtHSQq8OXRurhmx+PWYEE8bXRROY8h0w==", + "dev": true + }, "hoist-non-react-statics": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.1.0.tgz", @@ -6295,6 +6390,16 @@ "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", "dev": true }, + "https-proxy-agent": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz", + "integrity": "sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==", + "dev": true, + "requires": { + "agent-base": "^4.1.0", + "debug": "^3.1.0" + } + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -6825,6 +6930,15 @@ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true }, + "isemail": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/isemail/-/isemail-3.2.0.tgz", + "integrity": "sha512-zKqkK+O+dGqevc93KNsbZ/TqTUFd46MwWjYOoMrjIMZ51eU7DtQG3Wmd9SQQT7i7RVnuTPEiYEWHU3MSbxC1Tg==", + "dev": true, + "requires": { + "punycode": "2.x.x" + } + }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -9078,6 +9192,17 @@ } } }, + "joi": { + "version": "13.7.0", + "resolved": "https://registry.npmjs.org/joi/-/joi-13.7.0.tgz", + "integrity": "sha512-xuY5VkHfeOYK3Hdi91ulocfuFopwgbSORmIwzcwHKESQhC7w1kD5jaVSPnqDxS2I8t3RZ9omCKAxNwXN5zG1/Q==", + "dev": true, + "requires": { + "hoek": "5.x.x", + "isemail": "3.x.x", + "topo": "3.x.x" + } + }, "js-levenshtein": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.4.tgz", @@ -9240,6 +9365,12 @@ "package-json": "^4.0.0" } }, + "lazy-ass": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz", + "integrity": "sha1-eZllXoZGwX8In90YfRUNMyTVRRM=", + "dev": true + }, "lcid": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", @@ -9521,6 +9652,12 @@ "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", "dev": true }, + "map-stream": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", + "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=", + "dev": true + }, "map-visit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", @@ -10533,6 +10670,15 @@ } } }, + "pause-stream": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", + "dev": true, + "requires": { + "through": "~2.3" + } + }, "pbkdf2": { "version": "3.0.17", "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", @@ -10546,6 +10692,12 @@ "sha.js": "^2.4.8" } }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true + }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -10791,12 +10943,27 @@ "ipaddr.js": "1.8.0" } }, + "proxy-from-env": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", + "integrity": "sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4=", + "dev": true + }, "prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", "dev": true }, + "ps-tree": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.2.0.tgz", + "integrity": "sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==", + "dev": true, + "requires": { + "event-stream": "=3.3.4" + } + }, "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", @@ -10850,6 +11017,48 @@ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, + "puppeteer": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-1.12.2.tgz", + "integrity": "sha512-xWSyCeD6EazGlfnQweMpM+Hs6X6PhUYhNTHKFj/axNZDq4OmrVERf70isBf7HsnFgB3zOC1+23/8+wCAZYg+Pg==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "extract-zip": "^1.6.6", + "https-proxy-agent": "^2.2.1", + "mime": "^2.0.3", + "progress": "^2.0.1", + "proxy-from-env": "^1.0.0", + "rimraf": "^2.6.1", + "ws": "^6.1.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "mime": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.0.tgz", + "integrity": "sha512-ikBcWwyqXQSHKtciCcctu9YfPbFYZ4+gbHEmE0Q8jzcTYQg5dHCr3g2wwAZjPoJfQVXZq6KXAjpXOTf5/cjT7w==", + "dev": true + }, + "ws": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.4.tgz", + "integrity": "sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==", + "dev": true, + "requires": { + "async-limiter": "~1.0.0" + } + } + } + }, "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", @@ -11817,6 +12026,12 @@ "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", "integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q=" }, + "rx": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/rx/-/rx-4.1.0.tgz", + "integrity": "sha1-pfE/957zt0D+MKqAP7CfmIBdR4I=", + "dev": true + }, "rxjs": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz", @@ -12637,6 +12852,15 @@ "integrity": "sha512-TfOfPcYGBB5sDuPn3deByxPhmfegAhpDYKSOXZQN81Oyrrif8ZCodOLzK3AesELnCx03kikhyDwh0pfvvQvF8w==", "dev": true }, + "split": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", + "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", + "dev": true, + "requires": { + "through": "2" + } + }, "split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", @@ -12684,6 +12908,63 @@ "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==", "dev": true }, + "start-server-and-test": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/start-server-and-test/-/start-server-and-test-1.7.11.tgz", + "integrity": "sha512-651SCOfhPT65Xjhecvx/ZMJs8UOd5VItjjmpYH95aM6Hr4P8N8UIcxEgDhY/aaVmthACH7qFTqs4EA/KHykjtw==", + "dev": true, + "requires": { + "bluebird": "3.5.3", + "check-more-types": "2.24.0", + "debug": "3.2.6", + "execa": "0.11.0", + "lazy-ass": "1.6.0", + "ps-tree": "1.2.0", + "wait-on": "3.2.0" + }, + "dependencies": { + "bluebird": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.3.tgz", + "integrity": "sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw==", + "dev": true + }, + "execa": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.11.0.tgz", + "integrity": "sha512-k5AR22vCt1DcfeiRixW46U5tMLtBg44ssdJM9PiXw3D8Bn5qyxFCSnKY/eR22y+ctFDGPqafpaXg2G4Emyua4A==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, "static-extend": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", @@ -12727,6 +13008,15 @@ "readable-stream": "^2.0.2" } }, + "stream-combiner": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", + "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", + "dev": true, + "requires": { + "duplexer": "~0.1.1" + } + }, "stream-each": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", @@ -13174,6 +13464,23 @@ } } }, + "topo": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/topo/-/topo-3.0.3.tgz", + "integrity": "sha512-IgpPtvD4kjrJ7CRA3ov2FhWQADwv+Tdqbsf1ZnPUSAtCJ9e1Z44MmoSGDXGk4IppoZA7jd/QRkNddlLJWlUZsQ==", + "dev": true, + "requires": { + "hoek": "6.x.x" + }, + "dependencies": { + "hoek": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-6.1.2.tgz", + "integrity": "sha512-6qhh/wahGYZHFSFw12tBbJw5fsAhhwrrG/y3Cs0YMTv2WzMnL0oLPnQJjv1QJvEfylRSOFuP+xCu+tdx0tD16Q==", + "dev": true + } + } + }, "toposort": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/toposort/-/toposort-1.0.7.tgz", @@ -13695,6 +14002,27 @@ "browser-process-hrtime": "^0.1.2" } }, + "wait-on": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-3.2.0.tgz", + "integrity": "sha512-QUGNKlKLDyY6W/qHdxaRlXUAgLPe+3mLL/tRByHpRNcHs/c7dZXbu+OnJWGNux6tU1WFh/Z8aEwvbuzSAu79Zg==", + "dev": true, + "requires": { + "core-js": "^2.5.7", + "joi": "^13.0.0", + "minimist": "^1.2.0", + "request": "^2.88.0", + "rx": "^4.1.0" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + }, "walker": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", @@ -14575,6 +14903,15 @@ "dev": true } } + }, + "yauzl": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz", + "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=", + "dev": true, + "requires": { + "fd-slicer": "~1.0.1" + } } } } diff --git a/client/package.json b/client/package.json index 332f2481..82e6a3f1 100644 --- a/client/package.json +++ b/client/package.json @@ -7,11 +7,14 @@ "scripts": { "backend-dev": "python3.6 -m venv cellxgene && source cellxgene/bin/activate && yes | pip uninstall cellxgene || true && pip install -e .. && cellxgene launch ", "build": "npm run clean && webpack --config configuration/webpack/webpack.config.prod.js", - "dev": "npm run clean && webpack --config configuration/webpack/webpack.config.dev.js", "clean": "rimraf build", - "start": "node server/development.js", + "dev": "npm run clean && webpack --config configuration/webpack/webpack.config.dev.js", + "e2e": "jest e2e", "lint": "eslint src", - "test": "jest" + "smoke-test": "start-server-and-test start-server-for-test :5000 e2e", + "start": "node server/development.js", + "start-server-for-test": "cellxgene launch -p 5000 ../example-dataset/pbmc3k.h5ad", + "unit-test": "jest --testPathIgnorePatterns e2e" }, "engineStrict": true, "engines": { @@ -97,8 +100,10 @@ "jest": "^24.1.0", "json-loader": "^0.5.4", "mini-css-extract-plugin": "^0.4.1", + "puppeteer": "^1.12.1", "rimraf": "^2.6.3", "serve-favicon": "^2.3.0", + "start-server-and-test": "^1.7.11", "style-loader": "^0.23.1", "sw-precache-webpack-plugin": "^0.11.5", "url-loader": "^1.1.0", diff --git a/client/src/components/brushableHistogram/index.js b/client/src/components/brushableHistogram/index.js index 800880db..19bf0f73 100644 --- a/client/src/components/brushableHistogram/index.js +++ b/client/src/components/brushableHistogram/index.js @@ -237,6 +237,7 @@ class HistogramBrush extends React.Component { d3.select(svgRef) .append("g") .attr("class", "brush") + .attr("data-testid", `${svgRef.id}-brush`) .call( d3 .brushX() @@ -281,6 +282,8 @@ class HistogramBrush extends React.Component { return (