mirror of
https://github.com/nasa/openmct.git
synced 2024-12-18 12:56:25 +00:00
* 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:
parent
689f7cc815
commit
e3fcbe1a35
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -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({
|
||||
|
@ -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 }) => {
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
});
|
||||
|
||||
|
@ -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(
|
||||
|
@ -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();
|
||||
|
@ -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>
|
||||
|
@ -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) {
|
||||
input.setCustomValidity('');
|
||||
input.title = '';
|
||||
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) {
|
||||
validationResult = this.openmct.time.validateBounds(boundsValues);
|
||||
}
|
||||
} 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);
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user