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"