Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add polling function management | refs #35772 #11

Merged
merged 1 commit into from
Mar 1, 2024
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
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