fix(#7623): Resize ConductorAxis properly (#7624)

* fix: resize conductor properly

* refactor: more computed properties, unregister listener

* fix: beforeUnmounted hook

* test(visual): add time conductor visual test for fixed mode

* fix: initialize to `null`

* feat: extend the base `screenshot` function to mask elements which will always create variance in an Open MCT screenshot

* docs: add types for fixtures

* fix: remove unneeded await

* chore: add sinon timers types package back

* docs: remove unused docs

* doc: remove unused docs

* test: add visual realtime url, update imports

* feat: provide wrapped page.screenshot fixture that applies defaults

* test: add basic timeConductor snapshot tests

* chore: update eslint config

* lint: remove unused disable directives

* test: remove redundant navigation

* fix: remove listeners

* fix: maybe stabilize unit tests

* docs: remove

* fix: provide sourcemaps in unit tests

* test: add regression snapshot test for time conductor axis

* lint: remove unused imports

* feat(e2e): add fixture to manually tick the clock and use it

* test: reactivate test now that we don't use deploysentinel :(

* test: update snapshots

* test: add test for clockOptions and tick fixtures

* test: add afterEach stub and fixme

* test: try and stabilize fault management flake

* lint: defy the word gods

* chore: ignore `*-darwin.png` screenshots

* chore: remove darwin screenshot binaries

* docs: markdownlint

* docs: remove MacOS specific instructions from snapshot testing

* fix: remove a11y
This commit is contained in:
Jesse Mazzella 2024-03-26 16:52:33 -07:00 committed by GitHub
parent 7e926ccbb7
commit 5b4ee1949f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
54 changed files with 672 additions and 394 deletions

View File

@ -1,10 +1,13 @@
const LEGACY_FILES = ['example/**'];
module.exports = {
/** @type {import('eslint').Linter.Config} */
const config = {
env: {
browser: true,
es6: true,
es2024: true,
jasmine: true,
amd: true
node: true,
worker: true,
serviceworker: true
},
globals: {
_: 'readonly'
@ -23,10 +26,11 @@ module.exports = {
parser: '@babel/eslint-parser',
requireConfigFile: false,
allowImportExportEverywhere: true,
ecmaVersion: 2015,
ecmaVersion: 'latest',
ecmaFeatures: {
impliedStrict: true
}
},
sourceType: 'module'
},
rules: {
'simple-import-sort/imports': 'warn',
@ -152,7 +156,7 @@ module.exports = {
cases: {
pascalCase: true
},
ignore: ['^.*\\.js$']
ignore: ['^.*\\.(js|cjs|mjs)$']
}
],
'vue/first-attribute-linebreak': 'error',
@ -179,3 +183,5 @@ module.exports = {
}
]
};
module.exports = config;

3
.gitignore vendored
View File

@ -47,3 +47,6 @@ index.html.bak
.nyc_output
coverage
codecov
# Don't commit MacOS screenshots
*-darwin.png

View File

@ -78,6 +78,7 @@ To read about how to write a good visual test, please see [How to write a great
- `npm run test:e2e:visual:ci` will run against every commit and PR.
- `npm run test:e2e:visual:full` will run every night with additional comparisons made for Larger Displays and with the `snow` theme.
#### Percy.io
To make this possible, we're leveraging a 3rd party service, [Percy](https://percy.io/). This service maintains a copy of all changes, users, scm-metadata, and baselines to verify that the application looks and feels the same _unless approved by a Open MCT developer_. To request a Percy API token, please reach out to the Open MCT Dev team on GitHub. For more information, please see the official [Percy documentation](https://docs.percy.io/docs/visual-testing-basics).
@ -89,15 +90,16 @@ At present, we are using percy with two configuration files: `./e2e/.percy.night
While snapshot testing offers a precise way to detect changes in your application without relying on third-party services like Percy.io, we've found that it doesn't offer any advantages over visual testing in our use-cases. Therefore, snapshot testing is **not recommended** for further implementation.
#### CI vs Manual Checks
Snapshot tests can be reliably executed in Continuous Integration (CI) environments but lack the manual oversight provided by visual testing platforms like Percy.io. This means they may miss issues that a human reviewer could catch during manual checks.
#### Example
A single visual test assertion in Percy.io can be executed across 10 different browser and resolution combinations without additional setup, providing comprehensive testing with minimal configuration. In contrast, a snapshot test is restricted to a single OS and browser resolution, requiring more effort to achieve the same level of coverage.
#### Further Reading
For those interested in the mechanics of snapshot testing with Playwright, you can refer to the [Playwright Snapshots Documentation](https://playwright.dev/docs/test-snapshots). However, keep in mind that we do not recommend using this approach.
For those interested in the mechanics of snapshot testing with Playwright, you can refer to the [Playwright Snapshots Documentation](https://playwright.dev/docs/test-snapshots). However, keep in mind that we do not recommend using this approach.
#### Open MCT's implementation
@ -118,14 +120,6 @@ When the `@snapshot` tests fail, they will need to be evaluated to determine if
To compare a snapshot, run a test and open the html report with the 'Expected' vs 'Actual' screenshot. If the actual screenshot is preferred, then the source-controlled 'Expected' snapshots will need to be updated with the following scripts.
MacOS
```
npm run test:e2e:updatesnapshots
```
Linux/CI
```sh
// Replace {X.X.X} with the current Playwright version
// from our package.json or circleCI configuration file
@ -335,9 +329,11 @@ We have a Mission-need to support iPad and mobile devices. To run our test suite
In general, our test suite is not designed to run against mobile devices as the mobile experience is a focused version of the application. Core functionality is missing (chiefly the 'Create' button). To bypass the object creation, we leverage the `storageState` properties for starting the mobile tests with localstorage.
For now, the mobile tests will exist in the /tests/mobile/ suites and be executed with the
```sh
npm run test:e2e:mobile
```
command.
#### **Skipping or executing tests based on browser, os, and/os browser version:**
@ -377,6 +373,7 @@ In general, strive to test only through the UI as a user would. As stated in the
By adhering to this principle, we can create tests that are both robust and reflective of actual user experiences.
#### How to make tests robust to function in other contexts (VISTA, COUCHDB, YAMCS, VIPER, etc.)
1. Leverage the use of `appActions.js` methods such as `createDomainObjectWithDefaults()`. This ensures that your tests will create unique instances of objects for your test to interact with.
1. Do not assert on the order or structure of objects available unless you created them yourself. These tests may be used against a persistent datastore like couchdb with many objects in the tree.
1. Do not search for your created objects. Open MCT does not performance uniqueness checks so it's possible that your tests will break when run twice.
@ -384,6 +381,7 @@ By adhering to this principle, we can create tests that are both robust and refl
1. Leverage `await page.goto('./', { waitUntil: 'domcontentloaded' });` instead of `{ waitUntil: 'networkidle' }`. Tests run against deployments with websockets often have issues with the networkidle detection.
#### How to make tests faster and more resilient
1. Avoid app interaction when possible. The best way of doing this is to navigate directly by URL:
```js
@ -400,6 +398,7 @@ By adhering to this principle, we can create tests that are both robust and refl
This ensures that your changes will be picked up with large refactors.
##### Utilizing LocalStorage
1. In order to save test runtime in the case of tests that require a decent amount of initial setup (such as in the case of testing complex displays), you may use [Playwright's `storageState` feature](https://playwright.dev/docs/api/class-browsercontext#browser-context-storage-state) to generate and load localStorage states.
1. To generate a localStorage state to be used in a test:
- Add an e2e test to our generateLocalStorageData suite which sets the initial state (creating/configuring objects, etc.), saving it in the `test-data` folder:
@ -420,7 +419,6 @@ By adhering to this principle, we can create tests that are both robust and refl
});
```
### How to write a great test
- Avoid using css locators to find elements to the page. Use modern web accessible locators like `getByRole`
@ -445,6 +443,7 @@ By adhering to this principle, we can create tests that are both robust and refl
3. **Expect the Unexpected**: Use functional expect statements only to verify assumptions about the state between steps. A great visual test doesn't fail during the test itself, but rather when changes are reviewed in Percy.io.
4. **Control Variability**: Account for variations inherent in working with time-based telemetry and clocks.
- Utilize `percyCSS` to ignore time-based elements. For more details, consult our [percyCSS file](./.percy.ci.yml).
- Use Open MCT's fixed-time mode unless explicitly testing realtime clock
- Employ the `createExampleTelemetryObject` appAction to source telemetry and specify a `name` to avoid autogenerated names.
@ -454,14 +453,17 @@ By adhering to this principle, we can create tests that are both robust and refl
- `await page.goto('./#/browse/mine?hideTree=true&hideInspector=true')`
6. **Component-Specific Tests**: If you wish to focus on a particular component, use the `/visual-a11y/component/` folder and limit the scope of the comparison to that component. For instance:
```js
await percySnapshot(page, `Tree Pane w/ single level expanded (theme: ${theme})`, {
scope: treePane
});
```
- Note: The `scope` variable can be any valid CSS selector.
7. **Write many `percySnapshot` commands in a single test**: In line with our approach to longer functional tests, we recommend that many test percySnapshots are taken in a single test. For instance:
```js
//<Some interesting state>
await percySnapshot(page, `Before object expanded (theme: ${theme})`);
@ -511,6 +513,7 @@ test.describe('foo test suite', () => {
});
});
```
More info and options for `overrideClock` can be found in [baseFixtures.js](baseFixtures.js)
- Working with multiple pages
@ -539,7 +542,6 @@ const key = getFirstKeyFromOpenMctJson(jsonData);
expect(jsonData.openmct[key]).toHaveProperty('name', 'e2e folder');
```
### Reporting
Test Reporting is done through official Playwright reporters and the CI Systems which execute them.
@ -615,6 +617,7 @@ A single e2e test in Open MCT is extended to run:
### Writing Tests
Playwright provides 3 supported methods of debugging and authoring tests:
- A 'watch mode' for running tests locally and debugging on the fly
- A 'debug mode' for debugging tests and writing assertions against tests
- A 'VSCode plugin' for debugging tests within the VSCode IDE.

View File

@ -36,27 +36,67 @@
import AxeBuilder from '@axe-core/playwright';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { expect, test } from './pluginFixtures.js';
// Constants for repeated values
const TEST_RESULTS_DIR = './test-results';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const TEST_RESULTS_DIR = path.join(__dirname, './test-results');
const extendedTest = test.extend({
/**
* Overrides the default screenshot function to apply default options that should apply to all
* screenshots taken in the AVP tests.
*
* @param {import('@playwright/test').PlaywrightTestArgs} args - The Playwright test arguments.
* @param {Function} use - The function to use the page object.
* Defaults:
* - Disables animations
* - Masks the clock indicator
* - Masks the time conductor last update time in realtime mode
* - Masks the time conductor start bounds in fixed mode
* - Masks the time conductor end bounds in fixed mode
*/
page: async ({ page }, use) => {
const playwrightScreenshot = page.screenshot;
/**
* Override the screenshot function to always mask a given set of locators which will always
* show variance across screenshots. Defaults may be overridden by passing in options to the
* screenshot function.
* @param {import('@playwright/test').PageScreenshotOptions} options - The options for the screenshot.
* @returns {Promise<Buffer>} Returns the screenshot as a buffer.
*/
page.screenshot = async function (options = {}) {
const mask = [
this.getByLabel('Clock Indicator'), // Mask the clock indicator
this.getByLabel('Last update'), // Mask the time conductor last update time in realtime mode
this.getByLabel('Start bounds'), // Mask the time conductor start bounds in fixed mode
this.getByLabel('End bounds') // Mask the time conductor end bounds in fixed mode
];
const result = await playwrightScreenshot.call(this, {
animations: 'disabled',
mask,
...options // Pass through or override any options
});
return result;
};
await use(page);
}
});
/**
* Scans for accessibility violations on a page and writes a report to disk if violations are found.
* Automatically asserts that no violations should be present.
*
* @typedef {Object} GenerateReportOptions
* @property {string} [reportName] - The name for the report file.
*
* @param {import('playwright').Page} page - The page object from Playwright.
* @param {string} testCaseName - The name of the test case.
* @param {GenerateReportOptions} [options={}] - The options for the report generation.
*
* @returns {Promise<object|null>} Returns the accessibility scan results if violations are found,
* otherwise returns null.
* @param {{ reportName?: string }} [options={}] - The options for the report generation.
* @returns {Promise<Object|null>} Returns the accessibility scan results if violations are found, otherwise returns null.
*/
/* eslint-disable no-undef */
export async function scanForA11yViolations(page, testCaseName, options = {}) {
const builder = new AxeBuilder({ page });
builder.withTags(['wcag2aa']);
@ -93,4 +133,4 @@ export async function scanForA11yViolations(page, testCaseName, options = {}) {
}
}
export { expect, test };
export { expect, extendedTest as test };

View File

@ -1,4 +1,3 @@
/* eslint-disable no-undef */
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space
@ -111,6 +110,40 @@ const extendedTest = test.extend({
scope: 'test'
}
],
/**
* Exposes a function to manually tick the clock. This is useful when overriding the clock to not
* tick (`shouldAdvanceTime: false`) for visual tests, as events such as re-renders and router params
* updates are clock-driven and must be manually ticked.
*
* Usage:
* ```js
* test.describe('Manual Clock Tick', () => {
* test.use({
* clockOptions: {
* now: MISSION_TIME, // Set to the desired time
* shouldAdvanceTime: false // Clock overridden to no longer tick
* }
* });
* test('Visual - Manual Clock Tick', async ({ page, tick }) => {
* // Tick the clock 2 seconds in the future
* await tick(2000);
* });
* });
* ```
*
* @param {Object} param0
* @param {import('@playwright/test').Page} param0.page
* @param {import('@playwright/test').Use} param0.use
*/
tick: async ({ page }, use) => {
// eslint-disable-next-line func-style
const tick = async (milliseconds) => {
await page.evaluate((_milliseconds) => {
window.__clock.tick(_milliseconds);
}, milliseconds);
};
await use(tick);
},
/**
* Extends the base context class to add codecoverage shim.
* @see {@link https://github.com/mxschmitt/playwright-test-coverage Github Project}
@ -154,17 +187,13 @@ const extendedTest = test.extend({
// function in the generatorWorker context. This is necessary
// to ensure that example telemetry data is generated for the new clock time.
if (clockOptions?.now !== undefined) {
page.on(
'worker',
(worker) => {
page.on('worker', (worker) => {
if (worker.url().includes('generatorWorker')) {
worker.evaluate((time) => {
self.Date.now = () => time;
});
}, clockOptions.now);
}
},
clockOptions.now
);
});
}
// Capture any console errors during test execution

View File

@ -1,4 +1,3 @@
/* eslint-disable prettier/prettier */
/**
* Constants which may be used across all e2e tests.
*/
@ -8,12 +7,30 @@
* - Used for overriding the browser clock in tests.
*/
export const MISSION_TIME = 1732413600000; // Saturday, November 23, 2024 6:00:00 PM GMT-08:00 (Thanksgiving Dinner Time)
// Subtracting 30 minutes from MISSION_TIME
export const MISSION_TIME_FIXED_START = 1732413600000 - 1800000; // 1732411800000
// Adding 1 minute to MISSION_TIME
export const MISSION_TIME_FIXED_END = 1732413600000 + 60000; // 1732413660000
/**
* URL Constants
* - This is the URL that the browser will be directed to when running visual tests. This URL
* - hides the tree and inspector to prevent visual noise
* - sets the time bounds to a fixed range
* These constants are used for initial navigation in visual tests, in either fixed or realtime mode.
* They navigate to the 'My Items' folder at MISSION_TIME.
* They set the following url parameters:
* - tc.mode - The time conductor mode ('fixed' or 'local')
* - tc.startBound - The time conductor start bound (when in fixed mode)
* - tc.endBound - The time conductor end bound (when in fixed mode)
* - tc.startDelta - The time conductor start delta (when in realtime mode)
* - tc.endDelta - The time conductor end delta (when in realtime mode)
* - tc.timeSystem - The time conductor time system ('utc')
* - view - The view to display ('grid')
* - hideInspector - Whether to hide the inspector (true)
* - hideTree - Whether to hide the tree (true)
* @typedef {string} VisualUrl
*/
export const VISUAL_URL =
'./#/browse/mine?tc.mode=fixed&tc.startBound=1693592063607&tc.endBound=1693593893607&tc.timeSystem=utc&view=grid&hideInspector=true&hideTree=true';
/** @type {VisualUrl} */
export const VISUAL_FIXED_URL = `./#/browse/mine?tc.mode=fixed&tc.startBound=${MISSION_TIME_FIXED_START}&tc.endBound=${MISSION_TIME_FIXED_END}&tc.timeSystem=utc&view=grid&hideInspector=true&hideTree=true`;
/** @type {VisualUrl} */
export const VISUAL_REALTIME_URL =
'./#/browse/mine?tc.mode=local&tc.timeSystem=utc&view=grid&tc.startDelta=1800000&tc.endDelta=30000&hideTree=true&hideInspector=true';

View File

@ -68,7 +68,6 @@ async function commitEntry(page) {
* @param {import('@playwright/test').Page} page
*/
async function startAndAddRestrictedNotebookObject(page) {
// eslint-disable-next-line no-undef
await page.addInitScript({
path: fileURLToPath(new URL('./addInitRestrictedNotebook.js', import.meta.url))
});

View File

@ -1,4 +1,3 @@
/* eslint-disable no-undef */
// playwright.config.js
// @ts-check

View File

@ -1,4 +1,3 @@
/* eslint-disable no-undef */
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space
@ -123,7 +122,6 @@ const extendedTest = test.extend({
theme: [theme, { option: true }],
// eslint-disable-next-line no-shadow
page: async ({ page, theme }, use, testInfo) => {
// eslint-disable-next-line playwright/no-conditional-in-test
if (theme === 'snow') {
//inject snow theme
await page.addInitScript({

View File

@ -26,11 +26,12 @@ relates to how we've extended it (i.e. ./e2e/baseFixtures.js) and assumptions ma
(`npm start` and ./e2e/webpack-dev-middleware.js)
*/
import { test } from '../../baseFixtures.js';
import { expect, test } from '../../baseFixtures.js';
import { MISSION_TIME } from '../../constants.js';
test.describe('baseFixtures tests', () => {
//Skip this test for now https://github.com/nasa/openmct/issues/6785
test.fixme('Verify that tests fail if console.error is thrown', async ({ page }) => {
test('Verify that tests fail if console.error is thrown', async ({ page }) => {
test.fail();
//Go to baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' });
@ -52,3 +53,27 @@ test.describe('baseFixtures tests', () => {
]);
});
});
test.describe('baseFixtures tests @clock', () => {
test.use({
clockOptions: {
now: MISSION_TIME,
shouldAdvanceTime: false
}
});
test.beforeEach(async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' });
});
test('Can use clockOptions and tick fixtures to control the clock', async ({ page, tick }) => {
let time = await page.evaluate(() => new Date().getTime());
expect(time).toBe(MISSION_TIME);
await tick(1000);
time = await page.evaluate(() => new Date().getTime());
expect(time).toBe(MISSION_TIME + 1000 * 1);
await tick(1000);
time = await page.evaluate(() => new Date().getTime());
expect(time).toBe(MISSION_TIME + 1000 * 2);
});
});

View File

@ -24,7 +24,7 @@
This test suite is dedicated to tests which verify the basic operations surrounding imagery,
but only assume that example imagery is present.
*/
/* globals process */
import { createDomainObjectWithDefaults, setRealTimeMode } from '../../../../appActions.js';
import { waitForAnimations } from '../../../../baseFixtures.js';
import { expect, test } from '../../../../pluginFixtures.js';

View File

@ -27,7 +27,6 @@ import { expect, test } from '../../../../pluginFixtures.js';
test.describe('Testing numeric data with inspector data visualization (i.e., data pivoting)', () => {
test.beforeEach(async ({ page }) => {
// eslint-disable-next-line no-undef
await page.addInitScript({
path: fileURLToPath(
new URL('../../../../helper/addInitDataVisualization.js', import.meta.url)

View File

@ -277,7 +277,6 @@ test.describe('Notebook entry tests', () => {
// Create Notebook with URL Whitelist
let notebookObject;
test.beforeEach(async ({ page }) => {
// eslint-disable-next-line no-undef
await page.addInitScript({
path: fileURLToPath(new URL('../../../../helper/addInitNotebookWithUrls.js', import.meta.url))
});

View File

@ -48,7 +48,7 @@ test.describe('Time conductor operations', () => {
await setTimeConductorBounds(page, startDate);
// Bring up the time conductor popup
const timeConductorMode = await page.locator('.c-compact-tc');
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);

View File

@ -299,7 +299,6 @@ test.describe('Navigation memory leak is not detected in', () => {
// for detecting memory leaks.
await page.evaluate(() => {
window.gcPromise = new Promise((resolve) => {
// eslint-disable-next-line no-undef
window.fr = new FinalizationRegistry(resolve);
window.fr.register(
window.openmct.layout.$refs.browseObject.$refs.objectViewWrapper.firstChild,

View File

@ -21,14 +21,13 @@
*****************************************************************************/
import { test } from '../../avpFixtures.js';
import { VISUAL_URL } from '../../constants.js';
import { VISUAL_FIXED_URL } from '../../constants.js';
test.describe('a11y - Default', () => {
test.beforeEach(async ({ page }) => {
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
});
test('main view', async ({ page }, testInfo) => {
await page.goto('./');
//Skipping for https://github.com/nasa/openmct/issues/7421
//await scanForA11yViolations(page, testInfo.title);
});

View File

@ -27,12 +27,12 @@ Tests the branding associated with the default deployment. At least the about mo
import percySnapshot from '@percy/playwright';
import { expect, scanForA11yViolations, test } from '../../../avpFixtures.js';
import { VISUAL_URL } from '../../../constants.js';
import { VISUAL_FIXED_URL } from '../../../constants.js';
test.describe('Visual - Branding @a11y', () => {
test.beforeEach(async ({ page }) => {
//Go to baseURL and Hide Tree
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
});
test('Visual - About Modal', async ({ page, theme }) => {

View File

@ -28,7 +28,7 @@ import percySnapshot from '@percy/playwright';
import { fileURLToPath } from 'url';
import { expect, test } from '../../../avpFixtures.js';
import { VISUAL_URL } from '../../../constants.js';
import { VISUAL_FIXED_URL } from '../../../constants.js';
//Declare the component scope of the visual test for Percy
const header = '.l-shell__head';
@ -36,7 +36,7 @@ const header = '.l-shell__head';
test.describe('Visual - Header @a11y', () => {
test.beforeEach(async ({ page }) => {
//Go to baseURL and Hide Tree
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
// Wait for status bar to load
await expect(
page.getByRole('status', {

View File

@ -23,14 +23,14 @@
import percySnapshot from '@percy/playwright';
import { test } from '../../../avpFixtures.js';
import { MISSION_TIME, VISUAL_URL } from '../../../constants.js';
import { MISSION_TIME, VISUAL_FIXED_URL } from '../../../constants.js';
//Declare the scope of the visual test
const inspectorPane = '.l-shell__pane-inspector';
test.describe('Visual - Inspector @ally @clock', () => {
test.beforeEach(async ({ page }) => {
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
});
test.use({
storageState: './e2e/test-data/overlay_plot_with_delay_storage.json',

View File

@ -0,0 +1,116 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*
Tests the visual appearance of the Time Conductor component
*/
import { expect, test } from '../../../avpFixtures.js';
import {
MISSION_TIME,
MISSION_TIME_FIXED_END,
MISSION_TIME_FIXED_START,
VISUAL_REALTIME_URL
} from '../../../constants.js';
test.describe('Visual - Time Conductor', () => {
test.use({
clockOptions: {
now: MISSION_TIME,
shouldAdvanceTime: false
}
});
test.beforeEach(async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' });
});
// FIXME: checking for a11y violations times out. Might have something to do with the frozen clock.
// test.afterEach(async ({ page }, testInfo) => {
// await scanForA11yViolations(page, testInfo.title);
// });
test('Visual - Time Conductor (Fixed time) @clock @snapshot', async ({ page }) => {
// Navigate to a specific view that uses the Time Conductor in Fixed Time mode with inspect and browse panes collapsed
await page.goto(
`./#/browse/mine?tc.mode=fixed&tc.startBound=${MISSION_TIME_FIXED_START}&tc.endBound=${MISSION_TIME_FIXED_END}&tc.timeSystem=utc&view=grid&hideInspector=true&hideTree=true`,
{
waitUntil: 'domcontentloaded'
}
);
// Take a snapshot for comparison
const snapshot = await page.screenshot({
mask: []
});
expect(snapshot).toMatchSnapshot('time-conductor-fixed-time.png');
});
test('Visual - Time Conductor (Realtime) @clock @snapshot', async ({ page }) => {
// Navigate to a specific view that uses the Time Conductor in Fixed Time mode with inspect and browse panes collapsed
await page.goto(VISUAL_REALTIME_URL, {
waitUntil: 'domcontentloaded'
});
const mask = [];
// Take a snapshot for comparison
const snapshot = await page.screenshot({
mask
});
expect(snapshot).toMatchSnapshot('time-conductor-realtime.png');
});
test(
'Visual - Time Conductor Axis Resized @clock @snapshot',
{ annotation: [{ type: 'issue', description: 'https://github.com/nasa/openmct/issues/7623' }] },
async ({ page, tick }) => {
const VISUAL_REALTIME_WITH_PANES = VISUAL_REALTIME_URL.replace(
'hideTree=true',
'hideTree=false'
).replace('hideInspector=true', 'hideInspector=false');
// Navigate to a specific view that uses the Time Conductor in Fixed Time mode with inspect
await page.goto(VISUAL_REALTIME_WITH_PANES, {
waitUntil: 'domcontentloaded'
});
// Set the time conductor to fixed time mode
await page.getByLabel('Time Conductor Mode').click();
await page.getByLabel('Time Conductor Mode Menu').click();
await page.getByLabel('Fixed Timespan').click();
await page.getByLabel('Submit time bounds').click();
// Collapse the inspect and browse panes to trigger a resize of the conductor axis
await page.getByLabel('Collapse Inspect Pane').click();
await page.getByLabel('Collapse Browse Pane').click();
// manually tick the clock to trigger the resize / re-render
await tick(1000 * 2);
const mask = [];
// Take a snapshot for comparison
const snapshot = await page.screenshot({
mask
});
expect(snapshot).toMatchSnapshot('time-conductor-axis-resized.png');
}
);
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@ -23,7 +23,7 @@
import percySnapshot from '@percy/playwright';
import { createDomainObjectWithDefaults, expandTreePaneItemByName } from '../../../appActions.js';
import { VISUAL_URL } from '../../../constants.js';
import { VISUAL_FIXED_URL } from '../../../constants.js';
import { test } from '../../../pluginFixtures.js';
//Declare the scope of the visual test
@ -32,7 +32,7 @@ const treePane = "[role=tree][aria-label='Main Tree']";
test.describe('Visual - Tree Pane', () => {
test('Tree pane in various states', async ({ page, theme, openmctConfig }) => {
const { myItemsFolderName } = openmctConfig;
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
//Open Tree
await page.getByRole('button', { name: 'Browse' }).click();

View File

@ -27,12 +27,12 @@ clockOptions plugin fixture.
import percySnapshot from '@percy/playwright';
import { MISSION_TIME, VISUAL_URL } from '../../constants.js';
import { MISSION_TIME, VISUAL_FIXED_URL } from '../../constants.js';
import { expect, test } from '../../pluginFixtures.js';
test.describe('Visual - Controlled Clock @clock', () => {
test.beforeEach(async ({ page }) => {
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
});
test.use({
storageState: './e2e/test-data/overlay_plot_with_delay_storage.json',
@ -43,7 +43,7 @@ test.describe('Visual - Controlled Clock @clock', () => {
});
test('Overlay Plot Loading Indicator @localStorage', async ({ page, theme }) => {
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
await page
.getByRole('gridcell', { hasText: 'Overlay Plot with 5s Delay Overlay Plot' })
.click();

View File

@ -30,11 +30,11 @@ import percySnapshot from '@percy/playwright';
import { createDomainObjectWithDefaults } from '../../appActions.js';
import { expect, scanForA11yViolations, test } from '../../avpFixtures.js';
import { VISUAL_URL } from '../../constants.js';
import { VISUAL_FIXED_URL } from '../../constants.js';
test.describe('Visual - Default @a11y', () => {
test.beforeEach(async ({ page }) => {
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
});
test('Visual - Default Dashboard', async ({ page, theme }) => {

View File

@ -23,12 +23,18 @@
import percySnapshot from '@percy/playwright';
import { createDomainObjectWithDefaults } from '../../appActions.js';
import { VISUAL_URL } from '../../constants.js';
import { MISSION_TIME, VISUAL_FIXED_URL } from '../../constants.js';
import { test } from '../../pluginFixtures.js';
test.describe('Visual - Display Layout', () => {
test.beforeEach(async ({ page, theme }) => {
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
test.describe('Visual - Display Layout @clock', () => {
test.use({
clockOptions: {
now: MISSION_TIME,
shouldAdvanceTime: true
}
});
test.beforeEach(async ({ page }) => {
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
const parentLayout = await createDomainObjectWithDefaults(page, {
type: 'Display Layout',
@ -59,12 +65,15 @@ test.describe('Visual - Display Layout', () => {
await page.goto(parentLayout.url, { waitUntil: 'domcontentloaded' });
await page.getByRole('button', { name: 'Edit Object' }).click();
//Move the Child Right Layout to the Right. It should be on top of the Left Layout at this point.
// Select the child right layout
await page
.getByLabel('Child Right Layout Layout', { exact: true })
.getByLabel('Move Sub-object Frame')
.click();
await page.getByLabel('Move Sub-object Frame').nth(3).click(); //I'm not sure why this step is necessary
// FIXME: Click to select the parent object (layout)
await page.getByLabel('Move Sub-object Frame').nth(3).click();
// Move the second layout element to the right
await page.getByLabel('X:').click();
await page.getByLabel('X:').fill('35');
});

View File

@ -84,6 +84,12 @@ test.describe('Fault Management Visual Tests', () => {
await shelveFault(page, 1);
await changeViewTo(page, 'shelved');
/* cspell:disable-next-line */
// Since fault management is heavily dependent on events (bleh), we need to wait for the correct
// element counts
await expect(page.getByLabel('Select fault:')).toHaveCount(1);
await expect(page.getByLabel('Disposition Actions')).toHaveCount(1);
await percySnapshot(page, `Shelved faults appear in the shelved view (theme: '${theme}')`);
await openFaultRowMenu(page, 1);

View File

@ -24,7 +24,7 @@ import percySnapshot from '@percy/playwright';
import { createDomainObjectWithDefaults, setRealTimeMode } from '../../appActions.js';
import { waitForAnimations } from '../../baseFixtures.js';
import { VISUAL_URL } from '../../constants.js';
import { VISUAL_FIXED_URL } from '../../constants.js';
import { expect, test } from '../../pluginFixtures.js';
test.describe('Visual - Example Imagery', () => {
@ -32,7 +32,7 @@ test.describe('Visual - Example Imagery', () => {
let parentLayout;
test.beforeEach(async ({ page }) => {
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
parentLayout = await createDomainObjectWithDefaults(page, {
type: 'Display Layout',

View File

@ -23,14 +23,14 @@
import percySnapshot from '@percy/playwright';
import { createDomainObjectWithDefaults } from '../../appActions.js';
import { VISUAL_URL } from '../../constants.js';
import { VISUAL_FIXED_URL } from '../../constants.js';
import { expect, test } from '../../pluginFixtures.js';
test.describe('Visual - LAD Table', () => {
let ladTable;
test.beforeEach(async ({ page }) => {
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
// Create LAD Table
ladTable = await createDomainObjectWithDefaults(page, {

View File

@ -24,7 +24,7 @@ import percySnapshot from '@percy/playwright';
import { createDomainObjectWithDefaults, expandTreePaneItemByName } from '../../appActions.js';
import { expect, test } from '../../avpFixtures.js';
import { VISUAL_URL } from '../../constants.js';
import { VISUAL_FIXED_URL } from '../../constants.js';
import { enterTextEntry, startAndAddRestrictedNotebookObject } from '../../helper/notebookUtils.js';
test.describe('Visual - Restricted Notebook @a11y', () => {
@ -80,7 +80,7 @@ test.describe('Visual - Notebook Snapshot @a11y', () => {
test.describe('Visual - Notebook @a11y', () => {
let notebook;
test.beforeEach(async ({ page }) => {
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
notebook = await createDomainObjectWithDefaults(page, {
type: 'Notebook',
name: 'Test Notebook'

View File

@ -28,11 +28,11 @@ import percySnapshot from '@percy/playwright';
import { createNotification } from '../../appActions.js';
import { expect, scanForA11yViolations, test } from '../../avpFixtures.js';
import { VISUAL_URL } from '../../constants.js';
import { VISUAL_FIXED_URL } from '../../constants.js';
test.describe('Visual - Notifications @a11y', () => {
test.beforeEach(async ({ page }) => {
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
});
test('Alert Levels and Notification List Modal', async ({ page, theme }) => {

View File

@ -25,7 +25,7 @@ import fs from 'fs';
import { createDomainObjectWithDefaults, createPlanFromJSON } from '../../appActions.js';
import { test } from '../../avpFixtures.js';
import { VISUAL_URL } from '../../constants.js';
import { VISUAL_FIXED_URL } from '../../constants.js';
import {
createTimelistWithPlanAndSetActivityInProgress,
getFirstActivity,
@ -64,7 +64,7 @@ test.describe('Visual - Timelist progress bar @clock', () => {
test.describe('Visual - Planning', () => {
test.beforeEach(async ({ page }) => {
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
});
test('Plan View', async ({ page, theme }) => {
@ -83,7 +83,7 @@ test.describe('Visual - Planning', () => {
});
const newPage = await newContext.newPage();
await newPage.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
await newPage.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
const plan = await createPlanFromJSON(newPage, {
name: 'Plan Visual Test',
json: examplePlanSmall2
@ -100,7 +100,7 @@ test.describe('Visual - Planning', () => {
name: 'Plan Visual Test (Draft)',
json: examplePlanSmall2
});
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
await setDraftStatusForPlan(page, plan);
await setBoundsToSpanAllActivities(page, examplePlanSmall2, plan.url);
@ -110,7 +110,7 @@ test.describe('Visual - Planning', () => {
test.describe('Visual - Gantt Chart', () => {
test.beforeEach(async ({ page }) => {
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
});
test('Gantt Chart View', async ({ page, theme }) => {
const ganttChart = await createDomainObjectWithDefaults(page, {
@ -153,7 +153,7 @@ test.describe('Visual - Gantt Chart', () => {
await setDraftStatusForPlan(page, plan);
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
await setBoundsToSpanAllActivities(page, examplePlanSmall2, ganttChart.url);
await percySnapshot(page, `Gantt Chart View w/ draft status (theme: ${theme})`);

View File

@ -28,13 +28,13 @@ import percySnapshot from '@percy/playwright';
import { createDomainObjectWithDefaults } from '../../appActions.js';
import { expect, scanForA11yViolations, test } from '../../avpFixtures.js';
import { VISUAL_URL } from '../../constants.js';
import { VISUAL_FIXED_URL } from '../../constants.js';
test.describe('Grand Search @a11y', () => {
let conditionWidget;
let displayLayout;
test.beforeEach(async ({ page }) => {
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
displayLayout = await createDomainObjectWithDefaults(page, {
type: 'Display Layout',

View File

@ -23,14 +23,14 @@
import percySnapshot from '@percy/playwright';
import { createDomainObjectWithDefaults } from '../../appActions.js';
import { VISUAL_URL } from '../../constants.js';
import { VISUAL_FIXED_URL } from '../../constants.js';
import { expect, test } from '../../pluginFixtures.js';
test.describe('Visual - Telemetry Views', () => {
let telemetry;
test.beforeEach(async ({ page }) => {
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
// Create SWG inside of LAD Table
telemetry = await createDomainObjectWithDefaults(page, {

View File

@ -20,26 +20,32 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global module,process*/
// eslint-disable-next-line func-style
const loadWebpackConfig = async () => {
if (process.env.KARMA_DEBUG) {
return {
config: (await import('./.webpack/webpack.dev.js')).default,
browsers: ['ChromeDebugging'],
singleRun: false
};
} else {
return {
config: (await import('./.webpack/webpack.coverage.js')).default,
browsers: ['ChromeHeadless'],
singleRun: true
};
}
};
module.exports = async (config) => {
let webpackConfig;
let browsers;
let singleRun;
if (process.env.KARMA_DEBUG) {
webpackConfig = (await import('./.webpack/webpack.dev.js')).default;
browsers = ['ChromeDebugging'];
singleRun = false;
} else {
webpackConfig = (await import('./.webpack/webpack.coverage.js')).default;
browsers = ['ChromeHeadless'];
singleRun = true;
}
const { config: webpackConfig, browsers, singleRun } = await loadWebpackConfig();
// Adjust webpack config for Karma
delete webpackConfig.output;
// karma doesn't support webpack entry
delete webpackConfig.entry;
delete webpackConfig.entry; // Karma doesn't support webpack entry
// Ensure source maps are enabled for better debugging
webpackConfig.devtool = 'inline-source-map';
config.set({
basePath: '',
@ -106,7 +112,7 @@ module.exports = async (config) => {
},
webpack: webpackConfig,
webpackMiddleware: {
stats: 'errors-warnings'
stats: 'detailed' // Changed to 'detailed' for more debugging info
},
concurrency: 1,
singleRun,

409
package-lock.json generated
View File

@ -22,6 +22,7 @@
"@types/eventemitter3": "1.2.0",
"@types/jasmine": "5.1.2",
"@types/lodash": "4.17.0",
"@types/sinonjs__fake-timers": "8.1.5",
"@vue/compiler-sfc": "3.4.3",
"babel-loader": "9.1.0",
"babel-plugin-istanbul": "6.1.1",
@ -1904,6 +1905,12 @@
"@types/node": "*"
}
},
"node_modules/@types/sinonjs__fake-timers": {
"version": "8.1.5",
"resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.5.tgz",
"integrity": "sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==",
"dev": true
},
"node_modules/@types/sockjs": {
"version": "0.3.36",
"resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz",
@ -1932,207 +1939,6 @@
"@types/node": "*"
}
},
"node_modules/@typescript-eslint/parser": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz",
"integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==",
"dev": true,
"dependencies": {
"@typescript-eslint/scope-manager": "6.21.0",
"@typescript-eslint/types": "6.21.0",
"@typescript-eslint/typescript-estree": "6.21.0",
"@typescript-eslint/visitor-keys": "6.21.0",
"debug": "^4.3.4"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^7.0.0 || ^8.0.0"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz",
"integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "6.21.0",
"@typescript-eslint/visitor-keys": "6.21.0"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/types": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz",
"integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==",
"dev": true,
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz",
"integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "6.21.0",
"@typescript-eslint/visitor-keys": "6.21.0",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
"minimatch": "9.0.3",
"semver": "^7.5.4",
"ts-api-utils": "^1.0.1"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true,
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/globby": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
"integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
"dev": true,
"dependencies": {
"array-union": "^2.1.0",
"dir-glob": "^3.0.1",
"fast-glob": "^3.2.9",
"ignore": "^5.2.0",
"merge2": "^1.4.1",
"slash": "^3.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
"version": "9.0.3",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
"integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
"dev": true,
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
"integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz",
"integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "6.21.0",
"eslint-visitor-keys": "^3.4.1"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
"integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
"dev": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/@ungap/structured-clone": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
@ -9255,6 +9061,207 @@
}
}
},
"node_modules/prettier-eslint/node_modules/@typescript-eslint/parser": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz",
"integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==",
"dev": true,
"dependencies": {
"@typescript-eslint/scope-manager": "6.21.0",
"@typescript-eslint/types": "6.21.0",
"@typescript-eslint/typescript-estree": "6.21.0",
"@typescript-eslint/visitor-keys": "6.21.0",
"debug": "^4.3.4"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^7.0.0 || ^8.0.0"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/prettier-eslint/node_modules/@typescript-eslint/scope-manager": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz",
"integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "6.21.0",
"@typescript-eslint/visitor-keys": "6.21.0"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/prettier-eslint/node_modules/@typescript-eslint/types": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz",
"integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==",
"dev": true,
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/prettier-eslint/node_modules/@typescript-eslint/typescript-estree": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz",
"integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "6.21.0",
"@typescript-eslint/visitor-keys": "6.21.0",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
"minimatch": "9.0.3",
"semver": "^7.5.4",
"ts-api-utils": "^1.0.1"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/prettier-eslint/node_modules/@typescript-eslint/visitor-keys": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz",
"integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "6.21.0",
"eslint-visitor-keys": "^3.4.1"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/prettier-eslint/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true,
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/prettier-eslint/node_modules/eslint-visitor-keys": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
"integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
"dev": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/prettier-eslint/node_modules/globby": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
"integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
"dev": true,
"dependencies": {
"array-union": "^2.1.0",
"dir-glob": "^3.0.1",
"fast-glob": "^3.2.9",
"ignore": "^5.2.0",
"merge2": "^1.4.1",
"slash": "^3.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/prettier-eslint/node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/prettier-eslint/node_modules/minimatch": {
"version": "9.0.3",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
"integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
"dev": true,
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/prettier-eslint/node_modules/semver": {
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
"integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/prettier-eslint/node_modules/slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/prettier-eslint/node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"node_modules/prettier-linter-helpers": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",

View File

@ -12,12 +12,13 @@
"@percy/playwright": "1.0.4",
"@playwright/test": "1.42.1",
"@types/d3-axis": "3.0.6",
"@types/d3-shape": "3.0.0",
"@types/d3-scale": "4.0.8",
"@types/d3-selection": "3.0.10",
"@types/d3-shape": "3.0.0",
"@types/eventemitter3": "1.2.0",
"@types/jasmine": "5.1.2",
"@types/lodash": "4.17.0",
"@types/sinonjs__fake-timers": "8.1.5",
"@vue/compiler-sfc": "3.4.3",
"babel-loader": "9.1.0",
"babel-plugin-istanbul": "6.1.1",
@ -27,9 +28,9 @@
"cspell": "7.3.8",
"css-loader": "6.10.0",
"d3-axis": "3.0.0",
"d3-shape": "3.0.0",
"d3-scale": "4.0.2",
"d3-selection": "3.0.0",
"d3-shape": "3.0.0",
"eslint": "8.56.0",
"eslint-config-prettier": "9.1.0",
"eslint-plugin-compat": "4.2.0",

View File

@ -61,7 +61,6 @@ describe('DeviceClassifier', function () {
mockAgent[m].and.returnValue(true);
});
// eslint-disable-next-line no-new
DeviceClassifier(mockAgent, mockDocument);
});

View File

@ -28,7 +28,7 @@ describe('The URLIndicator', function () {
let openmct;
let indicatorElement;
let pluginOptions;
let urlIndicator; // eslint-disable-line
let urlIndicator;
let fetchSpy;
beforeEach(function () {

View File

@ -125,7 +125,7 @@ describe('the plugin', function () {
describe('The bar graph view', () => {
let barGraphObject;
// eslint-disable-next-line no-unused-vars
let mockComposition;
beforeEach(async () => {
@ -222,7 +222,7 @@ describe('the plugin', function () {
describe('The spectral plot view for telemetry objects with array values', () => {
let barGraphObject;
// eslint-disable-next-line no-unused-vars
let mockComposition;
beforeEach(async () => {

View File

@ -117,7 +117,7 @@ describe('the plugin', function () {
describe('The scatter plot view', () => {
let testDomainObject;
let scatterPlotObject;
// eslint-disable-next-line no-unused-vars
let mockComposition;
beforeEach(async () => {

View File

@ -134,7 +134,7 @@ describe('The import JSON action', function () {
const pollutedResponse = {
selectFile: {
name: 'imported object',
// eslint-disable-next-line prettier/prettier
body: '{"openmct":{"c28d230d-e909-4a3e-9840-d9ef469dda70":{"identifier":{"key":"c28d230d-e909-4a3e-9840-d9ef469dda70","namespace":""},"name":"Unnamed Overlay Plot","type":"telemetry.plot.overlay","composition":[],"configuration":{"series":[]},"modified":1695837546833,"location":"mine","created":1695837546833,"persisted":1695837546833,"__proto__":{"toString":"foobar"}}},"rootId":"c28d230d-e909-4a3e-9840-d9ef469dda70"}'
}
};

View File

@ -1,4 +1,3 @@
/* eslint-disable no-invalid-this */
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space

View File

@ -161,7 +161,6 @@ export default function Rule(
*/
function onDragStart(event) {
document.querySelectorAll('.t-drag-indicator').forEach((indicator) => {
// eslint-disable-next-line no-invalid-this
const ruleHeader = self.domElement.querySelectorAll('.widget-rule-header')[0].cloneNode(true);
indicator.textContent = '';
indicator.appendChild(ruleHeader);

View File

@ -106,7 +106,6 @@ export default function ColorPalette(cssClass, container, colors) {
domElement.querySelector('.c-palette').classList.add('c-palette--color');
domElement.querySelectorAll('.c-palette__item').forEach((item) => {
// eslint-disable-next-line no-invalid-this
item.style.backgroundColor = item.dataset.item;
});

View File

@ -51,7 +51,6 @@ export default function IconPalette(cssClass, container, icons) {
domElement.querySelector('.c-palette').classList.add('c-palette--icon');
domElement.querySelectorAll('.c-palette-item').forEach((item) => {
// eslint-disable-next-line no-invalid-this
item.classList.add(item.dataset.item);
});

View File

@ -20,7 +20,12 @@
at runtime from the About dialog for additional information.
-->
<template>
<div ref="axisHolder" class="c-conductor-axis" @mousedown="dragStart($event)">
<div
ref="axisHolder"
aria-label="Time Conductor Axis"
class="c-conductor-axis"
@mousedown="dragStart($event)"
>
<div class="c-conductor-axis__zoom-indicator" :style="zoomStyle"></div>
</div>
</template>
@ -29,7 +34,9 @@
import { axisTop } from 'd3-axis';
import { scaleLinear, scaleUtc } from 'd3-scale';
import { select } from 'd3-selection';
import { onMounted, ref } from 'vue';
import { useResizeObserver } from '../../../src/ui/composables/resize.js';
import { TIME_CONTEXT_EVENTS } from '../../api/time/constants.js';
import utcMultiTimeFormat from './utcMultiTimeFormat.js';
@ -55,20 +62,69 @@ export default {
}
},
emits: ['pan-axis', 'end-pan', 'zoom-axis', 'end-zoom'],
setup() {
const axisHolder = ref(null);
const { size: containerSize, startObserving } = useResizeObserver();
onMounted(() => {
startObserving(axisHolder.value);
});
return {
axisHolder,
containerSize
};
},
data() {
return {
inPanMode: false,
dragStartX: undefined,
dragX: undefined,
zoomStyle: {}
zoomStyle: {},
rect: null
};
},
computed: {
inZoomMode() {
return !this.inPanMode;
},
left() {
return this.rect.left;
},
panBounds() {
const bounds = this.openmct.time.getBounds();
const deltaTime = bounds.end - bounds.start;
const deltaX = this.dragX - this.dragStartX;
const percX = deltaX / this.width;
const panStart = bounds.start - percX * deltaTime;
return {
start: parseInt(panStart, 10),
end: parseInt(panStart + deltaTime, 10)
};
},
zoomRange() {
const leftBound = this.left;
const rightBound = this.left + this.width;
const zoomStart = this.dragX < leftBound ? leftBound : Math.min(this.dragX, this.dragStartX);
const zoomEnd = this.dragX > rightBound ? rightBound : Math.max(this.dragX, this.dragStartX);
return {
start: zoomStart,
end: zoomEnd
};
},
isChangingViewBounds() {
return this.dragStartX && this.dragX && this.dragStartX !== this.dragX;
}
},
watch: {
containerSize: {
handler() {
this.resize();
},
deep: true
},
viewBounds: {
handler() {
this.setScale();
@ -77,7 +133,7 @@ export default {
}
},
mounted() {
let vis = select(this.$refs.axisHolder).append('svg:svg');
const vis = select(this.axisHolder).append('svg:svg');
this.xAxis = axisTop();
this.dragging = false;
@ -93,15 +149,14 @@ export default {
this.openmct.time.on(TIME_CONTEXT_EVENTS.timeSystemChanged, this.setViewFromTimeSystem);
},
beforeUnmount() {
clearInterval(this.resizeTimer);
// Remove the listeners in case the component is unmounted while dragging
document.removeEventListener('mousemove', this.drag);
document.removeEventListener('mouseup', this.dragEnd);
},
methods: {
setAxisDimensions() {
const axisHolder = this.$refs.axisHolder;
const rect = axisHolder.getBoundingClientRect();
this.left = Math.round(rect.left);
this.width = axisHolder.clientWidth;
this.rect = this.axisHolder.getBoundingClientRect();
this.width = this.axisHolder.clientWidth;
},
setScale() {
if (!this.width) {
@ -204,26 +259,14 @@ export default {
this.dragX = undefined;
},
pan() {
const panBounds = this.getPanBounds();
const panBounds = this.panBounds;
this.$emit('pan-axis', panBounds);
},
endPan() {
const panBounds = this.isChangingViewBounds() ? this.getPanBounds() : undefined;
const panBounds = this.isChangingViewBounds ? this.panBounds : undefined;
this.$emit('end-pan', panBounds);
this.inPanMode = false;
},
getPanBounds() {
const bounds = this.openmct.time.getBounds();
const deltaTime = bounds.end - bounds.start;
const deltaX = this.dragX - this.dragStartX;
const percX = deltaX / this.width;
const panStart = bounds.start - percX * deltaTime;
return {
start: parseInt(panStart, 10),
end: parseInt(panStart + deltaTime, 10)
};
},
startZoom() {
const x = this.scaleToBounds(this.dragStartX);
@ -237,7 +280,7 @@ export default {
});
},
zoom() {
const zoomRange = this.getZoomRange();
const zoomRange = this.zoomRange;
this.zoomStyle = {
left: `${zoomRange.start - this.left}px`,
@ -251,8 +294,8 @@ export default {
},
endZoom() {
let zoomBounds;
if (this.isChangingViewBounds()) {
const zoomRange = this.getZoomRange();
if (this.isChangingViewBounds) {
const zoomRange = this.zoomRange;
zoomBounds = {
start: this.scaleToBounds(zoomRange.start),
end: this.scaleToBounds(zoomRange.end)
@ -262,19 +305,6 @@ export default {
this.zoomStyle = {};
this.$emit('end-zoom', zoomBounds);
},
getZoomRange() {
const leftBound = this.left;
const rightBound = this.left + this.width;
const zoomStart = this.dragX < leftBound ? leftBound : Math.min(this.dragX, this.dragStartX);
const zoomEnd = this.dragX > rightBound ? rightBound : Math.max(this.dragX, this.dragStartX);
return {
start: zoomStart,
end: zoomEnd
};
},
scaleToBounds(value) {
const bounds = this.openmct.time.getBounds();
const timeDelta = bounds.end - bounds.start;
@ -283,11 +313,8 @@ export default {
return parseInt(bounds.start + offset, 10);
},
isChangingViewBounds() {
return this.dragStartX && this.dragX && this.dragStartX !== this.dragX;
},
resize() {
if (this.$refs.axisHolder.clientWidth !== this.width) {
if (this.axisHolder.clientWidth !== this.width) {
this.setAxisDimensions();
this.setScale();
}

View File

@ -53,7 +53,6 @@ export default {
formattedItemValues() {
let values = [];
this.itemProperties.forEach((property) => {
// eslint-disable-next-line you-dont-need-lodash-underscore/get
let value = _.get(this.item, property.key);
if (property.format) {
value = property.format(value, this.item, property.key, this.openmct);

View File

@ -83,29 +83,26 @@ export function clearBuiltinSpies() {
}
export function resetApplicationState(openmct) {
let promise;
clearBuiltinSpies();
if (openmct !== undefined) {
if (openmct) {
openmct.destroy();
}
if (window.location.hash !== '#' && window.location.hash !== '') {
promise = new Promise((resolve, reject) => {
window.addEventListener('hashchange', cleanup);
window.location.hash = '#';
function cleanup() {
window.removeEventListener('hashchange', cleanup);
// Optionally wait for hashchange if necessary
return new Promise((resolve) => {
// eslint-disable-next-line func-style
const onHashChange = () => {
window.removeEventListener('hashchange', onHashChange);
resolve();
}
};
window.addEventListener('hashchange', onHashChange);
});
} else {
promise = Promise.resolve();
return Promise.resolve();
}
return promise;
}
// required: key