Skip to content

Commit 0d6ec57

Browse files
authored
Update packages to address GHSA-mh29-5h37-fv8m & add Jest, ESlint, Playwright and Ruff support (#43)
When I was just updating the npm packages to address GHSA-mh29-5h37-fv8m (https://github.com/BioAnalyticResource/eFP-Seq_Browser/security/dependabot/27), I notice there was no tests or code quality checks to ensure reliability and confidence when making code changes so in addition to just updating packages, also added JavaScript Jest unit tests, ESLint linting, and Playwright e2e tests & Python ruff linting and formatting. **CI/CD Workflow Improvements** - Renamed and updated GitHub Actions workflow files for JavaScript, R, and Markdown to use a consistent `code-qa-*` naming scheme, updated file triggers, and upgraded action versions for improved maintainability and clarity. - Added a new Python QA workflow (`code-qa-python.yaml`) with steps for dependency installation, formatting, linting, and running tests using modern tools (`uv`, `ruff`, `pytest`). - Upgraded CodeQL workflow actions to v4 for enhanced security analysis. **Testing Enhancements** - Added Jest-based unit tests for `cgi-bin/core/custom.js` and `cgi-bin/Submission_page/XMLgenerator.js`, covering DOM manipulation functions and color generation logic for more robust JavaScript code quality. **Developer Experience and Tooling** - Expanded `.prettierignore` to exclude Python, Playwright, and test output directories, reducing noise in formatting checks. - Added a `validate-all` target and new Python validation commands to the `Makefile` for unified project validation across languages. **Minor Code Modernization** - Minor code cleanup in `custom.js` for improved readability.
1 parent 6e14dd7 commit 0d6ec57

File tree

66 files changed

+12954
-4624
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+12954
-4624
lines changed
Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Code Quality Assurance
1+
name: Code Quality Assurance - JavaScript
22

33
on:
44
push:
@@ -9,7 +9,7 @@ on:
99
- "**/*.js"
1010
- "**/*.css"
1111
- "**/*.html"
12-
- ".github/workflows/code-qa.yaml"
12+
- ".github/workflows/code-qa-js.yaml"
1313
pull_request:
1414
branches: [main]
1515
paths:
@@ -18,7 +18,7 @@ on:
1818
- "**/*.js"
1919
- "**/*.css"
2020
- "**/*.html"
21-
- ".github/workflows/code-qa.yaml"
21+
- ".github/workflows/code-qa-js.yaml"
2222

2323
permissions:
2424
contents: read
@@ -29,13 +29,12 @@ jobs:
2929
strategy:
3030
matrix:
3131
node-version: [22.x]
32-
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
3332

3433
steps:
3534
- name: Checkout repository
3635
uses: actions/checkout@v5
3736
- name: Use Node.js ${{ matrix.node-version }}
38-
uses: actions/setup-node@v4
37+
uses: actions/setup-node@v6
3938
with:
4039
node-version: ${{ matrix.node-version }}
4140
- name: Cache Node.js modules
@@ -45,7 +44,18 @@ jobs:
4544
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
4645
restore-keys: |
4746
${{ runner.os }}-node-
47+
4848
- name: Check installs
4949
run: npm ci
50+
- name: Install Playwright Browsers
51+
run: npx playwright install --with-deps
52+
5053
- name: Quality check - prettier
5154
run: npm run prettier:check
55+
- name: Lint check - ESLint
56+
run: npm run eslint:check
57+
58+
- name: Unit tests - jest
59+
run: npm run test:jest
60+
- name: E2E tests - Playwright
61+
run: npm run test:playwright:headless
Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Markdown Lint
1+
name: Code Quality Assurance - Markdown
22

33
on:
44
push:
@@ -7,26 +7,30 @@ on:
77
- "**/*.md"
88
- ".markdownlint.json"
99
- ".markdownlintignore"
10-
- ".github/workflows/markdown-lint.yml"
10+
- ".github/workflows/code-qa-markdown.yaml"
1111
pull_request:
1212
branches: [main]
1313
paths:
1414
- "**/*.md"
1515
- ".markdownlint.json"
1616
- ".markdownlintignore"
17-
- ".github/workflows/markdown-lint.yml"
17+
- ".github/workflows/code-qa-markdown.yaml"
1818

1919
permissions:
2020
contents: read
2121
jobs:
2222
lint-markdown:
2323
runs-on: ubuntu-latest
2424

25+
strategy:
26+
matrix:
27+
node-version: [22.x]
28+
2529
steps:
2630
- name: Checkout repository
2731
uses: actions/checkout@v5
2832
- name: Use Node.js ${{ matrix.node-version }}
29-
uses: actions/setup-node@v4
33+
uses: actions/setup-node@v6
3034
with:
3135
node-version: ${{ matrix.node-version }}
3236
- name: Cache Node.js modules
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
name: Code Quality Assurance - Python
2+
3+
on:
4+
push:
5+
branches: [main]
6+
paths:
7+
- "cgi-bin/**/*.cgi"
8+
- "requirements*.txt"
9+
- ".github/workflows/code-qa-python.yaml"
10+
pull_request:
11+
branches: [main]
12+
paths:
13+
- "cgi-bin/**/*.cgi"
14+
- "requirements*.txt"
15+
- ".github/workflows/code-qa-python.yaml"
16+
17+
permissions:
18+
contents: read
19+
jobs:
20+
build:
21+
runs-on: ubuntu-latest
22+
23+
strategy:
24+
matrix:
25+
python-version: [3.13]
26+
27+
steps:
28+
- name: Checkout code
29+
uses: actions/checkout@v4
30+
31+
- name: Set up Python ${{ matrix.python-version }}
32+
uses: actions/setup-python@v6
33+
with:
34+
python-version: ${{ matrix.python-version }}
35+
36+
- name: Install uv
37+
uses: astral-sh/setup-uv@v5
38+
with:
39+
version: "latest"
40+
41+
- name: Cache Python packages
42+
uses: actions/cache@v4
43+
with:
44+
path: |
45+
~/.cache/uv
46+
~/.cache/pip
47+
key: ${{ runner.os }}-python-${{ hashFiles('**/requirements*.txt') }}
48+
restore-keys: |
49+
${{ runner.os }}-python-
50+
51+
- name: Create virtual environment
52+
run: uv venv
53+
54+
- name: Install dependencies
55+
run: make py-install
56+
57+
- name: Format check - Ruff
58+
run: uv run ruff format cgi-bin/*.cgi
59+
60+
- name: Lint check - Ruff
61+
run: uv run ruff check --fix cgi-bin/*.cgi
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: R Quality Assurance
1+
name: Code Quality Assurance - R
22

33
on:
44
push:
@@ -7,14 +7,14 @@ on:
77
paths:
88
- "**/*.Rmd"
99
- "**/*.R"
10-
- ".github/workflows/r.yaml"
10+
- ".github/workflows/code-qa-r.yaml"
1111
pull_request:
1212
branches:
1313
- main
1414
paths:
1515
- "**/*.Rmd"
1616
- "**/*.R"
17-
- ".github/workflows/r.yaml"
17+
- ".github/workflows/code-qa-r.yaml"
1818

1919
permissions:
2020
contents: read

.github/workflows/codeql-analysis.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ jobs:
5555

5656
# Initializes the CodeQL tools for scanning.
5757
- name: Initialize CodeQL
58-
uses: github/codeql-action/init@v3
58+
uses: github/codeql-action/init@v4
5959
with:
6060
languages: ${{ matrix.language }}
6161
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -72,7 +72,7 @@ jobs:
7272
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
7373
# If this step fails, then you should remove it and run the build manually (see below)
7474
- name: Autobuild
75-
uses: github/codeql-action/autobuild@v3
75+
uses: github/codeql-action/autobuild@v4
7676

7777
# ℹ️ Command-line programs to run using the OS shell.
7878
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
@@ -85,6 +85,6 @@ jobs:
8585
# ./location_of_script_within_repo/buildscript.sh
8686

8787
- name: Perform CodeQL Analysis
88-
uses: github/codeql-action/analyze@v3
88+
uses: github/codeql-action/analyze@v4
8989
with:
9090
category: "/language:${{matrix.language}}"

.gitignore

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,18 @@ renv/.gitignore
3636

3737
# Python virtual environment
3838
venv/
39+
__pycache__/
40+
.mypy_cache/
41+
.pytest_cache/
42+
.ruff_cache/
43+
.venv/
44+
45+
# Test output
46+
coverage/
47+
48+
# Playwright
49+
/test-results/
50+
/playwright-report/
51+
/blob-report/
52+
/playwright/.cache/
53+
/playwright/.auth/

.prettierignore

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,22 @@ renv/lock
6060
renv/python
6161
renv/sandbox
6262
renv/staging
63+
renv/.gitignore
6364

6465
# Python virtual environment
6566
venv/
67+
__pycache__/
68+
.mypy_cache/
69+
.pytest_cache/
70+
.ruff_cache/
71+
.venv/
72+
73+
# Test output
74+
coverage/
75+
76+
# Playwright
77+
/test-results/
78+
/playwright-report/
79+
/blob-report/
80+
/playwright/.cache/
81+
/playwright/.auth/

Makefile

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Validate all
2+
.PHONY: validate-all
3+
validate-all: py-validate
4+
# Validate JavaScript/TypeScript
5+
@if [ ! -d node_modules ]; then npm ci; fi
6+
npm run validate
7+
# Validate R installation
8+
Rscript -e 'renv::restore()'
9+
10+
# === Python Commands for eFP-Seq_Browser ===
11+
.PHONY: py-install py-format py-lint py-test py-clean py-validate
12+
13+
PY_FILES = cgi-bin/*.cgi
14+
15+
py-install:
16+
uv pip install -r requirements-dev.txt
17+
18+
py-format:
19+
uv run ruff format $(PY_FILES)
20+
21+
py-lint:
22+
uv run ruff check --fix $(PY_FILES)
23+
24+
py-validate: py-install py-format py-lint
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/**
2+
* @jest-environment jsdom
3+
*/
4+
5+
const fs = require("fs");
6+
const path = require("path");
7+
8+
describe("update (XMLgenerator.js)", () => {
9+
let update;
10+
let $;
11+
12+
beforeAll(() => {
13+
// Minimal jQuery mock
14+
$ = () => ({
15+
find: (selector) => ({
16+
val: () => {
17+
const map = {
18+
".channelcontrols": "ctrl1, ctrl2",
19+
".channelgroupwidtho": "rep1, rep2",
20+
".channeldescription": "desc",
21+
".channelrecordnumber": "42",
22+
".channelhexcolor": "#ff0000",
23+
".channelbamType": "Google Drive",
24+
".channelbamlink": "https://drive.google.com/file/d/abc",
25+
".channeltotalreadsmapped": "12345",
26+
".channelreadmapmethod": "STAR",
27+
".channelpublicationlink": "https://pub.com/xyz",
28+
".channeltissue": "Leaf",
29+
".channelsvgname": "ath-leaf.svg",
30+
".channeltitle": "Test Title",
31+
".channelsralink": "SRR000001",
32+
".channelspecies": "Arabidopsis",
33+
".channelforeground": "#000000",
34+
".channelfilename": "file1.bam",
35+
};
36+
return map[selector] || "dummy";
37+
},
38+
}),
39+
});
40+
41+
// Robust document.getElementById mock for all ids
42+
const getElementByIdMock = (id) => {
43+
const values = {
44+
reqxml: { value: "TestXML" },
45+
reqauthor: { value: "Author" },
46+
contectinfo: { value: "contact@email.com" },
47+
};
48+
return values[id] || { value: "dummy" };
49+
};
50+
if (global.document) {
51+
global.document.getElementById = getElementByIdMock;
52+
} else {
53+
global.document = { getElementById: getElementByIdMock };
54+
}
55+
global.$ = $;
56+
57+
// Required top-level variables for update()
58+
global.topXML = [
59+
'\t\t<file info="<?channeldescription?>" record_number="<?channelrecordnumber?>" foreground="<?channelforeground?>" hex_colour="<?channelhexcolor?>" bam_type="<?channelbamType?>" name="<?channelbamlink?>" filename="<?channelfilename?>" total_reads_mapped="<?channeltotalreadsmapped?>" read_map_method="<?channelreadmapmethod?>" publication_link="<?channelpublicationlink?>" svg_subunit="<?channeltissue?>" svgname="<?channelsvgname?>" description="<?channeltitle?>" url="<?channelsralink?>" species="<?channelspecies?>" title="<?channeligbtitle?>">',
60+
"\t\t\t<controls>\n",
61+
].join("\r\n");
62+
global.controlsXML = "";
63+
global.replicatesXML = ["\t\t\t</controls>", "\t\t\t<groupwith>\n"].join("\r\n");
64+
global.endingXML = ["\t\t\t</groupwith>", "\t\t</file>", "\n"].join("\r\n");
65+
global.existingXML = "";
66+
global.all_controls = "";
67+
global.all_replicates = "";
68+
69+
// Load the update function from XMLgenerator.js
70+
const code = fs.readFileSync(path.join(__dirname, "../Submission_page/XMLgenerator.js"), "utf8");
71+
const fnMatch = code.match(/function update\s*\(([^)]*)\)\s*{([\s\S]*?)^}/m);
72+
if (!fnMatch) throw new Error("update function not found in XMLgenerator.js");
73+
const args = fnMatch[1];
74+
const body = fnMatch[2];
75+
76+
update = new Function(args, body);
77+
});
78+
79+
it("generates correct XML for given form values", () => {
80+
const v = {}; // dummy, not used in our $ mock
81+
const result = update("", v);
82+
// Check for key XML elements and values
83+
expect(result).toContain('info="desc"');
84+
expect(result).toContain('record_number="42"');
85+
expect(result).toContain('hex_colour="#ff0000"');
86+
expect(result).toContain('bam_type="Google Drive"');
87+
expect(result).toContain('name="https://drive.google.com/file/d/abc"');
88+
expect(result).toContain('filename="file1.bam"');
89+
expect(result).toContain("<bam_exp>ctrl1</bam_exp>");
90+
expect(result).toContain("<bam_exp>ctrl2</bam_exp>");
91+
expect(result).toContain("<bam_exp>rep1</bam_exp>");
92+
expect(result).toContain("<bam_exp>rep2</bam_exp>");
93+
});
94+
});

cgi-bin/core/custom.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,8 @@ let loadNewDataset = false;
7474

7575
/** Used to count and determine how many BAM entries are in the XML file */
7676
let count_bam_entries_in_xml = 113;
77-
/**
78-
* Count the amount of entries in a BAM file
79-
*/
77+
78+
/** Count the amount of entries in a BAM file */
8079
function count_bam_num() {
8180
const xhr = new XMLHttpRequest();
8281
const url = base_src;

0 commit comments

Comments
 (0)