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 Feb 29, 2024
1 parent f52fcd0 commit 3c55fdf
Show file tree
Hide file tree
Showing 7 changed files with 169 additions and 29 deletions.
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 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
82 changes: 81 additions & 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 Expand Up @@ -749,4 +749,84 @@ export default class JavaScriptUtilities {
return send.apply(this, arguments);
};
}
setupPolling (fct, interval, enabled) {
/*
This function purpose is to handle a function used for polling.
The polling will be stopped when page is hidden and restarted if visible.
The returned object can be used as an API to control the polling.
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`.
*/
const polling = {
enabled: false,
timeoutId: null,
running: false,
lastRun: 0,
interval: interval,
fct: fct
};
polling.enable = function () {
if (!polling.enabled) {
polling.enabled = true;
polling.resume();
}
};
polling.disable = function () {
if (polling.enabled) {
polling.enabled = false;
polling.cancel();
}
};
polling.run = function () {
if (polling.enabled && !polling.running) {
polling.running = true;
polling.cancel();
polling.fct(function () {
polling.lastRun = (new Date()).getTime();
polling.running = false;
polling.plan(polling.interval);
});
}
};
polling.plan = function (delay) {
if (polling.enabled && !polling.timeoutId && document.visibilityState === 'visible') {
polling.timeoutId = setTimeout(function () {
polling.run();
}, delay);
}
};
polling.resume = function () {
const now = (new Date()).getTime();
const delay = Math.max(polling.lastRun + polling.interval - now, 1);
polling.plan(delay);
};
polling.cancel = function () {
if (polling.timeoutId !== null) {
clearTimeout(polling.timeoutId);
polling.timeoutId = null;
}
};
if (enabled === true || enabled === undefined) {
polling.enable();
}
document.addEventListener('visibilitychange', function () {
if (document.visibilityState === 'visible') {
polling.resume();
} else {
polling.cancel();
}
});
return polling;
}
}
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
22 changes: 22 additions & 0 deletions tests/manual_test.js
Original file line number Diff line number Diff line change
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 = jsu.setupPolling(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
81 changes: 56 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('test version', () => {
assert(jsu.version === 11);
});
it('should set/get cookies', () => {
it('test set/get cookies', () => {
jsu.setCookie('a', '1');
const value = jsu.getCookie('a');
assert(value == '1');
});
it('should strip', () => {
it('test 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('test slugify', () => {
const text = '>@)(#<!test?/"\'][{}=+&^`%$';
const value = jsu.slugify(text);
assert(value == 'test');
});
it('should stripHTML', () => {
it('test stripHTML', () => {
const text = '<div><div class="test">test</div></div>';
const value = jsu.stripHTML(text);
assert(value == 'test');
});
it('should escapeHTML and decodeHTML', () => {
it('test 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('test 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('test 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('test 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('test 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('test 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('test 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('test 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('test 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('test attemptFocus', () => {
const focusableElement = document.createElement('input');
document.body.appendChild(focusableElement);
assert(jsu.isFocusable(focusableElement));
assert(jsu.attemptFocus(focusableElement));
});
it('should focusFirstDescendant and focusLastDescendant', () => {
it('test 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('test UA', () => {
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('test 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('test 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,8 +217,39 @@ 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');
});
it('test setupPolling', async () => {
const sleep = (time) => {
return new Promise((resolve) => {
setTimeout(resolve, time);
});
};

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

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

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

polling.disable();
await sleep(1000);
assert(calls.length == 2);

polling.enable();
await sleep(100);
assert(calls.length == 3);
assert(Math.abs(calls[2] - 2500) < 100, `${calls[2]} ~= 2500`);
}).timeout(5000);
});

0 comments on commit 3c55fdf

Please sign in to comment.