Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Continuous integration for Able Player
#
# Runs ESLint and both Jest projects (jsdom + puppeteer) on every push and
# pull request. The puppeteer project runs headless with request
# interception, so no demo server is needed. Build artifacts are compiled
# to confirm Grunt + Rollup succeed, but are never committed (per
# contributing.md).
name: CI

on:
push:
branches: [main, develop]
pull_request:

permissions:
contents: read

# Superseded runs on the same ref are cancelled, so a force-push to an open
# pull request does not leave stale jobs occupying the queue.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
lint:
name: ESLint
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
# Only the puppeteer job launches a browser; skip puppeteer's Chromium
# download elsewhere (setup-node's npm cache does not cover it).
- run: npm ci
env:
PUPPETEER_SKIP_DOWNLOAD: "1"
- run: npm run lint

test:
name: Jest (jsdom)
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
env:
PUPPETEER_SKIP_DOWNLOAD: "1"
# webvtt.test.cjs loads build/test/webvtt.umd.js, so the suite must run
# against a fresh build rather than the committed bundle — otherwise a
# change to the WebVTT source is never actually exercised.
- run: npm run build
- run: npx jest --selectProjects jsdom

test-browser:
name: Jest (puppeteer, headless)
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
# validate.test.cjs loads build/test/validate.umd.js
- run: npm run build
- run: npx jest --selectProjects puppeteer

build:
name: Build (Grunt + Rollup)
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
env:
PUPPETEER_SKIP_DOWNLOAD: "1"
- run: npm run build
- name: Confirm build outputs exist
run: |
test -s build/ableplayer.js
test -s build/ableplayer.min.js
test -s build/ableplayer.esm.js
test -s build/ableplayer.min.css
2 changes: 1 addition & 1 deletion contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ Able Player uses [Jest][] to run automated tests. Tests are found in `scripts/__
npm test
```

Because Able Player doesn't configure its own local environment, you can run it in any local setup; but you may need to adjust the test runner's target URL for tests requiring a local URL. The default is `http://localhost:8000`.
The suite runs headless and needs no local server: the browser-based tests intercept their own navigation. To watch the browser while debugging, run `HEADFUL=1 npm test`.

Please run the test suite against your changes to ensure there are no unexpected changes.

Expand Down
8 changes: 7 additions & 1 deletion jest-puppeteer.config.cjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
// jest-puppeteer.config.js
// Headless by default so `npm test` runs unattended (locally and in CI).
// Set HEADFUL=1 to watch the browser while debugging.
module.exports = {
launch: {
headless: false, // Set to true to run tests in headless mode
// Compared explicitly: HEADFUL=0 is a truthy string, and it should mean
// "stay headless" rather than launching a visible browser.
headless: !["1", "true"].includes(process.env.HEADFUL),
// Chromium's sandbox is unavailable in most CI containers.
args: process.env.CI ? ["--no-sandbox", "--disable-setuid-sandbox"] : [],
},
};
18 changes: 17 additions & 1 deletion scripts/__tests__/validate.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,23 @@ const path = require("path");
*/
describe("validate.js tests", () => {
beforeAll(async () => {
await page.goto("http://localhost:8000"); // Replace with your test URL
// The suite needs a real http(s) origin (isProtocolSafe resolves
// relative URLs against window.location.origin, which is opaque on
// about:blank), but no actual server: intercept the navigation and
// fulfill it with an empty page.
await page.setRequestInterception(true);
page.on("request", (request) => {
if (request.url().startsWith("http://ableplayer.test/")) {
request.respond({
status: 200,
contentType: "text/html",
body: "<!doctype html><html><head></head><body></body></html>",
});
} else {
request.continue();
}
});
await page.goto("http://ableplayer.test/");
const validatePath = path.resolve(__dirname, "../../build/test/validate.umd.js");
// Add DOMPurify script
const domPurifyPath = path.resolve(
Expand Down
3 changes: 0 additions & 3 deletions scripts/ableplayer-base.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,6 @@ class AblePlayer {
// set to "true" to write description text to a div
if (descriptionsAudible !== undefined && descriptionsAudible === false) {
this.readDescriptionsAloud = false;
} else if (descriptionsAudible !== undefined && descriptionsAudible === false) {
// support both singular and plural spelling of attribute
this.readDescriptionsAloud = false;
} else {
this.readDescriptionsAloud = true;
}
Expand Down
1 change: 1 addition & 0 deletions scripts/dialog.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import $ from 'jquery';
import AblePlayer from './ableplayer-base';

var focusableElementsSelector = "a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, *[tabindex], *[contenteditable]";

Expand Down
7 changes: 3 additions & 4 deletions scripts/dragdrop.js
Original file line number Diff line number Diff line change
Expand Up @@ -287,9 +287,8 @@ function addDragdropFunctions(AblePlayer) {

AblePlayer.prototype.handleWindowButtonClick = function (which, e) {

var thisObj, $windowPopup, $windowButton, $toolbar, popupTop;
var $windowPopup, $windowButton, $toolbar, popupTop;

thisObj = this;
if (this.focusNotClick) {
// transcript or sign window has just opened,
// and focus moved to the window button
Expand All @@ -309,6 +308,7 @@ function addDragdropFunctions(AblePlayer) {
if (e.type === 'keydown') {
// user pressed a key
if (e.key === ' ' || e.key === 'Enter') {
// Space or Enter: fall through to the toggle logic below
} else if (e.key === 'Escape') {
if ($windowPopup.is(':visible')) {
// close the popup menu
Expand Down Expand Up @@ -349,10 +349,9 @@ function addDragdropFunctions(AblePlayer) {

AblePlayer.prototype.handleMenuChoice = function (which, choice, e) {

var thisObj, $window, $windowPopup, $windowButton, resizeDialog, startingWidth, startingHeight,
var $window, $windowPopup, $windowButton, resizeDialog, startingWidth, startingHeight,
aspectRatio, tempWidth, tempHeight;

thisObj = this;
if (which === 'transcript') {
$window = this.$transcriptArea;
$windowPopup = this.$transcriptPopup;
Expand Down
7 changes: 1 addition & 6 deletions scripts/preference.js
Original file line number Diff line number Diff line change
Expand Up @@ -521,7 +521,7 @@
var thisObj, available,
$prefsDiv, formTitle, introText, $prefsIntro,$prefsIntroP2,p3Text,$prefsIntroP3,i, j,
$fieldset, groupHeading, groupObj, thisPref, $thisDiv, thisClass,
thisId, $thisLabel, $thisField, captionsOptions,options,$thisOption,optionValue,optionLang,optionText,
thisId, $thisField, captionsOptions,options,$thisOption,optionValue,optionLang,optionText,
changedPref,changedSpan,changedText, currentDescState, prefDescVoice, prefCaptionVoice, $kbHeading,$kbList,
kbLabels,keys,kbListText,$kbListItem, dialog,$saveButton,$cancelButton,$buttonContainer, sharedDialog;

Expand Down Expand Up @@ -639,7 +639,6 @@
} : undefined
});
$thisDiv = fieldObj.wrapper;
$thisLabel = fieldObj.label;
$thisField = fieldObj.field;
// add a change handler that updates the style of the sample caption text
let viewingOptions = ['prefCaptionsPosition','prefCaptionsFont','prefCaptionsSize','prefCaptionsColor','prefCaptionsBGColor','prefCaptionsOpacity'];
Expand Down Expand Up @@ -752,8 +751,6 @@
checked: this[thisPref] === 1
});
$thisDiv = fieldObj.wrapper;
$thisLabel = fieldObj.label;
$thisField = fieldObj.field;
} else if (this.synth) {
let isDescRateField = (thisPref === 'prefDescRate');
// Only show these options if browser supports speech synthesis
Expand All @@ -771,7 +768,6 @@
} : undefined
});
$thisDiv = fieldObj.wrapper;
$thisLabel = fieldObj.label;
$thisField = fieldObj.field;
if (isDescRateField) {
// Number field has no options to populate.
Expand Down Expand Up @@ -844,7 +840,6 @@
checked: this[thisPref] === 1
});
$thisDiv = fieldObj.wrapper;
$thisLabel = fieldObj.label;
$thisField = fieldObj.field;
if (form === 'keyboard') {
// add a change handler that updates the list of current keyboard shortcuts
Expand Down