Skip to content

Commit

Permalink
Add polling function management | refs #35772
Browse files Browse the repository at this point in the history
  • Loading branch information
sdiemer committed Mar 1, 2024
1 parent f52fcd0 commit 02af9e0
Show file tree
Hide file tree
Showing 13 changed files with 195 additions and 41 deletions.
1 change: 0 additions & 1 deletion dist/chunked-upload.min.js

This file was deleted.

2 changes: 1 addition & 1 deletion dist/jsu.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/jsu.min.mjs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ gulp.task('build', function () {
.pipe(gulp.dest('.'));

return gulp.src(['src/jsu.js', 'src/lib/*.js', 'src/load.js'])
.pipe(replace(/export (default ){0,1}/g, ''))
.pipe(replace(/export (default )?/g, ''))
.pipe(concat('dist/jsu.js'))
.pipe(minify({
ext: {'src': '.tmp.js', 'min': '.min.js'},
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "jsu",
"version": "8",
"version": "11",
"description": "",
"main": "gulpfile.js",
"type": "module",
Expand Down
2 changes: 1 addition & 1 deletion src/jsu.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ jsu (JavaScript Utilities)

export default class JavaScriptUtilities {
constructor () {
this.version = 10; // Change this when updating this script
this.version = 11; // Change this when updating this script
this.ignoreUntilFocusChanges = false;
this.userAgent = window.navigator && window.navigator.userAgent ? window.navigator.userAgent.toLowerCase() : 'unknown';
this.userAgentData = null;
Expand Down
77 changes: 77 additions & 0 deletions src/lib/polling-manager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
export class PollingManager {
constructor (fct, interval, enabled) {
/*
This class purpose is to handle a function used for polling.
The polling will be stopped when page is hidden and restarted if visible.
Arguments:
- `fct`:
The function to use for the polling.
It will receive a callback function as argument which must be
called once the function is done (after a request for example).
- `interval`:
The interval is the time between the end of the execution of the
function and the next execution of the function.
This means that the execution duration of the function will delay
the next run.
- `enabled`:
Boolean to indicate if the polling should be initially enabled.
Default is `true`.
*/
this.enabled = false;
this.timeoutId = null;
this.running = false;
this.lastRun = 0;
this.interval = interval;
this.fct = fct;

if (enabled === true || enabled === undefined) {
this.enable();
}
document.addEventListener('visibilitychange', function () {
if (document.visibilityState === 'visible') {
this.resume();
} else {
this.cancel();
}
}.bind(this));
}
enable () {
if (!this.enabled) {
this.enabled = true;
this.resume();
}
}
disable () {
if (this.enabled) {
this.enabled = false;
this.cancel();
}
}
run () {
if (this.enabled && !this.running) {
this.running = true;
this.cancel();
this.fct(function () {
this.lastRun = (new Date()).getTime();
this.running = false;
this.plan(this.interval);
}.bind(this));
}
}
plan (delay) {
if (this.enabled && !this.timeoutId && document.visibilityState === 'visible') {
this.timeoutId = setTimeout(this.run.bind(this), delay);
}
}
resume () {
const now = (new Date()).getTime();
const delay = Math.max(this.lastRun + this.interval - now, 1);
this.plan(delay);
}
cancel () {
if (this.timeoutId !== null) {
clearTimeout(this.timeoutId);
this.timeoutId = null;
}
}
}
7 changes: 7 additions & 0 deletions tests/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,13 @@ <h1><a href="https://github.com/UbiCastTeam/jsu">JSU test page</a></h1>
<div id="translations_report"></div>
</fieldset>

<fieldset>
<legend>Polling</legend>
<div id="polling_report">No run</div>
<button type="button" id="test_polling_disable">Disable</button>
<button type="button" id="test_polling_enable">Enable</button>
</fieldset>

<fieldset>
<legend>Requests</legend>
<button type="button" id="test_request_localhost_json">Test request on "https://localhost" as json</button>
Expand Down
24 changes: 23 additions & 1 deletion tests/manual_test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*******************************************
* jsu: Test script *
*******************************************/
/* global jsu */
/* globals jsu, PollingManager */
const host = 'http://localhost:8083';

function objectRepr (obj) {
Expand Down Expand Up @@ -177,6 +177,27 @@ function testTranslation () {
ele.innerHTML = '<p>' + jsu.escapeHTML(repr) + '</p>';
}

function testPolling () {
const ele = document.getElementById('polling_report');
const calls = [];
let count = 0;
const polling = new PollingManager(function (callback) {
count += 1;
calls.push('Call #' + count + ' at ' + (new Date()).toTimeString());
if (calls.length > 10) {
calls.shift();
}
ele.innerHTML = calls.join('<br/>');
callback();
}, 10000);
document.getElementById('test_polling_disable').addEventListener('click', function () {
polling.disable();
});
document.getElementById('test_polling_enable').addEventListener('click', function () {
polling.enable();
});
}

function testRequest ({method, url, json, params, data, append, noText}) {
jsu.httpRequest({
url: url,
Expand Down Expand Up @@ -207,6 +228,7 @@ jsu.onDOMLoad(function () {
displayUserAgent();
testWebGL();
testTranslation();
testPolling();
document.getElementById('test_request_localhost_json').addEventListener('click', function () {
testRequest({'url': host, 'json': true});
});
Expand Down
50 changes: 25 additions & 25 deletions tests/test_jsu.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,47 +5,47 @@ import JavaScriptUtilities from '../src/jsu.js';
const jsu = new JavaScriptUtilities();

describe('JSU', () => {
it('should return correct version', () => {
assert(jsu.version === 10);
it('should return the correct version', () => {
assert(jsu.version === 11);
});
it('should set/get cookies', () => {
it('should handle getCookie() and setCookie()', () => {
jsu.setCookie('a', '1');
const value = jsu.getCookie('a');
assert(value == '1');
});
it('should strip', () => {
it('should handle strip()', () => {
const text = ' test \n test \n test \n test ';
const value = jsu.strip(text);
assert(value == 'test \n test \n test \n test');
});
it('should slugify', () => {
it('should handle slugify()', () => {
const text = '>@)(#<!test?/"\'][{}=+&^`%$';
const value = jsu.slugify(text);
assert(value == 'test');
});
it('should stripHTML', () => {
it('should handle stripHTML()', () => {
const text = '<div><div class="test">test</div></div>';
const value = jsu.stripHTML(text);
assert(value == 'test');
});
it('should escapeHTML and decodeHTML', () => {
it('should handle escapeHTML() and decodeHTML()', () => {
const html = '<div class="test">test &#34;</div>';
const encodedHTML = jsu.escapeHTML(html);
assert(encodedHTML == '&lt;div class="test"&gt;test &amp;#34;&lt;/div&gt;');
const decodedHTML = jsu.decodeHTML(encodedHTML);
assert(decodedHTML == html);
});
it('should escapeAttribute', () => {
it('should handle escapeAttribute()', () => {
const html = '<div class="test">test\n\'</div>';
const encodedHTML = jsu.escapeAttribute(html);
assert(encodedHTML == '<div class=&quot;test&quot;>test&#13;&#10;&#39;</div>');
});
it('should getClickPosition', () => {
it('should handle getClickPosition()', () => {
const evt = {'pageX': 10, 'pageY': 10};
const positions = jsu.getClickPosition(evt, document.body);
assert(JSON.stringify(positions) == JSON.stringify({'x': 10, 'y': 10}));
});
it('should onDOMLoad', async () => {
it('should handle onDOMLoad()', async () => {
let load = false;
jsu.onDOMLoad(() => {
load = true;
Expand All @@ -55,7 +55,7 @@ describe('JSU', () => {
}, 100);
assert(load);
});
it('should do a httpRequest', async () => {
it('should handle httpRequest()', async () => {
const requestStatuses = [];
const testDatas = [
{'method': 'GET', 'params': {'test': 1}},
Expand All @@ -82,15 +82,15 @@ describe('JSU', () => {
}, 500);
assert(JSON.stringify(requestStatuses) == JSON.stringify([200, 200, 200, 200, 200, 200, 200]));
}).timeout(5000);
it('should compareVersions', () => {
it('should handle compareVersions()', () => {
let result = jsu.compareVersions('1.1.1', '=', '1.1.1');
assert(result == 0);
result = jsu.compareVersions('1.1.0', '=', '1.1.1');
assert(result == 1);
result = jsu.compareVersions('1.1.2', '=', '1.1.1');
assert(result == -1);
});
it('should setObjectAttributes', () => {
it('should handle setObjectAttributes()', () => {
const obj = {};
const data = {'a': 1, 'b': 1, 'translations': {'en': {'a': 'a'}}};
const allowedAttributes = ['b'];
Expand All @@ -102,7 +102,7 @@ describe('JSU', () => {
assert(!obj.translations);
assert(obj.b);
});
it('should getWebglContext', () => {
it('should handle getWebglContext()', () => {
console.error(process.env);
const testDatas = [
{'options': {}, 'browserName': 'chrome'},
Expand All @@ -113,17 +113,17 @@ describe('JSU', () => {
assert(jsu.getWebglContext(canvas, data.options, data.browserName));
}
});
it('should test isInIframe', () => {
it('should handle isInIframe()', () => {
// karma window is in an iframe <iframe id="context" src="context.html" width="100%" height="100%"></iframe>
assert(jsu.isInIframe());
});
it('should attemptFocus', () => {
it('should handle attemptFocus()', () => {
const focusableElement = document.createElement('input');
document.body.appendChild(focusableElement);
assert(jsu.isFocusable(focusableElement));
assert(jsu.attemptFocus(focusableElement));
});
it('should focusFirstDescendant and focusLastDescendant', () => {
it('should handle focusFirstDescendant() and focusLastDescendant()', () => {
const focusableElementOne = document.createElement('input');
focusableElementOne.id = '1';
const focusableElementTwo = document.createElement('button');
Expand All @@ -135,7 +135,7 @@ describe('JSU', () => {
assert(jsu.focusLastDescendant(document.body));
assert(document.activeElement == focusableElementTwo);
});
it('should test UA', () => {
it('should handle user agents', () => {
assert(jsu.userAgent);
assert(jsu.osName);
assert(jsu.osVersion !== undefined);
Expand All @@ -144,7 +144,7 @@ describe('JSU', () => {
assert(jsu.browserName);
assert(jsu.browserVersion);
});
it('should test isRecordingAvailable', () => {
it('should handle isRecordingAvailable()', () => {
const data = [
['safari', '6', false],
['firefox', '30', false],
Expand All @@ -161,7 +161,7 @@ describe('JSU', () => {
assert(jsu.isRecordingAvailable() === result, `${browserName}@${browserVersion} isRecordingAvailable ${jsu.isRecordingAvailable()}`);
}
});
it('should manage translations', () => {
it('should handle translations', () => {
const translations = {
'fr': {
'lang': 'fr',
Expand Down Expand Up @@ -205,10 +205,10 @@ describe('JSU', () => {
assert(jsu.getDateDisplay('2021-12-30 00:12:14') == '30 December 2021 at 12:12 AM', `${jsu.getDateDisplay('2021-12-30 00:12:14')} == '30 December 2021 at 12:12 AM'`);
assert(jsu.getDateDisplay('2021-19-57 69:98:84') == '2021-19-57 69:98:84', `${jsu.getDateDisplay('2021-19-57 69:98:84')} == '2021-19-57 69:98:84'`);
assert(jsu.getDateDisplay('Invalid date') == 'Invalid date', `${jsu.getDateDisplay('Invalid date')} == 'Invalid date'`);
assert(jsu.getSizeDisplay() == '0 B', `${jsu.getSizeDisplay()} == '0 B'`);
assert(jsu.getSizeDisplay('123456789') == '123.5 MB', `${jsu.getSizeDisplay('123456789')} == '123.5 MB'`);
assert(jsu.getSizeDisplay('12345678910') == '12.3 GB', `${jsu.getSizeDisplay('12345678910')} == '123.5 MB'`);
assert(jsu.getSizeDisplay('1234567891011') == '1.2 TB', `${jsu.getSizeDisplay('1234567891011')} == '123.5 MB'`);
assert(jsu.getSizeDisplay() == '0 B', `${jsu.getSizeDisplay()} == '0 B'`);
assert(jsu.getSizeDisplay('123456789') == '123.5 MB', `${jsu.getSizeDisplay('123456789')} == '123.5 MB'`);
assert(jsu.getSizeDisplay('12345678910') == '12.3 GB', `${jsu.getSizeDisplay('12345678910')} == '123.5 MB'`);
assert(jsu.getSizeDisplay('1234567891011') == '1.2 TB', `${jsu.getSizeDisplay('1234567891011')} == '123.5 MB'`);
jsu.useLang('fr');
assert(jsu.getCurrentLang() == 'fr');
assert(JSON.stringify(jsu.getCurrentCatalog()) == JSON.stringify(translations['fr']));
Expand All @@ -217,7 +217,7 @@ describe('JSU', () => {
assert(jsu.getDateDisplay('2021-12-30 00:12:14') == '30 décembre 2021 à 00:12', `${jsu.getDateDisplay('2021-12-30 00:12:14')} == '30 décembre 2021 à 00:12'`);
assert(jsu.getDateDisplay('2021-19-57 69:98:84') == '2021-19-57 69:98:84', `${jsu.getDateDisplay('2021-19-57 69:98:84')} == '2021-19-57 69:98:84'`);
assert(jsu.getDateDisplay('Invalid date') == 'Invalid date', `${jsu.getDateDisplay('Invalid date')} == 'Invalid date'`);
assert(jsu.getSizeDisplay('123456789') == '123.5 Mo', `${jsu.getSizeDisplay('123456789')} == '123.5 Mo'`);
assert(jsu.getSizeDisplay('123456789') == '123.5 Mo', `${jsu.getSizeDisplay('123456789')} == '123.5 Mo'`);
jsu.useLang('x');
assert(jsu.translate('lang') == 'en');
});
Expand Down
50 changes: 50 additions & 0 deletions tests/test_polling.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/* globals require, describe, it */
const assert = require('assert');
import { PollingManager } from '../src/lib/polling-manager.js';

describe('PollingManager', () => {
it('should handle polling function', async () => {
const sleep = (time) => {
return new Promise((resolve) => {
setTimeout(resolve, time);
});
};

const start = new Date().getTime();
const calls = [];
const polling = new PollingManager(function (callback) {
calls.push(new Date().getTime() - start);
callback();
}, 1000);

// Test call after init
await sleep(500);
assert(calls.length == 1);
assert(calls[0] < 100, `${calls[0]} ~= 0`);

// Test call after interval
await sleep(1000);
assert(calls.length == 2);
assert(Math.abs(calls[1] - 1000) < 100, `${calls[1]} ~= 1000`);

// Test no call when disabled
polling.disable();
await sleep(1000);
assert(calls.length == 2);

// Test immediate call after enabling because interval is already reached
polling.enable();
await sleep(100);
assert(calls.length == 3);
assert(Math.abs(calls[2] - 2500) < 100, `${calls[2]} ~= 2500`);

// Test no call after enabling because a too recent call was made
polling.disable();
polling.enable();
await sleep(100);
assert(calls.length == 3);

// Cleanup test
polling.disable();
}).timeout(5000);
});
Loading

0 comments on commit 02af9e0

Please sign in to comment.