From 405418b9d5418db17707dd8e8b756c6aa2eecfd1 Mon Sep 17 00:00:00 2001 From: Jesse Mazzella Date: Mon, 23 May 2022 14:10:59 -0700 Subject: [PATCH] Preserve local clock offsets on mode switch, fall back to defaults (#5217) --- .../imagery/exampleImagery.e2e.spec.js | 2 - .../timeConductor/timeConductor.e2e.spec.js | 178 +++++++++++++++--- src/api/menu/components/Menu.vue | 2 + src/api/menu/components/SuperMenu.vue | 2 + .../timeConductor/ConductorInputsRealtime.vue | 2 + src/plugins/timeConductor/ConductorMode.vue | 5 +- 6 files changed, 162 insertions(+), 29 deletions(-) diff --git a/e2e/tests/plugins/imagery/exampleImagery.e2e.spec.js b/e2e/tests/plugins/imagery/exampleImagery.e2e.spec.js index ed12d6b14b..1f98aa340a 100644 --- a/e2e/tests/plugins/imagery/exampleImagery.e2e.spec.js +++ b/e2e/tests/plugins/imagery/exampleImagery.e2e.spec.js @@ -430,9 +430,7 @@ test.describe('Example imagery thumbnails resize in display layouts', () => { expect(thumbsWrapperLocator.isVisible()).toBeTruthy(); await expect(thumbsWrapperLocator).not.toHaveClass(/is-small-thumbs/); - }); - }); test.describe('Example Imagery in Flexible layout', () => { diff --git a/e2e/tests/plugins/timeConductor/timeConductor.e2e.spec.js b/e2e/tests/plugins/timeConductor/timeConductor.e2e.spec.js index 8ca1285aa8..ed759a53df 100644 --- a/e2e/tests/plugins/timeConductor/timeConductor.e2e.spec.js +++ b/e2e/tests/plugins/timeConductor/timeConductor.e2e.spec.js @@ -23,9 +23,9 @@ const { test } = require('../../../fixtures.js'); const { expect } = require('@playwright/test'); -test.describe('Time counductor operations', () => { +test.describe('Time conductor operations', () => { test('validate start time does not exceeds end time', async ({ page }) => { - //Go to baseURL + // Go to baseURL await page.goto('/', { waitUntil: 'networkidle' }); const year = new Date().getFullYear(); @@ -73,37 +73,163 @@ test.describe('Time counductor operations', () => { // Try to change the realtime offsets when in realtime (local clock) mode. test.describe('Time conductor input fields real-time mode', () => { test('validate input fields in real-time mode', async ({ page }) => { - //Go to baseURL + const startOffset = { + secs: '23' + }; + + const endOffset = { + secs: '31' + }; + + // Go to baseURL await page.goto('/', { waitUntil: 'networkidle' }); - // Click fixed timespan button - await page.locator('.c-button__label >> text=Fixed Timespan').click(); + // Switch to real-time mode + await setRealTimeMode(page); - // Click local clock - await page.locator('.icon-clock >> text=Local Clock').click(); - - // Click time offset button - await page.locator('.c-conductor__delta-button >> text=00:30:00').click(); - - // Input start time offset - await page.fill('.pr-time-controls__secs', '23'); - - // Click the check button - await page.locator('.icon-check').click(); + // Set start time offset + await setStartOffset(page, startOffset); // Verify time was updated on time offset button - await expect(page.locator('.c-conductor__delta-button').first()).toContainText('00:30:23'); + await expect(page.locator('data-testid=conductor-start-offset-button')).toContainText('00:30:23'); - // Click time offset set preceding now button - await page.locator('.c-conductor__delta-button >> text=00:00:30').click(); - - // Input preceding time offset - await page.fill('.pr-time-controls__secs', '31'); - - // Click the check buttons - await page.locator('.icon-check').click(); + // Set end time offset + await setEndOffset(page, endOffset); // Verify time was updated on preceding time offset button - await expect(page.locator('.c-conductor__delta-button').nth(1)).toContainText('00:00:31'); + await expect(page.locator('data-testid=conductor-end-offset-button')).toContainText('00:00:31'); + }); + + /** + * Verify that offsets and url params are preserved when switching + * between fixed timespan and real-time mode. + */ + test('preserve offsets and url params when switching between fixed and real-time mode', async ({ page }) => { + const startOffset = { + mins: '30', + secs: '23' + }; + + const endOffset = { + secs: '01' + }; + + // Convert offsets to milliseconds + const startDelta = (30 * 60 * 1000) + (23 * 1000); + const endDelta = (1 * 1000); + + // Go to baseURL + await page.goto('/', { waitUntil: 'networkidle' }); + + // Switch to real-time mode + await setRealTimeMode(page); + + // Set start time offset + await setStartOffset(page, startOffset); + + // Set end time offset + await setEndOffset(page, endOffset); + + // Switch to fixed timespan mode + await setFixedTimeMode(page); + + // Switch back to real-time mode + await setRealTimeMode(page); + + // Verify updated start time offset persists after mode switch + await expect(page.locator('data-testid=conductor-start-offset-button')).toContainText('00:30:23'); + + // Verify updated end time offset persists after mode switch + await expect(page.locator('data-testid=conductor-end-offset-button')).toContainText('00:00:01'); + + // Verify url parameters persist after mode switch + await page.waitForNavigation(); + expect(page.url()).toContain(`startDelta=${startDelta}`); + expect(page.url()).toContain(`endDelta=${endDelta}`); }); }); + +/** + * @typedef {Object} OffsetValues + * @property {string | undefined} hours + * @property {string | undefined} mins + * @property {string | undefined} secs + */ + +/** + * Set the values (hours, mins, secs) for the start time offset when in realtime mode + * @param {import('@playwright/test').Page} page + * @param {OffsetValues} offset + */ +async function setStartOffset(page, offset) { + const startOffsetButton = page.locator('data-testid=conductor-start-offset-button'); + await setTimeConductorOffset(page, offset, startOffsetButton); +} + +/** + * Set the values (hours, mins, secs) for the end time offset when in realtime mode + * @param {import('@playwright/test').Page} page + * @param {OffsetValues} offset + */ +async function setEndOffset(page, offset) { + const endOffsetButton = page.locator('data-testid=conductor-end-offset-button'); + await setTimeConductorOffset(page, offset, endOffsetButton); +} + +/** + * Set the time conductor to fixed timespan mode + * @param {import('@playwright/test').Page} page + */ +async function setFixedTimeMode(page) { + await setTimeConductorMode(page, true); +} + +/** + * Set the time conductor to realtime mode + * @param {import('@playwright/test').Page} page + */ +async function setRealTimeMode(page) { + await setTimeConductorMode(page, false); +} + +/** + * Set the values (hours, mins, secs) for the TimeConductor offsets when in realtime mode + * @param {import('@playwright/test').Page} page + * @param {OffsetValues} offset + * @param {import('@playwright/test').Locator} offsetButton + */ +async function setTimeConductorOffset(page, {hours, mins, secs}, offsetButton) { + await offsetButton.click(); + + if (hours) { + await page.fill('.pr-time-controls__hrs', hours); + } + + if (mins) { + await page.fill('.pr-time-controls__mins', mins); + } + + if (secs) { + await page.fill('.pr-time-controls__secs', secs); + } + + // Click the check button + await page.locator('.icon-check').click(); +} + +/** + * Set the time conductor mode to either fixed timespan or realtime mode. + * @param {import('@playwright/test').Page} page + * @param {boolean} [isFixedTimespan=true] true for fixed timespan mode, false for realtime mode; default is true + */ +async function setTimeConductorMode(page, isFixedTimespan = true) { + // Click 'mode' button + await page.locator('.c-mode-button').click(); + + // Switch time conductor mode + if (isFixedTimespan) { + await page.locator('data-testid=conductor-modeOption-fixed').click(); + } else { + await page.locator('data-testid=conductor-modeOption-realtime').click(); + } +} diff --git a/src/api/menu/components/Menu.vue b/src/api/menu/components/Menu.vue index 0073062e0e..4d0cf39372 100644 --- a/src/api/menu/components/Menu.vue +++ b/src/api/menu/components/Menu.vue @@ -12,6 +12,7 @@ :key="action.name" :class="[action.cssClass, action.isDisabled ? 'disabled' : '']" :title="action.description" + :data-testid="action.testId || false" @click="action.onItemClicked" > {{ action.name }} @@ -37,6 +38,7 @@ :key="action.name" :class="action.cssClass" :title="action.description" + :data-testid="action.testId || false" @click="action.onItemClicked" > {{ action.name }} diff --git a/src/api/menu/components/SuperMenu.vue b/src/api/menu/components/SuperMenu.vue index d7fe6a7a67..7b66c68b65 100644 --- a/src/api/menu/components/SuperMenu.vue +++ b/src/api/menu/components/SuperMenu.vue @@ -15,6 +15,7 @@ :key="action.name" :class="[action.cssClass, action.isDisabled ? 'disabled' : '']" :title="action.description" + :data-testid="action.testId || false" @click="action.onItemClicked" @mouseover="toggleItemDescription(action)" @mouseleave="toggleItemDescription()" @@ -45,6 +46,7 @@ :key="action.name" :class="action.cssClass" :title="action.description" + :data-testid="action.testId || false" @click="action.onItemClicked" @mouseover="toggleItemDescription(action)" @mouseleave="toggleItemDescription()" diff --git a/src/plugins/timeConductor/ConductorInputsRealtime.vue b/src/plugins/timeConductor/ConductorInputsRealtime.vue index dc1cb77e58..c6aee38ee7 100644 --- a/src/plugins/timeConductor/ConductorInputsRealtime.vue +++ b/src/plugins/timeConductor/ConductorInputsRealtime.vue @@ -22,6 +22,7 @@ ref="startOffset" class="c-button c-conductor__delta-button" title="Set the time offset after now" + data-testid="conductor-start-offset-button" @click.prevent.stop="showTimePopupStart" > {{ offsets.start }} @@ -61,6 +62,7 @@ ref="endOffset" class="c-button c-conductor__delta-button" title="Set the time offset preceding now" + data-testid="conductor-end-offset-button" @click.prevent.stop="showTimePopupEnd" > {{ offsets.end }} diff --git a/src/plugins/timeConductor/ConductorMode.vue b/src/plugins/timeConductor/ConductorMode.vue index 869ca1b0a1..fbe4fa2f77 100644 --- a/src/plugins/timeConductor/ConductorMode.vue +++ b/src/plugins/timeConductor/ConductorMode.vue @@ -105,6 +105,7 @@ export default { name: 'Fixed Timespan', description: 'Query and explore data that falls between two fixed datetimes.', cssClass: 'icon-tabular', + testId: 'conductor-modeOption-fixed', onItemClicked: () => this.setOption(key) }; } else { @@ -116,6 +117,7 @@ export default { description: "Monitor streaming data in real-time. The Time " + "Conductor and displays will automatically advance themselves based on this clock. " + clock.description, cssClass: clock.cssClass || 'icon-clock', + testId: 'conductor-modeOption-realtime', onItemClicked: () => this.setOption(key) }; } @@ -148,7 +150,8 @@ export default { if (clockKey === undefined) { this.openmct.time.stopClock(); } else { - this.openmct.time.clock(clockKey, configuration.clockOffsets); + const offsets = this.openmct.time.clockOffsets() || configuration.clockOffsets; + this.openmct.time.clock(clockKey, offsets); } },