Compare commits
	
		
			10 Commits
		
	
	
		
			mct7313
			...
			v4.0.0-rc2
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					a5c6b141a6 | ||
| 
						 | 
					46823ec225 | ||
| 
						 | 
					5b4ee1949f | ||
| 
						 | 
					7e926ccbb7 | ||
| 
						 | 
					6e264517f8 | ||
| 
						 | 
					986da5782b | ||
| 
						 | 
					539138437b | ||
| 
						 | 
					493b31d0b9 | ||
| 
						 | 
					d68ac31ab5 | ||
| 
						 | 
					f504ee29cc | 
@@ -5,11 +5,11 @@ orbs:
 | 
			
		||||
executors:
 | 
			
		||||
  pw-focal-development:
 | 
			
		||||
    docker:
 | 
			
		||||
      - image: mcr.microsoft.com/playwright:v1.39.0-focal
 | 
			
		||||
      - image: mcr.microsoft.com/playwright:v1.42.1-focal
 | 
			
		||||
    environment:
 | 
			
		||||
      NODE_ENV: development # Needed to ensure 'dist' folder created and devDependencies installed
 | 
			
		||||
      PERCY_POSTINSTALL_BROWSER: "true" # Needed to store the percy browser in cache deps
 | 
			
		||||
      PERCY_LOGLEVEL: "debug" # Enable DEBUG level logging for Percy (Issue: https://github.com/nasa/openmct/issues/5742)
 | 
			
		||||
      PERCY_POSTINSTALL_BROWSER: 'true' # Needed to store the percy browser in cache deps
 | 
			
		||||
      PERCY_LOGLEVEL: 'debug' # Enable DEBUG level logging for Percy (Issue: https://github.com/nasa/openmct/issues/5742)
 | 
			
		||||
      PERCY_PARALLEL_TOTAL: 2
 | 
			
		||||
  ubuntu:
 | 
			
		||||
    machine:
 | 
			
		||||
@@ -17,7 +17,7 @@ executors:
 | 
			
		||||
      docker_layer_caching: true
 | 
			
		||||
commands:
 | 
			
		||||
  build_and_install:
 | 
			
		||||
    description: "All steps used to build and install."
 | 
			
		||||
    description: 'All steps used to build and install.'
 | 
			
		||||
    parameters:
 | 
			
		||||
      node-version:
 | 
			
		||||
        type: string
 | 
			
		||||
@@ -27,7 +27,7 @@ commands:
 | 
			
		||||
          node-version: << parameters.node-version >>
 | 
			
		||||
      - node/install-packages
 | 
			
		||||
  generate_and_store_version_and_filesystem_artifacts:
 | 
			
		||||
    description: "Track important packages and files"
 | 
			
		||||
    description: 'Track important packages and files'
 | 
			
		||||
    steps:
 | 
			
		||||
      - run: |
 | 
			
		||||
          [[ $EUID -ne 0 ]] && (sudo mkdir -p /tmp/artifacts && sudo chmod 777 /tmp/artifacts) || (mkdir -p /tmp/artifacts && chmod 777 /tmp/artifacts)
 | 
			
		||||
@@ -38,7 +38,7 @@ commands:
 | 
			
		||||
      - store_artifacts:
 | 
			
		||||
          path: /tmp/artifacts/
 | 
			
		||||
  generate_e2e_code_cov_report:
 | 
			
		||||
    description: "Generate e2e code coverage artifacts and publish to codecov.io. Needed to that we can ignore the exit code status of the npm run test"
 | 
			
		||||
    description: 'Generate e2e code coverage artifacts and publish to codecov.io. Needed to that we can ignore the exit code status of the npm run test'
 | 
			
		||||
    parameters:
 | 
			
		||||
      suite:
 | 
			
		||||
        type: string
 | 
			
		||||
@@ -102,7 +102,7 @@ jobs:
 | 
			
		||||
          node-version: lts/hydrogen
 | 
			
		||||
      - when: #Only install chrome-beta when running the 'full' suite to save $$$
 | 
			
		||||
          condition:
 | 
			
		||||
            equal: ["full", <<parameters.suite>>]
 | 
			
		||||
            equal: ['full', <<parameters.suite>>]
 | 
			
		||||
          steps:
 | 
			
		||||
            - run: npx playwright install chrome-beta
 | 
			
		||||
      - run:
 | 
			
		||||
@@ -159,7 +159,7 @@ jobs:
 | 
			
		||||
    steps:
 | 
			
		||||
      - build_and_install:
 | 
			
		||||
          node-version: lts/hydrogen
 | 
			
		||||
      - run: npx playwright@1.39.0 install #Necessary for bare ubuntu machine
 | 
			
		||||
      - run: npx playwright@1.42.1 install #Necessary for bare ubuntu machine
 | 
			
		||||
      - run: |
 | 
			
		||||
          export $(cat src/plugins/persistence/couch/.env.ci | xargs)
 | 
			
		||||
          docker-compose -f src/plugins/persistence/couch/couchdb-compose.yaml up --detach
 | 
			
		||||
@@ -230,11 +230,7 @@ jobs:
 | 
			
		||||
    steps:
 | 
			
		||||
      - build_and_install:
 | 
			
		||||
          node-version: lts/iron
 | 
			
		||||
      - run:
 | 
			
		||||
          command: |
 | 
			
		||||
            mkdir test-results
 | 
			
		||||
            TESTFILES=$(circleci tests glob "e2e/**/*.spec.js")
 | 
			
		||||
            echo "$TESTFILES" | circleci tests run --command="xargs npm run test:e2e:visual:<<parameters.suite>>" --verbose --split-by=timings
 | 
			
		||||
      - run: npm run test:e2e:visual:<<parameters.suite>>
 | 
			
		||||
      - store_test_results:
 | 
			
		||||
          path: test-results/results.xml
 | 
			
		||||
      - store_artifacts:
 | 
			
		||||
@@ -286,7 +282,7 @@ workflows:
 | 
			
		||||
      - e2e-couchdb
 | 
			
		||||
    triggers:
 | 
			
		||||
      - schedule:
 | 
			
		||||
          cron: "0 0 * * *"
 | 
			
		||||
          cron: '0 0 * * *'
 | 
			
		||||
          filters:
 | 
			
		||||
            branches:
 | 
			
		||||
              only:
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/e2e-couchdb.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -37,7 +37,7 @@ jobs:
 | 
			
		||||
          username: ${{ secrets.DOCKERHUB_USERNAME }}
 | 
			
		||||
          password: ${{ secrets.DOCKERHUB_TOKEN }}
 | 
			
		||||
 | 
			
		||||
      - run: npx playwright@1.39.0 install
 | 
			
		||||
      - run: npx playwright@1.42.1 install
 | 
			
		||||
 | 
			
		||||
      - name: Start CouchDB Docker Container and Init with Setup Scripts
 | 
			
		||||
        run: |
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/e2e-flakefinder.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -30,7 +30,7 @@ jobs:
 | 
			
		||||
          restore-keys: |
 | 
			
		||||
            ${{ runner.os }}-node-
 | 
			
		||||
 | 
			
		||||
      - run: npx playwright@1.39.0 install
 | 
			
		||||
      - run: npx playwright@1.42.1 install
 | 
			
		||||
      - run: npm ci --no-audit --progress=false
 | 
			
		||||
 | 
			
		||||
      - name: Run E2E Tests (Repeated 10 Times)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/e2e-perf.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -28,7 +28,7 @@ jobs:
 | 
			
		||||
          restore-keys: |
 | 
			
		||||
            ${{ runner.os }}-node-
 | 
			
		||||
 | 
			
		||||
      - run: npx playwright@1.39.0 install
 | 
			
		||||
      - run: npx playwright@1.42.1 install
 | 
			
		||||
      - run: npm ci --no-audit --progress=false
 | 
			
		||||
      - run: npm run test:perf:localhost
 | 
			
		||||
      - run: npm run test:perf:contract
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/e2e-pr.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -33,7 +33,7 @@ jobs:
 | 
			
		||||
          restore-keys: |
 | 
			
		||||
            ${{ runner.os }}-node-
 | 
			
		||||
 | 
			
		||||
      - run: npx playwright@1.39.0 install
 | 
			
		||||
      - run: npx playwright@1.42.1 install
 | 
			
		||||
      - run: npx playwright install chrome-beta
 | 
			
		||||
      - run: npm ci --no-audit --progress=false
 | 
			
		||||
      - run: npm run test:e2e:full -- --max-failures=40
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/prcop.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -5,6 +5,8 @@ on:
 | 
			
		||||
    types:
 | 
			
		||||
      - labeled
 | 
			
		||||
      - unlabeled
 | 
			
		||||
      - milestoned
 | 
			
		||||
      - demilestoned
 | 
			
		||||
      - opened
 | 
			
		||||
      - reopened
 | 
			
		||||
      - synchronize
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -47,3 +47,6 @@ index.html.bak
 | 
			
		||||
.nyc_output
 | 
			
		||||
coverage
 | 
			
		||||
codecov
 | 
			
		||||
 | 
			
		||||
# Don't commit MacOS screenshots
 | 
			
		||||
*-darwin.png
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										10
									
								
								API.md
									
									
									
									
									
								
							
							
						
						@@ -1305,6 +1305,16 @@ View provider Example:
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## User API
 | 
			
		||||
 | 
			
		||||
Open MCT provides a User API which can be used to define providers for user information. The API
 | 
			
		||||
can be used to manage user information and roles.
 | 
			
		||||
 | 
			
		||||
### Example
 | 
			
		||||
 | 
			
		||||
Open MCT provides an example [user](example/exampleUser/exampleUserCreator.js) and [user provider](example/exampleUser/ExampleUserProvider.js) which 
 | 
			
		||||
can be used as a starting point for creating a custom user provider.
 | 
			
		||||
 | 
			
		||||
## Visibility-Based Rendering in View Providers
 | 
			
		||||
 | 
			
		||||
To enhance performance and resource efficiency in OpenMCT, a visibility-based rendering feature has been added. This feature is designed to defer the execution of rendering logic for views that are not currently visible. It ensures that views are only updated when they are in the viewport, similar to how modern browsers handle rendering of inactive tabs but optimized for the OpenMCT tabbed display. It also works when views are scrolled outside the viewport (e.g., in a Display Layout).
 | 
			
		||||
 
 | 
			
		||||
@@ -76,28 +76,30 @@ 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: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: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).
 | 
			
		||||
 | 
			
		||||
At present, we are using percy with two configuration files: `./e2e/.percy.nightly.yml` and `./e2e/.percy.ci.yml`. This is mainly to reduce the number of snapshots. 
 | 
			
		||||
At present, we are using percy with two configuration files: `./e2e/.percy.nightly.yml` and `./e2e/.percy.ci.yml`. This is mainly to reduce the number of snapshots.
 | 
			
		||||
 | 
			
		||||
### Advanced: Snapshot Testing (Not Recommended)
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
@@ -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' });`
 | 
			
		||||
    - 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.
 | 
			
		||||
 | 
			
		||||
##### 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`
 | 
			
		||||
@@ -436,7 +434,7 @@ By adhering to this principle, we can create tests that are both robust and refl
 | 
			
		||||
  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.
 | 
			
		||||
 | 
			
		||||
@@ -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.
 | 
			
		||||
 | 
			
		||||
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.
 | 
			
		||||
  - Avoid creating objects with a time component like timers 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.
 | 
			
		||||
- 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:
 | 
			
		||||
    - `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.
 | 
			
		||||
 
 | 
			
		||||
@@ -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 };
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
@@ -40,7 +39,7 @@ import { v4 as uuid } from 'uuid';
 | 
			
		||||
 * @see {@link https://github.com/microsoft/playwright/discussions/11690 Github Discussion}
 | 
			
		||||
 * @private
 | 
			
		||||
 * @param {import('@playwright/test').ConsoleMessage} msg
 | 
			
		||||
 * @returns {String} formatted string with message type, text, url, and line and column numbers
 | 
			
		||||
 * @returns {string} formatted string with message type, text, url, and line and column numbers
 | 
			
		||||
 */
 | 
			
		||||
function _consoleMessageToString(msg) {
 | 
			
		||||
  const { url, lineNumber, columnNumber } = msg.location();
 | 
			
		||||
@@ -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) => {
 | 
			
		||||
          if (worker.url().includes('generatorWorker')) {
 | 
			
		||||
            worker.evaluate((time) => {
 | 
			
		||||
              self.Date.now = () => time;
 | 
			
		||||
            });
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        clockOptions.now
 | 
			
		||||
      );
 | 
			
		||||
      page.on('worker', (worker) => {
 | 
			
		||||
        if (worker.url().includes('generatorWorker')) {
 | 
			
		||||
          worker.evaluate((time) => {
 | 
			
		||||
            self.Date.now = () => time;
 | 
			
		||||
          }, clockOptions.now);
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Capture any console errors during test execution
 | 
			
		||||
 
 | 
			
		||||
@@ -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';
 | 
			
		||||
 
 | 
			
		||||
@@ -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))
 | 
			
		||||
  });
 | 
			
		||||
 
 | 
			
		||||
@@ -29,7 +29,7 @@ import { expect } from '../pluginFixtures.js';
 | 
			
		||||
 * for each activity in the plan data per group, using the earliest activity's
 | 
			
		||||
 * start time as the start bound and the current activity's end time as the end bound.
 | 
			
		||||
 * @param {import('@playwright/test').Page} page the page
 | 
			
		||||
 * @param {object} plan The raw plan json to assert against
 | 
			
		||||
 * @param {Object} plan The raw plan json to assert against
 | 
			
		||||
 * @param {string} objectUrl The URL of the object to assert against (plan or gantt chart)
 | 
			
		||||
 */
 | 
			
		||||
export async function assertPlanActivities(page, plan, objectUrl) {
 | 
			
		||||
@@ -86,7 +86,7 @@ function activitiesWithinTimeBounds(start1, end1, start2, end2) {
 | 
			
		||||
 * Asserts that the swim lanes / groups in the plan view matches the order of
 | 
			
		||||
 * groups in the plan data.
 | 
			
		||||
 * @param {import('@playwright/test').Page} page the page
 | 
			
		||||
 * @param {object} plan The raw plan json to assert against
 | 
			
		||||
 * @param {Object} plan The raw plan json to assert against
 | 
			
		||||
 * @param {string} objectUrl The URL of the object to assert against (plan or gantt chart)
 | 
			
		||||
 */
 | 
			
		||||
export async function assertPlanOrderedSwimLanes(page, plan, objectUrl) {
 | 
			
		||||
@@ -110,7 +110,7 @@ export async function assertPlanOrderedSwimLanes(page, plan, objectUrl) {
 | 
			
		||||
 * Navigate to the plan view, switch to fixed time mode,
 | 
			
		||||
 * and set the bounds to span all activities.
 | 
			
		||||
 * @param {import('@playwright/test').Page} page
 | 
			
		||||
 * @param {object} planJson
 | 
			
		||||
 * @param {Object} planJson
 | 
			
		||||
 * @param {string} planObjectUrl
 | 
			
		||||
 */
 | 
			
		||||
export async function setBoundsToSpanAllActivities(page, planJson, planObjectUrl) {
 | 
			
		||||
@@ -125,7 +125,7 @@ export async function setBoundsToSpanAllActivities(page, planJson, planObjectUrl
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @param {object} planJson
 | 
			
		||||
 * @param {Object} planJson
 | 
			
		||||
 * @returns {number}
 | 
			
		||||
 */
 | 
			
		||||
export function getEarliestStartTime(planJson) {
 | 
			
		||||
@@ -135,7 +135,7 @@ export function getEarliestStartTime(planJson) {
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 *
 | 
			
		||||
 * @param {object} planJson
 | 
			
		||||
 * @param {Object} planJson
 | 
			
		||||
 * @returns {number}
 | 
			
		||||
 */
 | 
			
		||||
export function getLatestEndTime(planJson) {
 | 
			
		||||
 
 | 
			
		||||
@@ -27,8 +27,8 @@ import { expect } from '../pluginFixtures.js';
 | 
			
		||||
 * Given a canvas and a set of points, tags the points on the canvas.
 | 
			
		||||
 * @param {import('@playwright/test').Page} page
 | 
			
		||||
 * @param {HTMLCanvasElement} canvas a telemetry item with a plot
 | 
			
		||||
 * @param {Number} xEnd a telemetry item with a plot
 | 
			
		||||
 * @param {Number} yEnd a telemetry item with a plot
 | 
			
		||||
 * @param {number} xEnd a telemetry item with a plot
 | 
			
		||||
 * @param {number} yEnd a telemetry item with a plot
 | 
			
		||||
 * @returns {Promise}
 | 
			
		||||
 */
 | 
			
		||||
export async function createTags({ page, canvas, xEnd = 700, yEnd = 520 }) {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,3 @@
 | 
			
		||||
/* eslint-disable no-undef */
 | 
			
		||||
// playwright.config.js
 | 
			
		||||
// @ts-check
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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({
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -292,6 +292,16 @@ test.describe('Basic Condition Set Use', () => {
 | 
			
		||||
    await expect(page.getByRole('menuitem', { name: /Conditions View/ })).toBeVisible();
 | 
			
		||||
    await expect(page.getByRole('menuitem', { name: /Plot/ })).toBeVisible();
 | 
			
		||||
    await expect(page.getByRole('menuitem', { name: /Telemetry Table/ })).toBeVisible();
 | 
			
		||||
    await page.getByLabel('Plot').click();
 | 
			
		||||
    await expect(
 | 
			
		||||
      page.getByLabel('Plot Legend Collapsed').getByText('Test Condition Set')
 | 
			
		||||
    ).toBeVisible();
 | 
			
		||||
    await page.getByLabel('Open the View Switcher Menu').click();
 | 
			
		||||
    await page.getByLabel('Telemetry Table').click();
 | 
			
		||||
    await expect(page.getByRole('searchbox', { name: 'output filter input' })).toBeVisible();
 | 
			
		||||
    await page.getByLabel('Open the View Switcher Menu').click();
 | 
			
		||||
    await page.getByLabel('Conditions View').click();
 | 
			
		||||
    await expect(page.getByText('Current Output')).toBeVisible();
 | 
			
		||||
  });
 | 
			
		||||
  test('ConditionSet has correct outputs when telemetry is and is not available', async ({
 | 
			
		||||
    page
 | 
			
		||||
 
 | 
			
		||||
@@ -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';
 | 
			
		||||
@@ -773,7 +773,7 @@ async function dragContrastSliderAndAssertFilterValues(page) {
 | 
			
		||||
 * Gets the filter:brightness value of the current background-image and
 | 
			
		||||
 * asserts against an expected value
 | 
			
		||||
 * @param {import('@playwright/test').Page} page
 | 
			
		||||
 * @param {String} expected The expected brightness value
 | 
			
		||||
 * @param {string} expected The expected brightness value
 | 
			
		||||
 */
 | 
			
		||||
async function assertBackgroundImageBrightness(page, expected) {
 | 
			
		||||
  const backgroundImage = page.locator('.c-imagery__main-image__background-image');
 | 
			
		||||
@@ -938,7 +938,7 @@ async function buttonZoomOnImageAndAssert(page) {
 | 
			
		||||
 * Gets the filter:contrast value of the current background-image and
 | 
			
		||||
 * asserts against an expected value
 | 
			
		||||
 * @param {import('@playwright/test').Page} page
 | 
			
		||||
 * @param {String} expected The expected contrast value
 | 
			
		||||
 * @param {string} expected The expected contrast value
 | 
			
		||||
 */
 | 
			
		||||
async function assertBackgroundImageContrast(page, expected) {
 | 
			
		||||
  const backgroundImage = page.locator('.c-imagery__main-image__background-image');
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										86
									
								
								e2e/tests/functional/plugins/lad/ladTable.e2e.spec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,86 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
import { createDomainObjectWithDefaults } from '../../../../appActions.js';
 | 
			
		||||
import { expect, test } from '../../../../pluginFixtures.js';
 | 
			
		||||
 | 
			
		||||
test.describe('LAD Table', () => {
 | 
			
		||||
  let ladTable;
 | 
			
		||||
  let swg;
 | 
			
		||||
 | 
			
		||||
  test.beforeEach(async ({ page }) => {
 | 
			
		||||
    await page.goto('./', { waitUntil: 'domcontentloaded' });
 | 
			
		||||
 | 
			
		||||
    ladTable = await createDomainObjectWithDefaults(page, {
 | 
			
		||||
      type: 'LAD Table'
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    swg = await createDomainObjectWithDefaults(page, {
 | 
			
		||||
      type: 'Sine Wave Generator',
 | 
			
		||||
      parent: ladTable.uuid
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    await page.goto(ladTable.url);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  test('Ensure we have numbers in cells', async ({ page }) => {
 | 
			
		||||
    // Wait for the initial value to show after mount
 | 
			
		||||
    await expect(page.getByLabel('lad value').first()).not.toContainText('---');
 | 
			
		||||
 | 
			
		||||
    const valueFromFirstSineWave = await page.getByLabel('lad value').first().innerText();
 | 
			
		||||
    const firstSineWaveNumber = parseFloat(valueFromFirstSineWave);
 | 
			
		||||
    // ensure we have a float value in the cell and it's finite
 | 
			
		||||
    expect(Number.isFinite(firstSineWaveNumber)).toBeTruthy();
 | 
			
		||||
 | 
			
		||||
    const valueFromSecondSineWave = await page.getByLabel('lad value').last().innerText();
 | 
			
		||||
    const secondSineWaveNumber = parseFloat(valueFromSecondSineWave);
 | 
			
		||||
    // ensure we have a float value in the cell and it's finite
 | 
			
		||||
    expect(Number.isFinite(secondSineWaveNumber)).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  test(
 | 
			
		||||
    'Can remove telemetry from composition',
 | 
			
		||||
    {
 | 
			
		||||
      annotation: {
 | 
			
		||||
        type: 'issue',
 | 
			
		||||
        description: 'https://github.com/nasa/openmct/issues/7633'
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    async ({ page }) => {
 | 
			
		||||
      // Assert that the table is initially populated
 | 
			
		||||
      await expect(page.getByLabel('lad row')).toHaveCount(1);
 | 
			
		||||
 | 
			
		||||
      // Expand the tree so the SWG is visible
 | 
			
		||||
      await page.getByLabel('Expand My Items').click();
 | 
			
		||||
      await page.getByLabel('Expand LAD Table').click();
 | 
			
		||||
 | 
			
		||||
      // Right-click the SWG treeitem context menu and click 'Remove' and confirm
 | 
			
		||||
      await page.getByRole('treeitem', { name: swg.name }).click({ button: 'right' });
 | 
			
		||||
      await page.getByRole('menuitem', { name: 'Remove' }).click();
 | 
			
		||||
      await page.getByRole('button', { name: 'OK', exact: true }).click();
 | 
			
		||||
 | 
			
		||||
      // Assert that the SWG is no longer in the tree and the table is empty
 | 
			
		||||
      await expect(page.getByRole('treeitem', { name: swg.name })).toBeHidden();
 | 
			
		||||
      await expect(page.getByLabel('lad row')).toHaveCount(0);
 | 
			
		||||
    }
 | 
			
		||||
  );
 | 
			
		||||
});
 | 
			
		||||
@@ -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))
 | 
			
		||||
    });
 | 
			
		||||
 
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 18 KiB  | 
| 
		 Before Width: | Height: | Size: 19 KiB  | 
| 
		 Before Width: | Height: | Size: 21 KiB  | 
| 
		 Before Width: | Height: | Size: 19 KiB  | 
@@ -122,4 +122,14 @@ test.describe('Reload action', () => {
 | 
			
		||||
    expect(fullReloadAlphaTelemetryValue).not.toEqual(afterReloadAlphaTelemetryValue);
 | 
			
		||||
    expect(fullReloadBetaTelemetryValue).not.toEqual(afterReloadBetaTelemetryValue);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  test('is disabled in Previews', async ({ page }) => {
 | 
			
		||||
    test.info().annotations.push({
 | 
			
		||||
      type: 'issue',
 | 
			
		||||
      description: 'https://github.com/nasa/openmct/issues/7638'
 | 
			
		||||
    });
 | 
			
		||||
    await page.getByLabel('Alpha Table Frame Controls').getByLabel('Large View').click();
 | 
			
		||||
    await page.getByLabel('Modal Overlay').getByLabel('More actions').click();
 | 
			
		||||
    await expect(page.getByLabel('Reload')).toBeHidden();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -69,5 +69,9 @@ test.describe('Preview mode', () => {
 | 
			
		||||
    await page.getByLabel('Overlay').getByLabel('More actions').click();
 | 
			
		||||
    await expect(page.getByLabel('Export Table Data')).toBeVisible();
 | 
			
		||||
    await expect(page.getByLabel('Export Marked Rows')).toBeVisible();
 | 
			
		||||
    await expect(page.getByLabel('Export Marked Rows')).toBeDisabled();
 | 
			
		||||
    await page.getByLabel('Pause').click();
 | 
			
		||||
    const tableWrapper = page.getByLabel('Preview Container').locator('div.c-table-wrapper');
 | 
			
		||||
    await expect(tableWrapper).toHaveClass(/is-paused/);
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
 
 | 
			
		||||
@@ -34,40 +34,62 @@ Make no assumptions about the order that elements appear in the DOM.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
import { expect, test } from '../../pluginFixtures.js';
 | 
			
		||||
test('Verify that My Items Tree appears @mobile', async ({ page, openmctConfig }) => {
 | 
			
		||||
  const { myItemsFolderName } = openmctConfig;
 | 
			
		||||
  //Go to baseURL
 | 
			
		||||
  await page.goto('./');
 | 
			
		||||
  //My Items to be visible
 | 
			
		||||
  await expect(page.getByRole('treeitem', { name: `${myItemsFolderName}` })).toBeVisible();
 | 
			
		||||
});
 | 
			
		||||
test('Verify that user can search @mobile', async ({ page }) => {
 | 
			
		||||
  //For now, this test is going to be hardcoded against './test-data/display_layout_with_child_layouts.json'
 | 
			
		||||
  await page.goto('./');
 | 
			
		||||
  await page.getByRole('searchbox', { name: 'Search Input' }).click();
 | 
			
		||||
  await page.getByRole('searchbox', { name: 'Search Input' }).fill('Parent Display Layout');
 | 
			
		||||
  //Search Results appear in search modal
 | 
			
		||||
  await expect(page.getByLabel('Object Results').getByText('Parent Display Layout')).toBeVisible();
 | 
			
		||||
  //Clicking on the search result takes you to the object
 | 
			
		||||
  await page.getByLabel('Object Results').getByText('Parent Display Layout').click();
 | 
			
		||||
  await expect(page.getByRole('main').getByText('Parent Display Layout')).toBeVisible();
 | 
			
		||||
});
 | 
			
		||||
test('Remove Object and confirmation dialog @mobile', async ({ page }) => {
 | 
			
		||||
  await page.goto('./');
 | 
			
		||||
  await page.getByRole('searchbox', { name: 'Search Input' }).click();
 | 
			
		||||
  await page.getByRole('searchbox', { name: 'Search Input' }).fill('Parent Display Layout');
 | 
			
		||||
  //Search Results appear in search modal
 | 
			
		||||
  //Clicking on the search result takes you to the object
 | 
			
		||||
  await page.getByLabel('Object Results').getByText('Parent Display Layout').click();
 | 
			
		||||
  await expect(page.getByRole('main').getByText('Parent Display Layout')).toBeVisible();
 | 
			
		||||
  //Verify both objects are in view
 | 
			
		||||
  await expect(await page.getByLabel('Child Layout 1 Layout')).toBeVisible();
 | 
			
		||||
  await expect(await page.getByLabel('Child Layout 2 Layout')).toBeVisible();
 | 
			
		||||
  //Remove First Object to bring up confirmation dialog
 | 
			
		||||
  await page.getByLabel('View menu items').nth(1).click();
 | 
			
		||||
  await page.getByLabel('Remove').click();
 | 
			
		||||
  await page.getByRole('button', { name: 'OK' }).click();
 | 
			
		||||
  //Verify that the object is removed
 | 
			
		||||
  await expect(await page.getByLabel('Child Layout 1 Layout')).toBeVisible();
 | 
			
		||||
  expect(await page.getByLabel('Child Layout 2 Layout').count()).toBe(0);
 | 
			
		||||
 | 
			
		||||
test.describe('Smoke tests for @mobile', () => {
 | 
			
		||||
  test.beforeEach(async ({ page }) => {
 | 
			
		||||
    //For now, this test is going to be hardcoded against './test-data/display_layout_with_child_layouts.json'
 | 
			
		||||
    await page.goto('./');
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  test('Verify that My Items Tree appears @mobile', async ({ page }) => {
 | 
			
		||||
    //My Items to be visible
 | 
			
		||||
    await expect(page.getByRole('treeitem', { name: 'My Items' })).toBeVisible();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  test('Verify that user can search @mobile', async ({ page }) => {
 | 
			
		||||
    await page.getByRole('searchbox', { name: 'Search Input' }).click();
 | 
			
		||||
    await page.getByRole('searchbox', { name: 'Search Input' }).fill('Parent Display Layout');
 | 
			
		||||
    //Search Results appear in search modal
 | 
			
		||||
    await expect(
 | 
			
		||||
      page.getByLabel('Object Results').getByText('Parent Display Layout')
 | 
			
		||||
    ).toBeVisible();
 | 
			
		||||
    //Clicking on the search result takes you to the object
 | 
			
		||||
    await page.getByLabel('Object Results').getByText('Parent Display Layout').click();
 | 
			
		||||
    await page.getByTitle('Collapse Browse Pane').click();
 | 
			
		||||
    await expect(page.getByRole('main').getByText('Parent Display Layout')).toBeVisible();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  test('Verify that user can change time conductor @mobile', async ({ page }) => {
 | 
			
		||||
    //Collapse Browse Pane to get more Time Conductor space
 | 
			
		||||
    await page.getByLabel('Collapse Browse Pane').click();
 | 
			
		||||
    //Open Time Conductor and change to Real Time Mode and set offset hour by 1 hour
 | 
			
		||||
    // Disabling line because we're intentionally obscuring the text
 | 
			
		||||
    // eslint-disable-next-line playwright/no-force-option
 | 
			
		||||
    await page.getByLabel('Time Conductor Mode').click({ force: true });
 | 
			
		||||
    await page.getByLabel('Time Conductor Mode Menu').click();
 | 
			
		||||
    await page.getByLabel('Real-Time').click();
 | 
			
		||||
    await page.getByLabel('Start offset hours').fill('01');
 | 
			
		||||
    await page.getByLabel('Submit time offsets').click();
 | 
			
		||||
    await expect(page.getByLabel('Start offset: 01:30:00')).toBeVisible();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  test('Remove Object and confirmation dialog @mobile', async ({ page }) => {
 | 
			
		||||
    await page.getByRole('searchbox', { name: 'Search Input' }).click();
 | 
			
		||||
    await page.getByRole('searchbox', { name: 'Search Input' }).fill('Parent Display Layout');
 | 
			
		||||
    //Search Results appear in search modal
 | 
			
		||||
    //Clicking on the search result takes you to the object
 | 
			
		||||
    await page.getByLabel('Object Results').getByText('Parent Display Layout').click();
 | 
			
		||||
    await page.getByTitle('Collapse Browse Pane').click();
 | 
			
		||||
    await expect(page.getByRole('main').getByText('Parent Display Layout')).toBeVisible();
 | 
			
		||||
    //Verify both objects are in view
 | 
			
		||||
    await expect(await page.getByLabel('Child Layout 1 Layout')).toBeVisible();
 | 
			
		||||
    await expect(await page.getByLabel('Child Layout 2 Layout')).toBeVisible();
 | 
			
		||||
    //Remove First Object to bring up confirmation dialog
 | 
			
		||||
    await page.getByLabel('View menu items').nth(1).click();
 | 
			
		||||
    await page.getByLabel('Remove').click();
 | 
			
		||||
    await page.getByRole('button', { name: 'OK' }).click();
 | 
			
		||||
    //Verify that the object is removed
 | 
			
		||||
    await expect(await page.getByLabel('Child Layout 1 Layout')).toBeVisible();
 | 
			
		||||
    expect(await page.getByLabel('Child Layout 2 Layout').count()).toBe(0);
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
  });
 | 
			
		||||
 
 | 
			
		||||
@@ -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 }) => {
 | 
			
		||||
 
 | 
			
		||||
@@ -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', {
 | 
			
		||||
 
 | 
			
		||||
@@ -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',
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										116
									
								
								e2e/tests/visual-a11y/components/timeConductor.visual.spec.js
									
									
									
									
									
										Normal 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');
 | 
			
		||||
    }
 | 
			
		||||
  );
 | 
			
		||||
});
 | 
			
		||||
| 
		 After Width: | Height: | Size: 33 KiB  | 
| 
		 After Width: | Height: | Size: 34 KiB  | 
| 
		 After Width: | Height: | Size: 29 KiB  | 
@@ -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();
 | 
			
		||||
 
 | 
			
		||||
@@ -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();
 | 
			
		||||
 
 | 
			
		||||
@@ -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 }) => {
 | 
			
		||||
 
 | 
			
		||||
@@ -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');
 | 
			
		||||
  });
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
 
 | 
			
		||||
@@ -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',
 | 
			
		||||
 
 | 
			
		||||
@@ -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, {
 | 
			
		||||
 
 | 
			
		||||
@@ -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'
 | 
			
		||||
 
 | 
			
		||||
@@ -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 }) => {
 | 
			
		||||
 
 | 
			
		||||
@@ -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})`);
 | 
			
		||||
 
 | 
			
		||||
@@ -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',
 | 
			
		||||
 
 | 
			
		||||
@@ -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, {
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										18
									
								
								openmct.js
									
									
									
									
									
								
							
							
						
						@@ -30,23 +30,22 @@ if (document.currentScript) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @typedef {object} BuildInfo
 | 
			
		||||
 * @typedef {Object} BuildInfo
 | 
			
		||||
 * @property {string} version
 | 
			
		||||
 * @property {string} buildDate
 | 
			
		||||
 * @property {string} revision
 | 
			
		||||
 * @property {string} branch
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @typedef {object} OpenMCT
 | 
			
		||||
 * @typedef {Object} OpenMCT
 | 
			
		||||
 * @property {BuildInfo} buildInfo
 | 
			
		||||
 * @property {*} selection
 | 
			
		||||
 * @property {import('./src/selection/Selection').default} selection
 | 
			
		||||
 * @property {import('./src/api/time/TimeAPI').default} time
 | 
			
		||||
 * @property {import('./src/api/composition/CompositionAPI').default} composition
 | 
			
		||||
 * @property {*} objectViews
 | 
			
		||||
 * @property {*} inspectorViews
 | 
			
		||||
 * @property {*} propertyEditors
 | 
			
		||||
 * @property {*} toolbars
 | 
			
		||||
 * @property {import('./src/ui/registries/ViewRegistry').default} objectViews
 | 
			
		||||
 * @property {import('./src/ui/registries/InspectorViewRegistry').default} inspectorViews
 | 
			
		||||
 * @property {import('./src/ui/registries/ViewRegistry').default} propertyEditors
 | 
			
		||||
 * @property {import('./src/ui/registries/ToolbarRegistry').default} toolbars
 | 
			
		||||
 * @property {import('./src/api/types/TypeRegistry').default} types
 | 
			
		||||
 * @property {import('./src/api/objects/ObjectAPI').default} objects
 | 
			
		||||
 * @property {import('./src/api/telemetry/TelemetryAPI').default} telemetry
 | 
			
		||||
@@ -59,7 +58,7 @@ if (document.currentScript) {
 | 
			
		||||
 * @property {import('./src/api/menu/MenuAPI').default} menus
 | 
			
		||||
 * @property {import('./src/api/actions/ActionsAPI').default} actions
 | 
			
		||||
 * @property {import('./src/api/status/StatusAPI').default} status
 | 
			
		||||
 * @property {*} priority
 | 
			
		||||
 * @property {import('./src/api/priority/PriorityAPI').default} priority
 | 
			
		||||
 * @property {import('./src/ui/router/ApplicationRouter')} router
 | 
			
		||||
 * @property {import('./src/api/faultmanagement/FaultManagementAPI').default} faults
 | 
			
		||||
 * @property {import('./src/api/forms/FormsAPI').default} forms
 | 
			
		||||
@@ -74,7 +73,6 @@ if (document.currentScript) {
 | 
			
		||||
 * @property {OpenMCTPlugin[]} plugins
 | 
			
		||||
 * @property {OpenMCTComponent[]} components
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import { MCT } from './src/MCT.js';
 | 
			
		||||
 | 
			
		||||
/** @type {OpenMCT} */
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										461
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						@@ -14,7 +14,7 @@
 | 
			
		||||
        "@braintree/sanitize-url": "6.0.4",
 | 
			
		||||
        "@percy/cli": "1.27.4",
 | 
			
		||||
        "@percy/playwright": "1.0.4",
 | 
			
		||||
        "@playwright/test": "1.39.0",
 | 
			
		||||
        "@playwright/test": "1.42.1",
 | 
			
		||||
        "@types/d3-axis": "3.0.6",
 | 
			
		||||
        "@types/d3-scale": "4.0.8",
 | 
			
		||||
        "@types/d3-selection": "3.0.10",
 | 
			
		||||
@@ -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",
 | 
			
		||||
@@ -1548,12 +1549,12 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@playwright/test": {
 | 
			
		||||
      "version": "1.39.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.39.0.tgz",
 | 
			
		||||
      "integrity": "sha512-3u1iFqgzl7zr004bGPYiN/5EZpRUSFddQBra8Rqll5N0/vfpqlP9I9EXqAoGacuAbX6c9Ulg/Cjqglp5VkK6UQ==",
 | 
			
		||||
      "version": "1.42.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.42.1.tgz",
 | 
			
		||||
      "integrity": "sha512-Gq9rmS54mjBL/7/MvBaNOBwbfnh7beHvS6oS4srqXFcQHpQCV1+c8JXWE8VLPyRDhgS3H8x8A7hztqI9VnwrAQ==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "playwright": "1.39.0"
 | 
			
		||||
        "playwright": "1.42.1"
 | 
			
		||||
      },
 | 
			
		||||
      "bin": {
 | 
			
		||||
        "playwright": "cli.js"
 | 
			
		||||
@@ -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",
 | 
			
		||||
@@ -5635,9 +5441,9 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/express": {
 | 
			
		||||
      "version": "4.18.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/express/-/express-4.18.3.tgz",
 | 
			
		||||
      "integrity": "sha512-6VyCijWQ+9O7WuVMTRBTl+cjNNIzD5cY5mQ1WM8r/LEkI2u8EYpOotESNwzNlyCn3g+dmjKYI6BmNneSr/FSRw==",
 | 
			
		||||
      "version": "4.19.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
 | 
			
		||||
      "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "accepts": "~1.3.8",
 | 
			
		||||
@@ -5645,7 +5451,7 @@
 | 
			
		||||
        "body-parser": "1.20.2",
 | 
			
		||||
        "content-disposition": "0.5.4",
 | 
			
		||||
        "content-type": "~1.0.4",
 | 
			
		||||
        "cookie": "0.5.0",
 | 
			
		||||
        "cookie": "0.6.0",
 | 
			
		||||
        "cookie-signature": "1.0.6",
 | 
			
		||||
        "debug": "2.6.9",
 | 
			
		||||
        "depd": "2.0.0",
 | 
			
		||||
@@ -5677,9 +5483,9 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/express/node_modules/cookie": {
 | 
			
		||||
      "version": "0.5.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
 | 
			
		||||
      "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
 | 
			
		||||
      "version": "0.6.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
 | 
			
		||||
      "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">= 0.6"
 | 
			
		||||
@@ -9026,12 +8832,12 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/playwright": {
 | 
			
		||||
      "version": "1.39.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.39.0.tgz",
 | 
			
		||||
      "integrity": "sha512-naE5QT11uC/Oiq0BwZ50gDmy8c8WLPRTEWuSSFVG2egBka/1qMoSqYQcROMT9zLwJ86oPofcTH2jBY/5wWOgIw==",
 | 
			
		||||
      "version": "1.42.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.42.1.tgz",
 | 
			
		||||
      "integrity": "sha512-PgwB03s2DZBcNRoW+1w9E+VkLBxweib6KTXM0M3tkiT4jVxKSi6PmVJ591J+0u10LUrgxB7dLRbiJqO5s2QPMg==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "playwright-core": "1.39.0"
 | 
			
		||||
        "playwright-core": "1.42.1"
 | 
			
		||||
      },
 | 
			
		||||
      "bin": {
 | 
			
		||||
        "playwright": "cli.js"
 | 
			
		||||
@@ -9048,7 +8854,6 @@
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.42.1.tgz",
 | 
			
		||||
      "integrity": "sha512-mxz6zclokgrke9p1vtdy/COWBH+eOZgYUVVU34C73M+4j4HLlQJHtfcqiqqxpP0o8HhMkflvfbquLX5dg6wlfA==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "peer": true,
 | 
			
		||||
      "bin": {
 | 
			
		||||
        "playwright-core": "cli.js"
 | 
			
		||||
      },
 | 
			
		||||
@@ -9070,18 +8875,6 @@
 | 
			
		||||
        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/playwright/node_modules/playwright-core": {
 | 
			
		||||
      "version": "1.39.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.39.0.tgz",
 | 
			
		||||
      "integrity": "sha512-+k4pdZgs1qiM+OUkSjx96YiKsXsmb59evFoqv8SKO067qBA+Z2s/dCzJij/ZhdQcs2zlTAgRKfeiiLm8PQ2qvw==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "bin": {
 | 
			
		||||
        "playwright-core": "cli.js"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=16"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/plotly.js-basic-dist-min": {
 | 
			
		||||
      "version": "2.29.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/plotly.js-basic-dist-min/-/plotly.js-basic-dist-min-2.29.1.tgz",
 | 
			
		||||
@@ -9268,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",
 | 
			
		||||
@@ -11648,14 +11642,15 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/webpack-dev-middleware": {
 | 
			
		||||
      "version": "7.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.0.0.tgz",
 | 
			
		||||
      "integrity": "sha512-tZ5hqsWwww/8DislmrzXE3x+4f+v10H1z57mA2dWFrILb4i3xX+dPhTkcdR0DLyQztrhF2AUmO5nN085UYjd/Q==",
 | 
			
		||||
      "version": "7.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-NmRVq4AvRQs66dFWyDR4GsFDJggtSi2Yn38MXLk0nffgF9n/AIP4TFBg2TQKYaRAN4sHuKOTiz9BnNCENDLEVA==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "colorette": "^2.0.10",
 | 
			
		||||
        "memfs": "^4.6.0",
 | 
			
		||||
        "mime-types": "^2.1.31",
 | 
			
		||||
        "on-finished": "^2.4.1",
 | 
			
		||||
        "range-parser": "^1.2.1",
 | 
			
		||||
        "schema-utils": "^4.0.0"
 | 
			
		||||
      },
 | 
			
		||||
 
 | 
			
		||||
@@ -10,14 +10,15 @@
 | 
			
		||||
    "@braintree/sanitize-url": "6.0.4",
 | 
			
		||||
    "@percy/cli": "1.27.4",
 | 
			
		||||
    "@percy/playwright": "1.0.4",
 | 
			
		||||
    "@playwright/test": "1.39.0",
 | 
			
		||||
    "@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",
 | 
			
		||||
@@ -156,4 +157,4 @@
 | 
			
		||||
  "keywords": [
 | 
			
		||||
    "nasa"
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
@@ -74,6 +74,7 @@ import Browse from './ui/router/Browse.js';
 | 
			
		||||
 * or registering extensions before the application is started.
 | 
			
		||||
 * @constructor
 | 
			
		||||
 * @memberof module:openmct
 | 
			
		||||
 * @extends EventEmitter
 | 
			
		||||
 */
 | 
			
		||||
export class MCT extends EventEmitter {
 | 
			
		||||
  constructor() {
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,7 @@
 | 
			
		||||
let brandingOptions = {};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @typedef {object} BrandingOptions
 | 
			
		||||
 * @typedef {Object} BrandingOptions
 | 
			
		||||
 * @property {string} smallLogoImage URL to the image to use as the applications logo.
 | 
			
		||||
 * This logo will appear on every screen and when clicked will launch the about dialog.
 | 
			
		||||
 * @property {string} aboutHtml Custom content for the about screen. When defined the
 | 
			
		||||
 
 | 
			
		||||
@@ -26,12 +26,12 @@ import { v4 as uuid } from 'uuid';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @readonly
 | 
			
		||||
 * @enum {String} AnnotationType
 | 
			
		||||
 * @property {String} NOTEBOOK The notebook annotation type
 | 
			
		||||
 * @property {String} GEOSPATIAL The geospatial annotation type
 | 
			
		||||
 * @property {String} PIXEL_SPATIAL The pixel-spatial annotation type
 | 
			
		||||
 * @property {String} TEMPORAL The temporal annotation type
 | 
			
		||||
 * @property {String} PLOT_SPATIAL The plot-spatial annotation type
 | 
			
		||||
 * @enum {string} AnnotationType
 | 
			
		||||
 * @property {string} NOTEBOOK The notebook annotation type
 | 
			
		||||
 * @property {string} GEOSPATIAL The geospatial annotation type
 | 
			
		||||
 * @property {string} PIXEL_SPATIAL The pixel-spatial annotation type
 | 
			
		||||
 * @property {string} TEMPORAL The temporal annotation type
 | 
			
		||||
 * @property {string} PLOT_SPATIAL The plot-spatial annotation type
 | 
			
		||||
 */
 | 
			
		||||
const ANNOTATION_TYPES = Object.freeze({
 | 
			
		||||
  NOTEBOOK: 'NOTEBOOK',
 | 
			
		||||
@@ -47,9 +47,9 @@ const ANNOTATION_LAST_CREATED = 'annotationLastCreated';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @typedef {Object} Tag
 | 
			
		||||
 * @property {String} key a unique identifier for the tag
 | 
			
		||||
 * @property {String} backgroundColor eg. "#cc0000"
 | 
			
		||||
 * @property {String} foregroundColor eg. "#ffffff"
 | 
			
		||||
 * @property {string} key a unique identifier for the tag
 | 
			
		||||
 * @property {string} backgroundColor eg. "#cc0000"
 | 
			
		||||
 * @property {string} foregroundColor eg. "#ffffff"
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -112,11 +112,11 @@ export default class AnnotationAPI extends EventEmitter {
 | 
			
		||||
  /**
 | 
			
		||||
   * Creates an annotation on a given domain object (e.g., a plot) and a set of targets (e.g., telemetry objects)
 | 
			
		||||
   * @typedef {Object} CreateAnnotationOptions
 | 
			
		||||
   * @property {String} name a name for the new annotation (e.g., "Plot annnotation")
 | 
			
		||||
   * @property {string} name a name for the new annotation (e.g., "Plot annnotation")
 | 
			
		||||
   * @property {DomainObject} domainObject the domain object this annotation was created with
 | 
			
		||||
   * @property {ANNOTATION_TYPES} annotationType the type of annotation to create (e.g., PLOT_SPATIAL)
 | 
			
		||||
   * @property {Tag[]} tags tags to add to the annotation, e.g., SCIENCE for science related annotations
 | 
			
		||||
   * @property {String} contentText Some text to add to the annotation, e.g. ("This annotation is about science")
 | 
			
		||||
   * @property {string} contentText Some text to add to the annotation, e.g. ("This annotation is about science")
 | 
			
		||||
   * @property {Array<Object>} targets The targets ID keystrings and their specific properties.
 | 
			
		||||
   * For plots, this will be a bounding box, e.g.: {keyString: "d8385009-789d-457b-acc7-d50ba2fd55ea", maxY: 100, minY: 0, maxX: 100, minX: 0}
 | 
			
		||||
   * For notebooks, this will be an entry ID, e.g.: {entryId: "entry-ecb158f5-d23c-45e1-a704-649b382622ba"}
 | 
			
		||||
@@ -208,7 +208,7 @@ export default class AnnotationAPI extends EventEmitter {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @method defineTag
 | 
			
		||||
   * @param {String} key a unique identifier for the tag
 | 
			
		||||
   * @param {string} key a unique identifier for the tag
 | 
			
		||||
   * @param {Tag} tagsDefinition the definition of the tag to add
 | 
			
		||||
   */
 | 
			
		||||
  defineTag(tagKey, tagsDefinition) {
 | 
			
		||||
@@ -217,7 +217,7 @@ export default class AnnotationAPI extends EventEmitter {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @method setNamespaceToSaveAnnotations
 | 
			
		||||
   * @param {String} namespace the namespace to save new annotations to
 | 
			
		||||
   * @param {string} namespace the namespace to save new annotations to
 | 
			
		||||
   */
 | 
			
		||||
  setNamespaceToSaveAnnotations(namespace) {
 | 
			
		||||
    this.namespaceToSaveAnnotations = namespace;
 | 
			
		||||
@@ -226,7 +226,7 @@ export default class AnnotationAPI extends EventEmitter {
 | 
			
		||||
  /**
 | 
			
		||||
   * @method isAnnotation
 | 
			
		||||
   * @param {DomainObject} domainObject the domainObject in question
 | 
			
		||||
   * @returns {Boolean} Returns true if the domain object is an annotation
 | 
			
		||||
   * @returns {boolean} Returns true if the domain object is an annotation
 | 
			
		||||
   */
 | 
			
		||||
  isAnnotation(domainObject) {
 | 
			
		||||
    return domainObject && domainObject.type === ANNOTATION_TYPE;
 | 
			
		||||
@@ -442,7 +442,7 @@ export default class AnnotationAPI extends EventEmitter {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @method searchForTags
 | 
			
		||||
   * @param {String} query A query to match against tags. E.g., "dr" will match the tags "drilling" and "driving"
 | 
			
		||||
   * @param {string} query A query to match against tags. E.g., "dr" will match the tags "drilling" and "driving"
 | 
			
		||||
   * @param {Object} [abortController] An optional abort method to stop the query
 | 
			
		||||
   * @returns {Promise} returns a model of matching tags with their target domain objects attached
 | 
			
		||||
   */
 | 
			
		||||
 
 | 
			
		||||
@@ -33,7 +33,7 @@
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @typedef {object} ListenerMap
 | 
			
		||||
 * @typedef {Object} ListenerMap
 | 
			
		||||
 * @property {Array.<any>} add
 | 
			
		||||
 * @property {Array.<any>} remove
 | 
			
		||||
 * @property {Array.<any>} load
 | 
			
		||||
@@ -271,7 +271,7 @@ export default class CompositionCollection {
 | 
			
		||||
  /**
 | 
			
		||||
   * Handle reorder from provider.
 | 
			
		||||
   * @private
 | 
			
		||||
   * @param {object} reorderMap
 | 
			
		||||
   * @param {Object} reorderMap
 | 
			
		||||
   */
 | 
			
		||||
  #onProviderReorder(reorderMap) {
 | 
			
		||||
    this.#emit('reorder', reorderMap);
 | 
			
		||||
 
 | 
			
		||||
@@ -88,21 +88,21 @@ export default class FaultManagementAPI {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @typedef {object} TriggerValueInfo
 | 
			
		||||
 * @typedef {Object} TriggerValueInfo
 | 
			
		||||
 * @property {number} value
 | 
			
		||||
 * @property {string} rangeCondition
 | 
			
		||||
 * @property {string} monitoringResult
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @typedef {object} CurrentValueInfo
 | 
			
		||||
 * @typedef {Object} CurrentValueInfo
 | 
			
		||||
 * @property {number} value
 | 
			
		||||
 * @property {string} rangeCondition
 | 
			
		||||
 * @property {string} monitoringResult
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @typedef {object} Fault
 | 
			
		||||
 * @typedef {Object} Fault
 | 
			
		||||
 * @property {boolean} acknowledged
 | 
			
		||||
 * @property {CurrentValueInfo} currentValueInfo
 | 
			
		||||
 * @property {string} id
 | 
			
		||||
@@ -117,7 +117,7 @@ export default class FaultManagementAPI {
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @typedef {object} FaultAPIResponse
 | 
			
		||||
 * @typedef {Object} FaultAPIResponse
 | 
			
		||||
 * @property {string} type
 | 
			
		||||
 * @property {Fault} fault
 | 
			
		||||
 */
 | 
			
		||||
 
 | 
			
		||||
@@ -48,7 +48,7 @@ export default class FormsAPI {
 | 
			
		||||
   *      this formControlViewProvider is used inside form overlay to show/render a form row
 | 
			
		||||
   *
 | 
			
		||||
   * @public
 | 
			
		||||
   * @param {String} controlName a form structure, array of section
 | 
			
		||||
   * @param {string} controlName a form structure, array of section
 | 
			
		||||
   * @param {ControlViewProvider} controlViewProvider
 | 
			
		||||
   */
 | 
			
		||||
  addNewFormControl(controlName, controlViewProvider) {
 | 
			
		||||
@@ -59,7 +59,7 @@ export default class FormsAPI {
 | 
			
		||||
   * Get a ControlViewProvider for a given/stored form controlName
 | 
			
		||||
   *
 | 
			
		||||
   * @public
 | 
			
		||||
   * @param {String} controlName a form structure, array of section
 | 
			
		||||
   * @param {string} controlName a form structure, array of section
 | 
			
		||||
   * @return {ControlViewProvider}
 | 
			
		||||
   */
 | 
			
		||||
  getFormControl(controlName) {
 | 
			
		||||
@@ -69,7 +69,7 @@ export default class FormsAPI {
 | 
			
		||||
  /**
 | 
			
		||||
   * Section definition for formStructure
 | 
			
		||||
   * @typedef Section
 | 
			
		||||
   * @property {object} name Name of the section to display on Form
 | 
			
		||||
   * @property {Object} name Name of the section to display on Form
 | 
			
		||||
   * @property {string} cssClass class name for styling section
 | 
			
		||||
   * @property {array<Row>} rows collection of rows inside a section
 | 
			
		||||
   */
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,7 @@ import Menu, { MENU_PLACEMENT } from './menu.js';
 | 
			
		||||
/**
 | 
			
		||||
 * Popup Menu options
 | 
			
		||||
 * @typedef {Object} MenuOptions
 | 
			
		||||
 * @property {String} menuClass Class for popup menu
 | 
			
		||||
 * @property {string} menuClass Class for popup menu
 | 
			
		||||
 * @property {MENU_PLACEMENT} placement Placement for menu relative to click
 | 
			
		||||
 * @property {Function} onDestroy callback function: invoked when menu is destroyed
 | 
			
		||||
 */
 | 
			
		||||
@@ -33,10 +33,10 @@ import Menu, { MENU_PLACEMENT } from './menu.js';
 | 
			
		||||
/**
 | 
			
		||||
 * Popup Menu Item/action
 | 
			
		||||
 * @typedef {Object} Action
 | 
			
		||||
 * @property {String} cssClass Class for menu item
 | 
			
		||||
 * @property {Boolean} isDisabled adds disable class if true
 | 
			
		||||
 * @property {String} name Menu item text
 | 
			
		||||
 * @property {String} description Menu item description
 | 
			
		||||
 * @property {string} cssClass Class for menu item
 | 
			
		||||
 * @property {boolean} isDisabled adds disable class if true
 | 
			
		||||
 * @property {string} name Menu item text
 | 
			
		||||
 * @property {string} description Menu item description
 | 
			
		||||
 * @property {Function} onItemClicked callback function: invoked when item is clicked
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -34,7 +34,7 @@ import EventEmitter from 'eventemitter3';
 | 
			
		||||
import moment from 'moment';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @typedef {object} NotificationProperties
 | 
			
		||||
 * @typedef {Object} NotificationProperties
 | 
			
		||||
 * @property {function} dismiss Dismiss the notification
 | 
			
		||||
 * @property {NotificationModel} model The Notification model
 | 
			
		||||
 * @property {(progressPerc: number, progressText: string) => void} [progress] Update the progress of the notification
 | 
			
		||||
@@ -45,14 +45,14 @@ import moment from 'moment';
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @typedef {object} NotificationLink
 | 
			
		||||
 * @typedef {Object} NotificationLink
 | 
			
		||||
 * @property {function} onClick The function to be called when the link is clicked
 | 
			
		||||
 * @property {string} cssClass A CSS class name to style the link
 | 
			
		||||
 * @property {string} text The text to be displayed for the link
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @typedef {object} NotificationOptions
 | 
			
		||||
 * @typedef {Object} NotificationOptions
 | 
			
		||||
 * @property {number} [autoDismissTimeout] Milliseconds to wait before automatically dismissing the notification
 | 
			
		||||
 * @property {boolean} [minimized] Allows for a notification to be minimized into the indicator by default
 | 
			
		||||
 * @property {NotificationLink} [link] A link for the notification
 | 
			
		||||
@@ -66,7 +66,7 @@ import moment from 'moment';
 | 
			
		||||
 * and then minimized to a banner notification if needed, or vice-versa.
 | 
			
		||||
 *
 | 
			
		||||
 * @see DialogModel
 | 
			
		||||
 * @typedef {object} NotificationModel
 | 
			
		||||
 * @typedef {Object} NotificationModel
 | 
			
		||||
 * @property {string} message The message to be displayed by the notification
 | 
			
		||||
 * @property {number | 'unknown'} [progress] The progress of some ongoing task. Should be a number between 0 and 100, or
 | 
			
		||||
 * with the string literal 'unknown'.
 | 
			
		||||
 
 | 
			
		||||
@@ -391,7 +391,7 @@ class InMemorySearchProvider {
 | 
			
		||||
   * Dispatch a search query to the worker and return a queryId.
 | 
			
		||||
   *
 | 
			
		||||
   * @private
 | 
			
		||||
   * @returns {String} a unique query Id for the query.
 | 
			
		||||
   * @returns {string} a unique query Id for the query.
 | 
			
		||||
   */
 | 
			
		||||
  #dispatchSearchToWorker({ queryId, searchType, query, maxResults }) {
 | 
			
		||||
    const message = {
 | 
			
		||||
 
 | 
			
		||||
@@ -34,7 +34,7 @@ import Transaction from './Transaction.js';
 | 
			
		||||
/**
 | 
			
		||||
 * Uniquely identifies a domain object.
 | 
			
		||||
 *
 | 
			
		||||
 * @typedef {object} Identifier
 | 
			
		||||
 * @typedef {Object} Identifier
 | 
			
		||||
 * @property {string} namespace the namespace to/from which this domain
 | 
			
		||||
 *           object should be loaded/stored.
 | 
			
		||||
 * @property {string} key a unique identifier for the domain object
 | 
			
		||||
@@ -51,7 +51,7 @@ import Transaction from './Transaction.js';
 | 
			
		||||
 * A few common properties are defined for domain objects. Beyond these,
 | 
			
		||||
 * individual types of domain objects may add more as they see fit.
 | 
			
		||||
 *
 | 
			
		||||
 * @typedef {object} DomainObject
 | 
			
		||||
 * @typedef {Object} DomainObject
 | 
			
		||||
 * @property {Identifier} identifier a key/namespace pair which
 | 
			
		||||
 *           uniquely identifies this domain object
 | 
			
		||||
 * @property {string} type the type of domain object
 | 
			
		||||
@@ -572,8 +572,8 @@ export default class ObjectAPI {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Return path of telemetry objects in the object composition
 | 
			
		||||
   * @param {object} identifier the identifier for the domain object to query for
 | 
			
		||||
   * @param {object} [telemetryIdentifier] the specific identifier for the telemetry
 | 
			
		||||
   * @param {Object} identifier the identifier for the domain object to query for
 | 
			
		||||
   * @param {Object} [telemetryIdentifier] the specific identifier for the telemetry
 | 
			
		||||
   *  to look for in the composition, uses first object in composition otherwise
 | 
			
		||||
   * @returns {Array} path of telemetry object in object composition
 | 
			
		||||
   */
 | 
			
		||||
 
 | 
			
		||||
@@ -107,7 +107,7 @@ class OverlayAPI {
 | 
			
		||||
   * displaying messages that require the user's
 | 
			
		||||
   * immediate attention.
 | 
			
		||||
   * @param {model} options defines options for the dialog
 | 
			
		||||
   * @returns {object} with an object with a dismiss function that can be called from the calling code
 | 
			
		||||
   * @returns {Object} with an object with a dismiss function that can be called from the calling code
 | 
			
		||||
   * to dismiss/destroy the dialog
 | 
			
		||||
   *
 | 
			
		||||
   * A description of the model options that may be passed to the
 | 
			
		||||
@@ -134,7 +134,7 @@ class OverlayAPI {
 | 
			
		||||
   * Displays a blocking (modal) progress dialog. This dialog can be used for
 | 
			
		||||
   * displaying messages that require the user's attention, and show progress
 | 
			
		||||
   * @param {model} options defines options for the dialog
 | 
			
		||||
   * @returns {object} with an object with a dismiss function that can be called from the calling code
 | 
			
		||||
   * @returns {Object} with an object with a dismiss function that can be called from the calling code
 | 
			
		||||
   * to dismiss/destroy the dialog and an updateProgress function that takes progressPercentage(Number 0-100)
 | 
			
		||||
   * and progressText (string)
 | 
			
		||||
   *
 | 
			
		||||
 
 | 
			
		||||
@@ -156,7 +156,7 @@ class BatchingWebSocket extends EventTarget {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {Number} maxBatchSize the maximum length of a batch of messages. For example,
 | 
			
		||||
   * @param {number} maxBatchSize the maximum length of a batch of messages. For example,
 | 
			
		||||
   * the maximum number of telemetry values to batch before dropping them
 | 
			
		||||
   * Note that this is a fail-safe that is only invoked if performance drops to the
 | 
			
		||||
   * point where Open MCT cannot keep up with the amount of telemetry it is receiving.
 | 
			
		||||
 
 | 
			
		||||
@@ -38,19 +38,19 @@ import TelemetryValueFormatter from './TelemetryValueFormatter.js';
 | 
			
		||||
 * Describes and bounds requests for telemetry data.
 | 
			
		||||
 *
 | 
			
		||||
 * @typedef TelemetryRequestOptions
 | 
			
		||||
 * @property {String} [sort] the key of the property to sort by. This may
 | 
			
		||||
 * @property {string} [sort] the key of the property to sort by. This may
 | 
			
		||||
 *           be prefixed with a "+" or a "-" sign to sort in ascending
 | 
			
		||||
 *           or descending order respectively. If no prefix is present,
 | 
			
		||||
 *           ascending order will be used.
 | 
			
		||||
 * @property {Number} [start] the lower bound for values of the sorting property
 | 
			
		||||
 * @property {Number} [end] the upper bound for values of the sorting property
 | 
			
		||||
 * @property {String} [strategy] symbolic identifier for strategies
 | 
			
		||||
 * @property {number} [start] the lower bound for values of the sorting property
 | 
			
		||||
 * @property {number} [end] the upper bound for values of the sorting property
 | 
			
		||||
 * @property {string} [strategy] symbolic identifier for strategies
 | 
			
		||||
 *           (such as `latest` or `minmax`) which may be recognized by providers;
 | 
			
		||||
 *           these will be tried in order until an appropriate provider
 | 
			
		||||
 *           is found
 | 
			
		||||
 * @property {AbortController} [signal] an AbortController which can be used
 | 
			
		||||
 *           to cancel a telemetry request
 | 
			
		||||
 * @property {String} [domain] the domain key of the request
 | 
			
		||||
 * @property {string} [domain] the domain key of the request
 | 
			
		||||
 * @property {TimeContext} [timeContext] the time context to use for this request
 | 
			
		||||
 * @memberof module:openmct.TelemetryAPI~
 | 
			
		||||
 */
 | 
			
		||||
@@ -59,7 +59,7 @@ import TelemetryValueFormatter from './TelemetryValueFormatter.js';
 | 
			
		||||
 * Describes and bounds requests for telemetry data.
 | 
			
		||||
 *
 | 
			
		||||
 * @typedef TelemetrySubscriptionOptions
 | 
			
		||||
 * @property {String} [strategy] symbolic identifier directing providers on how
 | 
			
		||||
 * @property {string} [strategy] symbolic identifier directing providers on how
 | 
			
		||||
 * to handle telemetry subscriptions. The default behavior is 'latest' which will
 | 
			
		||||
 * always return a single telemetry value with each callback, and in the event
 | 
			
		||||
 * of throttling will always prioritize the latest data, meaning intermediate
 | 
			
		||||
@@ -936,7 +936,7 @@ export default class TelemetryAPI {
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @typedef {object} LimitsResponseObject
 | 
			
		||||
 * @typedef {Object} LimitsResponseObject
 | 
			
		||||
 * @memberof {module:openmct.TelemetryAPI~}
 | 
			
		||||
 * @property {LimitDefinition} limitLevel the level name and it's limit definition
 | 
			
		||||
 * @example {
 | 
			
		||||
@@ -966,7 +966,7 @@ export default class TelemetryAPI {
 | 
			
		||||
 * @typedef LimitDefinitionValue
 | 
			
		||||
 * @memberof {module:openmct.TelemetryAPI~}
 | 
			
		||||
 * @property {string} color color to represent this limit
 | 
			
		||||
 * @property {Number} value the limit value
 | 
			
		||||
 * @property {number} value the limit value
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -1050,9 +1050,9 @@ export default class TelemetryAPI {
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @typedef {object} StalenessResponseObject
 | 
			
		||||
 * @property {Boolean} isStale boolean representing the staleness state
 | 
			
		||||
 * @property {Number} timestamp Unix timestamp in milliseconds
 | 
			
		||||
 * @typedef {Object} StalenessResponseObject
 | 
			
		||||
 * @property {boolean} isStale boolean representing the staleness state
 | 
			
		||||
 * @property {number} timestamp Unix timestamp in milliseconds
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 
 | 
			
		||||
@@ -55,7 +55,7 @@ export default function installWorker() {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Establish a new WebSocket connection to the given URL
 | 
			
		||||
     * @param {String} url
 | 
			
		||||
     * @param {string} url
 | 
			
		||||
     */
 | 
			
		||||
    connect(url) {
 | 
			
		||||
      this.#wsUrl = url;
 | 
			
		||||
 
 | 
			
		||||
@@ -62,7 +62,7 @@ class TimeAPI extends GlobalTimeContext {
 | 
			
		||||
   * another time system might be "sols" for a Martian mission. TimeSystems do
 | 
			
		||||
   * not address the issue of converting between time systems.
 | 
			
		||||
   *
 | 
			
		||||
   * @typedef {object} TimeSystem
 | 
			
		||||
   * @typedef {Object} TimeSystem
 | 
			
		||||
   * @property {string} key A unique identifier
 | 
			
		||||
   * @property {string} name A human-readable descriptor
 | 
			
		||||
   * @property {string} [cssClass] Specify a css class defining an icon for
 | 
			
		||||
@@ -95,7 +95,7 @@ class TimeAPI extends GlobalTimeContext {
 | 
			
		||||
   * Clocks provide a timing source that is used to
 | 
			
		||||
   * automatically update the time bounds of the data displayed in Open MCT.
 | 
			
		||||
   *
 | 
			
		||||
   * @typedef {object} Clock
 | 
			
		||||
   * @typedef {Object} Clock
 | 
			
		||||
   * @memberof openmct.timeAPI
 | 
			
		||||
   * @property {string} key A unique identifier
 | 
			
		||||
   * @property {string} name A human-readable name. The name will be used to
 | 
			
		||||
 
 | 
			
		||||
@@ -122,7 +122,7 @@ class TimeContext extends EventEmitter {
 | 
			
		||||
   * Clock offsets are used to calculate temporal bounds when the system is
 | 
			
		||||
   * ticking on a clock source.
 | 
			
		||||
   *
 | 
			
		||||
   * @typedef {object} ValidationResult
 | 
			
		||||
   * @typedef {Object} ValidationResult
 | 
			
		||||
   * @property {boolean} valid Result of the validation - true or false.
 | 
			
		||||
   * @property {string} message An error message if valid is false.
 | 
			
		||||
   */
 | 
			
		||||
@@ -241,7 +241,7 @@ class TimeContext extends EventEmitter {
 | 
			
		||||
   * Clock offsets are used to calculate temporal bounds when the system is
 | 
			
		||||
   * ticking on a clock source.
 | 
			
		||||
   *
 | 
			
		||||
   * @typedef {object} ClockOffsets
 | 
			
		||||
   * @typedef {Object} ClockOffsets
 | 
			
		||||
   * @property {number} start A time span relative to the current value of the
 | 
			
		||||
   * ticking clock, from which start bounds will be calculated. This value must
 | 
			
		||||
   * be < 0. When a clock is active, bounds will be calculated automatically
 | 
			
		||||
 
 | 
			
		||||
@@ -24,12 +24,12 @@ import Tooltip from './ToolTip.js';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @readonly
 | 
			
		||||
 * @enum {String} TooltipLocation
 | 
			
		||||
 * @property {String} ABOVE The string for locating tooltips above an element
 | 
			
		||||
 * @property {String} BELOW The string for locating tooltips below an element
 | 
			
		||||
 * @property {String} RIGHT The pixel-spatial annotation type
 | 
			
		||||
 * @property {String} LEFT The temporal annotation type
 | 
			
		||||
 * @property {String} CENTER The plot-spatial annotation type
 | 
			
		||||
 * @enum {string} TooltipLocation
 | 
			
		||||
 * @property {string} ABOVE The string for locating tooltips above an element
 | 
			
		||||
 * @property {string} BELOW The string for locating tooltips below an element
 | 
			
		||||
 * @property {string} RIGHT The pixel-spatial annotation type
 | 
			
		||||
 * @property {string} LEFT The temporal annotation type
 | 
			
		||||
 * @property {string} CENTER The plot-spatial annotation type
 | 
			
		||||
 */
 | 
			
		||||
const TOOLTIP_LOCATIONS = Object.freeze({
 | 
			
		||||
  ABOVE: 'above',
 | 
			
		||||
 
 | 
			
		||||
@@ -21,10 +21,23 @@
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
import EventEmitter from 'EventEmitter';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The StatusAPI is used to get and set various statuses linked to the current logged in user.
 | 
			
		||||
 * This includes the ability to set the status of the current user as a response to a poll question,
 | 
			
		||||
 * set the poll question itself, and the ability to set the status of mission actions.
 | 
			
		||||
 *
 | 
			
		||||
 * @augments EventEmitter
 | 
			
		||||
 */
 | 
			
		||||
export default class StatusAPI extends EventEmitter {
 | 
			
		||||
  /** @type {UserAPI} */
 | 
			
		||||
  #userAPI;
 | 
			
		||||
  /** @type {OpenMCT} */
 | 
			
		||||
  #openmct;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {UserAPI} userAPI
 | 
			
		||||
   * @param {OpenMCT} openmct
 | 
			
		||||
   */
 | 
			
		||||
  constructor(userAPI, openmct) {
 | 
			
		||||
    super();
 | 
			
		||||
    this.#userAPI = userAPI;
 | 
			
		||||
@@ -64,7 +77,7 @@ export default class StatusAPI extends EventEmitter {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Set a poll question for operators to respond to. When presented with a status poll question, all operators will reply with their current status.
 | 
			
		||||
   * @param {String} questionText - The text of the question
 | 
			
		||||
   * @param {string} questionText - The text of the question
 | 
			
		||||
   * @returns {Promise<Boolean>} true if operation was successful, otherwise false.
 | 
			
		||||
   */
 | 
			
		||||
  async setPollQuestion(questionText) {
 | 
			
		||||
@@ -316,7 +329,7 @@ export default class StatusAPI extends EventEmitter {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Private internal function that cannot be made #private because it needs to be registered as a callback to the user provider
 | 
			
		||||
   * Listen to status events from the UserProvider
 | 
			
		||||
   * @private
 | 
			
		||||
   */
 | 
			
		||||
  listenToStatusEvents(provider) {
 | 
			
		||||
@@ -328,6 +341,7 @@ export default class StatusAPI extends EventEmitter {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Emit a status change event
 | 
			
		||||
   * @private
 | 
			
		||||
   */
 | 
			
		||||
  onProviderStatusChange(newStatus) {
 | 
			
		||||
@@ -335,6 +349,7 @@ export default class StatusAPI extends EventEmitter {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Emit a poll question change event
 | 
			
		||||
   * @private
 | 
			
		||||
   */
 | 
			
		||||
  onProviderPollQuestionChange(pollQuestion) {
 | 
			
		||||
@@ -342,6 +357,7 @@ export default class StatusAPI extends EventEmitter {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Emit a mission action status change event
 | 
			
		||||
   * @private
 | 
			
		||||
   */
 | 
			
		||||
  onMissionActionStatusChange({ action, status }) {
 | 
			
		||||
@@ -349,6 +365,14 @@ export default class StatusAPI extends EventEmitter {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @typedef {import('./UserAPI').default} UserAPI
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @typedef {import('../../../openmct').OpenMCT} OpenMCT
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @typedef {import('./UserProvider')} UserProvider
 | 
			
		||||
 */
 | 
			
		||||
@@ -360,27 +384,29 @@ export default class StatusAPI extends EventEmitter {
 | 
			
		||||
/**
 | 
			
		||||
 * The PollQuestion type
 | 
			
		||||
 * @typedef {Object} PollQuestion
 | 
			
		||||
 * @property {String} question - The question to be presented to users
 | 
			
		||||
 * @property {Number} timestamp - The time that the poll question was set.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The MissionStatus type
 | 
			
		||||
 * @typedef {Object} MissionStatusOption
 | 
			
		||||
 * @extends {Status}
 | 
			
		||||
 * @property {String} color A color to be used when displaying the mission status
 | 
			
		||||
 * @property {string} question - The question to be presented to users
 | 
			
		||||
 * @property {number} timestamp - The time that the poll question was set.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @typedef {Object} MissionAction
 | 
			
		||||
 * @property {String} key A unique identifier for this action
 | 
			
		||||
 * @property {String} label A human readable label for this action
 | 
			
		||||
 * @property {string} key A unique identifier for this action
 | 
			
		||||
 * @property {string} label A human readable label for this action
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The MissionStatusOption type, extends Status.
 | 
			
		||||
 * @typedef {Object} MissionStatusOption
 | 
			
		||||
 * @property {string} key - A unique identifier for this status.
 | 
			
		||||
 * @property {string} label - A human-readable label for this status.
 | 
			
		||||
 * @property {number} timestamp - The time that the status was set.
 | 
			
		||||
 * @property {string} color - A color to be used when displaying the mission status.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The Status type
 | 
			
		||||
 * @typedef {Object} Status
 | 
			
		||||
 * @property {String} key - A unique identifier for this status
 | 
			
		||||
 * @property {String} label - A human readable label for this status
 | 
			
		||||
 * @property {Number} timestamp - The time that the status was set.
 | 
			
		||||
 * @property {string} key - A unique identifier for this status
 | 
			
		||||
 * @property {string} label - A human readable label for this status
 | 
			
		||||
 * @property {number} timestamp - The time that the status was set.
 | 
			
		||||
 */
 | 
			
		||||
 
 | 
			
		||||
@@ -20,6 +20,9 @@
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The example User class.
 | 
			
		||||
 */
 | 
			
		||||
export default class User {
 | 
			
		||||
  constructor(id, name) {
 | 
			
		||||
    this.id = id;
 | 
			
		||||
 
 | 
			
		||||
@@ -28,6 +28,8 @@ import StoragePersistance from './StoragePersistance.js';
 | 
			
		||||
import User from './User.js';
 | 
			
		||||
 | 
			
		||||
class UserAPI extends EventEmitter {
 | 
			
		||||
  /** @type {OpenMCT} */
 | 
			
		||||
  #openmct;
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {OpenMCT} openmct
 | 
			
		||||
   * @param {UserAPIConfiguration} config
 | 
			
		||||
@@ -35,7 +37,7 @@ class UserAPI extends EventEmitter {
 | 
			
		||||
  constructor(openmct, config) {
 | 
			
		||||
    super();
 | 
			
		||||
 | 
			
		||||
    this._openmct = openmct;
 | 
			
		||||
    this.#openmct = openmct;
 | 
			
		||||
    this._provider = undefined;
 | 
			
		||||
 | 
			
		||||
    this.User = User;
 | 
			
		||||
@@ -134,7 +136,7 @@ class UserAPI extends EventEmitter {
 | 
			
		||||
  /**
 | 
			
		||||
   * Will return if a role can provide a operator status response
 | 
			
		||||
   * @memberof module:openmct.UserApi#
 | 
			
		||||
   * @returns {Boolean}
 | 
			
		||||
   * @returns {boolean}
 | 
			
		||||
   */
 | 
			
		||||
  canProvideStatusForRole() {
 | 
			
		||||
    if (!this.hasProvider()) {
 | 
			
		||||
@@ -166,7 +168,7 @@ class UserAPI extends EventEmitter {
 | 
			
		||||
   * 'hasRole' method
 | 
			
		||||
   *
 | 
			
		||||
   * @memberof module:openmct.UserAPI#
 | 
			
		||||
   * @returns {Function|Boolean} user provider 'isLoggedIn' method
 | 
			
		||||
   * @returns {Function|boolean} user provider 'isLoggedIn' method
 | 
			
		||||
   * @param {string} roleId id of role to check for
 | 
			
		||||
   * @throws Will throw an error if no user provider is set
 | 
			
		||||
   */
 | 
			
		||||
@@ -201,20 +203,28 @@ class UserAPI extends EventEmitter {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default UserAPI;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @typedef {String} Role
 | 
			
		||||
 * @typedef {string} Role
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @typedef {Object} OpenMCT
 | 
			
		||||
 * @typedef {import('../../../openmct.js').OpenMCT} OpenMCT
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @typedef {{statusStyles: Object.<string, StatusStyleDefinition>}} UserAPIConfiguration
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @typedef {Object} StatusStyleDefinition
 | 
			
		||||
 * @property {String} iconClass The icon class to apply to the status indicator when this status is active "icon-circle-slash",
 | 
			
		||||
 * @property {String} iconClassPoll The icon class to apply to the poll question indicator when this style is active eg. "icon-status-poll-question-mark"
 | 
			
		||||
 * @property {String} statusClass The class to apply to the indicator when this status is active eg. "s-status-error"
 | 
			
		||||
 * @property {String} statusBgColor The background color to apply in the status summary section of the poll question popup for this status eg."#9900cc"
 | 
			
		||||
 * @property {String} statusFgColor The foreground color to apply in the status summary section of the poll question popup for this status eg. "#fff"
 | 
			
		||||
 * @property {string} iconClass The icon class to apply to the status indicator when this status is active "icon-circle-slash",
 | 
			
		||||
 * @property {string} iconClassPoll The icon class to apply to the poll question indicator when this style is active eg. "icon-status-poll-question-mark"
 | 
			
		||||
 * @property {string} statusClass The class to apply to the indicator when this status is active eg. "s-status-error"
 | 
			
		||||
 * @property {string} statusBgColor The background color to apply in the status summary section of the poll question popup for this status eg."#9900cc"
 | 
			
		||||
 * @property {string} statusFgColor The foreground color to apply in the status summary section of the poll question popup for this status eg. "#fff"
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @typedef {Object} UserProvider
 | 
			
		||||
 */
 | 
			
		||||
 
 | 
			
		||||
@@ -19,18 +19,24 @@
 | 
			
		||||
 * this source code distribution or the Licensing information page available
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A user provider is responsible for providing information about the currently
 | 
			
		||||
 * logged in user. This includes information about the user's roles, and whether
 | 
			
		||||
 * the user is currently logged in.
 | 
			
		||||
 */
 | 
			
		||||
export default class UserProvider {
 | 
			
		||||
  /**
 | 
			
		||||
   * @returns {Promise<User>} A promise that resolves with the currently logged in user
 | 
			
		||||
   */
 | 
			
		||||
  getCurrentUser() {}
 | 
			
		||||
  /**
 | 
			
		||||
   * @returns {Boolean} true if a user is currently logged in, otherwise false
 | 
			
		||||
   * @returns {boolean} true if a user is currently logged in, otherwise false
 | 
			
		||||
   */
 | 
			
		||||
  isLoggedIn() {}
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {String} role
 | 
			
		||||
   * @returns {Promise<Boolean>} true if the current user has the given role
 | 
			
		||||
   * @param {string} role
 | 
			
		||||
   * @returns {Promise<boolean>} true if the current user has the given role
 | 
			
		||||
   */
 | 
			
		||||
  hasRole(role) {}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -43,7 +43,7 @@ class ImageExporter {
 | 
			
		||||
   * Converts an HTML element into a PNG or JPG Blob.
 | 
			
		||||
   * @private
 | 
			
		||||
   * @param {node} element that will be converted to an image
 | 
			
		||||
   * @param {object} options Image options.
 | 
			
		||||
   * @param {Object} options Image options.
 | 
			
		||||
   * @returns {promise}
 | 
			
		||||
   */
 | 
			
		||||
  renderElement(element, { imageType, className, thumbnailSize }) {
 | 
			
		||||
 
 | 
			
		||||
@@ -61,7 +61,6 @@ describe('DeviceClassifier', function () {
 | 
			
		||||
          mockAgent[m].and.returnValue(true);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // eslint-disable-next-line no-new
 | 
			
		||||
        DeviceClassifier(mockAgent, mockDocument);
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -180,13 +180,13 @@ export default {
 | 
			
		||||
      this.items.push(item);
 | 
			
		||||
      this.subscribeToStaleness(domainObject);
 | 
			
		||||
    },
 | 
			
		||||
    removeItem(identifier) {
 | 
			
		||||
    async removeItem(identifier) {
 | 
			
		||||
      const keystring = this.openmct.objects.makeKeyString(identifier);
 | 
			
		||||
 | 
			
		||||
      const index = this.items.findIndex((item) => keystring === item.key);
 | 
			
		||||
      this.items.splice(index, 1);
 | 
			
		||||
 | 
			
		||||
      const domainObject = this.openmct.objects.get(keystring);
 | 
			
		||||
      const domainObject = await this.openmct.objects.get(keystring);
 | 
			
		||||
      this.triggerUnsubscribeFromStaleness(domainObject);
 | 
			
		||||
    },
 | 
			
		||||
    reorder(reorderPlan) {
 | 
			
		||||
 
 | 
			
		||||
@@ -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 () {
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,7 @@
 | 
			
		||||
import { ACTIVITY_STATES_KEY } from './createActivityStatesIdentifier.js';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @typedef {object} ActivityStatesInterceptorOptions
 | 
			
		||||
 * @typedef {Object} ActivityStatesInterceptorOptions
 | 
			
		||||
 * @property {import('../../api/objects/ObjectAPI').Identifier} identifier the {namespace, key} to use for the activity states object.
 | 
			
		||||
 * @property {string} name The name of the activity states model.
 | 
			
		||||
 * @property {number} priority the priority of the interceptor. By default, it is low.
 | 
			
		||||
@@ -34,7 +34,7 @@ import { ACTIVITY_STATES_KEY } from './createActivityStatesIdentifier.js';
 | 
			
		||||
 * This will only get invoked when an attempt is made to save the state for an activity and no activity states object exists in the store.
 | 
			
		||||
 * @param {import('../../../openmct').OpenMCT} openmct
 | 
			
		||||
 * @param {ActivityStatesInterceptorOptions} options
 | 
			
		||||
 * @returns {object}
 | 
			
		||||
 * @returns {Object}
 | 
			
		||||
 */
 | 
			
		||||
const ACTIVITY_STATES_TYPE = 'activity-states';
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -45,7 +45,7 @@ export default function AutoflowTabularController(domainObject, data, openmct) {
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Set the "Last Updated" value to be displayed.
 | 
			
		||||
 * @param {String} value the value to display
 | 
			
		||||
 * @param {string} value the value to display
 | 
			
		||||
 * @private
 | 
			
		||||
 */
 | 
			
		||||
AutoflowTabularController.prototype.trackLastUpdated = function (value) {
 | 
			
		||||
 
 | 
			
		||||
@@ -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 () => {
 | 
			
		||||
 
 | 
			
		||||
@@ -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 () => {
 | 
			
		||||
 
 | 
			
		||||
@@ -364,7 +364,7 @@ export default class ConditionManager extends EventEmitter {
 | 
			
		||||
    let nextLegOptions = { ...options };
 | 
			
		||||
    delete nextLegOptions.onPartialResponse;
 | 
			
		||||
 | 
			
		||||
    const results = Promise.all(
 | 
			
		||||
    const results = await Promise.all(
 | 
			
		||||
      this.conditions.map((condition) => condition.requestLADConditionResult(nextLegOptions))
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -39,7 +39,7 @@
 | 
			
		||||
 * with the mouse while the horizontal dimensions shrink in
 | 
			
		||||
 * kind (and vertical properties remain unmodified.)
 | 
			
		||||
 *
 | 
			
		||||
 * @param {object} rawPosition the initial position/dimensions
 | 
			
		||||
 * @param {Object} rawPosition the initial position/dimensions
 | 
			
		||||
 *                 of the frame being interacted with
 | 
			
		||||
 * @param {number[]} posFactor the position factor
 | 
			
		||||
 * @param {number[]} dimFactor the dimensions factor
 | 
			
		||||
 
 | 
			
		||||
@@ -59,7 +59,7 @@ export default class ExportAsJSONAction {
 | 
			
		||||
  // Public
 | 
			
		||||
  /**
 | 
			
		||||
   *
 | 
			
		||||
   * @param {object} objectPath
 | 
			
		||||
   * @param {Object} objectPath
 | 
			
		||||
   * @returns {boolean}
 | 
			
		||||
   */
 | 
			
		||||
  appliesTo(objectPath) {
 | 
			
		||||
@@ -191,9 +191,9 @@ export default class ExportAsJSONAction {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @private
 | 
			
		||||
   * @param {object} child
 | 
			
		||||
   * @param {object} parent
 | 
			
		||||
   * @returns {object}
 | 
			
		||||
   * @param {Object} child
 | 
			
		||||
   * @param {Object} parent
 | 
			
		||||
   * @returns {Object}
 | 
			
		||||
   */
 | 
			
		||||
  #rewriteLink(child, parent) {
 | 
			
		||||
    const originalKeyString = this.#getKeystring(child);
 | 
			
		||||
@@ -266,7 +266,7 @@ export default class ExportAsJSONAction {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @private
 | 
			
		||||
   * @param {object} domainObject
 | 
			
		||||
   * @param {Object} domainObject
 | 
			
		||||
   * @returns {string} A string representation of the given identifier, including namespace and key
 | 
			
		||||
   */
 | 
			
		||||
  #getKeystring(domainObject) {
 | 
			
		||||
@@ -275,7 +275,7 @@ export default class ExportAsJSONAction {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @private
 | 
			
		||||
   * @param {object} domainObject
 | 
			
		||||
   * @param {Object} domainObject
 | 
			
		||||
   * @returns {boolean}
 | 
			
		||||
   */
 | 
			
		||||
  #isCreatableAndPersistable(domainObject) {
 | 
			
		||||
@@ -287,8 +287,8 @@ export default class ExportAsJSONAction {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @private
 | 
			
		||||
   * @param {object} child
 | 
			
		||||
   * @param {object} parent
 | 
			
		||||
   * @param {Object} child
 | 
			
		||||
   * @param {Object} parent
 | 
			
		||||
   * @returns {boolean}
 | 
			
		||||
   */
 | 
			
		||||
  #isLinkedObject(child, parent) {
 | 
			
		||||
@@ -376,14 +376,14 @@ export default class ExportAsJSONAction {
 | 
			
		||||
  }
 | 
			
		||||
  /**
 | 
			
		||||
   * @private
 | 
			
		||||
   * @param {object} completedTree
 | 
			
		||||
   * @param {Object} completedTree
 | 
			
		||||
   */
 | 
			
		||||
  saveAs(completedTree) {
 | 
			
		||||
    this.JSONExportService.export(completedTree, { filename: this.root.name + '.json' });
 | 
			
		||||
  }
 | 
			
		||||
  /**
 | 
			
		||||
   * @private
 | 
			
		||||
   * @returns {object}
 | 
			
		||||
   * @returns {Object}
 | 
			
		||||
   */
 | 
			
		||||
  #wrapTree() {
 | 
			
		||||
    return {
 | 
			
		||||
 
 | 
			
		||||
@@ -39,7 +39,7 @@ export default class ImportAsJSONAction {
 | 
			
		||||
  // Public
 | 
			
		||||
  /**
 | 
			
		||||
   *
 | 
			
		||||
   * @param {object} objectPath
 | 
			
		||||
   * @param {Object} objectPath
 | 
			
		||||
   * @returns {boolean}
 | 
			
		||||
   */
 | 
			
		||||
  appliesTo(objectPath) {
 | 
			
		||||
@@ -58,15 +58,15 @@ export default class ImportAsJSONAction {
 | 
			
		||||
  }
 | 
			
		||||
  /**
 | 
			
		||||
   *
 | 
			
		||||
   * @param {object} objectPath
 | 
			
		||||
   * @param {Object} objectPath
 | 
			
		||||
   */
 | 
			
		||||
  invoke(objectPath) {
 | 
			
		||||
    this._showForm(objectPath[0]);
 | 
			
		||||
  }
 | 
			
		||||
  /**
 | 
			
		||||
   *
 | 
			
		||||
   * @param {object} object
 | 
			
		||||
   * @param {object} changes
 | 
			
		||||
   * @param {Object} object
 | 
			
		||||
   * @param {Object} changes
 | 
			
		||||
   */
 | 
			
		||||
 | 
			
		||||
  onSave(object, changes) {
 | 
			
		||||
@@ -79,9 +79,9 @@ export default class ImportAsJSONAction {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @private
 | 
			
		||||
   * @param {object} parent
 | 
			
		||||
   * @param {object} tree
 | 
			
		||||
   * @param {object} seen
 | 
			
		||||
   * @param {Object} parent
 | 
			
		||||
   * @param {Object} tree
 | 
			
		||||
   * @param {Object} seen
 | 
			
		||||
   * @param {Array} objectsToCreate tracks objects from import json that will need to be created
 | 
			
		||||
   */
 | 
			
		||||
  _deepInstantiate(parent, tree, seen, objectsToCreate) {
 | 
			
		||||
@@ -112,7 +112,7 @@ export default class ImportAsJSONAction {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @private
 | 
			
		||||
   * @param {object} parent
 | 
			
		||||
   * @param {Object} parent
 | 
			
		||||
   * @returns [identifiers]
 | 
			
		||||
   */
 | 
			
		||||
  _getObjectReferenceIds(parent) {
 | 
			
		||||
@@ -146,9 +146,9 @@ export default class ImportAsJSONAction {
 | 
			
		||||
  }
 | 
			
		||||
  /**
 | 
			
		||||
   * @private
 | 
			
		||||
   * @param {object} tree
 | 
			
		||||
   * @param {Object} tree
 | 
			
		||||
   * @param {string} namespace
 | 
			
		||||
   * @returns {object}
 | 
			
		||||
   * @returns {Object}
 | 
			
		||||
   */
 | 
			
		||||
  _generateNewIdentifiers(tree, newNamespace) {
 | 
			
		||||
    // For each domain object in the file, generate new ID, replace in tree
 | 
			
		||||
@@ -166,8 +166,8 @@ export default class ImportAsJSONAction {
 | 
			
		||||
  }
 | 
			
		||||
  /**
 | 
			
		||||
   * @private
 | 
			
		||||
   * @param {object} domainObject
 | 
			
		||||
   * @param {object} objTree
 | 
			
		||||
   * @param {Object} domainObject
 | 
			
		||||
   * @param {Object} objTree
 | 
			
		||||
   */
 | 
			
		||||
  async _importObjectTree(domainObject, objTree) {
 | 
			
		||||
    const objectsToCreate = [];
 | 
			
		||||
@@ -211,18 +211,18 @@ export default class ImportAsJSONAction {
 | 
			
		||||
  }
 | 
			
		||||
  /**
 | 
			
		||||
   * @private
 | 
			
		||||
   * @param {object} model
 | 
			
		||||
   * @returns {object}
 | 
			
		||||
   * @param {Object} model
 | 
			
		||||
   * @returns {Object}
 | 
			
		||||
   */
 | 
			
		||||
  _instantiate(model) {
 | 
			
		||||
    return this.openmct.objects.save(model);
 | 
			
		||||
  }
 | 
			
		||||
  /**
 | 
			
		||||
   * @private
 | 
			
		||||
   * @param {object} oldId
 | 
			
		||||
   * @param {object} newId
 | 
			
		||||
   * @param {object} tree
 | 
			
		||||
   * @returns {object}
 | 
			
		||||
   * @param {Object} oldId
 | 
			
		||||
   * @param {Object} newId
 | 
			
		||||
   * @param {Object} tree
 | 
			
		||||
   * @returns {Object}
 | 
			
		||||
   */
 | 
			
		||||
  _rewriteId(oldId, newId, tree) {
 | 
			
		||||
    let newIdKeyString = this.openmct.objects.makeKeyString(newId);
 | 
			
		||||
@@ -256,7 +256,7 @@ export default class ImportAsJSONAction {
 | 
			
		||||
  }
 | 
			
		||||
  /**
 | 
			
		||||
   * @private
 | 
			
		||||
   * @param {object} domainObject
 | 
			
		||||
   * @param {Object} domainObject
 | 
			
		||||
   */
 | 
			
		||||
  _showForm(domainObject) {
 | 
			
		||||
    const formStructure = {
 | 
			
		||||
@@ -285,7 +285,7 @@ export default class ImportAsJSONAction {
 | 
			
		||||
  }
 | 
			
		||||
  /**
 | 
			
		||||
   * @private
 | 
			
		||||
   * @param {object} data
 | 
			
		||||
   * @param {Object} data
 | 
			
		||||
   * @returns {boolean}
 | 
			
		||||
   */
 | 
			
		||||
  _validateJSON(data) {
 | 
			
		||||
 
 | 
			
		||||
@@ -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"}'
 | 
			
		||||
        }
 | 
			
		||||
      };
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -30,7 +30,7 @@
 | 
			
		||||
 * @memberof platform/persistence/couch
 | 
			
		||||
 * @constructor
 | 
			
		||||
 * @param {string} id the id under which to store this mode
 | 
			
		||||
 * @param {object} model the model to store
 | 
			
		||||
 * @param {Object} model the model to store
 | 
			
		||||
 * @param {string} rev the revision to include (or undefined,
 | 
			
		||||
 *        if no revision should be noted for couch)
 | 
			
		||||
 * @param {boolean} whether or not to mark this document as
 | 
			
		||||
 
 | 
			
		||||
@@ -30,11 +30,11 @@ import PlanViewProvider from './PlanViewProvider.js';
 | 
			
		||||
 | 
			
		||||
const ACTIVITY_STATES_DEFAULT_NAME = 'Activity States';
 | 
			
		||||
/**
 | 
			
		||||
 * @typedef {object} PlanOptions
 | 
			
		||||
 * @typedef {Object} PlanOptions
 | 
			
		||||
 * @property {boolean} creatable true/false to allow creation of a plan via the Create menu.
 | 
			
		||||
 * @property {string} name The name of the activity states model.
 | 
			
		||||
 * @property {string} namespace the namespace to use for the activity states object.
 | 
			
		||||
 * @property {Number} priority the priority of the interceptor. By default, it is low.
 | 
			
		||||
 * @property {number} priority the priority of the interceptor. By default, it is low.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 
 | 
			
		||||
@@ -22,15 +22,15 @@
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The SourceMap allows mapping specific implementations of plan domain objects to those expected by Open MCT.
 | 
			
		||||
 * @typedef {object} SourceMapOption
 | 
			
		||||
 * @typedef {Object} SourceMapOption
 | 
			
		||||
 * @property {string} orderedGroups the property of the plan that lists groups/swim lanes specifying what order they will be displayed in Open MCT.
 | 
			
		||||
 * @property {string} activities the property of the plan that has the list of activities to be displayed.
 | 
			
		||||
 * @property {string} groupId the property of the activity that maps to the group/swim lane it should be displayed in.
 | 
			
		||||
 * @property {string} start The start time property of the activity
 | 
			
		||||
 * @property {string} end The end time property of the activity
 | 
			
		||||
 * @property {string} id The unique id of the activity. This is required to allow setting activity states
 | 
			
		||||
 * @property {object} displayProperties a list of key: value pairs that specifies which properties of the activity should be displayed when it is selected. Ex. {'location': 'Location', 'metadata.length_in_meters', 'Length (meters)'}
 | 
			
		||||
 * @property {object} filterMetadata a list of strings that specifies which properties of the activity be included for filtering. Ex. {'description','properties.length_in_meters'}
 | 
			
		||||
 * @property {Object} displayProperties a list of key: value pairs that specifies which properties of the activity should be displayed when it is selected. Ex. {'location': 'Location', 'metadata.length_in_meters', 'Length (meters)'}
 | 
			
		||||
 * @property {Object} filterMetadata a list of strings that specifies which properties of the activity be included for filtering. Ex. {'description','properties.length_in_meters'}
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import _ from 'lodash';
 | 
			
		||||
 
 | 
			
		||||
@@ -22,8 +22,8 @@
 | 
			
		||||
import Model from './Model.js';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @template {object} T
 | 
			
		||||
 * @template {object} O
 | 
			
		||||
 * @template {Object} T
 | 
			
		||||
 * @template {Object} O
 | 
			
		||||
 * @extends {Model<T, O>}
 | 
			
		||||
 */
 | 
			
		||||
export default class Collection extends Model {
 | 
			
		||||
 
 | 
			
		||||
@@ -26,8 +26,8 @@ import _ from 'lodash';
 | 
			
		||||
import eventHelpers from '../lib/eventHelpers.js';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @template {object} T
 | 
			
		||||
 * @template {object} O
 | 
			
		||||
 * @template {Object} T
 | 
			
		||||
 * @template {Object} O
 | 
			
		||||
 */
 | 
			
		||||
export default class Model extends EventEmitter {
 | 
			
		||||
  /**
 | 
			
		||||
@@ -144,15 +144,15 @@ export default class Model extends EventEmitter {
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@template {object} T
 | 
			
		||||
@template {Object} T
 | 
			
		||||
@typedef {{
 | 
			
		||||
    id?: string
 | 
			
		||||
} & T} ModelType
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@template {object} T
 | 
			
		||||
@template {object} O
 | 
			
		||||
@template {Object} T
 | 
			
		||||
@template {Object} O
 | 
			
		||||
@typedef {{
 | 
			
		||||
    model?: ModelType<T>
 | 
			
		||||
    models?: T[]
 | 
			
		||||
 
 | 
			
		||||
@@ -422,7 +422,7 @@ export default class PlotSeries extends Model {
 | 
			
		||||
   *
 | 
			
		||||
   * @private
 | 
			
		||||
   * @param {Object} newData a telemetry datum.
 | 
			
		||||
   * @param {Boolean} [sorted] default false, if true will append
 | 
			
		||||
   * @param {boolean} [sorted] default false, if true will append
 | 
			
		||||
   *                  a point to the end without dupe checking.
 | 
			
		||||
   */
 | 
			
		||||
  add(newData, sorted = false) {
 | 
			
		||||
 
 | 
			
		||||
@@ -302,7 +302,7 @@ export default class YAxisModel extends Model {
 | 
			
		||||
   * For a given series collection, get the metadata of the current yKey for each series.
 | 
			
		||||
   * Then return first available value of the given property from the metadata.
 | 
			
		||||
   * @param {import('./SeriesCollection').default} series
 | 
			
		||||
   * @param {String} property
 | 
			
		||||
   * @param {string} property
 | 
			
		||||
   */
 | 
			
		||||
  getMetadataValueByProperty(series, property) {
 | 
			
		||||
    return series
 | 
			
		||||
 
 | 
			
		||||
@@ -315,9 +315,9 @@ export default {
 | 
			
		||||
    },
 | 
			
		||||
    /**
 | 
			
		||||
     * @typedef {Object} PlotYTickData
 | 
			
		||||
     * @property {Number} leftTickWidth the width of the ticks for all the y axes on the left of the plot.
 | 
			
		||||
     * @property {Number} rightTickWidth the width of the ticks for all the y axes on the right of the plot.
 | 
			
		||||
     * @property {Boolean} hasMultipleLeftAxes whether or not there is more than one left y axis.
 | 
			
		||||
     * @property {number} leftTickWidth the width of the ticks for all the y axes on the left of the plot.
 | 
			
		||||
     * @property {number} rightTickWidth the width of the ticks for all the y axes on the right of the plot.
 | 
			
		||||
     * @property {boolean} hasMultipleLeftAxes whether or not there is more than one left y axis.
 | 
			
		||||
     */
 | 
			
		||||
    onYTickWidthChange(data, plotId) {
 | 
			
		||||
      if (!Object.prototype.hasOwnProperty.call(this.tickWidthMap, plotId)) {
 | 
			
		||||
 
 | 
			
		||||