diff --git a/packages/locuszoom/.gitignore b/packages/locuszoom/.gitignore index 8c785b3e..0c5b4094 100644 --- a/packages/locuszoom/.gitignore +++ b/packages/locuszoom/.gitignore @@ -15,4 +15,12 @@ node_modules npm-debug.log* pnpm-debug.log* yarn-debug.log* -yarn-error.log* \ No newline at end of file +yarn-error.log* + +# Playwright +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ +/playwright/.auth/ +/.github/ diff --git a/packages/locuszoom/e2e/example.spec.js b/packages/locuszoom/e2e/example.spec.js new file mode 100644 index 00000000..cafb7f6d --- /dev/null +++ b/packages/locuszoom/e2e/example.spec.js @@ -0,0 +1,27 @@ +// @ts-check +import { test, expect } from '@playwright/test'; + +test('Run plot', async ({ page }) => { + + // Test variables + + const URL = 'http://localhost:5173/'; + const CHR_INPUT = 'raya560ne.2.1.2'; // a chromosome + const POINT = '#lz-plot_association_associationpvalues_-RAYA560NE21261284_TC' // a point in the plot + const POINT_LOCATOR = page.locator(POINT); + const PHENO_STAT = '×phenRAYA560NE.2.1.2:61284_T/'; // stats' table for the point + const PHENO_STAT_TO_CLICK = page.getByText(PHENO_STAT) + + // Test execution + + await page.goto(URL); + await page.getByRole('button').click(); + await page.getByRole('textbox', { name: 'Please Input' }).nth(1).fill(CHR_INPUT); + await expect(POINT_LOCATOR).toBeVisible(); + await POINT_LOCATOR.click(); + await PHENO_STAT_TO_CLICK.click(); + await expect(PHENO_STAT_TO_CLICK).toBeVisible(); + await page.getByRole('button', { name: '×' }).click(); + await expect(PHENO_STAT_TO_CLICK).toBeHidden(); +}); + diff --git a/packages/locuszoom/package.json b/packages/locuszoom/package.json index 08e5feea..67697eb0 100644 --- a/packages/locuszoom/package.json +++ b/packages/locuszoom/package.json @@ -1,35 +1,38 @@ { - "name": "@galaxyproject/locuszoom", - "version": "0.0.7", - "type": "module", - "license": "MIT", - "files": [ - "static" - ], - "scripts": { - "dev": "vite", - "build": "vite build", - "preview": "vite preview", - "prettier": "prettier --config ./prettier.config.js --write 'package.json' '**/*.{js,jsx,ts,tsx,json,css,md,vue}'", - "test": "vitest --run", - "test:watch": "vitest --watch", - "test:ui": "vitest --ui" - }, - "devDependencies": { - "@types/node": "^22.9.0", - "@vitejs/plugin-vue": "^5.0.5", - "@vitest/ui": "^3.0.9", - "autoprefixer": "^10.4.19", - "galaxy-charts": "^0.0.73", - "galaxy-charts-xml-parser": "^1.0.3", - "jsdom": "^25.0.1", - "locuszoom": "^0.14.0", - "postcss": "^8.4.40", - "prettier": "^3.3.3", - "tailwindcss": "^3.4.7", - "typescript": "^5.5.4", - "vite": "^6.2.2", - "vitest": "^3.0.9", - "vue": "^3.4.31" - } + "name": "@galaxyproject/locuszoom", + "version": "0.0.8", + "type": "module", + "license": "MIT", + "files": [ + "static" + ], + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "prettier": "prettier --config ./prettier.config.js --write 'package.json' '**/*.{js,jsx,ts,tsx,json,css,md,vue}'", + "test": "npm run test:unit && npm run test:e2e", + "test:e2e": "npx playwright test", + "test:unit": "vitest run", + "test:watch": "vitest --watch", + "test:ui": "vitest --ui" + }, + "devDependencies": { + "@playwright/test": "^1.58.0", + "@types/node": "^22.9.0", + "@vitejs/plugin-vue": "^5.0.5", + "@vitest/ui": "^3.0.9", + "autoprefixer": "^10.4.19", + "galaxy-charts": "^0.0.73", + "galaxy-charts-xml-parser": "^1.0.3", + "jsdom": "^25.0.1", + "locuszoom": "^0.14.0", + "postcss": "^8.4.40", + "prettier": "^3.3.3", + "tailwindcss": "^3.4.7", + "typescript": "^5.5.4", + "vite": "^6.2.2", + "vitest": "^3.0.9", + "vue": "^3.4.31" + } } diff --git a/packages/locuszoom/playwright.config.js b/packages/locuszoom/playwright.config.js new file mode 100644 index 00000000..307c3d9e --- /dev/null +++ b/packages/locuszoom/playwright.config.js @@ -0,0 +1,10 @@ +import { defineConfig } from "@playwright/test"; + +export default defineConfig({ + testIgnore: ["src/**"], + timeout: 120000, // 120 seconds per test + use: { + headless: !!process.env.CI, + }, + snapshotPathTemplate: "{testDir}/test-data/{arg}.png", +}); diff --git a/packages/locuszoom/public/locuszoom.xml b/packages/locuszoom/public/locuszoom.xml index 4ed12579..bad432dc 100644 --- a/packages/locuszoom/public/locuszoom.xml +++ b/packages/locuszoom/public/locuszoom.xml @@ -21,9 +21,7 @@ chromosome - integer - 1 - 1 + string @@ -97,7 +95,7 @@ - + ]]> diff --git a/packages/locuszoom/src/Plugin.vue b/packages/locuszoom/src/Plugin.vue index c9255ed8..b0950888 100644 --- a/packages/locuszoom/src/Plugin.vue +++ b/packages/locuszoom/src/Plugin.vue @@ -10,6 +10,11 @@ LocusZoom.use(LzTabixSource); const TabixUrlSource = LocusZoom.Adapters.get("TabixUrlSource"); +// Test files paths + +const BGZIP_PRIMARY_DATASET_PATH = "http://localhost:5173/test-data/weird.gwas_bgzip"; +const TABIX_SECONDARY_DATASET_PATH = "http://localhost:5173/test-data/weird.gwas_bgzip.tbi"; + // Patch URLFetchable prototype by accessing it through a reader instance let hasURLFetchablePrototypeBeenPatched = false; @@ -160,7 +165,31 @@ const props = defineProps({ const errorMessage = ref(""); +// Functions to retrieve URLs + +function getPrimaryURL(root,primaryID) { + if (primaryID==="__test__"){ + return BGZIP_PRIMARY_DATASET_PATH; + } + else { + return `${root}api/datasets/${primaryID}/display`; + } + +} + +function getSecondaryURL(root,primaryID, secondaryID) { + if (primaryID==="__test__"){ + return TABIX_SECONDARY_DATASET_PATH; + } + else { + return `${root}api/datasets/${secondaryID}/display`; + } + +} + function render() { + const bgzipURL = getPrimaryURL(props.root,props.datasetId); + const tabixURL = getSecondaryURL(props.root,props.datasetId, props.settings.tabix?.id); const id = props.settings.tabix?.id; const chrIn = props.settings.chromosome; const startIn = props.settings.start; @@ -173,7 +202,7 @@ function render() { const is_neg_log_pvalue = props.settings.is_neg_log_pvalue; const beta_col = props.settings.beta_col; const stderr_beta_col = props.settings.stderr_beta_col; - if (!id) { + if (tabixURL.includes("/api/datasets/undefined/display")) { errorMessage.value = "Please select a Tabix file."; return; } @@ -199,8 +228,8 @@ function render() { let data_sources = new LocusZoom.DataSources().add("assoc", [ "TabixUrlSource", { - url_data: `${props.root}api/datasets/${props.datasetId}/display`, - url_tbi: `${props.root}api/datasets/${id}/display`, + url_data: bgzipURL, + url_tbi: tabixURL, parser_func: gwasParser, overfetch: 0, }, diff --git a/packages/locuszoom/src/main.js b/packages/locuszoom/src/main.js index 75a95b77..93fa5e61 100644 --- a/packages/locuszoom/src/main.js +++ b/packages/locuszoom/src/main.js @@ -11,10 +11,10 @@ async function main() { // Construct the incoming data object with mock configuration and data const dataIncoming = { visualization_config: { - dataset_id: "b620c45fba703209", // id of primary_dataset.bgzip + dataset_id: process.env.dataset_id || "__test__", // id of primary_dataset.bgzip // Placeholder for additional visualization settings settings: { - tabix: { id: "abd164196b68b912" }, // id of secondary_dataset.tbi + tabix: {}, // id of secondary_dataset.tbi }, }, // Parse and load the visualization XML configuration diff --git a/packages/locuszoom/test-data/weird.gwas_bgzip b/packages/locuszoom/test-data/weird.gwas_bgzip new file mode 100644 index 00000000..1ab67f8b Binary files /dev/null and b/packages/locuszoom/test-data/weird.gwas_bgzip differ diff --git a/packages/locuszoom/test-data/weird.gwas_bgzip.tbi b/packages/locuszoom/test-data/weird.gwas_bgzip.tbi new file mode 100644 index 00000000..547826f6 Binary files /dev/null and b/packages/locuszoom/test-data/weird.gwas_bgzip.tbi differ diff --git a/packages/locuszoom/yarn.lock b/packages/locuszoom/yarn.lock index 1b3e556a..623dc993 100644 --- a/packages/locuszoom/yarn.lock +++ b/packages/locuszoom/yarn.lock @@ -42,10 +42,10 @@ "@babel/helper-validator-identifier" "^7.24.7" to-fast-properties "^2.0.0" -"@esbuild/darwin-arm64@0.25.1": +"@esbuild/linux-x64@0.25.1": version "0.25.1" - resolved "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.1.tgz" - integrity sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ== + resolved "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.1.tgz" + integrity sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA== "@hapi/hoek@^9.0.0": version "9.3.0" @@ -129,15 +129,22 @@ resolved "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz" integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== +"@playwright/test@^1.58.0": + version "1.58.0" + resolved "https://registry.npmjs.org/@playwright/test/-/test-1.58.0.tgz" + integrity sha512-fWza+Lpbj6SkQKCrU6si4iu+fD2dD3gxNHFhUPxsfXBPhnv3rRSQVd0NtBUT9Z/RhF/boCBcuUaMUSTRTopjZg== + dependencies: + playwright "1.58.0" + "@polka/url@^1.0.0-next.24": version "1.0.0-next.28" resolved "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.28.tgz" integrity sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw== -"@rollup/rollup-darwin-arm64@4.35.0": +"@rollup/rollup-linux-x64-gnu@4.35.0": version "4.35.0" - resolved "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.35.0.tgz" - integrity sha512-Uk+GjOJR6CY844/q6r5DR/6lkPFOw0hjfOIzVx22THJXMxktXG6CbejseJFznU8vHcEBLpiXKY3/6xc+cBm65Q== + resolved "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.35.0.tgz" + integrity sha512-Pim1T8rXOri+0HmV4CdKSGrqcBWX0d1HoPnQ0uw0bdp1aP5SdQVNBy8LjYncvnLgu3fnnCt17xjWGd4cqh8/hA== "@types/estree@^1.0.0", "@types/estree@1.0.6": version "1.0.6" @@ -1320,11 +1327,6 @@ fs.realpath@^1.0.0: resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -fsevents@~2.3.2, fsevents@~2.3.3: - version "2.3.3" - resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz" - integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== - function-bind@^1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" @@ -1660,7 +1662,7 @@ json-stable-stringify-without-jsonify@^1.0.1: "jszlib@git+https://github.com/dasmoth/jszlib.git#4e562c7": version "0.2.1" resolved "git+ssh://git@github.com/dasmoth/jszlib.git" - integrity sha512-L+slw277qqgqWvyz6srOUOqHnbuyLBGPkgzM7IgxCTb/MVTNiy11iQ15mS+dO2f8UIjKBb4z8AgO3vshLrjXVg== + integrity sha512-uvBAyIcKCRxAWN6fpZMzC5GOrFzWsTehZW6DkRn9RKfJ4QcSsHZFqnlFmHQOxSIkO4tWQ0FxGLNKJQ1pKy3PeA== just-clone@^3.2.1: version "3.2.1" @@ -1976,6 +1978,20 @@ pirates@^4.0.1: resolved "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz" integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== +playwright-core@1.58.0: + version "1.58.0" + resolved "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.0.tgz" + integrity sha512-aaoB1RWrdNi3//rOeKuMiS65UCcgOVljU46At6eFcOFPFHWtd2weHRRow6z/n+Lec0Lvu0k9ZPKJSjPugikirw== + +playwright@1.58.0: + version "1.58.0" + resolved "https://registry.npmjs.org/playwright/-/playwright-1.58.0.tgz" + integrity sha512-2SVA0sbPktiIY/MCOPX8e86ehA/e+tDNq+e5Y8qjKYti2Z/JG7xnronT/TXTIkKbYGWlCbuucZ6dziEgkoEjQQ== + dependencies: + playwright-core "1.58.0" + optionalDependencies: + fsevents "2.3.2" + postcss-import@^15.1.0: version "15.1.0" resolved "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz"