fix(#7791): tc form shouldn't submit bounds changes on dismiss (#7792)

* fix(#7791): tc form shouldn't submit bounds changes on dismiss

* test(e2e): add tests for validating time conductor popup

- update appAction for setting time conductor in fixed mode
- add a11y to time conductor in fixed mode
- update tests using `setTimeConductorBounds`

* fix(#7791): actually fix the problem. Also, add a test.

* test: add annotation to regression test

* docs: comments

* test: fix the reset image button flake ONCE AND FOR ALL

- wait for the rightmost image thumbnail to be in the viewport :D

* test: add tests for `setTimeConductorMode` and `setTimeConductorBounds`
This commit is contained in:
Jesse Mazzella 2024-07-25 16:55:50 -07:00 committed by GitHub
parent 689f7cc815
commit e3fcbe1a35
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 302 additions and 172 deletions

View File

@ -436,61 +436,67 @@ async function setRealTimeMode(page) {
/**
* 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
* @param {OffsetValues} offset - Object containing offset values
* @param {boolean} [offset.submitChanges=true] - If true, submit the offset changes; otherwise, discard them
*/
async function setTimeConductorOffset(
page,
{ startHours, startMins, startSecs, endHours, endMins, endSecs }
{ startHours, startMins, startSecs, endHours, endMins, endSecs, submitChanges = true }
) {
if (startHours) {
await page.getByRole('spinbutton', { name: 'Start offset hours' }).fill(startHours);
await page.getByLabel('Start offset hours').fill(startHours);
}
if (startMins) {
await page.getByRole('spinbutton', { name: 'Start offset minutes' }).fill(startMins);
await page.getByLabel('Start offset minutes').fill(startMins);
}
if (startSecs) {
await page.getByRole('spinbutton', { name: 'Start offset seconds' }).fill(startSecs);
await page.getByLabel('Start offset seconds').fill(startSecs);
}
if (endHours) {
await page.getByRole('spinbutton', { name: 'End offset hours' }).fill(endHours);
await page.getByLabel('End offset hours').fill(endHours);
}
if (endMins) {
await page.getByRole('spinbutton', { name: 'End offset minutes' }).fill(endMins);
await page.getByLabel('End offset minutes').fill(endMins);
}
if (endSecs) {
await page.getByRole('spinbutton', { name: 'End offset seconds' }).fill(endSecs);
await page.getByLabel('End offset seconds').fill(endSecs);
}
// Click the check button
await page.locator('.pr-time-input--buttons .icon-check').click();
if (submitChanges) {
await page.getByLabel('Submit time offsets').click();
} else {
await page.getByLabel('Discard changes and close time popup').click();
}
}
/**
* Set the values (hours, mins, secs) for the start time offset when in realtime mode
* @param {import('@playwright/test').Page} page
* @param {OffsetValues} offset
* @param {boolean} [submit=true] If true, submit the offset changes; otherwise, discard them
*/
async function setStartOffset(page, offset) {
async function setStartOffset(page, { submitChanges = true, ...offset }) {
// Click 'mode' button
await page.getByRole('button', { name: 'Time Conductor Mode', exact: true }).click();
await setTimeConductorOffset(page, offset);
await setTimeConductorOffset(page, { submitChanges, ...offset });
}
/**
* Set the values (hours, mins, secs) for the end time offset when in realtime mode
* @param {import('@playwright/test').Page} page
* @param {OffsetValues} offset
* @param {boolean} [submit=true] If true, submit the offset changes; otherwise, discard them
*/
async function setEndOffset(page, offset) {
async function setEndOffset(page, { submitChanges = true, ...offset }) {
// Click 'mode' button
await page.getByRole('button', { name: 'Time Conductor Mode', exact: true }).click();
await setTimeConductorOffset(page, offset);
await setTimeConductorOffset(page, { submitChanges, ...offset });
}
/**
@ -499,17 +505,40 @@ async function setEndOffset(page, offset) {
* NOTE: Unless explicitly testing the Time Conductor itself, it is advised to instead
* navigate directly to the object with the desired time bounds using `navigateToObjectWithFixedTimeBounds()`.
* @param {import('@playwright/test').Page} page
* @param {string} startDate
* @param {string} endDate
* @param {Object} bounds - The time conductor bounds
* @param {string} [bounds.startDate] - The start date in YYYY-MM-DD format
* @param {string} [bounds.startTime] - The start time in HH:mm:ss format
* @param {string} [bounds.endDate] - The end date in YYYY-MM-DD format
* @param {string} [bounds.endTime] - The end time in HH:mm:ss format
* @param {boolean} [bounds.submitChanges=true] - If true, submit the changes; otherwise, discard them.
*/
async function setTimeConductorBounds(page, startDate, endDate) {
// Bring up the time conductor popup
expect(await page.locator('.l-shell__time-conductor.c-compact-tc').count()).toBe(1);
await page.click('.l-shell__time-conductor.c-compact-tc');
async function setTimeConductorBounds(page, { submitChanges = true, ...bounds }) {
const { startDate, endDate, startTime, endTime } = bounds;
await setTimeBounds(page, startDate, endDate);
// Open the time conductor popup
await page.getByRole('button', { name: 'Time Conductor Mode', exact: true }).click();
await page.keyboard.press('Enter');
if (startDate) {
await page.getByLabel('Start date').fill(startDate);
}
if (startTime) {
await page.getByLabel('Start time').fill(startTime);
}
if (endDate) {
await page.getByLabel('End date').fill(endDate);
}
if (endTime) {
await page.getByLabel('End time').fill(endTime);
}
if (submitChanges) {
await page.getByLabel('Submit time bounds').click();
} else {
await page.getByLabel('Discard changes and close time popup').click();
}
}
/**

View File

@ -24,14 +24,18 @@ import {
createDomainObjectWithDefaults,
createNotification,
expandEntireTree,
openObjectTreeContextMenu
openObjectTreeContextMenu,
setFixedTimeMode,
setRealTimeMode,
setTimeConductorBounds
} from '../../appActions.js';
import { expect, test } from '../../pluginFixtures.js';
test.describe('AppActions', () => {
test('createDomainObjectsWithDefaults', async ({ page }) => {
test.beforeEach(async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' });
});
test('createDomainObjectsWithDefaults', async ({ page }) => {
const e2eFolder = await createDomainObjectWithDefaults(page, {
type: 'Folder',
name: 'e2e folder'
@ -91,7 +95,6 @@ test.describe('AppActions', () => {
});
});
test('createNotification', async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' });
await createNotification(page, {
message: 'Test info notification',
severity: 'info'
@ -115,8 +118,6 @@ test.describe('AppActions', () => {
await page.locator('[aria-label="Dismiss"]').click();
});
test('expandEntireTree', async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' });
const rootFolder = await createDomainObjectWithDefaults(page, {
type: 'Folder'
});
@ -168,12 +169,30 @@ test.describe('AppActions', () => {
expect(await locatorTreeCollapsedItems.count()).toBe(0);
});
test('openObjectTreeContextMenu', async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' });
const folder = await createDomainObjectWithDefaults(page, {
type: 'Folder'
});
await openObjectTreeContextMenu(page, folder.url);
await expect(page.getByLabel(`${folder.name} Context Menu`)).toBeVisible();
});
test('setTimeConductorMode', async ({ page }) => {
await setFixedTimeMode(page);
await expect(page.getByLabel('Start bounds:')).toBeVisible();
await expect(page.getByLabel('End bounds:')).toBeVisible();
await setRealTimeMode(page);
await expect(page.getByLabel('Start offset')).toBeVisible();
await expect(page.getByLabel('End offset')).toBeVisible();
});
test('setTimeConductorBounds', async ({ page }) => {
// Assume in real-time mode by default
await setFixedTimeMode(page);
await setTimeConductorBounds(page, {
startDate: '2024-01-01',
endDate: '2024-01-02',
startTime: '00:00:00',
endTime: '23:59:59'
});
await expect(page.getByLabel('Start bounds: 2024-01-01 00:00:00')).toBeVisible();
await expect(page.getByLabel('End bounds: 2024-01-02 23:59:59')).toBeVisible();
});
});

View File

@ -117,18 +117,25 @@ test.describe('Generate Visual Test Data @localStorage @generatedata @clock', ()
end: '2024-11-12 20:11:11.000Z'
});
const NEW_GLOBAL_START_BOUNDS = '2024-11-11 19:11:11.000Z';
const NEW_GLOBAL_END_BOUNDS = '2024-11-11 20:11:11.000Z';
const NEW_GLOBAL_START_DATE = '2024-11-11';
const NEW_GLOBAL_START_TIME = '19:11:11';
const NEW_GLOBAL_END_DATE = '2024-11-11';
const NEW_GLOBAL_END_TIME = '20:11:11';
await setTimeConductorBounds(page, NEW_GLOBAL_START_BOUNDS, NEW_GLOBAL_END_BOUNDS);
await setTimeConductorBounds(page, {
startDate: NEW_GLOBAL_START_DATE,
startTime: NEW_GLOBAL_START_TIME,
endDate: NEW_GLOBAL_END_DATE,
endTime: NEW_GLOBAL_END_TIME
});
// Verify that the global time conductor bounds have been updated
expect(
await page.getByLabel('Global Time Conductor').getByLabel('Start bounds').textContent()
).toEqual(NEW_GLOBAL_START_BOUNDS);
expect(
await page.getByLabel('Global Time Conductor').getByLabel('End bounds').textContent()
).toEqual(NEW_GLOBAL_END_BOUNDS);
await expect(
page.getByLabel(`Start bounds: ${NEW_GLOBAL_START_DATE} ${NEW_GLOBAL_START_TIME}.000Z`)
).toBeVisible();
await expect(
page.getByLabel(`End bounds: ${NEW_GLOBAL_END_DATE} ${NEW_GLOBAL_END_TIME}.000Z`)
).toBeVisible();
//Save localStorage for future test execution
await context.storageState({

View File

@ -53,6 +53,9 @@ test.describe('Example Imagery Object', () => {
// Verify that the created object is focused
await expect(page.locator('.l-browse-bar__object-name')).toContainText(exampleImagery.name);
await page.getByLabel('Focused Image Element').hover({ trial: true });
// Wait for image thumbnail auto-scroll to complete
await expect(page.getByLabel('Image Thumbnail from').last()).toBeInViewport();
});
test('Can use Mouse Wheel to zoom in and out of latest image', async ({ page }) => {

View File

@ -25,7 +25,7 @@ Tests to verify log plot functionality. Note this test suite if very much under
necessarily be used for reference when writing new tests in this area.
*/
import { setTimeConductorBounds } from '../../../../appActions.js';
import { createDomainObjectWithDefaults, setTimeConductorBounds } from '../../../../appActions.js';
import { expect, test } from '../../../../pluginFixtures.js';
test.describe('Log plot tests', () => {
@ -86,51 +86,36 @@ async function makeOverlayPlot(page, myItemsFolderName) {
// Set a specific time range for consistency, otherwise it will change
// on every test to a range based on the current time.
const start = '2022-03-29 22:00:00.000Z';
const end = '2022-03-29 22:00:30.000Z';
const startDate = '2022-03-29';
const startTime = '22:00:00';
const endDate = '2022-03-29';
const endTime = '22:00:30';
await setTimeConductorBounds(page, start, end);
await setTimeConductorBounds(page, { startDate, startTime, endDate, endTime });
// create overlay plot
await page.locator('button.c-create-button').click();
await page.locator('li[role="menuitem"]:has-text("Overlay Plot")').click();
// Click OK button and wait for Navigate event
await Promise.all([
page.waitForLoadState(),
await page.getByRole('button', { name: 'Save' }).click(),
// Wait for Save Banner to appear
page.waitForSelector('.c-message-banner__message')
]);
// save the overlay plot
await saveOverlayPlot(page);
const overlayPlot = await createDomainObjectWithDefaults(page, {
type: 'Overlay Plot',
name: 'Unnamed Overlay Plot'
});
// create a sinewave generator
await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator',
name: 'Unnamed Sine Wave Generator',
parent: overlayPlot.uuid
});
await page.locator('button.c-create-button').click();
await page.locator('li[role="menuitem"]:has-text("Sine Wave Generator")').click();
await page.getByLabel('More actions').click();
await page.getByLabel('Edit Properties...').click();
// set amplitude to 6, offset 4, data rate 2 hz
await page.getByLabel('Amplitude', { exact: true }).fill('6');
await page.getByLabel('Offset', { exact: true }).fill('4');
await page.getByLabel('Data Rate (hz)', { exact: true }).fill('2');
await page.getByLabel('Amplitude').fill('6');
await page.getByLabel('Offset').fill('4');
await page.getByLabel('Data Rate (hz)').fill('2');
await page.getByLabel('Save').click();
// Click OK button and wait for Navigate event
await Promise.all([
page.waitForLoadState(),
await page.getByRole('button', { name: 'Save' }).click(),
// Wait for Save Banner to appear
page.waitForSelector('.c-message-banner__message')
]);
// click on overlay plot
await page.locator(`text=Open MCT ${myItemsFolderName} >> span`).nth(3).click();
await Promise.all([
page.waitForLoadState(),
page.locator('text=Unnamed Overlay Plot').first().click()
]);
await page.goto(overlayPlot.url);
}
/**

View File

@ -112,13 +112,14 @@ test.describe('Telemetry Table', () => {
// Subtract 5 minutes from the current end bound datetime and set it
// Bring up the time conductor popup
let endDate = await page.locator('[aria-label="End bounds"]').textContent();
endDate = new Date(endDate);
let endTimeStamp = await page.getByLabel('End bounds').textContent();
endTimeStamp = new Date(endTimeStamp);
endDate.setUTCMinutes(endDate.getUTCMinutes() - 5);
endDate = endDate.toISOString().replace(/T/, ' ');
endTimeStamp.setUTCMinutes(endTimeStamp.getUTCMinutes() - 5);
const endDate = endTimeStamp.toISOString().split('T')[0];
const endTime = endTimeStamp.toISOString().split('T')[1];
await setTimeConductorBounds(page, undefined, endDate);
await setTimeConductorBounds(page, { endDate, endTime });
await expect(tableWrapper).not.toHaveClass(/is-paused/);
@ -131,7 +132,7 @@ test.describe('Telemetry Table', () => {
// Verify that it is <= our new end bound
const latestMilliseconds = Date.parse(latestTelemetryDate);
const endBoundMilliseconds = Date.parse(endDate);
const endBoundMilliseconds = Date.parse(endTimeStamp);
expect(latestMilliseconds).toBeLessThanOrEqual(endBoundMilliseconds);
});

View File

@ -30,68 +30,80 @@ import {
import { expect, test } from '../../../../pluginFixtures.js';
test.describe('Time conductor operations', () => {
test('validate start time does not exceeds end time', async ({ page }) => {
test('validate start time does not exceed end time', async ({ page }) => {
// Go to baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' });
const year = new Date().getFullYear();
let startDate = 'xxxx-01-01 01:00:00.000Z';
startDate = year + startDate.substring(4);
// Set initial valid time bounds
const startDate = `${year}-01-01`;
const startTime = '01:00:00';
const endDate = `${year}-01-01`;
const endTime = '02:00:00';
await setTimeConductorBounds(page, { startDate, startTime, endDate, endTime });
let endDate = 'xxxx-01-01 02:00:00.000Z';
endDate = year + endDate.substring(4);
// Open the time conductor popup
await page.getByRole('button', { name: 'Time Conductor Mode', exact: true }).click();
await setTimeConductorBounds(page, startDate, endDate);
// Test invalid start date
const invalidStartDate = `${year}-01-02`;
await page.getByLabel('Start date').fill(invalidStartDate);
await expect(page.getByLabel('Submit time bounds')).toBeDisabled();
await page.getByLabel('Start date').fill(startDate);
await expect(page.getByLabel('Submit time bounds')).toBeEnabled();
// invalid start date
startDate = year + 1 + startDate.substring(4);
await setTimeConductorBounds(page, startDate);
// Test invalid end date
const invalidEndDate = `${year - 1}-12-31`;
await page.getByLabel('End date').fill(invalidEndDate);
await expect(page.getByLabel('Submit time bounds')).toBeDisabled();
await page.getByLabel('End date').fill(endDate);
await expect(page.getByLabel('Submit time bounds')).toBeEnabled();
// Bring up the time conductor popup
const timeConductorMode = page.locator('.c-compact-tc');
await timeConductorMode.click();
const startDateLocator = page.locator('input[type="text"]').first();
const endDateLocator = page.locator('input[type="text"]').nth(2);
// Test invalid start time
const invalidStartTime = '42:00:00';
await page.getByLabel('Start time').fill(invalidStartTime);
await expect(page.getByLabel('Submit time bounds')).toBeDisabled();
await page.getByLabel('Start time').fill(startTime);
await expect(page.getByLabel('Submit time bounds')).toBeEnabled();
await endDateLocator.click();
// Test invalid end time
const invalidEndTime = '43:00:00';
await page.getByLabel('End time').fill(invalidEndTime);
await expect(page.getByLabel('Submit time bounds')).toBeDisabled();
await page.getByLabel('End time').fill(endTime);
await expect(page.getByLabel('Submit time bounds')).toBeEnabled();
const startDateValidityStatus = await startDateLocator.evaluate((element) =>
element.checkValidity()
// Submit valid time bounds
await page.getByLabel('Submit time bounds').click();
// Verify the submitted time bounds
await expect(page.getByLabel('Start bounds')).toHaveText(
new RegExp(`${startDate} ${startTime}.000Z`)
);
expect(startDateValidityStatus).not.toBeTruthy();
// fix to valid start date
startDate = year - 1 + startDate.substring(4);
await setTimeConductorBounds(page, startDate);
// invalid end date
endDate = year - 2 + endDate.substring(4);
await setTimeConductorBounds(page, undefined, endDate);
await startDateLocator.click();
const endDateValidityStatus = await endDateLocator.evaluate((element) =>
element.checkValidity()
await expect(page.getByLabel('End bounds')).toHaveText(
new RegExp(`${endDate} ${endTime}.000Z`)
);
expect(endDateValidityStatus).not.toBeTruthy();
});
});
// Testing instructions:
// 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 }) => {
test.describe('Global Time Conductor', () => {
test.beforeEach(async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' });
});
test('Input field validation: real-time mode', async ({ page }) => {
const startOffset = {
startHours: '01',
startMins: '29',
startSecs: '23'
};
const endOffset = {
endHours: '01',
endMins: '30',
endSecs: '31'
};
// Go to baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' });
// Switch to real-time mode
await setRealTimeMode(page);
@ -99,13 +111,95 @@ test.describe('Time conductor input fields real-time mode', () => {
await setStartOffset(page, startOffset);
// Verify time was updated on time offset button
await expect(page.locator('.c-compact-tc__setting-value.icon-minus')).toContainText('00:30:23');
await expect(page.getByLabel('Start offset: 01:29:23')).toBeVisible();
// Set end time offset
await setEndOffset(page, endOffset);
// Verify time was updated on preceding time offset button
await expect(page.locator('.c-compact-tc__setting-value.icon-plus')).toContainText('00:00:31');
await expect(page.getByLabel('End offset: 01:30:31')).toBeVisible();
// Discard changes and verify that offsets remain unchanged
await setStartOffset(page, {
startHours: '00',
startMins: '30',
startSecs: '00',
submitChanges: false
});
await expect(page.getByLabel('Start offset: 01:29:23')).toBeVisible();
await expect(page.getByLabel('End offset: 01:30:31')).toBeVisible();
});
test('Input field validation: fixed time mode', async ({ page }) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/7791'
});
// Switch to fixed time mode
await setFixedTimeMode(page);
// Define valid time bounds for testing
const validBounds = {
startDate: '2024-04-20',
startTime: '00:04:20',
endDate: '2024-04-20',
endTime: '16:04:20'
};
// Set valid time conductor bounds ✌️
await setTimeConductorBounds(page, validBounds);
// Verify that the time bounds are set correctly
await expect(page.getByLabel(`Start bounds: 2024-04-20 00:04:20.000Z`)).toBeVisible();
await expect(page.getByLabel(`End bounds: 2024-04-20 16:04:20.000Z`)).toBeVisible();
// Open the Time Conductor Mode popup
await page.getByLabel('Time Conductor Mode').click();
// Test invalid start date
const invalidStartDate = '2024-04-21';
await page.getByLabel('Start date').fill(invalidStartDate);
await expect(page.getByLabel('Submit time bounds')).toBeDisabled();
await page.getByLabel('Start date').fill(validBounds.startDate);
await expect(page.getByLabel('Submit time bounds')).toBeEnabled();
// Test invalid end date
const invalidEndDate = '2024-04-19';
await page.getByLabel('End date').fill(invalidEndDate);
await expect(page.getByLabel('Submit time bounds')).toBeDisabled();
await page.getByLabel('End date').fill(validBounds.endDate);
await expect(page.getByLabel('Submit time bounds')).toBeEnabled();
// Test invalid start time
const invalidStartTime = '16:04:21';
await page.getByLabel('Start time').fill(invalidStartTime);
await expect(page.getByLabel('Submit time bounds')).toBeDisabled();
await page.getByLabel('Start time').fill(validBounds.startTime);
await expect(page.getByLabel('Submit time bounds')).toBeEnabled();
// Test invalid end time
const invalidEndTime = '00:04:19';
await page.getByLabel('End time').fill(invalidEndTime);
await expect(page.getByLabel('Submit time bounds')).toBeDisabled();
await page.getByLabel('End time').fill(validBounds.endTime);
await expect(page.getByLabel('Submit time bounds')).toBeEnabled();
// Verify that the time bounds remain unchanged after invalid inputs
await expect(page.getByLabel(`Start bounds: 2024-04-20 00:04:20.000Z`)).toBeVisible();
await expect(page.getByLabel(`End bounds: 2024-04-20 16:04:20.000Z`)).toBeVisible();
// Discard changes and verify that bounds remain unchanged
await setTimeConductorBounds(page, {
startDate: validBounds.startDate,
startTime: '04:20:00',
endDate: validBounds.endDate,
endTime: '04:20:20',
submitChanges: false
});
// Verify that the original time bounds are still displayed after discarding changes
await expect(page.getByLabel(`Start bounds: 2024-04-20 00:04:20.000Z`)).toBeVisible();
await expect(page.getByLabel(`End bounds: 2024-04-20 16:04:20.000Z`)).toBeVisible();
});
/**
@ -147,14 +241,15 @@ test.describe('Time conductor input fields real-time mode', () => {
await setRealTimeMode(page);
// Verify updated start time offset persists after mode switch
await expect(page.locator('.c-compact-tc__setting-value.icon-minus')).toContainText('00:30:23');
await expect(page.getByLabel('Start offset: 00:30:23')).toBeVisible();
// Verify updated end time offset persists after mode switch
await expect(page.locator('.c-compact-tc__setting-value.icon-plus')).toContainText('00:00:01');
await expect(page.getByLabel('End offset: 00:00:01')).toBeVisible();
// Verify url parameters persist after mode switch
expect(page.url()).toContain(`startDelta=${startDelta}`);
expect(page.url()).toContain(`endDelta=${endDelta}`);
// eslint-disable-next-line no-useless-escape
const urlRegex = new RegExp(`.*tc\.startDelta=${startDelta}&tc\.endDelta=${endDelta}.*`);
await page.waitForURL(urlRegex);
});
test.fixme(

View File

@ -64,6 +64,10 @@ test.describe('Visual - Example Imagery', () => {
test('Example Imagery in Fixed Time', async ({ page, theme }) => {
await page.goto(exampleImagery.url, { waitUntil: 'domcontentloaded' });
// Wait for the thumbnails to finish their scroll animation
// (Wait until the rightmost thumbnail is in view)
await expect(page.getByLabel('Image Thumbnail from').last()).toBeInViewport();
await expect(page.getByLabel('Image Wrapper')).toBeVisible();
await percySnapshot(page, `Example Imagery in Fixed Time (theme: ${theme})`);
@ -76,6 +80,9 @@ test.describe('Visual - Example Imagery', () => {
test('Example Imagery in Real Time', async ({ page, theme }) => {
await page.goto(exampleImagery.url, { waitUntil: 'domcontentloaded' });
// Wait for the thumbnails to finish their scroll animation
// (Wait until the rightmost thumbnail is in view)
await expect(page.getByLabel('Image Thumbnail from').last()).toBeInViewport();
await setRealTimeMode(page, true);
await expect(page.getByLabel('Image Wrapper')).toBeVisible();

View File

@ -32,7 +32,7 @@
<div
class="c-compact-tc__setting-value u-fade-truncate--lg --no-sep"
:title="`Start bounds: ${formattedBounds.start}`"
aria-label="Start bounds"
:aria-label="`Start bounds: ${formattedBounds.start}`"
>
{{ formattedBounds.start }}
</div>
@ -40,7 +40,7 @@
<div
class="c-compact-tc__setting-value u-fade-truncate--lg --no-sep"
:title="`End bounds: ${formattedBounds.end}`"
aria-label="End bounds"
:aria-label="`End bounds: ${formattedBounds.end}`"
>
{{ formattedBounds.end }}
</div>

View File

@ -88,7 +88,7 @@
<button
class="c-button icon-x"
aria-label="Discard changes and close time popup"
@click.prevent="handleFormSubmission(true)"
@click.prevent="hide"
></button>
</div>
</div>
@ -119,25 +119,21 @@ export default {
},
emits: ['update', 'dismiss'],
data() {
let timeSystem = this.openmct.time.getTimeSystem();
let durationFormatter = this.getFormatter(
timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER
);
let timeFormatter = this.getFormatter(timeSystem.timeFormat);
let bounds = this.bounds || this.openmct.time.getBounds();
const timeSystem = this.openmct.time.getTimeSystem();
const bounds = this.openmct.time.getBounds();
return {
timeFormatter,
durationFormatter,
timeFormatter: this.getFormatter(timeSystem.timeFormat),
durationFormatter: this.getFormatter(timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER),
bounds: {
start: bounds.start,
end: bounds.end
},
formattedBounds: {
start: timeFormatter.format(bounds.start).split(' ')[0],
end: timeFormatter.format(bounds.end).split(' ')[0],
startTime: durationFormatter.format(Math.abs(bounds.start)),
endTime: durationFormatter.format(Math.abs(bounds.end))
start: '',
end: '',
startTime: '',
endTime: ''
},
isUTCBased: timeSystem.isUTCBased,
isDisabled: false
@ -157,9 +153,12 @@ export default {
deep: true
}
},
mounted() {
created() {
this.handleNewBounds = _.throttle(this.handleNewBounds, 300);
},
mounted() {
this.setTimeSystem(JSON.parse(JSON.stringify(this.openmct.time.getTimeSystem())));
this.setViewFromBounds(this.bounds);
},
beforeUnmount() {
this.clearAllValidation();
@ -173,8 +172,10 @@ export default {
[this.$refs.startDate, this.$refs.endDate].forEach(this.clearValidationForInput);
},
clearValidationForInput(input) {
if (input) {
input.setCustomValidity('');
input.title = '';
}
},
setBounds(bounds) {
this.bounds = bounds;
@ -186,7 +187,6 @@ export default {
this.formattedBounds.endTime = this.durationFormatter.format(Math.abs(bounds.end));
},
setTimeSystem(timeSystem) {
this.timeSystem = timeSystem;
this.timeFormatter = this.getFormatter(timeSystem.timeFormat);
this.durationFormatter = this.getFormatter(
timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER
@ -207,39 +207,31 @@ export default {
`${this.formattedBounds.end} ${this.formattedBounds.endTime}`
);
this.$emit('update', {
start: start,
end: end
});
this.$emit('update', { start, end });
}
if (dismiss) {
this.$emit('dismiss');
return false;
}
},
handleFormSubmission(shouldDismiss) {
// Validate bounds before submission
this.validateAllBounds('startDate');
this.validateAllBounds('endDate');
// Submit the form if it's valid
if (!this.isDisabled) {
this.setBoundsFromView(shouldDismiss);
}
},
validateAllBounds(ref) {
this.isDisabled = false; // Reset isDisabled at the start of validation
this.isDisabled = false;
if (!this.areBoundsFormatsValid()) {
this.isDisabled = true;
return false;
}
let validationResult = {
valid: true
};
let validationResult = { valid: true };
const currentInput = this.$refs[ref];
return [this.$refs.startDate, this.$refs.endDate].every((input) => {
@ -255,38 +247,30 @@ export default {
// const limit = this.getBoundsLimit();
const limit = false;
if (this.timeSystem.isUTCBased && limit && boundsValues.end - boundsValues.start > limit) {
if (this.isUTCBased && limit && boundsValues.end - boundsValues.start > limit) {
if (input === currentInput) {
validationResult = {
valid: false,
message: 'Start and end difference exceeds allowable limit'
};
}
} else {
if (input === currentInput) {
} else if (input === currentInput) {
validationResult = this.openmct.time.validateBounds(boundsValues);
}
}
return this.handleValidationResults(input, validationResult);
});
},
areBoundsFormatsValid() {
let validationResult = {
valid: true
};
return [this.$refs.startDate, this.$refs.endDate].every((input) => {
const formattedDate =
input === this.$refs.startDate
? `${this.formattedBounds.start} ${this.formattedBounds.startTime}`
: `${this.formattedBounds.end} ${this.formattedBounds.endTime}`;
if (!this.timeFormatter.validate(formattedDate)) {
validationResult = {
valid: false,
message: 'Invalid date'
};
}
const validationResult = this.timeFormatter.validate(formattedDate)
? { valid: true }
: { valid: false, message: 'Invalid date' };
return this.handleValidationResults(input, validationResult);
});