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/**']; const LEGACY_FILES = ['example/**'];
module.exports = { /** @type {import('eslint').Linter.Config} */
const config = {
env: { env: {
browser: true, browser: true,
es6: true, es2024: true,
jasmine: true, jasmine: true,
amd: true node: true,
worker: true,
serviceworker: true
}, },
globals: { globals: {
_: 'readonly' _: 'readonly'
@ -23,10 +26,11 @@ module.exports = {
parser: '@babel/eslint-parser', parser: '@babel/eslint-parser',
requireConfigFile: false, requireConfigFile: false,
allowImportExportEverywhere: true, allowImportExportEverywhere: true,
ecmaVersion: 2015, ecmaVersion: 'latest',
ecmaFeatures: { ecmaFeatures: {
impliedStrict: true impliedStrict: true
} },
sourceType: 'module'
}, },
rules: { rules: {
'simple-import-sort/imports': 'warn', 'simple-import-sort/imports': 'warn',
@ -152,7 +156,7 @@ module.exports = {
cases: { cases: {
pascalCase: true pascalCase: true
}, },
ignore: ['^.*\\.js$'] ignore: ['^.*\\.(js|cjs|mjs)$']
} }
], ],
'vue/first-attribute-linebreak': 'error', '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 .nyc_output
coverage coverage
codecov codecov
# Don't commit MacOS screenshots
*-darwin.png

View File

@ -76,8 +76,9 @@ To read about how to write a good visual test, please see [How to write a great
`npm run test:e2e:visual` commands will run all of the visual tests against a local instance of Open MCT. If no `PERCY_TOKEN` API key is found in the terminal or command line environment variables, no visual comparisons will be made. `npm run test:e2e:visual` commands will run all of the visual tests against a local instance of Open MCT. If no `PERCY_TOKEN` API key is found in the terminal or command line environment variables, no visual comparisons will be made.
- `npm run test:e2e:visual:ci` will run against every commit and PR. - `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. - `npm run test:e2e:visual:full` will run every night with additional comparisons made for Larger Displays and with the `snow` theme.
#### Percy.io #### 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). 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. 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 #### 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. 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 #### 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. 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 #### 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 #### 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. 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 ```sh
// Replace {X.X.X} with the current Playwright version // Replace {X.X.X} with the current Playwright version
// from our package.json or circleCI configuration file // 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. 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 For now, the mobile tests will exist in the /tests/mobile/ suites and be executed with the
```sh ```sh
npm run test:e2e:mobile npm run test:e2e:mobile
``` ```
command. command.
#### **Skipping or executing tests based on browser, os, and/os browser version:** #### **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. 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.) #### 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. 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 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. 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. 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 #### 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: 1. Avoid app interaction when possible. The best way of doing this is to navigate directly by URL:
```js ```js
@ -396,10 +394,11 @@ By adhering to this principle, we can create tests that are both robust and refl
1. Leverage `await page.goto('./', { waitUntil: 'domcontentloaded' });` 1. Leverage `await page.goto('./', { waitUntil: 'domcontentloaded' });`
- Initial navigation should _almost_ always use the `{ waitUntil: 'domcontentloaded' }` option. - Initial navigation should _almost_ always use the `{ waitUntil: 'domcontentloaded' }` option.
1. Avoid repeated setup to test a single assertion. Write longer tests with multiple soft assertions. 1. Avoid repeated setup to test a single assertion. Write longer tests with multiple soft assertions.
This ensures that your changes will be picked up with large refactors. This ensures that your changes will be picked up with large refactors.
##### Utilizing LocalStorage ##### 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. 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: 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: - 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 ### How to write a great test
- Avoid using css locators to find elements to the page. Use modern web accessible locators like `getByRole` - Avoid using css locators to find elements to the page. Use modern web accessible locators like `getByRole`
@ -436,7 +434,7 @@ By adhering to this principle, we can create tests that are both robust and refl
await notesInput.fill(testNotes); await notesInput.fill(testNotes);
``` ```
#### How to Write a Great Visual Test #### How to Write a Great Visual Test
1. **Look for the Unknown Unknowns**: Avoid asserting on specific differences in the visual diff. Visual tests are most effective for identifying unknown unknowns. 1. **Look for the Unknown Unknowns**: Avoid asserting on specific differences in the visual diff. Visual tests are most effective for identifying unknown unknowns.
@ -445,23 +443,27 @@ 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. 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. 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 - Utilize `percyCSS` to ignore time-based elements. For more details, consult our [percyCSS file](./.percy.ci.yml).
- Employ the `createExampleTelemetryObject` appAction to source telemetry and specify a `name` to avoid autogenerated names. - Use Open MCT's fixed-time mode unless explicitly testing realtime clock
- Avoid creating objects with a time component like timers and clocks. - Employ the `createExampleTelemetryObject` appAction to source telemetry and specify a `name` to avoid autogenerated names.
- Avoid creating objects with a time component like timers and clocks.
5. **Hide the Tree and Inspector**: Generally, your test will not require comparisons involving the tree and inspector. These aspects are covered in component-specific tests (explained below). To exclude them from the comparison by default, navigate to the root of the main view with the tree and inspector hidden: 5. **Hide the Tree and Inspector**: Generally, your test will not require comparisons involving the tree and inspector. These aspects are covered in component-specific tests (explained below). To exclude them from the comparison by default, navigate to the root of the main view with the tree and inspector hidden:
- `await page.goto('./#/browse/mine?hideTree=true&hideInspector=true')` - `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: 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 ```js
await percySnapshot(page, `Tree Pane w/ single level expanded (theme: ${theme})`, { await percySnapshot(page, `Tree Pane w/ single level expanded (theme: ${theme})`, {
scope: treePane scope: treePane
}); });
``` ```
- Note: The `scope` variable can be any valid CSS selector. - 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: 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 ```js
//<Some interesting state> //<Some interesting state>
await percySnapshot(page, `Before object expanded (theme: ${theme})`); 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) More info and options for `overrideClock` can be found in [baseFixtures.js](baseFixtures.js)
- Working with multiple pages - Working with multiple pages
@ -539,7 +542,6 @@ const key = getFirstKeyFromOpenMctJson(jsonData);
expect(jsonData.openmct[key]).toHaveProperty('name', 'e2e folder'); expect(jsonData.openmct[key]).toHaveProperty('name', 'e2e folder');
``` ```
### Reporting ### Reporting
Test Reporting is done through official Playwright reporters and the CI Systems which execute them. 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 ### Writing Tests
Playwright provides 3 supported methods of debugging and authoring tests: Playwright provides 3 supported methods of debugging and authoring tests:
- A 'watch mode' for running tests locally and debugging on the fly - A 'watch mode' for running tests locally and debugging on the fly
- A 'debug mode' for debugging tests and writing assertions against tests - A 'debug mode' for debugging tests and writing assertions against tests
- A 'VSCode plugin' for debugging tests within the VSCode IDE. - A 'VSCode plugin' for debugging tests within the VSCode IDE.

View File

@ -36,27 +36,67 @@
import AxeBuilder from '@axe-core/playwright'; import AxeBuilder from '@axe-core/playwright';
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import { fileURLToPath } from 'url';
import { expect, test } from './pluginFixtures.js'; import { expect, test } from './pluginFixtures.js';
// Constants for repeated values // 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. * 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. * 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 {import('playwright').Page} page - The page object from Playwright.
* @param {string} testCaseName - The name of the test case. * @param {string} testCaseName - The name of the test case.
* @param {GenerateReportOptions} [options={}] - The options for the report generation. * @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.
* @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 = {}) { export async function scanForA11yViolations(page, testCaseName, options = {}) {
const builder = new AxeBuilder({ page }); const builder = new AxeBuilder({ page });
builder.withTags(['wcag2aa']); 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 * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
@ -111,6 +110,40 @@ const extendedTest = test.extend({
scope: 'test' 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. * Extends the base context class to add codecoverage shim.
* @see {@link https://github.com/mxschmitt/playwright-test-coverage Github Project} * @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 // function in the generatorWorker context. This is necessary
// to ensure that example telemetry data is generated for the new clock time. // to ensure that example telemetry data is generated for the new clock time.
if (clockOptions?.now !== undefined) { if (clockOptions?.now !== undefined) {
page.on( page.on('worker', (worker) => {
'worker', if (worker.url().includes('generatorWorker')) {
(worker) => { worker.evaluate((time) => {
if (worker.url().includes('generatorWorker')) { self.Date.now = () => time;
worker.evaluate((time) => { }, clockOptions.now);
self.Date.now = () => time; }
}); });
}
},
clockOptions.now
);
} }
// Capture any console errors during test execution // 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. * Constants which may be used across all e2e tests.
*/ */
@ -8,12 +7,30 @@
* - Used for overriding the browser clock in tests. * - 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) 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 * URL Constants
* - This is the URL that the browser will be directed to when running visual tests. This URL * These constants are used for initial navigation in visual tests, in either fixed or realtime mode.
* - hides the tree and inspector to prevent visual noise * They navigate to the 'My Items' folder at MISSION_TIME.
* - sets the time bounds to a fixed range * 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 * @param {import('@playwright/test').Page} page
*/ */
async function startAndAddRestrictedNotebookObject(page) { async function startAndAddRestrictedNotebookObject(page) {
// eslint-disable-next-line no-undef
await page.addInitScript({ await page.addInitScript({
path: fileURLToPath(new URL('./addInitRestrictedNotebook.js', import.meta.url)) path: fileURLToPath(new URL('./addInitRestrictedNotebook.js', import.meta.url))
}); });

View File

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

View File

@ -1,4 +1,3 @@
/* eslint-disable no-undef */
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2024, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
@ -123,7 +122,6 @@ const extendedTest = test.extend({
theme: [theme, { option: true }], theme: [theme, { option: true }],
// eslint-disable-next-line no-shadow // eslint-disable-next-line no-shadow
page: async ({ page, theme }, use, testInfo) => { page: async ({ page, theme }, use, testInfo) => {
// eslint-disable-next-line playwright/no-conditional-in-test
if (theme === 'snow') { if (theme === 'snow') {
//inject snow theme //inject snow theme
await page.addInitScript({ 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) (`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', () => { test.describe('baseFixtures tests', () => {
//Skip this test for now https://github.com/nasa/openmct/issues/6785 //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(); test.fail();
//Go to baseURL //Go to baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' }); 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, This test suite is dedicated to tests which verify the basic operations surrounding imagery,
but only assume that example imagery is present. but only assume that example imagery is present.
*/ */
/* globals process */
import { createDomainObjectWithDefaults, setRealTimeMode } from '../../../../appActions.js'; import { createDomainObjectWithDefaults, setRealTimeMode } from '../../../../appActions.js';
import { waitForAnimations } from '../../../../baseFixtures.js'; import { waitForAnimations } from '../../../../baseFixtures.js';
import { expect, test } from '../../../../pluginFixtures.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.describe('Testing numeric data with inspector data visualization (i.e., data pivoting)', () => {
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
// eslint-disable-next-line no-undef
await page.addInitScript({ await page.addInitScript({
path: fileURLToPath( path: fileURLToPath(
new URL('../../../../helper/addInitDataVisualization.js', import.meta.url) new URL('../../../../helper/addInitDataVisualization.js', import.meta.url)

View File

@ -277,7 +277,6 @@ test.describe('Notebook entry tests', () => {
// Create Notebook with URL Whitelist // Create Notebook with URL Whitelist
let notebookObject; let notebookObject;
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
// eslint-disable-next-line no-undef
await page.addInitScript({ await page.addInitScript({
path: fileURLToPath(new URL('../../../../helper/addInitNotebookWithUrls.js', import.meta.url)) 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); await setTimeConductorBounds(page, startDate);
// Bring up the time conductor popup // Bring up the time conductor popup
const timeConductorMode = await page.locator('.c-compact-tc'); const timeConductorMode = page.locator('.c-compact-tc');
await timeConductorMode.click(); await timeConductorMode.click();
const startDateLocator = page.locator('input[type="text"]').first(); const startDateLocator = page.locator('input[type="text"]').first();
const endDateLocator = page.locator('input[type="text"]').nth(2); 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. // for detecting memory leaks.
await page.evaluate(() => { await page.evaluate(() => {
window.gcPromise = new Promise((resolve) => { window.gcPromise = new Promise((resolve) => {
// eslint-disable-next-line no-undef
window.fr = new FinalizationRegistry(resolve); window.fr = new FinalizationRegistry(resolve);
window.fr.register( window.fr.register(
window.openmct.layout.$refs.browseObject.$refs.objectViewWrapper.firstChild, window.openmct.layout.$refs.browseObject.$refs.objectViewWrapper.firstChild,

View File

@ -21,14 +21,13 @@
*****************************************************************************/ *****************************************************************************/
import { test } from '../../avpFixtures.js'; import { test } from '../../avpFixtures.js';
import { VISUAL_URL } from '../../constants.js'; import { VISUAL_FIXED_URL } from '../../constants.js';
test.describe('a11y - Default', () => { test.describe('a11y - Default', () => {
test.beforeEach(async ({ page }) => { 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) => { test('main view', async ({ page }, testInfo) => {
await page.goto('./');
//Skipping for https://github.com/nasa/openmct/issues/7421 //Skipping for https://github.com/nasa/openmct/issues/7421
//await scanForA11yViolations(page, testInfo.title); //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 percySnapshot from '@percy/playwright';
import { expect, scanForA11yViolations, test } from '../../../avpFixtures.js'; 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.describe('Visual - Branding @a11y', () => {
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
//Go to baseURL and Hide Tree //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 }) => { test('Visual - About Modal', async ({ page, theme }) => {

View File

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

View File

@ -23,14 +23,14 @@
import percySnapshot from '@percy/playwright'; import percySnapshot from '@percy/playwright';
import { test } from '../../../avpFixtures.js'; 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 //Declare the scope of the visual test
const inspectorPane = '.l-shell__pane-inspector'; const inspectorPane = '.l-shell__pane-inspector';
test.describe('Visual - Inspector @ally @clock', () => { test.describe('Visual - Inspector @ally @clock', () => {
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' }); await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
}); });
test.use({ test.use({
storageState: './e2e/test-data/overlay_plot_with_delay_storage.json', 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 percySnapshot from '@percy/playwright';
import { createDomainObjectWithDefaults, expandTreePaneItemByName } from '../../../appActions.js'; import { createDomainObjectWithDefaults, expandTreePaneItemByName } from '../../../appActions.js';
import { VISUAL_URL } from '../../../constants.js'; import { VISUAL_FIXED_URL } from '../../../constants.js';
import { test } from '../../../pluginFixtures.js'; import { test } from '../../../pluginFixtures.js';
//Declare the scope of the visual test //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.describe('Visual - Tree Pane', () => {
test('Tree pane in various states', async ({ page, theme, openmctConfig }) => { test('Tree pane in various states', async ({ page, theme, openmctConfig }) => {
const { myItemsFolderName } = openmctConfig; const { myItemsFolderName } = openmctConfig;
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' }); await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
//Open Tree //Open Tree
await page.getByRole('button', { name: 'Browse' }).click(); await page.getByRole('button', { name: 'Browse' }).click();

View File

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

View File

@ -30,11 +30,11 @@ import percySnapshot from '@percy/playwright';
import { createDomainObjectWithDefaults } from '../../appActions.js'; import { createDomainObjectWithDefaults } from '../../appActions.js';
import { expect, scanForA11yViolations, test } from '../../avpFixtures.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.describe('Visual - Default @a11y', () => {
test.beforeEach(async ({ page }) => { 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 }) => { test('Visual - Default Dashboard', async ({ page, theme }) => {

View File

@ -23,12 +23,18 @@
import percySnapshot from '@percy/playwright'; import percySnapshot from '@percy/playwright';
import { createDomainObjectWithDefaults } from '../../appActions.js'; 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'; import { test } from '../../pluginFixtures.js';
test.describe('Visual - Display Layout', () => { test.describe('Visual - Display Layout @clock', () => {
test.beforeEach(async ({ page, theme }) => { test.use({
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' }); clockOptions: {
now: MISSION_TIME,
shouldAdvanceTime: true
}
});
test.beforeEach(async ({ page }) => {
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
const parentLayout = await createDomainObjectWithDefaults(page, { const parentLayout = await createDomainObjectWithDefaults(page, {
type: 'Display Layout', type: 'Display Layout',
@ -59,12 +65,15 @@ test.describe('Visual - Display Layout', () => {
await page.goto(parentLayout.url, { waitUntil: 'domcontentloaded' }); await page.goto(parentLayout.url, { waitUntil: 'domcontentloaded' });
await page.getByRole('button', { name: 'Edit Object' }).click(); 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 await page
.getByLabel('Child Right Layout Layout', { exact: true }) .getByLabel('Child Right Layout Layout', { exact: true })
.getByLabel('Move Sub-object Frame') .getByLabel('Move Sub-object Frame')
.click(); .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:').click();
await page.getByLabel('X:').fill('35'); await page.getByLabel('X:').fill('35');
}); });

View File

@ -84,6 +84,12 @@ test.describe('Fault Management Visual Tests', () => {
await shelveFault(page, 1); await shelveFault(page, 1);
await changeViewTo(page, 'shelved'); 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 percySnapshot(page, `Shelved faults appear in the shelved view (theme: '${theme}')`);
await openFaultRowMenu(page, 1); await openFaultRowMenu(page, 1);

View File

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

View File

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

View File

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

View File

@ -28,11 +28,11 @@ import percySnapshot from '@percy/playwright';
import { createNotification } from '../../appActions.js'; import { createNotification } from '../../appActions.js';
import { expect, scanForA11yViolations, test } from '../../avpFixtures.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.describe('Visual - Notifications @a11y', () => {
test.beforeEach(async ({ page }) => { 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 }) => { 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 { createDomainObjectWithDefaults, createPlanFromJSON } from '../../appActions.js';
import { test } from '../../avpFixtures.js'; import { test } from '../../avpFixtures.js';
import { VISUAL_URL } from '../../constants.js'; import { VISUAL_FIXED_URL } from '../../constants.js';
import { import {
createTimelistWithPlanAndSetActivityInProgress, createTimelistWithPlanAndSetActivityInProgress,
getFirstActivity, getFirstActivity,
@ -64,7 +64,7 @@ test.describe('Visual - Timelist progress bar @clock', () => {
test.describe('Visual - Planning', () => { test.describe('Visual - Planning', () => {
test.beforeEach(async ({ page }) => { 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 }) => { test('Plan View', async ({ page, theme }) => {
@ -83,7 +83,7 @@ test.describe('Visual - Planning', () => {
}); });
const newPage = await newContext.newPage(); 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, { const plan = await createPlanFromJSON(newPage, {
name: 'Plan Visual Test', name: 'Plan Visual Test',
json: examplePlanSmall2 json: examplePlanSmall2
@ -100,7 +100,7 @@ test.describe('Visual - Planning', () => {
name: 'Plan Visual Test (Draft)', name: 'Plan Visual Test (Draft)',
json: examplePlanSmall2 json: examplePlanSmall2
}); });
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' }); await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
await setDraftStatusForPlan(page, plan); await setDraftStatusForPlan(page, plan);
await setBoundsToSpanAllActivities(page, examplePlanSmall2, plan.url); await setBoundsToSpanAllActivities(page, examplePlanSmall2, plan.url);
@ -110,7 +110,7 @@ test.describe('Visual - Planning', () => {
test.describe('Visual - Gantt Chart', () => { test.describe('Visual - Gantt Chart', () => {
test.beforeEach(async ({ page }) => { 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 }) => { test('Gantt Chart View', async ({ page, theme }) => {
const ganttChart = await createDomainObjectWithDefaults(page, { const ganttChart = await createDomainObjectWithDefaults(page, {
@ -153,7 +153,7 @@ test.describe('Visual - Gantt Chart', () => {
await setDraftStatusForPlan(page, plan); 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 setBoundsToSpanAllActivities(page, examplePlanSmall2, ganttChart.url);
await percySnapshot(page, `Gantt Chart View w/ draft status (theme: ${theme})`); 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 { createDomainObjectWithDefaults } from '../../appActions.js';
import { expect, scanForA11yViolations, test } from '../../avpFixtures.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', () => { test.describe('Grand Search @a11y', () => {
let conditionWidget; let conditionWidget;
let displayLayout; let displayLayout;
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' }); await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
displayLayout = await createDomainObjectWithDefaults(page, { displayLayout = await createDomainObjectWithDefaults(page, {
type: 'Display Layout', type: 'Display Layout',

View File

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

View File

@ -20,26 +20,32 @@
* at runtime from the About dialog for additional information. * 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) => { module.exports = async (config) => {
let webpackConfig; const { config: webpackConfig, browsers, singleRun } = await loadWebpackConfig();
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;
}
// Adjust webpack config for Karma
delete webpackConfig.output; delete webpackConfig.output;
// karma doesn't support webpack entry delete webpackConfig.entry; // Karma doesn't support webpack entry
delete webpackConfig.entry;
// Ensure source maps are enabled for better debugging
webpackConfig.devtool = 'inline-source-map';
config.set({ config.set({
basePath: '', basePath: '',
@ -106,7 +112,7 @@ module.exports = async (config) => {
}, },
webpack: webpackConfig, webpack: webpackConfig,
webpackMiddleware: { webpackMiddleware: {
stats: 'errors-warnings' stats: 'detailed' // Changed to 'detailed' for more debugging info
}, },
concurrency: 1, concurrency: 1,
singleRun, singleRun,

409
package-lock.json generated
View File

@ -22,6 +22,7 @@
"@types/eventemitter3": "1.2.0", "@types/eventemitter3": "1.2.0",
"@types/jasmine": "5.1.2", "@types/jasmine": "5.1.2",
"@types/lodash": "4.17.0", "@types/lodash": "4.17.0",
"@types/sinonjs__fake-timers": "8.1.5",
"@vue/compiler-sfc": "3.4.3", "@vue/compiler-sfc": "3.4.3",
"babel-loader": "9.1.0", "babel-loader": "9.1.0",
"babel-plugin-istanbul": "6.1.1", "babel-plugin-istanbul": "6.1.1",
@ -1904,6 +1905,12 @@
"@types/node": "*" "@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": { "node_modules/@types/sockjs": {
"version": "0.3.36", "version": "0.3.36",
"resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz",
@ -1932,207 +1939,6 @@
"@types/node": "*" "@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": { "node_modules/@ungap/structured-clone": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", "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": { "node_modules/prettier-linter-helpers": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", "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", "@percy/playwright": "1.0.4",
"@playwright/test": "1.42.1", "@playwright/test": "1.42.1",
"@types/d3-axis": "3.0.6", "@types/d3-axis": "3.0.6",
"@types/d3-shape": "3.0.0",
"@types/d3-scale": "4.0.8", "@types/d3-scale": "4.0.8",
"@types/d3-selection": "3.0.10", "@types/d3-selection": "3.0.10",
"@types/d3-shape": "3.0.0",
"@types/eventemitter3": "1.2.0", "@types/eventemitter3": "1.2.0",
"@types/jasmine": "5.1.2", "@types/jasmine": "5.1.2",
"@types/lodash": "4.17.0", "@types/lodash": "4.17.0",
"@types/sinonjs__fake-timers": "8.1.5",
"@vue/compiler-sfc": "3.4.3", "@vue/compiler-sfc": "3.4.3",
"babel-loader": "9.1.0", "babel-loader": "9.1.0",
"babel-plugin-istanbul": "6.1.1", "babel-plugin-istanbul": "6.1.1",
@ -27,9 +28,9 @@
"cspell": "7.3.8", "cspell": "7.3.8",
"css-loader": "6.10.0", "css-loader": "6.10.0",
"d3-axis": "3.0.0", "d3-axis": "3.0.0",
"d3-shape": "3.0.0",
"d3-scale": "4.0.2", "d3-scale": "4.0.2",
"d3-selection": "3.0.0", "d3-selection": "3.0.0",
"d3-shape": "3.0.0",
"eslint": "8.56.0", "eslint": "8.56.0",
"eslint-config-prettier": "9.1.0", "eslint-config-prettier": "9.1.0",
"eslint-plugin-compat": "4.2.0", "eslint-plugin-compat": "4.2.0",

View File

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

View File

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

View File

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

View File

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

View File

@ -134,7 +134,7 @@ describe('The import JSON action', function () {
const pollutedResponse = { const pollutedResponse = {
selectFile: { selectFile: {
name: 'imported object', 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"}' 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 * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space

View File

@ -161,7 +161,6 @@ export default function Rule(
*/ */
function onDragStart(event) { function onDragStart(event) {
document.querySelectorAll('.t-drag-indicator').forEach((indicator) => { 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); const ruleHeader = self.domElement.querySelectorAll('.widget-rule-header')[0].cloneNode(true);
indicator.textContent = ''; indicator.textContent = '';
indicator.appendChild(ruleHeader); 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.querySelector('.c-palette').classList.add('c-palette--color');
domElement.querySelectorAll('.c-palette__item').forEach((item) => { domElement.querySelectorAll('.c-palette__item').forEach((item) => {
// eslint-disable-next-line no-invalid-this
item.style.backgroundColor = item.dataset.item; 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.querySelector('.c-palette').classList.add('c-palette--icon');
domElement.querySelectorAll('.c-palette-item').forEach((item) => { domElement.querySelectorAll('.c-palette-item').forEach((item) => {
// eslint-disable-next-line no-invalid-this
item.classList.add(item.dataset.item); item.classList.add(item.dataset.item);
}); });

View File

@ -20,7 +20,12 @@
at runtime from the About dialog for additional information. at runtime from the About dialog for additional information.
--> -->
<template> <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 class="c-conductor-axis__zoom-indicator" :style="zoomStyle"></div>
</div> </div>
</template> </template>
@ -29,7 +34,9 @@
import { axisTop } from 'd3-axis'; import { axisTop } from 'd3-axis';
import { scaleLinear, scaleUtc } from 'd3-scale'; import { scaleLinear, scaleUtc } from 'd3-scale';
import { select } from 'd3-selection'; 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 { TIME_CONTEXT_EVENTS } from '../../api/time/constants.js';
import utcMultiTimeFormat from './utcMultiTimeFormat.js'; import utcMultiTimeFormat from './utcMultiTimeFormat.js';
@ -55,20 +62,69 @@ export default {
} }
}, },
emits: ['pan-axis', 'end-pan', 'zoom-axis', 'end-zoom'], 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() { data() {
return { return {
inPanMode: false, inPanMode: false,
dragStartX: undefined, dragStartX: undefined,
dragX: undefined, dragX: undefined,
zoomStyle: {} zoomStyle: {},
rect: null
}; };
}, },
computed: { computed: {
inZoomMode() { inZoomMode() {
return !this.inPanMode; 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: { watch: {
containerSize: {
handler() {
this.resize();
},
deep: true
},
viewBounds: { viewBounds: {
handler() { handler() {
this.setScale(); this.setScale();
@ -77,7 +133,7 @@ export default {
} }
}, },
mounted() { mounted() {
let vis = select(this.$refs.axisHolder).append('svg:svg'); const vis = select(this.axisHolder).append('svg:svg');
this.xAxis = axisTop(); this.xAxis = axisTop();
this.dragging = false; this.dragging = false;
@ -93,15 +149,14 @@ export default {
this.openmct.time.on(TIME_CONTEXT_EVENTS.timeSystemChanged, this.setViewFromTimeSystem); this.openmct.time.on(TIME_CONTEXT_EVENTS.timeSystemChanged, this.setViewFromTimeSystem);
}, },
beforeUnmount() { 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: { methods: {
setAxisDimensions() { setAxisDimensions() {
const axisHolder = this.$refs.axisHolder; this.rect = this.axisHolder.getBoundingClientRect();
const rect = axisHolder.getBoundingClientRect(); this.width = this.axisHolder.clientWidth;
this.left = Math.round(rect.left);
this.width = axisHolder.clientWidth;
}, },
setScale() { setScale() {
if (!this.width) { if (!this.width) {
@ -204,26 +259,14 @@ export default {
this.dragX = undefined; this.dragX = undefined;
}, },
pan() { pan() {
const panBounds = this.getPanBounds(); const panBounds = this.panBounds;
this.$emit('pan-axis', panBounds); this.$emit('pan-axis', panBounds);
}, },
endPan() { endPan() {
const panBounds = this.isChangingViewBounds() ? this.getPanBounds() : undefined; const panBounds = this.isChangingViewBounds ? this.panBounds : undefined;
this.$emit('end-pan', panBounds); this.$emit('end-pan', panBounds);
this.inPanMode = false; 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() { startZoom() {
const x = this.scaleToBounds(this.dragStartX); const x = this.scaleToBounds(this.dragStartX);
@ -237,7 +280,7 @@ export default {
}); });
}, },
zoom() { zoom() {
const zoomRange = this.getZoomRange(); const zoomRange = this.zoomRange;
this.zoomStyle = { this.zoomStyle = {
left: `${zoomRange.start - this.left}px`, left: `${zoomRange.start - this.left}px`,
@ -251,8 +294,8 @@ export default {
}, },
endZoom() { endZoom() {
let zoomBounds; let zoomBounds;
if (this.isChangingViewBounds()) { if (this.isChangingViewBounds) {
const zoomRange = this.getZoomRange(); const zoomRange = this.zoomRange;
zoomBounds = { zoomBounds = {
start: this.scaleToBounds(zoomRange.start), start: this.scaleToBounds(zoomRange.start),
end: this.scaleToBounds(zoomRange.end) end: this.scaleToBounds(zoomRange.end)
@ -262,19 +305,6 @@ export default {
this.zoomStyle = {}; this.zoomStyle = {};
this.$emit('end-zoom', zoomBounds); 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) { scaleToBounds(value) {
const bounds = this.openmct.time.getBounds(); const bounds = this.openmct.time.getBounds();
const timeDelta = bounds.end - bounds.start; const timeDelta = bounds.end - bounds.start;
@ -283,11 +313,8 @@ export default {
return parseInt(bounds.start + offset, 10); return parseInt(bounds.start + offset, 10);
}, },
isChangingViewBounds() {
return this.dragStartX && this.dragX && this.dragStartX !== this.dragX;
},
resize() { resize() {
if (this.$refs.axisHolder.clientWidth !== this.width) { if (this.axisHolder.clientWidth !== this.width) {
this.setAxisDimensions(); this.setAxisDimensions();
this.setScale(); this.setScale();
} }

View File

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

View File

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