From 40749363060fc2d48b5d174126bb8fed3b5c37b5 Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Thu, 3 Sep 2026 11:40:27 +0200 Subject: [PATCH 01/19] Add copy URL action to command logs Register the InfoLogger model with `StatefulComponent` so shared stateful UI components can render correctly. Add a `CopyToClipboardComponent` button in the command logs toolbar that copies the current filter query string as a URL. --- InfoLogger/public/index.js | 3 ++- InfoLogger/public/log/commandLogs.js | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/InfoLogger/public/index.js b/InfoLogger/public/index.js index 7b689a2b81..ec64790e86 100644 --- a/InfoLogger/public/index.js +++ b/InfoLogger/public/index.js @@ -19,13 +19,14 @@ sessionService.loadAndHideParameters(); window.sessionService = sessionService; // Import MVC -import { mount } from '/js/src/index.js'; +import { mount, StatefulComponent } from '/js/src/index.js'; import view from './view.js'; import Model from './Model.js'; // Start application const model = new Model(); const debug = true; // shows when redraw is done +StatefulComponent.useRenderer(model); // Register the model for the stateful components mount(document.body, view, model, debug); // Expose model to interact with it the browser's console diff --git a/InfoLogger/public/log/commandLogs.js b/InfoLogger/public/log/commandLogs.js index 06ca8932f7..3df04cd0d7 100644 --- a/InfoLogger/public/log/commandLogs.js +++ b/InfoLogger/public/log/commandLogs.js @@ -20,6 +20,7 @@ import { h, iconMagnifyingGlass, iconPlus, iconMinus, + CopyToClipboardComponent, } from '/js/src/index.js'; import { BUTTON } from '../constants/button-states.const.js'; import { MODE } from '../constants/mode.const.js'; @@ -67,8 +68,22 @@ export const commandLogs = (model) => [ ]), h('', downloadButtonGroup(model.log)), h('', zoomButtonGroup(model.zoom)), + copyButtonOption(model.log.filter), + ]; +/** + * A button component that lets the user copy the url + * + * @param {Model} filterModel - filter model of the application + * @returns {Component} the copy button component + */ +const copyButtonOption = (filterModel) => h( + CopyToClipboardComponent, + { value: filterModel.queryString, id: 'url', className: 'button.btn', style: { minWidth: '100px' } }, + 'Copy URL', +); + /** * Group of buttons for switching between Query and Live modes. * @param {Model} model - root model of the application From 476503be3d0f93ede74a0756c1ee138663a9eb26 Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Thu, 3 Sep 2026 12:11:06 +0200 Subject: [PATCH 02/19] Fix copy url value Switch the command logs copy action to use `location.href`. --- InfoLogger/public/log/commandLogs.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/InfoLogger/public/log/commandLogs.js b/InfoLogger/public/log/commandLogs.js index 3df04cd0d7..b5a4f66098 100644 --- a/InfoLogger/public/log/commandLogs.js +++ b/InfoLogger/public/log/commandLogs.js @@ -68,19 +68,18 @@ export const commandLogs = (model) => [ ]), h('', downloadButtonGroup(model.log)), h('', zoomButtonGroup(model.zoom)), - copyButtonOption(model.log.filter), + copyButtonOption(), ]; /** * A button component that lets the user copy the url * - * @param {Model} filterModel - filter model of the application * @returns {Component} the copy button component */ -const copyButtonOption = (filterModel) => h( +const copyButtonOption = () => h( CopyToClipboardComponent, - { value: filterModel.queryString, id: 'url', className: 'button.btn', style: { minWidth: '100px' } }, + { value: location.href, id: 'url', className: 'button.btn', style: { minWidth: '100px' } }, 'Copy URL', ); From 46320e4aa574f1d2cf59c219b9c88048acf6ebad Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Mon, 7 Sep 2026 16:33:14 +0200 Subject: [PATCH 03/19] Should copy the non-debounced version of URL --- InfoLogger/public/log/commandLogs.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/InfoLogger/public/log/commandLogs.js b/InfoLogger/public/log/commandLogs.js index b5a4f66098..21b8f67028 100644 --- a/InfoLogger/public/log/commandLogs.js +++ b/InfoLogger/public/log/commandLogs.js @@ -68,18 +68,25 @@ export const commandLogs = (model) => [ ]), h('', downloadButtonGroup(model.log)), h('', zoomButtonGroup(model.zoom)), - copyButtonOption(), + copyButtonOption(model.log.filter.queryString), ]; /** * A button component that lets the user copy the url * + * @param {string} queryString - the query string to be appended to the URL * @returns {Component} the copy button component */ -const copyButtonOption = () => h( +const copyButtonOption = (queryString) => h( CopyToClipboardComponent, - { value: location.href, id: 'url', className: 'button.btn', style: { minWidth: '100px' } }, + { + // Copy the non-debounced URL with the current query string + value: `${location.origin}${location.pathname}${queryString}`, + id: 'url', + className: 'button.btn', + style: { minWidth: '100px' }, + }, 'Copy URL', ); From 028b17e29b3edfd3b477ff01a8a04fb138564a3f Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Mon, 7 Sep 2026 16:34:16 +0200 Subject: [PATCH 04/19] Add tests for copy URL button Add a new test suite for the copy URL button. The new tests verify the button label and confirm that clicking it copies a URL containing the encoded active filter query. --- InfoLogger/test/mocha-index.js | 1 + InfoLogger/test/public/copy-url-btn-mocha.js | 49 ++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 InfoLogger/test/public/copy-url-btn-mocha.js diff --git a/InfoLogger/test/mocha-index.js b/InfoLogger/test/mocha-index.js index b8e50c7373..c94df8820d 100644 --- a/InfoLogger/test/mocha-index.js +++ b/InfoLogger/test/mocha-index.js @@ -115,6 +115,7 @@ describe('InfoLogger', function () { require('./public/status-bar-mocha'); require('./public/zoom.mocha'); require('./public/log-context-menu-mocha'); + require('./public/copy-url-btn-mocha'); after(async () => { await browser.close(); diff --git a/InfoLogger/test/public/copy-url-btn-mocha.js b/InfoLogger/test/public/copy-url-btn-mocha.js new file mode 100644 index 0000000000..14bc5de832 --- /dev/null +++ b/InfoLogger/test/public/copy-url-btn-mocha.js @@ -0,0 +1,49 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +const assert = require('assert'); +const test = require('../mocha-index'); + +describe('Copy URL button test-suite', async () => { + let baseUrl = null; + let page = null; + + before(async () => { + ({ helpers: { baseUrl }, page } = test); + await page.browser().defaultBrowserContext().setPermission( + new URL(baseUrl).origin, + { permission: { name: 'clipboard-read' }, state: 'granted' }, + { permission: { name: 'clipboard-write' }, state: 'granted' }, + ); + await page.goto(baseUrl, { waitUntil: 'networkidle0' }); + }); + + it('should display the button with the correct label', async () => { + const button = await page.$('#copy-url'); + const label = await page.evaluate((el) => el.textContent, button); + assert.strictEqual(label, 'Copy URL'); + }); + + it('should copy a URL carrying the active filter', async () => { + await page.evaluate(() => { + window.model.log.filter.setCriteria('message', 'match', 'needle'); + window.model.notify(); + }); + await page.click('#copy-url'); + const copiedText = await page.evaluate(() => navigator.clipboard.readText()); + const expectedUrl = `${baseUrl}?q=%7B%22message%22%3A%7B%22match%22` + + '%3A%22needle%22%7D%2C%22severity%22%3A%7B%22in%22%3A%22I%20W%20E%20F%22%7D%7D'; + assert.strictEqual(copiedText, expectedUrl); + }); +}); From 21f6c729a0f65731d423dcf6a46d8d2cb86e36bc Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Tue, 8 Sep 2026 12:11:10 +0200 Subject: [PATCH 05/19] Rename copy button --- InfoLogger/public/log/commandLogs.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/InfoLogger/public/log/commandLogs.js b/InfoLogger/public/log/commandLogs.js index 21b8f67028..32f32a9ecd 100644 --- a/InfoLogger/public/log/commandLogs.js +++ b/InfoLogger/public/log/commandLogs.js @@ -68,7 +68,7 @@ export const commandLogs = (model) => [ ]), h('', downloadButtonGroup(model.log)), h('', zoomButtonGroup(model.zoom)), - copyButtonOption(model.log.filter.queryString), + copyURLButton(model.log.filter.queryString), ]; @@ -78,7 +78,7 @@ export const commandLogs = (model) => [ * @param {string} queryString - the query string to be appended to the URL * @returns {Component} the copy button component */ -const copyButtonOption = (queryString) => h( +const copyURLButton = (queryString) => h( CopyToClipboardComponent, { // Copy the non-debounced URL with the current query string From 08b98570a6c521a049b98b881d0e283e06684023 Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Tue, 8 Sep 2026 13:28:55 +0200 Subject: [PATCH 06/19] Remove references to location in a view Switch the log view Copy URL button to consume a new `LogFilter.filterURL` getter instead of rebuilding from a query string in the button component. --- InfoLogger/public/log/commandLogs.js | 9 ++++----- InfoLogger/public/logFilter/LogFilter.js | 4 ++++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/InfoLogger/public/log/commandLogs.js b/InfoLogger/public/log/commandLogs.js index 32f32a9ecd..866b1bc7c5 100644 --- a/InfoLogger/public/log/commandLogs.js +++ b/InfoLogger/public/log/commandLogs.js @@ -68,21 +68,20 @@ export const commandLogs = (model) => [ ]), h('', downloadButtonGroup(model.log)), h('', zoomButtonGroup(model.zoom)), - copyURLButton(model.log.filter.queryString), - + copyURLButton(model.log.filter.filterURL), ]; /** * A button component that lets the user copy the url * - * @param {string} queryString - the query string to be appended to the URL + * @param {string} url - the url string to be appended to the URL * @returns {Component} the copy button component */ -const copyURLButton = (queryString) => h( +const copyURLButton = (url) => h( CopyToClipboardComponent, { // Copy the non-debounced URL with the current query string - value: `${location.origin}${location.pathname}${queryString}`, + value: url, id: 'url', className: 'button.btn', style: { minWidth: '100px' }, diff --git a/InfoLogger/public/logFilter/LogFilter.js b/InfoLogger/public/logFilter/LogFilter.js index 61e0bfe7e0..a1e52d2a67 100644 --- a/InfoLogger/public/logFilter/LogFilter.js +++ b/InfoLogger/public/logFilter/LogFilter.js @@ -159,6 +159,10 @@ export default class LogFilter extends Observable { return buildUrl('?', { q: JSON.stringify(this.toObject()) }); } + get filterURL() { + return `${location.origin}${location.pathname}${this.queryString}`; + } + /** * Set criterias according to object passed as argument * @param {object} criterias - object with criterias to be set From f780f4912a6bc3bb87312ee6684d6b77009de12d Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Tue, 8 Sep 2026 13:29:59 +0200 Subject: [PATCH 07/19] Change copyURL button to reference correct attr --- InfoLogger/public/log/commandLogs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InfoLogger/public/log/commandLogs.js b/InfoLogger/public/log/commandLogs.js index 866b1bc7c5..7e3c4356ff 100644 --- a/InfoLogger/public/log/commandLogs.js +++ b/InfoLogger/public/log/commandLogs.js @@ -83,7 +83,7 @@ const copyURLButton = (url) => h( // Copy the non-debounced URL with the current query string value: url, id: 'url', - className: 'button.btn', + classes: '', style: { minWidth: '100px' }, }, 'Copy URL', From faeeac895c1dc691c33d04fbcd2c9f93288baa34 Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Tue, 8 Sep 2026 14:13:28 +0200 Subject: [PATCH 08/19] Reset clipboard test permissions and improve JSDOC --- InfoLogger/test/public/copy-url-btn-mocha.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/InfoLogger/test/public/copy-url-btn-mocha.js b/InfoLogger/test/public/copy-url-btn-mocha.js index 14bc5de832..c838d5ef5c 100644 --- a/InfoLogger/test/public/copy-url-btn-mocha.js +++ b/InfoLogger/test/public/copy-url-btn-mocha.js @@ -29,6 +29,11 @@ describe('Copy URL button test-suite', async () => { await page.goto(baseUrl, { waitUntil: 'networkidle0' }); }); + after(async () => { + await page.browser().defaultBrowserContext().clearPermissionOverrides(); + await page.goto(baseUrl, { waitUntil: 'networkidle0' }); + }); + it('should display the button with the correct label', async () => { const button = await page.$('#copy-url'); const label = await page.evaluate((el) => el.textContent, button); From 6d92f9e8c0c1aea51592bbce4e11fe10476384d7 Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Tue, 8 Sep 2026 15:26:20 +0200 Subject: [PATCH 09/19] Remove window location calls from log filter model Move the shareable URL logic to the main model from the more specific log filter model. Build URL using `queryRouter.getUrl` and replacing the search. --- InfoLogger/public/Model.js | 10 ++++++++++ InfoLogger/public/log/commandLogs.js | 2 +- InfoLogger/public/logFilter/LogFilter.js | 4 ---- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/InfoLogger/public/Model.js b/InfoLogger/public/Model.js index fbd4d5cb32..4bd33d59dd 100644 --- a/InfoLogger/public/Model.js +++ b/InfoLogger/public/Model.js @@ -390,6 +390,16 @@ export default class Model extends Observable { this.router.go(this.log.filter.queryString, true, true); } + /** + * Get the shareable URL with the current filter query string + * @returns {string} - the shareable URL + */ + get shareableURL() { + const url = this.router.getUrl(); + url.search = this.log.filter.queryString; + return url.href; + } + /** * Toggle inspector on the right */ diff --git a/InfoLogger/public/log/commandLogs.js b/InfoLogger/public/log/commandLogs.js index 7e3c4356ff..dbd30ff586 100644 --- a/InfoLogger/public/log/commandLogs.js +++ b/InfoLogger/public/log/commandLogs.js @@ -68,7 +68,7 @@ export const commandLogs = (model) => [ ]), h('', downloadButtonGroup(model.log)), h('', zoomButtonGroup(model.zoom)), - copyURLButton(model.log.filter.filterURL), + copyURLButton(model.shareableURL), ]; /** diff --git a/InfoLogger/public/logFilter/LogFilter.js b/InfoLogger/public/logFilter/LogFilter.js index a1e52d2a67..61e0bfe7e0 100644 --- a/InfoLogger/public/logFilter/LogFilter.js +++ b/InfoLogger/public/logFilter/LogFilter.js @@ -159,10 +159,6 @@ export default class LogFilter extends Observable { return buildUrl('?', { q: JSON.stringify(this.toObject()) }); } - get filterURL() { - return `${location.origin}${location.pathname}${this.queryString}`; - } - /** * Set criterias according to object passed as argument * @param {object} criterias - object with criterias to be set From 6ff184e41b99eb1172fa250186356e760aa86db8 Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Tue, 8 Sep 2026 15:29:15 +0200 Subject: [PATCH 10/19] Share render wait helper across UI tests --- InfoLogger/test/public/context-menu-test-utils.js | 12 ++---------- InfoLogger/test/public/copy-url-btn-mocha.js | 3 +++ InfoLogger/test/utils/utils.js | 11 +++++++++++ 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/InfoLogger/test/public/context-menu-test-utils.js b/InfoLogger/test/public/context-menu-test-utils.js index 26dd310423..e66d663257 100644 --- a/InfoLogger/test/public/context-menu-test-utils.js +++ b/InfoLogger/test/public/context-menu-test-utils.js @@ -12,22 +12,14 @@ * or submit itself to any jurisdiction. */ +const { waitForNextRender } = require('../utils/utils.js'); + const isContextMenuOpen = async (page) => await page.evaluate(() => window.model.log.contextMenu.isOpen); const getMenuActionLabels = async (page) => page.evaluate(() => Array.from(document.querySelectorAll('.cell-context-menu-item .ph2.w-100')) .map((el) => el.textContent.trim())); -/* - * A stale menu from a previous test can already satisfy a waitForSelector check - * before the pending redraw (reflecting the new state) has actually run. - * Waiting for two animation frames guarantees the debounced redraw has fired - * at least once since the mutation. - */ -const waitForNextRender = (page) => page.evaluate(() => new Promise((resolve) => { - requestAnimationFrame(() => requestAnimationFrame(resolve)); -})); - const openContextMenu = async (page, field, value, x, y) => { await page.evaluate((field, value, x, y) => { window.model.log.contextMenu.show(field, value, x, y); diff --git a/InfoLogger/test/public/copy-url-btn-mocha.js b/InfoLogger/test/public/copy-url-btn-mocha.js index c838d5ef5c..fac6251eb4 100644 --- a/InfoLogger/test/public/copy-url-btn-mocha.js +++ b/InfoLogger/test/public/copy-url-btn-mocha.js @@ -15,6 +15,8 @@ const assert = require('assert'); const test = require('../mocha-index'); +const { waitForNextRender } = require('../utils/utils.js'); + describe('Copy URL button test-suite', async () => { let baseUrl = null; let page = null; @@ -45,6 +47,7 @@ describe('Copy URL button test-suite', async () => { window.model.log.filter.setCriteria('message', 'match', 'needle'); window.model.notify(); }); + await waitForNextRender(page); await page.click('#copy-url'); const copiedText = await page.evaluate(() => navigator.clipboard.readText()); const expectedUrl = `${baseUrl}?q=%7B%22message%22%3A%7B%22match%22` diff --git a/InfoLogger/test/utils/utils.js b/InfoLogger/test/utils/utils.js index d4a21ae580..d2addae2ba 100644 --- a/InfoLogger/test/utils/utils.js +++ b/InfoLogger/test/utils/utils.js @@ -44,7 +44,18 @@ async function waitForTextInElement(page, selector, text) { ); } +/* + * A stale element from a previous test can already satisfy a waitForSelector check + * before the pending redraw (reflecting the new state) has actually run. + * Waiting for two animation frames guarantees the redraw has fired at least once + * since the mutation. + */ +const waitForNextRender = (page) => page.evaluate(() => new Promise((resolve) => { + requestAnimationFrame(() => requestAnimationFrame(resolve)); +})); + module.exports = { injectLogs, waitForTextInElement, + waitForNextRender, }; From e74a139b1244b154b6c82180f97d31a21f4ddd46 Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Tue, 8 Sep 2026 15:48:14 +0200 Subject: [PATCH 11/19] Fix URL copy button prop name --- InfoLogger/public/log/commandLogs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InfoLogger/public/log/commandLogs.js b/InfoLogger/public/log/commandLogs.js index dbd30ff586..73326ca3a3 100644 --- a/InfoLogger/public/log/commandLogs.js +++ b/InfoLogger/public/log/commandLogs.js @@ -83,7 +83,7 @@ const copyURLButton = (url) => h( // Copy the non-debounced URL with the current query string value: url, id: 'url', - classes: '', + className: '', style: { minWidth: '100px' }, }, 'Copy URL', From 033245fcbd94fb8f4a0eca1208c0b5ff899874b6 Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Tue, 8 Sep 2026 16:56:58 +0200 Subject: [PATCH 12/19] Notify on Copy URL clipboard failures Wires an `onFailure` handler so clipboard errors are surfaced to users as a danger notification. Adds a test that simulates a clipboard rejection and verifies the expected notification state, type, and message. --- InfoLogger/public/log/commandLogs.js | 5 +++-- InfoLogger/test/public/copy-url-btn-mocha.js | 12 ++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/InfoLogger/public/log/commandLogs.js b/InfoLogger/public/log/commandLogs.js index 73326ca3a3..1fa7484f7e 100644 --- a/InfoLogger/public/log/commandLogs.js +++ b/InfoLogger/public/log/commandLogs.js @@ -68,7 +68,7 @@ export const commandLogs = (model) => [ ]), h('', downloadButtonGroup(model.log)), h('', zoomButtonGroup(model.zoom)), - copyURLButton(model.shareableURL), + copyURLButton(model.shareableURL, model.notification), ]; /** @@ -77,7 +77,7 @@ export const commandLogs = (model) => [ * @param {string} url - the url string to be appended to the URL * @returns {Component} the copy button component */ -const copyURLButton = (url) => h( +const copyURLButton = (url, notification) => h( CopyToClipboardComponent, { // Copy the non-debounced URL with the current query string @@ -85,6 +85,7 @@ const copyURLButton = (url) => h( id: 'url', className: '', style: { minWidth: '100px' }, + onFailure: ({ message }) => notification.show(`Could not copy URL: ${message}`, 'danger', 3000), }, 'Copy URL', ); diff --git a/InfoLogger/test/public/copy-url-btn-mocha.js b/InfoLogger/test/public/copy-url-btn-mocha.js index fac6251eb4..0a116ab7a3 100644 --- a/InfoLogger/test/public/copy-url-btn-mocha.js +++ b/InfoLogger/test/public/copy-url-btn-mocha.js @@ -54,4 +54,16 @@ describe('Copy URL button test-suite', async () => { + '%3A%22needle%22%7D%2C%22severity%22%3A%7B%22in%22%3A%22I%20W%20E%20F%22%7D%7D'; assert.strictEqual(copiedText, expectedUrl); }); + + it('should display a notification on copy failure', async () => { + await page.evaluate(() => { + navigator.clipboard.writeText = () => Promise.reject(new Error('Simulated copy failure')); + }); + + await page.click('#copy-url'); + + await page.waitForFunction('window.model.notification.state === \'shown\''); + await page.waitForFunction('window.model.notification.type === \'danger\''); + await page.waitForFunction('window.model.notification.message === "Could not copy URL: Simulated copy failure"'); + }); }); From 602a2eaccc41ed11ec299d39dc621d88fb461526 Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Tue, 8 Sep 2026 17:23:52 +0200 Subject: [PATCH 13/19] Stabilize copy URL failure notification test --- InfoLogger/test/public/copy-url-btn-mocha.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/InfoLogger/test/public/copy-url-btn-mocha.js b/InfoLogger/test/public/copy-url-btn-mocha.js index 0a116ab7a3..06b1b891b9 100644 --- a/InfoLogger/test/public/copy-url-btn-mocha.js +++ b/InfoLogger/test/public/copy-url-btn-mocha.js @@ -62,8 +62,9 @@ describe('Copy URL button test-suite', async () => { await page.click('#copy-url'); - await page.waitForFunction('window.model.notification.state === \'shown\''); - await page.waitForFunction('window.model.notification.type === \'danger\''); - await page.waitForFunction('window.model.notification.message === "Could not copy URL: Simulated copy failure"'); + const notification = await page.evaluate(() => window.model.notification); + assert.strictEqual(notification.state, 'shown'); + assert.strictEqual(notification.type, 'danger'); + assert.strictEqual(notification.message, 'Could not copy URL: Simulated copy failure'); }); }); From 8528683fc6979b74a3228ebefc669a9eaf78933d Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Tue, 8 Sep 2026 17:32:03 +0200 Subject: [PATCH 14/19] Trying to stabilise notifcation test --- InfoLogger/test/public/copy-url-btn-mocha.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/InfoLogger/test/public/copy-url-btn-mocha.js b/InfoLogger/test/public/copy-url-btn-mocha.js index 06b1b891b9..d88ae25900 100644 --- a/InfoLogger/test/public/copy-url-btn-mocha.js +++ b/InfoLogger/test/public/copy-url-btn-mocha.js @@ -57,14 +57,15 @@ describe('Copy URL button test-suite', async () => { it('should display a notification on copy failure', async () => { await page.evaluate(() => { + model.notification.hide(); navigator.clipboard.writeText = () => Promise.reject(new Error('Simulated copy failure')); }); + await waitForNextRender(page); await page.click('#copy-url'); - const notification = await page.evaluate(() => window.model.notification); - assert.strictEqual(notification.state, 'shown'); - assert.strictEqual(notification.type, 'danger'); - assert.strictEqual(notification.message, 'Could not copy URL: Simulated copy failure'); + await page.waitForFunction('model.notification.state === \'shown\''); + await page.waitForFunction('model.notification.type === \'danger\''); + await page.waitForFunction('model.notification.message === "Could not copy URL: Simulated copy failure"'); }); }); From 16d7cc145fab6d3cf727cc4a27714836c51068eb Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Tue, 8 Sep 2026 17:44:23 +0200 Subject: [PATCH 15/19] Fix unreliable test --- InfoLogger/test/public/copy-url-btn-mocha.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/InfoLogger/test/public/copy-url-btn-mocha.js b/InfoLogger/test/public/copy-url-btn-mocha.js index d88ae25900..09bd2200ca 100644 --- a/InfoLogger/test/public/copy-url-btn-mocha.js +++ b/InfoLogger/test/public/copy-url-btn-mocha.js @@ -64,8 +64,15 @@ describe('Copy URL button test-suite', async () => { await page.click('#copy-url'); - await page.waitForFunction('model.notification.state === \'shown\''); - await page.waitForFunction('model.notification.type === \'danger\''); - await page.waitForFunction('model.notification.message === "Could not copy URL: Simulated copy failure"'); + await page.waitForFunction(() => window.model.notification.state === 'shown'); + const notification = await page.evaluate(() => ({ + message: window.model.notification.message, + type: window.model.notification.type, + })); + + assert.strictEqual(notification.message, 'Could not copy URL: Simulated copy failure'); + assert.strictEqual(notification.type, 'danger'); + + await page.evaluate(() => delete navigator.clipboard.writeText); }); }); From 46fce707c7d7c1121c68d1f1fbce616c5be467aa Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Tue, 8 Sep 2026 17:47:12 +0200 Subject: [PATCH 16/19] Use another way to mock erroneous clipboard --- InfoLogger/test/public/copy-url-btn-mocha.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/InfoLogger/test/public/copy-url-btn-mocha.js b/InfoLogger/test/public/copy-url-btn-mocha.js index 09bd2200ca..b301b27ca3 100644 --- a/InfoLogger/test/public/copy-url-btn-mocha.js +++ b/InfoLogger/test/public/copy-url-btn-mocha.js @@ -57,10 +57,13 @@ describe('Copy URL button test-suite', async () => { it('should display a notification on copy failure', async () => { await page.evaluate(() => { - model.notification.hide(); - navigator.clipboard.writeText = () => Promise.reject(new Error('Simulated copy failure')); + Object.defineProperty(navigator, 'clipboard', { + value: { + writeText: () => Promise.reject(new Error('Clipboard access denied')), + }, + configurable: true, + }); }); - await waitForNextRender(page); await page.click('#copy-url'); From ff3ac9f276a70d81ee708e046557881f2564b9f3 Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Tue, 8 Sep 2026 17:47:52 +0200 Subject: [PATCH 17/19] Fix wrong assert text --- InfoLogger/test/public/copy-url-btn-mocha.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InfoLogger/test/public/copy-url-btn-mocha.js b/InfoLogger/test/public/copy-url-btn-mocha.js index b301b27ca3..52954f7bd7 100644 --- a/InfoLogger/test/public/copy-url-btn-mocha.js +++ b/InfoLogger/test/public/copy-url-btn-mocha.js @@ -59,7 +59,7 @@ describe('Copy URL button test-suite', async () => { await page.evaluate(() => { Object.defineProperty(navigator, 'clipboard', { value: { - writeText: () => Promise.reject(new Error('Clipboard access denied')), + writeText: () => Promise.reject(new Error('Simulated copy failure')), }, configurable: true, }); From 1cab269cce4068240c7d736c75a1fdac27fa2d3e Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Tue, 8 Sep 2026 17:54:33 +0200 Subject: [PATCH 18/19] Stabilise test again --- InfoLogger/test/public/copy-url-btn-mocha.js | 1 + 1 file changed, 1 insertion(+) diff --git a/InfoLogger/test/public/copy-url-btn-mocha.js b/InfoLogger/test/public/copy-url-btn-mocha.js index 52954f7bd7..29d2c4c44d 100644 --- a/InfoLogger/test/public/copy-url-btn-mocha.js +++ b/InfoLogger/test/public/copy-url-btn-mocha.js @@ -57,6 +57,7 @@ describe('Copy URL button test-suite', async () => { it('should display a notification on copy failure', async () => { await page.evaluate(() => { + window.model.notification.hide(); Object.defineProperty(navigator, 'clipboard', { value: { writeText: () => Promise.reject(new Error('Simulated copy failure')), From dd04f15602a8c46eeb1fe8e7d51639ab1c3b0782 Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Tue, 8 Sep 2026 17:58:46 +0200 Subject: [PATCH 19/19] Use global model in copy URL mocha test --- InfoLogger/test/public/copy-url-btn-mocha.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/InfoLogger/test/public/copy-url-btn-mocha.js b/InfoLogger/test/public/copy-url-btn-mocha.js index 29d2c4c44d..c69ed038cf 100644 --- a/InfoLogger/test/public/copy-url-btn-mocha.js +++ b/InfoLogger/test/public/copy-url-btn-mocha.js @@ -44,8 +44,8 @@ describe('Copy URL button test-suite', async () => { it('should copy a URL carrying the active filter', async () => { await page.evaluate(() => { - window.model.log.filter.setCriteria('message', 'match', 'needle'); - window.model.notify(); + model.log.filter.setCriteria('message', 'match', 'needle'); + model.notify(); }); await waitForNextRender(page); await page.click('#copy-url'); @@ -57,7 +57,7 @@ describe('Copy URL button test-suite', async () => { it('should display a notification on copy failure', async () => { await page.evaluate(() => { - window.model.notification.hide(); + model.notification.hide(); Object.defineProperty(navigator, 'clipboard', { value: { writeText: () => Promise.reject(new Error('Simulated copy failure')), @@ -68,10 +68,10 @@ describe('Copy URL button test-suite', async () => { await page.click('#copy-url'); - await page.waitForFunction(() => window.model.notification.state === 'shown'); + await page.waitForFunction(() => model.notification.state === 'shown'); const notification = await page.evaluate(() => ({ - message: window.model.notification.message, - type: window.model.notification.type, + message: model.notification.message, + type: model.notification.type, })); assert.strictEqual(notification.message, 'Could not copy URL: Simulated copy failure');