Compare commits

...

43 Commits

Author SHA1 Message Date
Andrew Henry
c1dd82cf5f cherry-pick(#7121): Do not store state in singleton action (#7134)
do not store state in singleton action (#7121)

Co-authored-by: David Tsay <3614296+davetsay@users.noreply.github.com>
2023-10-10 16:17:36 -07:00
Khalid Adil
e43ddf6a4a cherry-pick(#7122): [Staleness] Fix issue with object view staleness styles not being res… (#7123)
[Staleness] Fix issue with object view staleness styles not being reset on clock change (#7122)

Add logic to un/re-subscribe when clock changes to object view
2023-10-06 16:37:09 -07:00
Jesse Mazzella
018a64eb86 chore: bump version to 3.1.0 (#7118) 2023-10-05 15:48:05 -05:00
Khalid Adil
b1785da838 cherry-pick(#7088): [Staleness] Fix staleness on clock change (#7112)
[Staleness] Fix staleness on clock change (#7088)

* Update staleness mixin
* Fix listeners and add guard
* Add check to make sure staleness only shows for correct clock
* Add guard for time api
* Cleanup the setting of isStale in ObjectView
* Cleanup use of combinedKey on LadTableSet
2023-10-04 14:29:54 -07:00
Shefali Joshi
ce59c0f50a Check realtime mode in remote clock interceptor (#6985)
* Revert Date.now() purge change
Check that the request is using realtime before using the remote-clock start and end timestamps for telemetry requests
* docs: clarify comment

---------

Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>
2023-10-03 20:41:02 +00:00
David Tsay
d53d8d562e Eagerly lose WebGL context on DrawWebGL.destroy() (#7080)
* test default for preserveDrawingBuffer
* fix: delete WebGL resources on destroy
* fix: eagerly lose WebGL contexts on destroy
- Recommended by Mozilla's [WebGL best practices]-(https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/WebGL_best_practices#lose_contexts_eagerly).

Co-authored-by: Jesse Mazzella <jesse.d.mazzella@nasa.gov>
Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>
2023-10-03 19:33:51 +00:00
Jamie V
d973140906 [Documentation] Time API docs update new/deprecated functionality (#7107)
* update time api docs to reflect recent changes

* clarify what mode openmct starts in and update setMode arguments
2023-10-03 18:42:35 +00:00
dependabot[bot]
b1169ffd7d chore(deps-dev): bump eslint-plugin-compat from 4.1.4 to 4.2.0 (#7104)
Bumps [eslint-plugin-compat](https://github.com/amilajack/eslint-plugin-compat) from 4.1.4 to 4.2.0.
- [Release notes](https://github.com/amilajack/eslint-plugin-compat/releases)
- [Changelog](https://github.com/amilajack/eslint-plugin-compat/blob/main/CHANGELOG.md)
- [Commits](https://github.com/amilajack/eslint-plugin-compat/compare/v4.1.4...v4.2.0)

---
updated-dependencies:
- dependency-name: eslint-plugin-compat
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>
2023-10-03 16:56:06 +00:00
Shefali Joshi
ede93d881c Ensure CouchDB changes for plans trigger updates in the view (#7099)
Listen to ALL changes for a plan since couchdb feed updates does not trigger a property only event. It triggers a catchall '*' event.

Co-authored-by: Scott Bell <scott@traclabs.com>
2023-10-02 23:26:13 +00:00
Scott Bell
6947c912a7 Add markdown to notebook entries (#7084)
* try marked out

* fix url validation

* now rendering blockquotes properly

* add abbrv, link titles, and strikethrough

* fix tests and lint

* Closes #6060
- CSS resets and styling for markdown-related HTML markup in Notebook entries.
- Better styling and cursor affordances for Notebook entry selection and editing interaction flow.

* add line breaks option

* Closes #6060
- Tab

* Closes #6060
- Conversion of contenteditable-div to textarea started.
- Stubbed in textarea with styles.

* have it markdown with a textarea and adjust size automatically

* Closes #6060
- Padding added back to text `div` area.

* Closes #6060
- Styles added to support Shift Log and hover behavior for entries on locked pages.
- Removed `--major` styling from Shift Log Commit Entries button
to reduce confusion with entry commit button.
- CSS code cleanups.

* two step focus/edit. also scroll into view for editing

* add markdown, strip all tags, and truncate

* lint

* remove unneeded code

* fix notebook entry, selected page may also be null

* fix existing notebook tests

* lint

* fix whitelist

* readd whitelist

* lint

* fix link tests

* fix tests

* fix tagging test

* add some markdown test

* get rid of pause

* add another sanitization step

---------

Co-authored-by: Charles Hacskaylo <charlesh88@gmail.com>
Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>
2023-10-02 22:28:02 +00:00
David Tsay
2243381d52 Protect against prototype pollution in import action (#7094) 2023-10-02 14:50:53 -07:00
dependabot[bot]
3c7d3397d6 chore(deps-dev): bump cspell from 7.1.1 to 7.3.6 (#7067)
Bumps [cspell](https://github.com/streetsidesoftware/cspell) from 7.1.1 to 7.3.6.
- [Release notes](https://github.com/streetsidesoftware/cspell/releases)
- [Changelog](https://github.com/streetsidesoftware/cspell/blob/main/CHANGELOG.md)
- [Commits](https://github.com/streetsidesoftware/cspell/compare/v7.1.1...v7.3.6)

---
updated-dependencies:
- dependency-name: cspell
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>
2023-10-02 17:38:42 +00:00
Shefali Joshi
9f8338124f Catchall for bug fix PRs and add performance category (#7096)
* Catchall for bug fix PRs and add performance category

* Move Bug fixes to the bottom

* Fix performance label
2023-09-29 17:17:14 +00:00
Scott Bell
ab0e2d2c96 Hide image controls when tagging, and hide compass HUD by default (#7028)
* make compass HUD configurable and hide image controls when tagging

* lint fixes

* address PR comments

* change prop to inject

---------

Co-authored-by: Shefali Joshi <simplyrender@gmail.com>
2023-09-28 03:58:24 +00:00
Jesse Mazzella
c3b5e4e1e3 chore: add release.yml (#7090)
Co-authored-by: Shefali Joshi <simplyrender@gmail.com>
2023-09-27 12:18:19 -07:00
Jesse Mazzella
8fc5aa6ed5 Revert "fix(templates): move to own folder" (#7092)
Revert "fix(templates): move to own folder (#7000)"

This reverts commit 5592d20ab1.
2023-09-26 16:02:48 -05:00
Even Stensberg
5592d20ab1 fix(templates): move to own folder (#7000)
Co-authored-by: John Hill <john.c.hill@nasa.gov>
2023-09-26 13:37:55 -07:00
Scott Bell
ce2305455a Add script to delete annotations (#7069)
* add script

* add package.json and script to delete annotations

* amend help

* fix issues

* added explicit runs

* add design document and index creation to script

* update tests to wait for url to change

* i think we can remove this deprecated function now
2023-09-25 10:15:00 -07:00
Jesse Mazzella
ff2c8b35b0 🙅🚮␡ Removeopenmct.components🚮🙅 (#7075)
🙅🚮␡`openmct.components`␡🚮🙅

Co-authored-by: Andrew Henry <akhenry@gmail.com>
2023-09-21 14:05:35 -07:00
Scott Bell
482f1f68dd Have annotations work with domain objects that have dots (#7065)
* migrating to new structure - wip

* notebooks work, now to plots and images

* resolve conflicts

* fix search

* add to readme

* spelling

* fix unit test

* add search by view for big search speedup

* spelling

* fix out of order search

* improve reliability of plot tagging tests
2023-09-21 13:50:08 -07:00
Andrew Henry
05c7a81630 Fix mojibake (#7073)
Add charset preamble to generated css
2023-09-21 01:01:30 +00:00
Shefali Joshi
b8949db767 Memory leak fixes for several views (#7057)
* Change the mount utility to use Vue's createApp and defineComponent methods

* Fix display layout memory leaks caused by `getSelectionContext`

* fix some display layout leaks due to use of slots

* Fix imagery memory leak (removed span tag). NOTE: CompassRose svg leaks memory - must test on firefox to see if this is a Chrome leak.

* Fix ActionsAPI action collection and applicable actions leak.

* Fix flexible layout memory leaks - remove listeners on unmount. NOTE: One type of overlay plot (Rover Yaw) is still leaking.

* pass in the el on mount

* e2e test config and spec changes

* Remove mounting of limit lines. Use components directly

* test: remove `.only()`

* Fix display layout memory leaks

* Enable passing tests

* e2e README and appActions should be what master has.

* lint: add word to cspell list

* lint: fixes

* lint:fix

* fix: revert `el` change

* fix: remove empty span

* fix: creating shapes in displayLayout

* fix: avoid `splice` as it loses reactivity

* test: reduce timeout time

* quick fixes

* add prod mode and convert the test config to select the correct mode

* Fix webpack prod config

* Add launch flag for exposing window.gc

* never worked

* explicit naming

* rename

* We don't need to destroy view providers

* test: increase timeout time

* test: unskip all mem tests

* fix(vue-loader): disable static hoisting

* chore: run `test:perf:memory`

* Don't destroy view providers

* Move context menu once listener to beforeUnmount instead.

* Disconnect all resize observers on unmount

* Delete Test vue component

* Use beforeUnmount and remove splice(0) in favor of [] for emptying arrays

* re-structure

* fix: unregister listener in pane.vue

* test: tweak timeouts

* chore: lint:fix

* test: unskip perf tests

* fix: unregister events properly

* fix: unregister listener

* fix: unregister listener

* fix: unregister listener

* fix: use `unmounted()`

* fix: unregister listeners

* fix: unregister listener properly

* chore: lint:fix

* test: fix imagery layer toggle test

* test: increase timeout

* Don't use anonymous functions for listeners

* Destroy objects and event listeners properly

* Delete config stores that are created by components

* Use the right unmount hook. Destroy mounted view on unmount.

* Use unmounted, not beforeUnmounted

* Lint fixes

* Fix time strip memory leak

* Undo unneeded change for memory leaks.

* chore: combine common webpack configs

---------

Co-authored-by: Jesse Mazzella <jesse.d.mazzella@nasa.gov>
Co-authored-by: John Hill <john.c.hill@nasa.gov>
2023-09-20 10:34:05 -07:00
dependabot[bot]
61e7050391 chore(deps-dev): bump typescript from 5.1.3 to 5.2.2 (#7007)
Bumps [typescript](https://github.com/Microsoft/TypeScript) from 5.1.3 to 5.2.2.
- [Release notes](https://github.com/Microsoft/TypeScript/releases)
- [Commits](https://github.com/Microsoft/TypeScript/compare/v5.1.3...v5.2.2)

---
updated-dependencies:
- dependency-name: typescript
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>
2023-09-18 18:45:24 +00:00
dependabot[bot]
3f51516da9 chore(deps-dev): bump eslint-config-prettier from 8.8.0 to 9.0.0 (#6897)
Bumps [eslint-config-prettier](https://github.com/prettier/eslint-config-prettier) from 8.8.0 to 9.0.0.
- [Changelog](https://github.com/prettier/eslint-config-prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/eslint-config-prettier/compare/v8.8.0...v9.0.0)

---
updated-dependencies:
- dependency-name: eslint-config-prettier
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-18 18:24:20 +00:00
Scott Bell
541a022f36 Embedding images in notebook entries (#7048)
* initial drag drop, wip

* images work as snapshots, but need to disable navigate to actions

* embed image name

* works now with images, need to be refactor so can duplicate code for entries too

* works dropping on entries too

* handle remote images too

* add e2e test

* spelling

* address most PR comments
2023-09-18 10:56:49 -07:00
David Tsay
c7b5ecbd68 Allow Data Visualization in inspector based on current selection (#7052)
* visualize data in inspector per selection

---------

Co-authored-by: Khalid Adil <khalidadil29@gmail.com>
Co-authored-by: John Hill <john.c.hill@nasa.gov>
2023-09-15 12:30:58 -07:00
David 'Epper' Marshall
6776cc308f PascalCase files (#6955)
* PascalCase files

* git mv for file name change

* renamed files

* merge changes from master

* fix: template name

* sort imports

---------

Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>
Co-authored-by: Jesse Mazzella <jesse.d.mazzella@nasa.gov>
2023-09-14 09:55:44 -07:00
dependabot[bot]
95ac919ddf chore(deps): bump docker/login-action from 2 to 3 (#7053)
Bumps [docker/login-action](https://github.com/docker/login-action) from 2 to 3.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-12 16:17:23 -07:00
John Hill
2a1064cbab [CI] Stabilize visual tests, remove appAction, and update pane buttons (#7033)
* Add a VISUAL_URL constant and remove all vestiges of hide inspector and tree

* hide timer and add concurrency

* turn off concurrency

* factor out old appAction

* Add expand button to panes

* remove old slow annotations

* fix fault

* update domcontentloaded

* missed refactor

* driveby: setTimeBounds private

* add comments to the percyCSS

* suggest MISSION_TIME

* more notes

* regen

* clean up test

* driveby: clean up order

* restructure

* add new suite now that i'ts hidden

* use mission time everywhere possible

* driveby

* rerun generatedata

* comments

* lintfix
2023-09-11 23:33:46 +00:00
Scott Bell
8e917b2679 Remove large series models from reactive data in plots (#6961)
* remove series object from highlights

* remove series models from legend reactive data

* drawing all annotations at once is way faster

* fix multi annotations

* lots of reactive things depending on config

* make annotation lookup faster

* lint

* readd perf test

* address PR comments

* fix highlight typo

---------

Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>
Co-authored-by: Shefali Joshi <simplyrender@gmail.com>
2023-09-06 09:32:36 -07:00
Jesse Mazzella
0be106f29e Fix and re-enable disabled unit test suites (#6990)
* fix: register event listeners etc in `created()` hook

* fix: initialize `stalenessSubscription` before composition load and in `created()` hook

* refactor(test): make `openmct` const

* refactor: update overlayPlot spec to Vue 3 and fix tests

* refactor: fix eslint errors

* refactor: move initialization steps to `created()` hook

* test: re-enable and fix stackedPlot test suite

* fix: `hideTree=true` hides the tree again

* fix: add back in check on mount

* test: fix Layout tests

* fix: BarGraph test

* fix: plot inspector tests

* fix: reenable grand search tests

* fix: inspectorStyles test suite

* fix: re-enable most timeline tests

* fix: no need to hideTree in appactions

* fix: re-enable more tests

* test: re-enable more tests

* test: re-enable most plot tests

* chore: `lint:fix`

* test: re-enable timelist suite

* fix(#7016): timers count down or up to a target date

* test: add regression tests to cover disabled unit tests

* refactor: lint:fix

* refactor: no need for momentjs here

* fix: timerAction missed refactor

* fix: ensure timestamp is always UTC string

* test: use role selectors

* docs: add instructions for clock override in e2e

* docs: update

* Update readme

* lint

* spelling fixes

---------

Co-authored-by: Scott Bell <scott@traclabs.com>
Co-authored-by: John Hill <john.c.hill@nasa.gov>
2023-09-05 08:53:03 +00:00
Jesse Mazzella
9f7b3b9225 fix(#7022): remove ProgressBar artifacts from Notifications (#7024)
* fix: check for null or undefined

* refactor: `lint:fix`

---------

Co-authored-by: Scott Bell <scott@traclabs.com>
2023-09-04 12:51:32 +00:00
Jesse Mazzella
e9b94c308b chore: pin vue package versions (#7032) 2023-09-03 15:14:54 +00:00
dependabot[bot]
64740e133f chore(deps-dev): bump eslint from 8.43.0 to 8.48.0 (#7010)
Bumps [eslint](https://github.com/eslint/eslint) from 8.43.0 to 8.48.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.43.0...v8.48.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-01 23:33:24 +00:00
Even Stensberg
c27ad469f6 feat(eslint): sort import rule (#6939)
* feat(eslint): sort import rule

* chore(deps): pin dep

* refactor: sort imports

---------

Co-authored-by: Jesse Mazzella <jesse.d.mazzella@nasa.gov>
2023-08-31 13:40:00 -07:00
Even Stensberg
e09a7aebae feat(linting): concurrent linting (#6969) 2023-08-30 23:12:06 +00:00
David 'Epper' Marshall
b87459dfd7 ProgressBar null not undefined (#6953)
* ProgressBar null not undefined

* notification banner null

* update progress dialog

* docs: update type

---------

Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>
Co-authored-by: Jesse Mazzella <jesse.d.mazzella@nasa.gov>
2023-08-29 23:49:31 +00:00
Jesse Mazzella
ca06a6a047 docs: update staticRootPlugin README (#7014) 2023-08-29 23:09:28 +02:00
Even Stensberg
95e3ab25a4 fix(dialog): empty description (#6986)
* fix(dialog): empty description

* tests(e2e): adds roleview test

* tests(e2e): test in beforeEach

---------

Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>
2023-08-28 15:54:49 -07:00
Even Stensberg
f9db6433a1 docs(readme): indent correctly (#6996)
* fix(readme): indent correctly

* fix(readme): review

* fix(readme): review

* chore(docs): revise

Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>

* docs: tidy up a bit

---------

Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>
Co-authored-by: Jesse Mazzella <jesse.d.mazzella@nasa.gov>
2023-08-28 19:59:25 +00:00
Even Stensberg
cbac2c1c82 docs(readme): note sections (#6997)
* fix(readme): note sections

* fix: notes

* fix: notes

* fix(readme): better markdown

---------

Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>
2023-08-28 19:50:20 +00:00
Shefali Joshi
a8678aa739 Imagery layer checkbox should match layer visiblity (#7003)
Set layer visibility to false if layer visibility cannot be persisted
2023-08-28 10:32:37 -07:00
Jamie V
244e3b7938 [Aborts] Abort Telemetry Collections requests on Navigation, Add abort functionality to getLimits (#6872)
* debug

* abort any pending requests on router "change:path" event, this should catch cases where the UI is bogged down and doesnt destroy the tc first

* english

* cant always be on

* adding abort to limits requests

* finally-ing off the promise

* need to just return the object not the property

* sticking with the try/catch structure we use elsewhere

* removing abort on nav, as views should be calling destroy

---------

Co-authored-by: John Hill <john.c.hill@nasa.gov>
Co-authored-by: Shefali Joshi <simplyrender@gmail.com>
2023-08-24 21:17:58 +00:00
444 changed files with 6191 additions and 3270 deletions

View File

@@ -94,7 +94,6 @@ jobs:
- build_and_install:
node-version: <<parameters.node-version>>
- run: npm run lint
- run: npm run lint:spelling
- generate_and_store_version_and_filesystem_artifacts
unit-test:
parameters:
@@ -198,7 +197,9 @@ jobs:
steps:
- build_and_install:
node-version: <<parameters.node-version>>
- run: npm run test:perf
- run: npm run test:perf:memory
- run: npm run test:perf:localhost
- run: npm run test:perf:contract
- store_test_results:
path: test-results/results.xml
- store_artifacts:

View File

@@ -479,7 +479,11 @@
"mediump",
"sinonjs",
"generatedata",
"grandsearch"
"grandsearch",
"websockets",
"swgs",
"memlab",
"devmode"
],
"dictionaries": ["npm", "softwareTerms", "node", "html", "css", "bash", "en_US"],
"ignorePaths": [

View File

@@ -9,7 +9,7 @@ module.exports = {
globals: {
_: 'readonly'
},
plugins: ['prettier'],
plugins: ['prettier', 'unicorn', 'simple-import-sort'],
extends: [
'eslint:recommended',
'plugin:compat/recommended',
@@ -28,6 +28,8 @@ module.exports = {
}
},
rules: {
'simple-import-sort/imports': 'warn',
'simple-import-sort/exports': 'warn',
'vue/no-deprecated-dollar-listeners-api': 'warn',
'vue/no-deprecated-events-api': 'warn',
'vue/no-v-for-template-key': 'off',
@@ -143,6 +145,17 @@ module.exports = {
'no-implicit-coercion': 'error',
//https://eslint.org/docs/rules/no-unneeded-ternary
'no-unneeded-ternary': 'error',
"unicorn/filename-case": [
"error",
{
"cases": {
"pascalCase": true
},
"ignore": [
"^.*\\.js$"
]
}
],
'vue/first-attribute-linebreak': 'error',
'vue/multiline-html-element-content-newline': 'off',
'vue/singleline-html-element-content-newline': 'off',

23
.github/release.yml vendored Normal file
View File

@@ -0,0 +1,23 @@
changelog:
categories:
- title: 🏕 Features
labels:
- type:feature
- title: 🎉 Enhancements
labels:
- type:enhancement
exclude:
labels:
- type:feature
- title: 🔧 Maintenance
labels:
- type:maintenance
- title: ⚡ Performance
labels:
- performance
- title: 👒 Dependencies
labels:
- dependencies
- title: 🐛 Bug Fixes
labels:
- '*'

View File

@@ -31,7 +31,7 @@ jobs:
- run: npm install --cache ~/.npm --no-audit --progress=false
- name: Login to DockerHub
uses: docker/login-action@v2
uses: docker/login-action@v3
continue-on-error: true
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}

View File

@@ -26,11 +26,11 @@ jobs:
- lts/hydrogen
architecture:
- x64
name: Node ${{ matrix.node_version }} - ${{ matrix.architecture }} on ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- name: Setup node
uses: actions/setup-node@v3
with:
@@ -46,11 +46,11 @@ jobs:
${{ runner.os }}-${{ matrix.node_version }}-
- run: npm install --cache ~/.npm --no-audit --progress=false
- run: npm test
- run: npm run lint -- --quiet
- name: Remove pr:platform label (if present)
if: always()
uses: actions/github-script@v6

View File

@@ -33,6 +33,16 @@ const projectRootDir = path.resolve(__dirname, '..');
/** @type {import('webpack').Configuration} */
const config = {
context: projectRootDir,
devServer: {
client: {
progress: true,
overlay: {
// Disable overlay for runtime errors.
// See: https://github.com/webpack/webpack-dev-server/issues/4771
runtimeErrors: false
}
}
},
entry: {
openmct: './openmct.js',
generatorWorker: './example/generator/generatorWorker.js',
@@ -100,6 +110,12 @@ const config = {
new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[name].css'
}),
// Add a UTF-8 BOM to CSS output to avoid random mojibake
new webpack.BannerPlugin({
test: /.*Theme\.css$/,
raw: true,
banner: '@charset "UTF-8";',
})
],
module: {
@@ -125,6 +141,7 @@ const config = {
loader: 'vue-loader',
options: {
compilerOptions: {
hoistStatic: false,
whitespace: 'preserve',
compatConfig: {
MODE: 2

View File

@@ -45,14 +45,6 @@ module.exports = merge(common, {
directory: path.join(__dirname, '..', '/dist'),
publicPath: '/dist',
watch: false
},
client: {
progress: true,
overlay: {
// Disable overlay for runtime errors.
// See: https://github.com/webpack/webpack-dev-server/issues/4771
runtimeErrors: false
}
}
}
});

176
API.md
View File

@@ -44,8 +44,10 @@
- [Clocks](#clocks)
- [Defining and registering clocks](#defining-and-registering-clocks)
- [Getting and setting active clock](#getting-and-setting-active-clock)
- [Stopping an active clock](#stopping-an-active-clock)
- [⚠️ \[DEPRECATED\] Stopping an active clock](#-deprecated-stopping-an-active-clock)
- [Clock Offsets](#clock-offsets)
- [Time Modes](#time-modes)
- [Time Mode Helper Methods](#time-mode-helper-methods)
- [Time Events](#time-events)
- [List of Time Events](#list-of-time-events)
- [The Time Conductor](#the-time-conductor)
@@ -685,8 +687,9 @@ state of the application, and emits events to inform listeners when the state ch
Because the data displayed tends to be time domain data, Open MCT must always
have at least one time system installed and activated. When you download Open
MCT, it will be pre-configured to use the UTC time system, which is installed and activated, along with other default plugins, in `index.html`. Installing and activating a time system is simple, and is covered
[in the next section](#defining-and-registering-time-systems).
MCT, it will be pre-configured to use the UTC time system, which is installed and activated,
along with other default plugins, in `index.html`. Installing and activating a time system
is simple, and is covered [in the next section](#defining-and-registering-time-systems).
### Time Systems and Bounds
@@ -737,28 +740,38 @@ numbers in UTC terrestrial time.
#### Getting and Setting the Active Time System
Once registered, a time system can be activated by calling `timeSystem` with
the timeSystem `key` or an instance of the time system. If you are not using a
[clock](#clocks), you must also specify valid [bounds](#time-bounds) for the
timeSystem.
Once registered, a time system can be activated by calling `setTimeSystem` with
the timeSystem `key` or an instance of the time system. You can also specify
valid [bounds](#time-bounds) for the timeSystem.
```javascript
openmct.time.timeSystem('utc', bounds);
openmct.time.setTimeSystem('utc', bounds);
```
The current time system can be retrieved as well by calling `getTimeSystem`.
```javascript
openmct.time.getTimeSystem();
```
A time system can be immediately activated after registration:
```javascript
openmct.time.addTimeSystem(utcTimeSystem);
openmct.time.timeSystem(utcTimeSystem, bounds);
openmct.time.setTimeSystem(utcTimeSystem, bounds);
```
Setting the active time system will trigger a [`'timeSystem'`](#time-events)
event. If you supplied bounds, a [`'bounds'`](#time-events) event will be triggered afterwards with your newly supplied bounds.
Setting the active time system will trigger a [`'timeSystemChanged'`](#time-events)
event. If you supplied bounds, a [`'boundsChanged'`](#time-events) event will be triggered afterwards with your newly supplied bounds.
> ⚠️ **Deprecated**
> - The method `timeSystem()` is deprecated. Please use `getTimeSystem()` and `setTimeSystem()` as a replacement.
#### Time Bounds
The TimeAPI provides a getter/setter for querying and setting time bounds. Time
The TimeAPI provides a getter and setter for querying and setting time bounds. Time
bounds are simply an object with a `start` and an end `end` attribute.
- `start`: A `number` representing a moment in time in the active [Time System](#defining-and-registering-time-systems).
@@ -768,26 +781,34 @@ telemetry views.
This will be used as the end of the time period displayed by time-responsive
telemetry views.
If invoked with bounds, it will set the new time bounds system-wide. If invoked
without any parameters, it will return the current application-wide time bounds.
New bounds can be set system wide by calling `setBounds` with [bounds](#time-bounds).
``` javascript
const ONE_HOUR = 60 * 60 * 1000;
let now = Date.now();
openmct.time.bounds({start: now - ONE_HOUR, now);
openmct.time.setBounds({start: now - ONE_HOUR, now);
```
To respond to bounds change events, listen for the [`'bounds'`](#time-events)
Calling `getBounds` will return the current application-wide time bounds.
``` javascript
openmct.time.getBounds();
```
To respond to bounds change events, listen for the [`'boundsChanged'`](#time-events)
event.
> ⚠️ **Deprecated**
> - The method `bounds()` is deprecated and will be removed in a future release. Please use `getBounds()` and `setBounds()` as a replacement.
### Clocks
The Time API can be set to follow a clock source which will cause the bounds
to be updated automatically whenever the clock source "ticks". A clock is simply
an object that supports registration of listeners and periodically invokes its
listeners with a number. Open MCT supports registration of new clock sources that
tick on almost anything. A tick occurs when the clock invokes callback functions
registered by its listeners with a new time value.
The Time API requires a clock source which will cause the bounds to be updated
automatically whenever the clock source "ticks". A clock is simply an object that
supports registration of listeners and periodically invokes its listeners with a
number. Open MCT supports registration of new clock sources that tick on almost
anything. A tick occurs when the clock invokes callback functions registered by its
listeners with a new time value.
An example of a clock source is the [LocalClock](https://github.com/nasa/openmct/blob/master/src/plugins/utcTimeSystem/LocalClock.js)
which emits the current time in UTC every 100ms. Clocks can tick on anything. For
@@ -855,23 +876,29 @@ An example clock implementation is provided in the form of the [LocalClock](http
#### Getting and setting active clock
Once registered a clock can be activated by calling the `clock` function on the
Once registered a clock can be activated by calling the `setClock` function on the
Time API passing in the key or instance of a registered clock. Only one clock
may be active at once, so activating a clock will deactivate any currently
active clock. [`clockOffsets`](#clock-offsets) must be specified when changing a clock.
active clock and start the new clock. [`clockOffsets`](#clock-offsets) must be specified when changing a clock.
Setting the clock triggers a [`'clock'`](#time-events) event, followed by a [`'clockOffsets'`](#time-events) event, and then a [`'bounds'`](#time-events) event as the offsets are applied to the clock's currentValue().
Setting the clock triggers a [`'clockChanged'`](#time-events) event, followed by a [`'clockOffsetsChanged'`](#time-events) event, and then a [`'boundsChanged'`](#time-events) event as the offsets are applied to the clock's currentValue().
```
openmct.time.clock(someClock, clockOffsets);
openmct.time.setClock(someClock, clockOffsets);
```
Upon being activated, the time API will listen for tick events on the clock by calling `clock.on`.
The currently active clock (if any) can be retrieved by calling the same
function without any arguments.
The currently active clock can be retrieved by calling `getClock`.
#### Stopping an active clock
```
openmct.time.getClock();
```
> ⚠️ **Deprecated**
> - The method `clock()` is deprecated and will be removed in a future release. Please use `getClock()` and `setClock()` as a replacement.
#### ⚠️ [DEPRECATED] Stopping an active clock
_As of July 2023, this method will be deprecated. Open MCT will always have a ticking clock._
@@ -882,12 +909,14 @@ will stop the clock from ticking, and set the active clock to `undefined`.
openmct.time.stopClock();
```
> ⚠️ **Deprecated**
> - The method `stopClock()` is deprecated and will be removed in a future release.
#### Clock Offsets
When a clock is active, the time bounds of the application will be updated
automatically each time the clock "ticks". The bounds are calculated based on
the current value provided by the active clock (via its `tick` event, or its
`currentValue()` method).
When in Real-time [mode](#time-modes), the time bounds of the application will be updated automatically each time the
clock "ticks". The bounds are calculated based on the current value provided by
the active clock (via its `tick` event, or its `currentValue()` method).
Unlike bounds, which represent absolute time values, clock offsets represent
relative time spans. Offsets are defined as an object with two properties:
@@ -898,21 +927,77 @@ value provided by a clock's tick callback, or its `currentValue()` function.
- `end`: A `number` that must be >= 0 and which is used to calculate the end
bounds on each clock tick.
The `clockOffsets` function can be used to get or set clock offsets. For example,
The `setClockOffsets` function can be used to get or set clock offsets. For example,
to show the last fifteen minutes in a ms-based time system:
```javascript
var FIFTEEN_MINUTES = 15 * 60 * 1000;
openmct.time.clockOffsets({
openmct.time.setClockOffsets({
start: -FIFTEEN_MINUTES,
end: 0
})
```
The `getClockOffsets` method will return the currently set clock offsets.
```javascript
openmct.time.getClockOffsets()
```
**Note:** Setting the clock offsets will trigger an immediate bounds change, as
new bounds will be calculated based on the `currentValue()` of the active clock.
Clock offsets are only relevant when a clock source is active.
Clock offsets are only relevant when in Real-time [mode](#time-modes).
> ⚠️ **Deprecated**
> - The method `clockOffsets()` is deprecated and will be removed in a future release. Please use `getClockOffsets()` and `setClockOffsets()` as a replacement.
### Time Modes
There are two time modes in Open MCT, "Fixed" and "Real-time". In Real-time mode the
time bounds of the application will be updated automatically each time the clock "ticks".
The bounds are calculated based on the current value provided by the active clock. In
Fixed mode, the time bounds are set for a specified time range. When Open MCT is first
initialized, it will be in Real-time mode.
The `setMode` method can be used to set the current time mode. It accepts a mode argument,
`'realtime'` or `'fixed'` and it also accepts an optional [offsets](#clock-offsets)/[bounds](#time-bounds) argument dependent
on the current mode.
``` javascript
openmct.time.setMode('fixed');
openmct.time.setMode('fixed', bounds); // with optional bounds
```
or
``` javascript
openmct.time.setMode('realtime');
openmct.time.setMode('realtime', offsets); // with optional offsets
```
The `getMode` method will return the current time mode, either `'realtime'` or `'fixed'`.
``` javascript
openmct.time.getMode();
```
#### Time Mode Helper Methods
There are two methods available to determine the current time mode in Open MCT programmatically,
`isRealTime` and `isFixed`. Each one will return a boolean value based on the current mode.
``` javascript
if (openmct.time.isRealTime()) {
// do real-time stuff
}
```
``` javascript
if (openmct.time.isFixed()) {
// do fixed-time stuff
}
```
### Time Events
@@ -921,7 +1006,7 @@ The Time API is a standard event emitter; you can register callbacks for events
For example:
``` javascript
openmct.time.on('bounds', function callback (newBounds, tick) {
openmct.time.on('boundsChanged', function callback (newBounds, tick) {
// Do something with new bounds
});
```
@@ -930,7 +1015,7 @@ openmct.time.on('bounds', function callback (newBounds, tick) {
The events emitted by the Time API are:
- `bounds`: emitted whenever the bounds change. The callback will be invoked
- `boundsChanged`: emitted whenever the bounds change. The callback will be invoked
with two arguments:
- `bounds`: A [bounds](#getting-and-setting-bounds) bounds object
representing a new time period bound by the specified start and send times.
@@ -945,15 +1030,24 @@ The events emitted by the Time API are:
If `tick` is false,then the bounds change was not due to an automatic tick,
and a query for historical data may be necessary, depending on your data
caching strategy, and how significantly the start bound has changed.
- `timeSystem`: emitted whenever the active time system changes. The callback will be invoked with a single argument:
- `timeSystemChanged`: emitted whenever the active time system changes. The callback will be invoked with a single argument:
- `timeSystem`: The newly active [time system](#defining-and-registering-time-systems).
- `clock`: emitted whenever the clock changes. The callback will be invoked
- `clockChanged`: emitted whenever the clock changes. The callback will be invoked
with a single argument:
- `clock`: The newly active [clock](#clocks), or `undefined` if an active
clock has been deactivated.
- `clockOffsets`: emitted whenever the active clock offsets change. The
- `clockOffsetsChanged`: emitted whenever the active clock offsets change. The
callback will be invoked with a single argument:
- `clockOffsets`: The new [clock offsets](#clock-offsets).
- `modeChanged`: emitted whenever the time [mode](#time-modes) changed. The callback will
be invoked with one argument:
- `mode`: A string representation of the current time mode, either `'realtime'` or `'fixed'`.
> ⚠️ **Deprecated Events** (These will be removed in a future release):
> - `bounds` → `boundsChanged`
> - `timeSystem` → `timeSystemChanged`
> - `clock` → `clockChanged`
> - `clockOffsets` → `clockOffsetsChanged`
### The Time Conductor

View File

@@ -2,7 +2,9 @@
Open MCT (Open Mission Control Technologies) is a next-generation mission control framework for visualization of data on desktop and mobile devices. It is developed at NASA's Ames Research Center, and is being used by NASA for data analysis of spacecraft missions, as well as planning and operation of experimental rover systems. As a generalizable and open source framework, Open MCT could be used as the basis for building applications for planning, operation, and analysis of any systems producing telemetry data.
Please visit our [Official Site](https://nasa.github.io/openmct/) and [Getting Started Guide](https://nasa.github.io/openmct/getting-started/)
> [!NOTE]
> Please visit our [Official Site](https://nasa.github.io/openmct/) and [Getting Started Guide](https://nasa.github.io/openmct/getting-started/)
Once you've created something amazing with Open MCT, showcase your work in our GitHub Discussions [Show and Tell](https://github.com/nasa/openmct/discussions/categories/show-and-tell) section. We love seeing unique and wonderful implementations of Open MCT!
@@ -14,20 +16,32 @@ Once you've created something amazing with Open MCT, showcase your work in our G
Building and running Open MCT in your local dev environment is very easy. Be sure you have [Git](https://git-scm.com/downloads) and [Node.js](https://nodejs.org/) installed, then follow the directions below. Need additional information? Check out the [Getting Started](https://nasa.github.io/openmct/getting-started/) page on our website.
(These instructions assume you are installing as a non-root user; developers have [reported issues](https://github.com/nasa/openmct/issues/1151) running these steps with root privileges.)
1. Clone the source code
1. Clone the source code:
`git clone https://github.com/nasa/openmct.git`
```
git clone https://github.com/nasa/openmct.git
```
2. (Optionally) Install the correct node version using [nvm](https://github.com/nvm-sh/nvm) (`nvm install`)
3. Install development dependencies. Note: Check the package.json engine for our tested and supported node versions.
2. (Optional) Install the correct node version using [nvm](https://github.com/nvm-sh/nvm):
`npm install`
```
nvm install
```
4. Run a local development server
3. Install development dependencies (Note: Check the `package.json` engine for our tested and supported node versions):
`npm start`
```
npm install
```
Open MCT is now running, and can be accessed by pointing a web browser at [http://localhost:8080/](http://localhost:8080/)
4. Run a local development server:
```
npm start
```
> [!IMPORTANT]
> Open MCT is now running, and can be accessed by pointing a web browser at [http://localhost:8080/](http://localhost:8080/)
Open MCT is built using [`npm`](http://npmjs.com/) and [`webpack`](https://webpack.js.org/).
@@ -41,8 +55,12 @@ The clearest examples for developing Open MCT plugins are in the
[tutorials](https://github.com/nasa/openmct-tutorial) provided in
our documentation.
We want Open MCT to be as easy to use, install, run, and develop for as
possible, and your feedback will help us get there! Feedback can be provided via [GitHub issues](https://github.com/nasa/openmct/issues/new/choose), [Starting a GitHub Discussion](https://github.com/nasa/openmct/discussions), or by emailing us at [arc-dl-openmct@mail.nasa.gov](mailto:arc-dl-openmct@mail.nasa.gov).
> [!NOTE]
> We want Open MCT to be as easy to use, install, run, and develop for as
> possible, and your feedback will help us get there!
> Feedback can be provided via [GitHub issues](https://github.com/nasa/openmct/issues/new/choose),
> [Starting a GitHub Discussion](https://github.com/nasa/openmct/discussions),
> or by emailing us at [arc-dl-openmct@mail.nasa.gov](mailto:arc-dl-openmct@mail.nasa.gov).
## Developing Applications With Open MCT

View File

@@ -3,15 +3,22 @@ snapshot:
widths: [1024]
min-height: 1440 # px
percyCSS: |
/* Clock indicator... your days are numbered */
.t-indicator-clock > .label {
opacity: 0 !important;
}
.c-input--datetime {
opacity: 0 !important;
}
/* Timer object text */
.c-ne__time-and-creator {
opacity: 0 !important;
}
/* Time Conductor ticks */
div.c-conductor-axis.c-conductor__ticks > svg {
opacity: 0 !important;
}
div.c-inspector__properties.c-inspect-properties > ul > li:nth-child(3) {
display: none !important;
}
/* Embedded timestamp in notebooks */
.c-ne__embed__time{
opacity: 0 !important;
}

View File

@@ -3,15 +3,22 @@ snapshot:
widths: [1024, 2000]
min-height: 1440 # px
percyCSS: |
/* Clock indicator... your days are numbered */
.t-indicator-clock > .label {
opacity: 0 !important;
}
.c-input--datetime {
opacity: 0 !important;
}
/* Timer object text */
.c-ne__time-and-creator {
opacity: 0 !important;
}
/* Time Conductor ticks */
div.c-conductor-axis.c-conductor__ticks > svg {
opacity: 0 !important;
}
div.c-inspector__properties.c-inspect-properties > ul > li:nth-child(3) {
display: none !important;
}
/* Embedded timestamp in notebooks */
.c-ne__embed__time{
opacity: 0 !important;
}

View File

@@ -82,13 +82,20 @@ To make this possible, we're leveraging a 3rd party service, [Percy](https://per
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
### Advanced: Snapshot Testing (Not Recommended)
Snapshot testing is very similar to visual testing but allows us to be more precise in detecting change without relying on a 3rd party service. Unfortuantely, this precision requires advanced test setup and teardown and so we're using this pattern as a last resort.
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.
To give an example, if a _single_ visual test assertion for an Overlay plot is run through multiple DOM rendering engines at various viewports to see how the Plot looks. If that same test were run as a snapshot test, it could only be executed against a single browser, on a single platform (ubuntu docker container).
#### 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.
Read more about [Playwright Snapshots](https://playwright.dev/docs/test-snapshots)
#### Open MCT's implementation
@@ -127,11 +134,11 @@ npm run test:e2e:updatesnapshots
## Performance Testing
The open source performance tests function mostly as a contract for the locator logic, functionality, and assumptions will work in our downstream, closed source test suites.
The open source performance tests function in three ways which match their naming and folder structure:
They're found under `./e2e/tests/performance` and are to be executed with the following npm script:
`npm run test:perf`
`./e2e/tests/performance` - The tests at the root of this folder path detect functional changes which are mostly apparent with large performance regressions like [this](https://github.com/nasa/openmct/issues/6879). These tests run against openmct webpack in `production-mode` with the `npm run test:perf:localhost` script.
`./e2e/tests/performance/contract/` - These tests serve as [contracts](https://martinfowler.com/bliki/ContractTest.html) for the locator logic, functionality, and assumptions will work in our downstream, closed source test suites. These tests run against openmct webpack in `dev-mode` with the `npm run test:perf:contract` script.
`./e2e/tests/performance/memory/` - These tests execute memory leak detection checks in various ways. This is expected to evolve as we move to the `memlab` project. These tests run against openmct webpack in `production-mode` with the `npm run test:perf:memory` script.
These tests are expected to become blocking and gating with assertions as we extend the capabilities of Playwright.
@@ -151,8 +158,11 @@ Our file structure follows the type of type of testing being excercised at the e
|`./tests/functional/example/` | Tests which specifically verify the example plugins (e.g.: Sine Wave Generator).|
|`./tests/functional/plugins/` | Tests which loosely test each plugin. This folder is the most likely to change. Note: some `@snapshot` tests are still contained within this structure.|
|`./tests/framework/` | Tests which verify that our testing framework's functionality and assumptions will continue to work based on further refactoring or Playwright version changes (e.g.: verifying custom fixtures and appActions).|
|`./tests/performance/` | Performance tests.|
|`./tests/performance/` | Performance tests which should be run on every commit.|
|`./tests/performance/contract/` | A subset of performance tests which are designed to provide a contract between the open source tests which are run on every commit and the downstream tests which are run post merge and with other frameworks.|
|`./tests/performance/memory` | A subset of performance tests which are designed to test for memory leaks.|
|`./tests/visual/` | Visual tests.|
|`./tests/visual/component/` | Visual tests which are only run against a single component.|
|`./appActions.js` | Contains common methods which can be leveraged by test case authors to quickly move through the application when writing new tests.|
|`./baseFixture.js` | Contains base fixtures which only extend default `@playwright/test` functionality. The expectation is that these fixtures will be removed as the native Playwright API improves|
@@ -169,6 +179,7 @@ Open MCT is leveraging the [config file](https://playwright.dev/docs/test-config
|`./playwright-ci.config.js` | Used when running in CI or to debug CI issues locally|
|`./playwright-local.config.js` | Used when running locally|
|`./playwright-performance.config.js` | Used when running performance tests in CI or locally|
|`./playwright-performance-devmode.config.js` | Used when running performance tests in CI or locally|
|`./playwright-visual.config.js` | Used to run the visual tests in CI or locally|
#### Test Tags
@@ -308,14 +319,27 @@ Skipping based on browser version (Rarely used): <https://github.com/microsoft/p
## Test Design, Best Practices, and Tips & Tricks
### Test Design (TODO)
### Test Design
- How to make tests robust to function in other contexts (VISTA, VIPER, etc.)
- Leverage the use of `appActions.js` methods such as `createDomainObjectWithDefaults()`
- How to make tests faster and more resilient
- When possible, navigate directly by URL:
#### Test as the User
```javascript
In general, strive to test only through the UI as a user would. As stated in the [Playwright Best Practices](https://playwright.dev/docs/best-practices#test-user-visible-behavior):
> "Automated tests should verify that the application code works for the end users, and avoid relying on implementation details such as things which users will not typically use, see, or even know about such as the name of a function, whether something is an array, or the CSS class of some element. The end user will see or interact with what is rendered on the page, so your test should typically only see/interact with the same rendered output."
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.
1. Avoid creating locator aliases. This likely means that you're compensating for a bad locator. Improve the application instead.
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
// You can capture the CreatedObjectInfo returned from this appAction:
const clock = await createDomainObjectWithDefaults(page, { type: 'Clock' });
@@ -323,12 +347,14 @@ Skipping based on browser version (Rarely used): <https://github.com/microsoft/p
await page.goto(clock.url);
```
- Leverage `await page.goto('./', { waitUntil: 'domcontentloaded' });`
1. Leverage `await page.goto('./', { waitUntil: 'domcontentloaded' });`
- Initial navigation should _almost_ always use the `{ waitUntil: 'domcontentloaded' }` option.
- Avoid repeated setup to test 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.
### How to write a great test
- Avoid using css locators to find elements to the page. Use modern web accessible locators like `getByRole`
- Use our [App Actions](./appActions.js) for performing common actions whenever applicable.
- Use `waitForPlotsToRender()` before asserting against anything that is dependent upon plot series data being loaded and drawn.
- If you create an object outside of using the `createDomainObjectWithDefaults` App Action, make sure to fill in the 'Notes' section of your object with `page.testNotes`:
@@ -341,26 +367,39 @@ Skipping based on browser version (Rarely used): <https://github.com/microsoft/p
await notesInput.fill(testNotes);
```
#### How to write a great visual test
#### How to Write a Great Visual Test
- Generally speaking, you should avoid being "specific" in what you hope to find in the diff. Visual tests are best suited for finding unknown unknowns.
- These should only use functional expect statements to verify assumptions about the state
in a test and not for functional verification of correctness. Visual tests are not supposed
to "fail" on assertions. Instead, they should be used to detect changes between builds or branches.
- A great visual test controls for the variation inherent to working with time-based telemetry and clocks. We do our best to remove this variation by using `percyCSS` to ignore all possible time-based components. For more, please see our [percyCSS file](./.percy.ci.yml).
- Additionally, you should try the following:
- Use fixed-time mode of Open MCT
- Use the `createExampleTelemetryObject` appAction to source telemetry
- When using the `createDomainObjectWithDefaults` appAction, make sure to specify a `name` which is explicit to avoid the autogenerated name
- Very likely, your test will not need to compare changes on the tree. Keep it out of the comparison with the following
- `await page.goto('./#/browse/mine')` will go to the root of the main view with the tree collapsed.
- If you only want to compare changes on a specific component, use the /visual/component/ folder and limit the scope of the comparison to the object like so:
- ```
await percySnapshot(page, `Tree Pane w/ single level expanded (theme: ${theme})`, {
scope: treePane
});
1. **Look for the Unknown Unknowns**: Avoid asserting on specific differences in the visual diff. Visual tests are most effective for identifying unknown unknowns.
2. **Get the App into Interesting States**: Prioritize getting Open MCT into unusual layouts or behaviors before capturing a visual snapshot. For instance, you could open a dropdown menu.
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.
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/component/` folder and limit the scope of the comparison to that component. For instance:
```js
- The `scope` variable can be any valid css selector
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})`);
//<Click on object>
await percySnapshot(page, `object expanded (theme: ${theme})`);
//Select from object
await percySnapshot(page, `object selected (theme: ${theme})`)
```
#### How to write a great network test
@@ -377,12 +416,35 @@ For now, our best practices exist as self-tested, living documentation in our [e
For best practices with regards to mocking network responses, see our [couchdb.e2e.spec.js](./tests/functional/couchdb.e2e.spec.js) file.
### Tips & Tricks (TODO)
### Tips & Tricks
The following contains a list of tips and tricks which don't exactly fit into a FAQ or Best Practices doc.
- (Advanced) Overriding the Browser's Clock
It is possible to override the browser's clock in order to control time-based elements. Since this can cause unwanted behavior (i.e. Tree not rendering), only use this sparingly. To do this, use the `overrideClock` fixture as such:
```js
const { test, expect } = require('../../pluginFixtures.js');
test.describe('foo test suite', () => {
// All subsequent tests in this suite will override the clock
test.use({
clockOptions: {
now: 1732413600000, // A timestamp given as milliseconds since the epoch
shouldAdvanceTime: true // Should the clock tick?
}
});
test('bar test', async ({ page }) => {
// ...
});
});
```
More info and options for `overrideClock` can be found in [baseFixtures.js](baseFixtures.js)
- Working with multiple pages
There are instances where multiple browser pages will need to be opened to verify multi-page or multi-tab application behavior.
There are instances where multiple browser pages will needed to verify multi-page or multi-tab application behavior. Make sure to use the `@2p` annotation as well as name each page appropriately: i.e. `page1` and `page2` or `tab1` and `tab2` depending on the intended use case. Generally pages should be used unless testing `sharedWorker` code, specifically.
### Reporting

View File

@@ -78,7 +78,7 @@ async function createDomainObjectWithDefaults(
// Navigate to the parent object. This is necessary to create the object
// in the correct location, such as a folder, layout, or plot.
await page.goto(`${parentUrl}?hideTree=true`);
await page.goto(`${parentUrl}`);
//Click the Create button
await page.click('button:has-text("Create")');
@@ -179,7 +179,7 @@ async function createPlanFromJSON(page, { name, json, parent = 'mine' }) {
// Navigate to the parent object. This is necessary to create the object
// in the correct location, such as a folder, layout, or plot.
await page.goto(`${parentUrl}?hideTree=true`);
await page.goto(`${parentUrl}`);
// Click the Create button
await page.click('button:has-text("Create")');
@@ -228,17 +228,15 @@ async function createPlanFromJSON(page, { name, json, parent = 'mine' }) {
*/
async function createExampleTelemetryObject(page, parent = 'mine') {
const parentUrl = await getHashUrlToDomainObject(page, parent);
// TODO: Make this field even more accessible
const name = 'VIPER Rover Heading';
const nameInputLocator = page.getByRole('dialog').locator('input[type="text"]');
await page.goto(`${parentUrl}?hideTree=true`);
await page.goto(`${parentUrl}`);
await page.locator('button:has-text("Create")').click();
await page.locator('li:has-text("Sine Wave Generator")').click();
await nameInputLocator.fill(name);
const name = 'VIPER Rover Heading';
await page.getByRole('dialog').locator('input[type="text"]').fill(name);
// Fill out the fields with default values
await page.getByRole('spinbutton', { name: 'Period' }).fill('10');
@@ -386,11 +384,13 @@ async function setTimeConductorMode(page, isFixedTimespan = true) {
// Click 'mode' button
await page.getByRole('button', { name: 'Time Conductor Mode', exact: true }).click();
await page.getByRole('button', { name: 'Time Conductor Mode Menu' }).click();
// Switch time conductor mode
// Switch time conductor mode. Note, need to wait here for URL to update as the router is debounced.
if (isFixedTimespan) {
await page.getByRole('menuitem', { name: /Fixed Timespan/ }).click();
await page.waitForURL(/tc\.mode=fixed/);
} else {
await page.getByRole('menuitem', { name: /Real-Time/ }).click();
await page.waitForURL(/tc\.mode=local/);
}
}
@@ -520,6 +520,7 @@ async function setIndependentTimeConductorBounds(page, startDate, endDate) {
/**
* Set the bounds of the visible conductor in fixed time mode
* @private
* @param {import('@playwright/test').Page} page
* @param {string} startDate
* @param {string} endDate
@@ -544,18 +545,6 @@ async function setTimeBounds(page, startDate, endDate) {
}
}
/**
* Selects an inspector tab based on the provided tab name
*
* @param {import('@playwright/test').Page} page
* @param {String} name the name of the tab
*/
async function selectInspectorTab(page, name) {
const inspectorTabs = page.getByRole('tablist');
const inspectorTab = inspectorTabs.getByTitle(name);
await inspectorTab.click();
}
/**
* Waits and asserts that all plot series data on the page
* is loaded and drawn.
@@ -674,7 +663,6 @@ module.exports = {
setEndOffset,
setTimeConductorBounds,
setIndependentTimeConductorBounds,
selectInspectorTab,
waitForPlotsToRender,
renameObjectFromContextMenu
};

View File

@@ -81,7 +81,7 @@ exports.test = base.test.extend({
* ```js
* test.use({
* clockOptions: {
* now: 0,
* now: MISSION_TIME,
* shouldAdvanceTime: true
* ```
* If clockOptions are provided, will override the default clock with fake timers provided by SinonJS.

View File

@@ -1,3 +1,4 @@
/* eslint-disable prettier/prettier */
/**
* Constants which may be used across all e2e tests.
*/
@@ -7,3 +8,11 @@
* - 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)
/**
* 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
*/
export const VISUAL_URL = './#/browse/mine?tc.mode=fixed&tc.startBound=1693592063607&tc.endBound=1693593893607&tc.timeSystem=utc&view=grid&hideInspector=true&hideTree=true';

View File

@@ -20,7 +20,7 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
const { selectInspectorTab, createDomainObjectWithDefaults } = require('../appActions');
const { createDomainObjectWithDefaults } = require('../appActions');
const NOTEBOOK_DROP_AREA = '.c-notebook__drag-area';
const CUSTOM_NAME = 'CUSTOM_NAME';
@@ -34,7 +34,8 @@ async function enterTextEntry(page, text) {
await page.locator(NOTEBOOK_DROP_AREA).click();
// enter text
await page.locator('[aria-label="Notebook Entry"].is-selected div.c-ne__text').fill(text);
await page.getByLabel('Notebook Entry Display').last().click();
await page.getByLabel('Notebook Entry Input').last().fill(text);
await commitEntry(page);
}
@@ -52,7 +53,6 @@ async function dragAndDropEmbed(page, notebookObject) {
await page.click('button[title="Show selected item in tree"]');
// Drag and drop the SWG into the notebook
await page.dragAndDrop(`text=${swg.name}`, NOTEBOOK_DROP_AREA);
await commitEntry(page);
}
/**
@@ -111,7 +111,7 @@ async function createNotebookAndEntry(page, iterations = 1) {
*/
async function createNotebookEntryAndTags(page, iterations = 1) {
const notebook = await createNotebookAndEntry(page, iterations);
await selectInspectorTab(page, 'Annotations');
await page.getByRole('tab', { name: 'Annotations' }).click();
for (let iteration = 0; iteration < iterations; iteration++) {
// Hover and click "Add Tag" button

View File

@@ -99,3 +99,14 @@ export async function setBoundsToSpanAllActivities(page, planJson, planObjectUrl
`${planObjectUrl}?tc.mode=fixed&tc.startBound=${start}&tc.endBound=${end}&tc.timeSystem=utc&view=plan.view`
);
}
/**
* Uses the Open MCT API to set the status of a plan to 'draft'.
* @param {import('@playwright/test').Page} page
* @param {import('../../appActions').CreatedObjectInfo} plan
*/
export async function setDraftStatusForPlan(page, plan) {
await page.evaluate(async (planObject) => {
await window.openmct.status.set(planObject.uuid, 'draft');
}, plan);
}

View File

@@ -2,25 +2,24 @@
// playwright.config.js
// @ts-check
const CI = process.env.CI === 'true';
/** @type {import('@playwright/test').PlaywrightTestConfig} */
const config = {
retries: 1, //Only for debugging purposes for trace: 'on-first-retry'
testDir: 'tests/performance/',
testMatch: '*.contract.perf.spec.js', //Run everything except contract tests which require marks in dev mode
timeout: 60 * 1000,
workers: 1, //Only run in serial with 1 worker
webServer: {
command: 'npm run start', //coverage not generated
command: 'npm run start', //need development mode for performance.marks and others
url: 'http://localhost:8080/#',
timeout: 200 * 1000,
reuseExistingServer: !CI
reuseExistingServer: false
},
use: {
browserName: 'chromium',
baseURL: 'http://localhost:8080/',
headless: CI, //Only if running locally
ignoreHTTPSErrors: true,
headless: true,
ignoreHTTPSErrors: false, //HTTP performance varies!
screenshot: 'off',
trace: 'on-first-retry',
video: 'off'
@@ -28,6 +27,7 @@ const config = {
projects: [
{
name: 'chrome',
testIgnore: '*.memory.perf.spec.js', //Do not run memory tests without proper flags. Shouldn't get here
use: {
browserName: 'chromium'
}

View File

@@ -0,0 +1,60 @@
/* eslint-disable no-undef */
// playwright.config.js
// @ts-check
/** @type {import('@playwright/test').PlaywrightTestConfig} */
const config = {
retries: 0, //Only for debugging purposes for trace: 'on-first-retry'
testDir: 'tests/performance/',
testIgnore: '*.contract.perf.spec.js', //Run everything except contract tests which require marks in dev mode
timeout: 60 * 1000,
workers: 1, //Only run in serial with 1 worker
webServer: {
command: 'npm run start:prod', //Production mode
url: 'http://localhost:8080/#',
timeout: 200 * 1000,
reuseExistingServer: false //Must be run with this option to prevent dev mode
},
use: {
baseURL: 'http://localhost:8080/',
headless: true,
ignoreHTTPSErrors: false, //HTTP performance varies!
screenshot: 'off',
trace: 'on-first-retry',
video: 'off'
},
projects: [
{
name: 'chrome-memory',
testMatch: '*.memory.perf.spec.js', //Only run memory tests
use: {
browserName: 'chromium',
launchOptions: {
args: [
'--no-sandbox',
'--disable-notifications',
'--use-fake-ui-for-media-stream',
'--use-fake-device-for-media-stream',
'--js-flags=--no-move-object-start --expose-gc',
'--enable-precise-memory-info',
'--display=:100'
]
}
}
},
{
name: 'chrome',
testIgnore: '*.memory.perf.spec.js', //Do not run memory tests without proper flags
use: {
browserName: 'chromium'
}
}
],
reporter: [
['list'],
['junit', { outputFile: '../test-results/results.xml' }],
['json', { outputFile: '../test-results/results.json' }]
]
};
module.exports = config;

File diff suppressed because one or more lines are too long

View File

@@ -6,21 +6,17 @@
"localStorage": [
{
"name": "mct",
"value": "{\"mine\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"8c863964-4640-4db1-8a98-0e546c3c271d\",\"namespace\":\"\"},{\"key\":\"f5c7e86c-a5b4-4c6c-9038-9cf03cd9a99e\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1678741852556,\"created\":1678741830859,\"persisted\":1678741852557},\"8c863964-4640-4db1-8a98-0e546c3c271d\":{\"identifier\":{\"key\":\"8c863964-4640-4db1-8a98-0e546c3c271d\",\"namespace\":\"\"},\"name\":\"Overlay Plot with Telemetry Object\",\"type\":\"telemetry.plot.overlay\",\"composition\":[{\"key\":\"f5c7e86c-a5b4-4c6c-9038-9cf03cd9a99e\",\"namespace\":\"\"}],\"configuration\":{\"series\":[{\"identifier\":{\"key\":\"f5c7e86c-a5b4-4c6c-9038-9cf03cd9a99e\",\"namespace\":\"\"}}]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate Overlay Plot with Telemetry Object\\nchrome\",\"modified\":1678741862011,\"location\":\"mine\",\"created\":1678741839461,\"persisted\":1678741862011},\"f5c7e86c-a5b4-4c6c-9038-9cf03cd9a99e\":{\"name\":\"VIPER Rover Heading\",\"type\":\"generator\",\"identifier\":{\"key\":\"f5c7e86c-a5b4-4c6c-9038-9cf03cd9a99e\",\"namespace\":\"\"},\"telemetry\":{\"period\":\"1\",\"amplitude\":\"1\",\"offset\":\"0\",\"dataRateInHz\":\"1\",\"phase\":\"0\",\"randomness\":\"0\",\"loadDelay\":\"0\",\"infinityValues\":false,\"staleness\":false},\"modified\":1678741852553,\"location\":\"mine\",\"created\":1678741852553,\"persisted\":1678741852553}}"
"value": "{\"mine\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"20e7d5fe-9cf8-4099-8957-9453a8954c67\",\"namespace\":\"\"},{\"key\":\"2db521a9-996d-4d04-a171-93f4c5c220af\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413602540,\"created\":1732413600760,\"persisted\":1732413602540},\"20e7d5fe-9cf8-4099-8957-9453a8954c67\":{\"identifier\":{\"key\":\"20e7d5fe-9cf8-4099-8957-9453a8954c67\",\"namespace\":\"\"},\"name\":\"Overlay Plot with Telemetry Object\",\"type\":\"telemetry.plot.overlay\",\"composition\":[{\"key\":\"2db521a9-996d-4d04-a171-93f4c5c220af\",\"namespace\":\"\"}],\"configuration\":{\"series\":[]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate Overlay Plot with Telemetry Object\\nchrome\",\"modified\":1732413603960,\"location\":\"mine\",\"created\":1732413601820,\"persisted\":1732413603960},\"2db521a9-996d-4d04-a171-93f4c5c220af\":{\"name\":\"VIPER Rover Heading\",\"type\":\"generator\",\"identifier\":{\"key\":\"2db521a9-996d-4d04-a171-93f4c5c220af\",\"namespace\":\"\"},\"telemetry\":{\"period\":10,\"amplitude\":1,\"offset\":0,\"dataRateInHz\":1,\"phase\":0,\"randomness\":0,\"loadDelay\":0,\"infinityValues\":false,\"staleness\":false},\"modified\":1732413602540,\"location\":\"mine\",\"created\":1732413602540,\"persisted\":1732413602540}}"
},
{
"name": "mct-recent-objects",
"value": "[{\"objectPath\":[{\"identifier\":{\"key\":\"2db521a9-996d-4d04-a171-93f4c5c220af\",\"namespace\":\"\"},\"name\":\"VIPER Rover Heading\",\"type\":\"generator\",\"telemetry\":{\"period\":10,\"amplitude\":1,\"offset\":0,\"dataRateInHz\":1,\"phase\":0,\"randomness\":0,\"loadDelay\":0,\"infinityValues\":false,\"staleness\":false},\"modified\":1732413602540,\"location\":\"mine\",\"created\":1732413602540,\"persisted\":1732413602540},{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"20e7d5fe-9cf8-4099-8957-9453a8954c67\",\"namespace\":\"\"},{\"key\":\"2db521a9-996d-4d04-a171-93f4c5c220af\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413602540,\"created\":1732413600760,\"persisted\":1732413602540},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine/2db521a9-996d-4d04-a171-93f4c5c220af\",\"domainObject\":{\"identifier\":{\"key\":\"2db521a9-996d-4d04-a171-93f4c5c220af\",\"namespace\":\"\"},\"name\":\"VIPER Rover Heading\",\"type\":\"generator\",\"telemetry\":{\"period\":10,\"amplitude\":1,\"offset\":0,\"dataRateInHz\":1,\"phase\":0,\"randomness\":0,\"loadDelay\":0,\"infinityValues\":false,\"staleness\":false},\"modified\":1732413602540,\"location\":\"mine\",\"created\":1732413602540,\"persisted\":1732413602540}},{\"objectPath\":[{\"identifier\":{\"key\":\"20e7d5fe-9cf8-4099-8957-9453a8954c67\",\"namespace\":\"\"},\"name\":\"Overlay Plot with Telemetry Object\",\"type\":\"telemetry.plot.overlay\",\"composition\":[{\"key\":\"2db521a9-996d-4d04-a171-93f4c5c220af\",\"namespace\":\"\"}],\"configuration\":{\"series\":[{\"identifier\":{\"key\":\"2db521a9-996d-4d04-a171-93f4c5c220af\",\"namespace\":\"\"}}]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate Overlay Plot with Telemetry Object\\nchrome\",\"modified\":1732413603960,\"location\":\"mine\",\"created\":1732413601820,\"persisted\":1732413603960},{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"20e7d5fe-9cf8-4099-8957-9453a8954c67\",\"namespace\":\"\"},{\"key\":\"2db521a9-996d-4d04-a171-93f4c5c220af\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413602540,\"created\":1732413600760,\"persisted\":1732413602540},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine/20e7d5fe-9cf8-4099-8957-9453a8954c67\",\"domainObject\":{\"identifier\":{\"key\":\"20e7d5fe-9cf8-4099-8957-9453a8954c67\",\"namespace\":\"\"},\"name\":\"Overlay Plot with Telemetry Object\",\"type\":\"telemetry.plot.overlay\",\"composition\":[{\"key\":\"2db521a9-996d-4d04-a171-93f4c5c220af\",\"namespace\":\"\"}],\"configuration\":{\"series\":[{\"identifier\":{\"key\":\"2db521a9-996d-4d04-a171-93f4c5c220af\",\"namespace\":\"\"}}]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate Overlay Plot with Telemetry Object\\nchrome\",\"modified\":1732413603960,\"location\":\"mine\",\"created\":1732413601820,\"persisted\":1732413603960}},{\"objectPath\":[{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"20e7d5fe-9cf8-4099-8957-9453a8954c67\",\"namespace\":\"\"},{\"key\":\"2db521a9-996d-4d04-a171-93f4c5c220af\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413602540,\"created\":1732413600760,\"persisted\":1732413602540},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine\",\"domainObject\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"20e7d5fe-9cf8-4099-8957-9453a8954c67\",\"namespace\":\"\"},{\"key\":\"2db521a9-996d-4d04-a171-93f4c5c220af\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413602540,\"created\":1732413600760,\"persisted\":1732413602540}}]"
},
{
"name": "mct-tree-expanded",
"value": "[]"
},
{
"name": "tcHistory",
"value": "{\"utc\":[{\"start\":1678740030748,\"end\":1678741830748}]}"
},
{
"name": "mct-recent-objects",
"value": "[{\"objectPath\":[{\"identifier\":{\"key\":\"8c863964-4640-4db1-8a98-0e546c3c271d\",\"namespace\":\"\"},\"name\":\"Overlay Plot with Telemetry Object\",\"type\":\"telemetry.plot.overlay\",\"composition\":[{\"key\":\"f5c7e86c-a5b4-4c6c-9038-9cf03cd9a99e\",\"namespace\":\"\"}],\"configuration\":{\"series\":[]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate Overlay Plot with Telemetry Object\\nchrome\",\"modified\":1678741860389,\"location\":\"mine\",\"created\":1678741839461,\"persisted\":1678741860389},{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"8c863964-4640-4db1-8a98-0e546c3c271d\",\"namespace\":\"\"},{\"key\":\"f5c7e86c-a5b4-4c6c-9038-9cf03cd9a99e\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1678741852556,\"created\":1678741830859,\"persisted\":1678741852557},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine/8c863964-4640-4db1-8a98-0e546c3c271d\",\"domainObject\":{\"identifier\":{\"key\":\"8c863964-4640-4db1-8a98-0e546c3c271d\",\"namespace\":\"\"},\"name\":\"Overlay Plot with Telemetry Object\",\"type\":\"telemetry.plot.overlay\",\"composition\":[{\"key\":\"f5c7e86c-a5b4-4c6c-9038-9cf03cd9a99e\",\"namespace\":\"\"}],\"configuration\":{\"series\":[]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate Overlay Plot with Telemetry Object\\nchrome\",\"modified\":1678741860389,\"location\":\"mine\",\"created\":1678741839461,\"persisted\":1678741860389}},{\"objectPath\":[{\"identifier\":{\"key\":\"f5c7e86c-a5b4-4c6c-9038-9cf03cd9a99e\",\"namespace\":\"\"},\"name\":\"VIPER Rover Heading\",\"type\":\"generator\",\"telemetry\":{\"period\":\"1\",\"amplitude\":\"1\",\"offset\":\"0\",\"dataRateInHz\":\"1\",\"phase\":\"0\",\"randomness\":\"0\",\"loadDelay\":\"0\",\"infinityValues\":false,\"staleness\":false},\"modified\":1678741852553,\"location\":\"mine\",\"created\":1678741852553,\"persisted\":1678741852553},{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"8c863964-4640-4db1-8a98-0e546c3c271d\",\"namespace\":\"\"},{\"key\":\"f5c7e86c-a5b4-4c6c-9038-9cf03cd9a99e\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1678741852556,\"created\":1678741830859,\"persisted\":1678741852557},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine/f5c7e86c-a5b4-4c6c-9038-9cf03cd9a99e\",\"domainObject\":{\"identifier\":{\"key\":\"f5c7e86c-a5b4-4c6c-9038-9cf03cd9a99e\",\"namespace\":\"\"},\"name\":\"VIPER Rover Heading\",\"type\":\"generator\",\"telemetry\":{\"period\":\"1\",\"amplitude\":\"1\",\"offset\":\"0\",\"dataRateInHz\":\"1\",\"phase\":\"0\",\"randomness\":\"0\",\"loadDelay\":\"0\",\"infinityValues\":false,\"staleness\":false},\"modified\":1678741852553,\"location\":\"mine\",\"created\":1678741852553,\"persisted\":1678741852553}},{\"objectPath\":[{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"8c863964-4640-4db1-8a98-0e546c3c271d\",\"namespace\":\"\"},{\"key\":\"f5c7e86c-a5b4-4c6c-9038-9cf03cd9a99e\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1678741852556,\"created\":1678741830859,\"persisted\":1678741852557},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine\",\"domainObject\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"8c863964-4640-4db1-8a98-0e546c3c271d\",\"namespace\":\"\"},{\"key\":\"f5c7e86c-a5b4-4c6c-9038-9cf03cd9a99e\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1678741852556,\"created\":1678741830859,\"persisted\":1678741852557}}]"
}
]
}
]
}
}

View File

@@ -6,21 +6,13 @@
"localStorage": [
{
"name": "mct",
"value": "{\"mine\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"db9fb115-7a72-4c45-81a4-1f6021156b4e\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1678741890986,\"created\":1678741885545,\"persisted\":1678741890987},\"db9fb115-7a72-4c45-81a4-1f6021156b4e\":{\"identifier\":{\"key\":\"db9fb115-7a72-4c45-81a4-1f6021156b4e\",\"namespace\":\"\"},\"name\":\"Overlay Plot with Telemetry Object\",\"type\":\"telemetry.plot.overlay\",\"composition\":[{\"key\":\"4c0b2c16-5f28-4906-abd3-befd16e5a77e\",\"namespace\":\"\"}],\"configuration\":{\"series\":[{\"identifier\":{\"key\":\"4c0b2c16-5f28-4906-abd3-befd16e5a77e\",\"namespace\":\"\"}}]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate Overlay Plot with 5s Delay\\nchrome\",\"modified\":1678741904378,\"location\":\"mine\",\"created\":1678741890983,\"persisted\":1678741904385},\"4c0b2c16-5f28-4906-abd3-befd16e5a77e\":{\"name\":\"Unnamed Sine Wave Generator\",\"type\":\"generator\",\"identifier\":{\"key\":\"4c0b2c16-5f28-4906-abd3-befd16e5a77e\",\"namespace\":\"\"},\"telemetry\":{\"period\":10,\"amplitude\":1,\"offset\":0,\"dataRateInHz\":1,\"phase\":0,\"randomness\":0,\"loadDelay\":\"5000\",\"infinityValues\":false,\"staleness\":false},\"modified\":1678741896800,\"location\":\"db9fb115-7a72-4c45-81a4-1f6021156b4e\",\"created\":1678741896800,\"persisted\":1678741896800}}"
"value": "{\"mine\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"98161570-a735-4a50-9c75-11b346ad3789\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413601340,\"created\":1732413600580,\"persisted\":1732413601340},\"98161570-a735-4a50-9c75-11b346ad3789\":{\"identifier\":{\"key\":\"98161570-a735-4a50-9c75-11b346ad3789\",\"namespace\":\"\"},\"name\":\"Overlay Plot with 5s Delay\",\"type\":\"telemetry.plot.overlay\",\"composition\":[{\"key\":\"477e60bb-4cba-4603-b4c9-2281ccf7e054\",\"namespace\":\"\"}],\"configuration\":{\"series\":[{\"identifier\":{\"key\":\"477e60bb-4cba-4603-b4c9-2281ccf7e054\",\"namespace\":\"\"}}]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate Overlay Plot with 5s Delay\\nchrome\",\"modified\":1732413602660,\"location\":\"mine\",\"created\":1732413601340,\"persisted\":1732413602660},\"477e60bb-4cba-4603-b4c9-2281ccf7e054\":{\"identifier\":{\"key\":\"477e60bb-4cba-4603-b4c9-2281ccf7e054\",\"namespace\":\"\"},\"name\":\"VIPER Rover Heading\",\"type\":\"generator\",\"telemetry\":{\"period\":10,\"amplitude\":1,\"offset\":0,\"dataRateInHz\":1,\"phase\":0,\"randomness\":0,\"loadDelay\":5000,\"infinityValues\":false,\"staleness\":false},\"modified\":1732413602520,\"location\":\"98161570-a735-4a50-9c75-11b346ad3789\",\"created\":1732413602040,\"persisted\":1732413602520}}"
},
{
"name": "mct-tree-expanded",
"value": "[]"
},
{
"name": "tcHistory",
"value": "{\"utc\":[{\"start\":1678740085436,\"end\":1678741885436}]}"
},
{
"name": "mct-recent-objects",
"value": "[{\"objectPath\":[{\"identifier\":{\"key\":\"db9fb115-7a72-4c45-81a4-1f6021156b4e\",\"namespace\":\"\"},\"name\":\"Overlay Plot with Telemetry Object\",\"type\":\"telemetry.plot.overlay\",\"composition\":[{\"key\":\"4c0b2c16-5f28-4906-abd3-befd16e5a77e\",\"namespace\":\"\"}],\"configuration\":{\"series\":[]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate Overlay Plot with 5s Delay\\nchrome\",\"modified\":1678741896803,\"location\":\"mine\",\"created\":1678741890983,\"persisted\":1678741896803},{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"db9fb115-7a72-4c45-81a4-1f6021156b4e\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1678741890986,\"created\":1678741885545,\"persisted\":1678741890987},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine/db9fb115-7a72-4c45-81a4-1f6021156b4e\",\"domainObject\":{\"identifier\":{\"key\":\"db9fb115-7a72-4c45-81a4-1f6021156b4e\",\"namespace\":\"\"},\"name\":\"Overlay Plot with Telemetry Object\",\"type\":\"telemetry.plot.overlay\",\"composition\":[{\"key\":\"4c0b2c16-5f28-4906-abd3-befd16e5a77e\",\"namespace\":\"\"}],\"configuration\":{\"series\":[]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate Overlay Plot with 5s Delay\\nchrome\",\"modified\":1678741896803,\"location\":\"mine\",\"created\":1678741890983,\"persisted\":1678741896803}},{\"objectPath\":[{\"identifier\":{\"key\":\"4c0b2c16-5f28-4906-abd3-befd16e5a77e\",\"namespace\":\"\"},\"name\":\"Unnamed Sine Wave Generator\",\"type\":\"generator\",\"telemetry\":{\"period\":10,\"amplitude\":1,\"offset\":0,\"dataRateInHz\":1,\"phase\":0,\"randomness\":0,\"loadDelay\":\"5000\",\"infinityValues\":false,\"staleness\":false},\"modified\":1678741896800,\"location\":\"db9fb115-7a72-4c45-81a4-1f6021156b4e\",\"created\":1678741896800,\"persisted\":1678741896800},{\"identifier\":{\"key\":\"db9fb115-7a72-4c45-81a4-1f6021156b4e\",\"namespace\":\"\"},\"name\":\"Overlay Plot with Telemetry Object\",\"type\":\"telemetry.plot.overlay\",\"composition\":[{\"key\":\"4c0b2c16-5f28-4906-abd3-befd16e5a77e\",\"namespace\":\"\"}],\"configuration\":{\"series\":[]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate Overlay Plot with 5s Delay\\nchrome\",\"modified\":1678741896803,\"location\":\"mine\",\"created\":1678741890983,\"persisted\":1678741896803},{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"db9fb115-7a72-4c45-81a4-1f6021156b4e\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1678741890986,\"created\":1678741885545,\"persisted\":1678741890987},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine/db9fb115-7a72-4c45-81a4-1f6021156b4e/4c0b2c16-5f28-4906-abd3-befd16e5a77e\",\"domainObject\":{\"identifier\":{\"key\":\"4c0b2c16-5f28-4906-abd3-befd16e5a77e\",\"namespace\":\"\"},\"name\":\"Unnamed Sine Wave Generator\",\"type\":\"generator\",\"telemetry\":{\"period\":10,\"amplitude\":1,\"offset\":0,\"dataRateInHz\":1,\"phase\":0,\"randomness\":0,\"loadDelay\":\"5000\",\"infinityValues\":false,\"staleness\":false},\"modified\":1678741896800,\"location\":\"db9fb115-7a72-4c45-81a4-1f6021156b4e\",\"created\":1678741896800,\"persisted\":1678741896800}},{\"objectPath\":[{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"db9fb115-7a72-4c45-81a4-1f6021156b4e\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1678741890986,\"created\":1678741885545,\"persisted\":1678741890987},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine\",\"domainObject\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"db9fb115-7a72-4c45-81a4-1f6021156b4e\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1678741890986,\"created\":1678741885545,\"persisted\":1678741890987}}]"
}
]
}
]
}
}

File diff suppressed because one or more lines are too long

View File

@@ -35,8 +35,7 @@
const { test, expect } = require('../../pluginFixtures.js');
const {
createDomainObjectWithDefaults,
createExampleTelemetryObject,
selectInspectorTab
createExampleTelemetryObject
} = require('../../appActions.js');
const { MISSION_TIME } = require('../../constants.js');
const path = require('path');
@@ -91,7 +90,7 @@ test.describe('Generate Visual Test Data @localStorage @generatedata', () => {
// TODO: Flesh Out Assertions against created Objects
await expect(page.locator('.l-browse-bar__object-name')).toContainText(overlayPlotName);
await selectInspectorTab(page, 'Config');
await page.getByRole('tab', { name: 'Config' }).click();
await page
.getByRole('list', { name: 'Plot Series Properties' })
.locator('span')
@@ -122,7 +121,7 @@ test.describe('Generate Visual Test Data @localStorage @generatedata', () => {
).toBeVisible();
await page.goto(exampleTelemetry.url);
await selectInspectorTab(page, 'Properties');
await page.getByRole('tab', { name: 'Properties' }).click();
// TODO: assert Example Telemetry property values
// await page.goto(exampleTelemetry.url);
@@ -181,7 +180,7 @@ test.describe('Validate Overlay Plot with Telemetry Object @localStorage @genera
await page.locator('a').filter({ hasText: overlayPlotName }).click();
// TODO: Flesh Out Assertions against created Objects
await expect(page.locator('.l-browse-bar__object-name')).toContainText(overlayPlotName);
await selectInspectorTab(page, 'Config');
await page.getByRole('tab', { name: 'Config' }).click();
await page
.getByRole('list', { name: 'Plot Series Properties' })
.locator('span')
@@ -227,7 +226,7 @@ test.describe('Validate Overlay Plot with 5s Delay Telemetry Object @localStorag
await page.locator('a').filter({ hasText: plotName }).click();
// TODO: Flesh Out Assertions against created Objects
await expect(page.locator('.l-browse-bar__object-name')).toContainText(plotName);
await selectInspectorTab(page, 'Config');
await page.getByRole('tab', { name: 'Config' }).click();
await page
.getByRole('list', { name: 'Plot Series Properties' })
.locator('span')

View File

@@ -20,11 +20,7 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
const { test, expect } = require('../../../pluginFixtures');
const {
createPlanFromJSON,
createDomainObjectWithDefaults,
selectInspectorTab
} = require('../../../appActions');
const { createPlanFromJSON, createDomainObjectWithDefaults } = require('../../../appActions');
const testPlan1 = require('../../../test-data/examplePlans/ExamplePlan_Small1.json');
const testPlan2 = require('../../../test-data/examplePlans/ExamplePlan_Small2.json');
const {
@@ -80,7 +76,7 @@ test.describe('Gantt Chart', () => {
.locator('g')
.filter({ hasText: new RegExp(activity.name) })
.click();
await selectInspectorTab(page, 'Activity');
await page.getByRole('tab', { name: 'Activity' }).click();
const startDateTime = await page
.locator(

View File

@@ -263,7 +263,10 @@ test.describe('Display Layout', () => {
await setFixedTimeMode(page);
// Create another Sine Wave Generator
const anotherSineWaveObject = await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator'
type: 'Sine Wave Generator',
customParameters: {
'[aria-label="Data Rate (hz)"]': '0.01'
}
});
// Create a Display Layout
await createDomainObjectWithDefaults(page, {
@@ -306,7 +309,8 @@ test.describe('Display Layout', () => {
// Time to inspect some network traffic
let networkRequests = [];
page.on('request', (request) => {
const searchRequest = request.url().endsWith('_find');
const searchRequest =
request.url().endsWith('_find') || request.url().includes('by_keystring');
const fetchRequest = request.resourceType() === 'fetch';
if (searchRequest && fetchRequest) {
networkRequests.push(request);
@@ -322,6 +326,7 @@ test.describe('Display Layout', () => {
expect(networkRequests.length).toBe(1);
await setRealTimeMode(page);
networkRequests = [];
await page.reload();

View File

@@ -22,7 +22,6 @@
const { test, expect } = require('../../../../pluginFixtures');
const utils = require('../../../../helper/faultUtils');
const { selectInspectorTab } = require('../../../../appActions');
test.describe('The Fault Management Plugin using example faults', () => {
test.beforeEach(async ({ page }) => {
@@ -41,7 +40,7 @@ test.describe('The Fault Management Plugin using example faults', () => {
}) => {
await utils.selectFaultItem(page, 1);
await selectInspectorTab(page, 'Fault Management Configuration');
await page.getByRole('tab', { name: 'Fault Management Configuration' }).click();
const selectedFaultName = await page
.locator('.c-fault-mgmt__list.is-selected .c-fault-mgmt__list-faultname')
.textContent();
@@ -66,7 +65,7 @@ test.describe('The Fault Management Plugin using example faults', () => {
);
expect.soft(await selectedRows.count()).toEqual(2);
await selectInspectorTab(page, 'Fault Management Configuration');
await page.getByRole('tab', { name: 'Fault Management Configuration' }).click();
const firstSelectedFaultName = await selectedRows.nth(0).textContent();
const secondSelectedFaultName = await selectedRows.nth(1).textContent();
const firstNameInInspectorCount = await page

View File

@@ -57,6 +57,10 @@ test.describe('Example Imagery Object', () => {
await mouseZoomOnImageAndAssert(page, -2);
});
test('Compass HUD should be hidden by default', async ({ page }) => {
await expect(page.locator('.c-hud')).toBeHidden();
});
test('Can adjust image brightness/contrast by dragging the sliders', async ({
page,
browserName
@@ -198,7 +202,7 @@ test.describe('Example Imagery Object', () => {
expect(afterDownPanBoundingBox.y).toBeLessThan(afterUpPanBoundingBox.y);
});
test('Can use alt+shift+drag to create a tag', async ({ page }) => {
test('Can use alt+shift+drag to create a tag and ensure toolbars disappear', async ({ page }) => {
const canvas = page.locator('canvas');
await canvas.hover({ trial: true });
@@ -211,7 +215,11 @@ test.describe('Example Imagery Object', () => {
// steps not working for me here
await page.mouse.move(canvasCenterX - 20, canvasCenterY - 20);
await page.mouse.move(canvasCenterX - 100, canvasCenterY - 100);
// toolbar should hide when we're creating annotations with a drag
await expect(page.locator('[role="toolbar"][aria-label="Image controls"]')).toBeHidden();
await page.mouse.up();
// toolbar should reappear when we're done creating annotations
await expect(page.locator('[role="toolbar"][aria-label="Image controls"]')).toBeVisible();
await Promise.all(tagHotkey.map((x) => page.keyboard.up(x)));
//Wait for canvas to stabilize.
@@ -233,7 +241,6 @@ test.describe('Example Imagery Object', () => {
});
test('Can use the reset button to reset the image @unstable', async ({ page }, testInfo) => {
test.slow(testInfo.project === 'chrome-beta', 'This test is slow in chrome-beta');
// Get initial image dimensions
const initialBoundingBox = await page.locator(backgroundImageSelector).boundingBox();

View File

@@ -25,8 +25,7 @@ const {
createDomainObjectWithDefaults,
setStartOffset,
setFixedTimeMode,
setRealTimeMode,
selectInspectorTab
setRealTimeMode
} = require('../../../../appActions');
test.describe('Testing LAD table configuration', () => {
@@ -57,7 +56,7 @@ test.describe('Testing LAD table configuration', () => {
// // Add the Sine Wave Generator to the LAD table and save changes
// await page.dragAndDrop('role=treeitem[name=/Test Sine Wave Generator/]', '.c-lad-table-wrapper');
// select configuration tab in inspector
await selectInspectorTab(page, 'LAD Table Configuration');
await page.getByRole('tab', { name: 'LAD Table Configuration' }).click();
// make sure headers are visible initially
await expect(page.getByRole('cell', { name: 'Timestamp' })).toBeVisible();
@@ -87,7 +86,7 @@ test.describe('Testing LAD table configuration', () => {
// Edit LAD table
await page.locator('[title="Edit"]').click();
await selectInspectorTab(page, 'LAD Table Configuration');
await page.getByRole('tab', { name: 'LAD Table Configuration' }).click();
// show timestamp column
await page.getByLabel('Timestamp').check();
@@ -105,7 +104,7 @@ test.describe('Testing LAD table configuration', () => {
// Edit LAD table
await page.locator('[title="Edit"]').click();
await selectInspectorTab(page, 'LAD Table Configuration');
await page.getByRole('tab', { name: 'LAD Table Configuration' }).click();
// show units and type columns
await page.getByLabel('Units').check();

View File

@@ -279,8 +279,8 @@ test.describe('Notebook entry tests', () => {
// Click .c-notebook__drag-area
await page.locator('.c-notebook__drag-area').click();
await expect(page.locator('[aria-label="Notebook Entry Input"]')).toBeVisible();
await expect(page.locator('[aria-label="Notebook Entry"]')).toHaveClass(/is-selected/);
await expect(page.getByLabel('Notebook Entry Display')).toBeVisible();
await expect(page.getByLabel('Notebook Entry', { exact: true })).toHaveClass(/is-selected/);
});
test('When an object is dropped into a notebook, a new entry is created and it should be focused @unstable', async ({
page
@@ -369,6 +369,8 @@ test.describe('Notebook entry tests', () => {
const validLink = page.locator(`a[href="${TEST_LINK}"]`);
expect(await validLink.count()).toBe(1);
// Start waiting for popup before clicking. Note no await.
const popupPromise = page.waitForEvent('popup');
@@ -378,8 +380,6 @@ test.describe('Notebook entry tests', () => {
// Wait for the popup to load.
await popup.waitForLoadState();
expect.soft(popup.url()).toContain('www.google.com');
expect(await validLink.count()).toBe(1);
});
test('when an invalid link is entered into a notebook entry, it does not become clickable when viewing', async ({
page
@@ -447,6 +447,8 @@ test.describe('Notebook entry tests', () => {
const validLink = page.locator(`a[href="${TEST_LINK}"]`);
expect(await validLink.count()).toBe(1);
// Start waiting for popup before clicking. Note no await.
const popupPromise = page.waitForEvent('popup');
@@ -456,8 +458,6 @@ test.describe('Notebook entry tests', () => {
// Wait for the popup to load.
await popup.waitForLoadState();
expect.soft(popup.url()).toContain('www.google.com');
expect(await validLink.count()).toBe(1);
});
test('when a nefarious link is entered into a notebook entry, it is sanitized when viewing', async ({
page
@@ -482,4 +482,42 @@ test.describe('Notebook entry tests', () => {
expect.soft(await sanitizedLink.count()).toBe(1);
expect(await unsanitizedLink.count()).toBe(0);
});
test('Can add markdown to a notebook entry', async ({ page }) => {
await page.goto(notebookObject.url);
// Headers
const headerMarkdown = `# Big Header\n## Large Header\n### Medium Header\n#### Small Header`;
await nbUtils.enterTextEntry(page, headerMarkdown);
await expect(page.getByRole('heading', { name: 'Big Header' })).toBeVisible();
// Text markup
const markupText =
'**This is bold.** _This is italic_. `This is code`. ~This is strikethrough~';
await nbUtils.enterTextEntry(page, markupText);
await expect(page.locator('strong:has-text("This is bold.")')).toBeVisible();
// Tables
const tablesText = '|Col 1|Col 2|Col3|\n|-|-|-|\n |Value 1|Value 2|Value 3|\n';
await nbUtils.enterTextEntry(page, tablesText);
await expect(page.getByRole('cell', { name: 'Value 2' })).toBeVisible();
// Links
const linksText =
'Raw links https://www.google.com and Markdown links like [Google](https://www.google.com) work';
await nbUtils.enterTextEntry(page, linksText);
await expect(page.getByRole('link', { name: 'https://www.google.com' })).toBeVisible();
await expect(page.getByRole('link', { name: 'Google', exact: true })).toBeVisible();
// Lists
const listsText = '- List item 1\n - Item 1A \n- List Item 2\n 1. Order 1\n 1. Order 2\n';
await nbUtils.enterTextEntry(page, listsText);
const childItem = page.locator('li:has-text("List Item 2") ol li:has-text("Order 2")');
await expect(childItem).toBeVisible();
// Blocks
const blockTest = '```javascript\nconst foo = "bar";\nconst bar = "foo";\n```';
await nbUtils.enterTextEntry(page, blockTest);
const codeBlock = page.locator('code.language-javascript:has-text("const foo = \\"bar\\";")');
await expect(codeBlock).toBeVisible();
});
});

View File

@@ -24,9 +24,11 @@
This test suite is dedicated to tests which verify the basic operations surrounding Notebooks.
*/
const fs = require('fs').promises;
const { test, expect } = require('../../../../pluginFixtures');
// const { expandTreePaneItemByName, createDomainObjectWithDefaults } = require('../../../../appActions');
// const nbUtils = require('../../../../helper/notebookUtils');
const { createDomainObjectWithDefaults } = require('../../../../appActions');
const NOTEBOOK_NAME = 'Notebook';
test.describe('Snapshot Menu tests', () => {
test.fixme(
@@ -161,3 +163,55 @@ test.describe('Snapshot Container tests', () => {
}
);
});
test.describe('Snapshot image tests', () => {
test.beforeEach(async ({ page }) => {
//Navigate to baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' });
// Create Notebook
await createDomainObjectWithDefaults(page, {
type: NOTEBOOK_NAME
});
});
test('Can drop an image onto a notebook and create a new entry', async ({ page }) => {
const imageData = await fs.readFile('src/images/favicons/favicon-96x96.png');
const imageArray = new Uint8Array(imageData);
const fileData = Array.from(imageArray);
const dropTransfer = await page.evaluateHandle((data) => {
const dataTransfer = new DataTransfer();
const file = new File([new Uint8Array(data)], 'favicon-96x96.png', { type: 'image/png' });
dataTransfer.items.add(file);
return dataTransfer;
}, fileData);
await page.dispatchEvent('.c-notebook__drag-area', 'drop', { dataTransfer: dropTransfer });
// be sure that entry was created
await expect(page.getByText('favicon-96x96.png')).toBeVisible();
await page.getByRole('img', { name: 'favicon-96x96.png thumbnail' }).click();
// expect large image to be displayed
await expect(page.getByRole('dialog').getByText('favicon-96x96.png')).toBeVisible();
await page.getByLabel('Close').click();
// drop another image onto the entry
await page.dispatchEvent('.c-snapshots', 'drop', { dataTransfer: dropTransfer });
// expect two embedded images now
expect(await page.getByRole('img', { name: 'favicon-96x96.png thumbnail' }).count()).toBe(2);
await page.locator('.c-snapshot.c-ne__embed').first().getByTitle('More options').click();
await page.getByRole('menuitem', { name: /Remove This Embed/ }).click();
await page.getByRole('button', { name: 'Ok', exact: true }).click();
// expect one embedded image now as we deleted the other
expect(await page.getByRole('img', { name: 'favicon-96x96.png thumbnail' }).count()).toBe(1);
});
});

View File

@@ -25,7 +25,7 @@ This test suite is dedicated to tests which verify notebook tag functionality.
*/
const { test, expect } = require('../../../../pluginFixtures');
const { createDomainObjectWithDefaults, selectInspectorTab } = require('../../../../appActions');
const { createDomainObjectWithDefaults } = require('../../../../appActions');
const {
enterTextEntry,
createNotebookAndEntry,
@@ -40,7 +40,7 @@ test.describe('Tagging in Notebooks @addInit', () => {
test('Can load tags', async ({ page }) => {
await createNotebookAndEntry(page);
await selectInspectorTab(page, 'Annotations');
await page.getByRole('tab', { name: 'Annotations' }).click();
await page.locator('button:has-text("Add Tag")').click();
@@ -65,7 +65,7 @@ test.describe('Tagging in Notebooks @addInit', () => {
});
test('Can add tags with blank entry', async ({ page }) => {
await createDomainObjectWithDefaults(page, { type: 'Notebook' });
await selectInspectorTab(page, 'Annotations');
await page.getByRole('tab', { name: 'Annotations' }).click();
await enterTextEntry(page, '');
await page.hover(`button:has-text("Add Tag")`);
@@ -81,7 +81,7 @@ test.describe('Tagging in Notebooks @addInit', () => {
test('Can cancel adding tags', async ({ page }) => {
await createNotebookAndEntry(page);
await selectInspectorTab(page, 'Annotations');
await page.getByRole('tab', { name: 'Annotations' }).click();
// Test canceling adding a tag after we click "Type to select tag"
await page.locator('button:has-text("Add Tag")').click();
@@ -147,16 +147,14 @@ test.describe('Tagging in Notebooks @addInit', () => {
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/5823'
});
await createNotebookEntryAndTags(page);
await page.locator('text=To start a new entry, click here or drag and drop any object').click();
const entryLocator = `[aria-label="Notebook Entry Input"] >> nth = 1`;
await page.locator(entryLocator).click();
await page.locator(entryLocator).fill(`An entry without tags`);
await page.locator('[aria-label="Notebook Entry Input"] >> nth=1').press('Enter');
await page.getByLabel('Notebook Entry Display').last().click();
await page.getByLabel('Notebook Entry Input').fill(`An entry without tags`);
await page.locator('.c-ne__save-button > button').click();
await page.hover('[aria-label="Notebook Entry Input"] >> nth=1');
await page.hover('[aria-label="Notebook Entry Display"] >> nth=1');
await page.locator('button[title="Delete this entry"]').last().click();
await expect(
page.locator('text=This action will permanently delete this entry. Do you wish to continue?')
@@ -210,7 +208,7 @@ test.describe('Tagging in Notebooks @addInit', () => {
test('Can cancel adding a tag', async ({ page }) => {
await createNotebookAndEntry(page);
await selectInspectorTab(page, 'Annotations');
await page.getByRole('tab', { name: 'Annotations' }).click();
// Click on the "Add Tag" button
await page.locator('button:has-text("Add Tag")').click();

View File

@@ -48,6 +48,8 @@ test.describe('Operator Status', () => {
});
await page.goto('./', { waitUntil: 'domcontentloaded' });
await expect(page.getByText('Select Role')).toBeVisible();
// Description should be empty https://github.com/nasa/openmct/issues/6978
await expect(page.locator('.c-message__action-text')).toBeHidden();
// set role
await page.getByRole('button', { name: 'Select' }).click();
// dismiss role confirmation popup

View File

@@ -24,7 +24,7 @@
Testsuite for plot autoscale.
*/
const { selectInspectorTab, createDomainObjectWithDefaults } = require('../../../../appActions');
const { createDomainObjectWithDefaults } = require('../../../../appActions');
const { test, expect } = require('../../../../pluginFixtures');
test.use({
viewport: {
@@ -60,7 +60,7 @@ test.describe('Autoscale', () => {
// enter edit mode
await page.click('button[title="Edit"]');
await selectInspectorTab(page, 'Config');
await page.getByRole('tab', { name: 'Config' }).click();
await turnOffAutoscale(page);
await setUserDefinedMinAndMax(page, '-2', '2');

View File

@@ -26,7 +26,7 @@ necessarily be used for reference when writing new tests in this area.
*/
const { test, expect } = require('../../../../pluginFixtures');
const { selectInspectorTab, setTimeConductorBounds } = require('../../../../appActions');
const { setTimeConductorBounds } = require('../../../../appActions');
test.describe('Log plot tests', () => {
test('Log Plot ticks are functionally correct in regular and log mode and after refresh', async ({
@@ -34,14 +34,13 @@ test.describe('Log plot tests', () => {
openmctConfig
}) => {
const { myItemsFolderName } = openmctConfig;
//Test.slow decorator is currently broken. Needs to be fixed in https://github.com/nasa/openmct/issues/5374
//Test is slow and should be split in the future
test.slow();
await makeOverlayPlot(page, myItemsFolderName);
await testRegularTicks(page);
await enableEditMode(page);
await selectInspectorTab(page, 'Config');
await page.getByRole('tab', { name: 'Config' }).click();
await enableLogMode(page);
await testLogTicks(page);
await disableLogMode(page);

View File

@@ -29,7 +29,6 @@ const { test, expect } = require('../../../../pluginFixtures');
const {
createDomainObjectWithDefaults,
getCanvasPixels,
selectInspectorTab,
waitForPlotsToRender
} = require('../../../../appActions');
@@ -50,7 +49,7 @@ test.describe('Overlay Plot', () => {
await page.goto(overlayPlot.url);
await selectInspectorTab(page, 'Config');
await page.getByRole('tab', { name: 'Config' }).click();
// navigate to plot series color palette
await page.click('.l-browse-bar__actions__edit');
@@ -91,7 +90,7 @@ test.describe('Overlay Plot', () => {
await page.click('button[title="Edit"]');
// Expand the "Sine Wave Generator" plot series options and enable limit lines
await selectInspectorTab(page, 'Config');
await page.getByRole('tab', { name: 'Config' }).click();
await page
.getByRole('list', { name: 'Plot Series Properties' })
.locator('span')
@@ -117,7 +116,7 @@ test.describe('Overlay Plot', () => {
// Enter edit mode
await page.click('button[title="Edit"]');
await selectInspectorTab(page, 'Elements');
await page.getByRole('tab', { name: 'Elements' }).click();
// Drag Sine Wave Generator series from Y Axis 1 into Y Axis 2
await page
@@ -168,7 +167,7 @@ test.describe('Overlay Plot', () => {
await page.goto(overlayPlot.url);
await page.click('button[title="Edit"]');
await selectInspectorTab(page, 'Elements');
await page.getByRole('tab', { name: 'Elements' }).click();
// Drag swg a, c, e into Y Axis 2
await page
@@ -182,7 +181,7 @@ test.describe('Overlay Plot', () => {
.dragTo(page.locator('[aria-label="Element Item Group Y Axis 2"]'));
// Assert that Y Axis 1 and Y Axis 2 property groups are visible only
await selectInspectorTab(page, 'Config');
await page.getByRole('tab', { name: 'Config' }).click();
const yAxis1PropertyGroup = page.locator('[aria-label="Y Axis Properties"]');
const yAxis2PropertyGroup = page.locator('[aria-label="Y Axis 2 Properties"]');
@@ -196,7 +195,7 @@ test.describe('Overlay Plot', () => {
const yAxis2Group = page.getByLabel('Y Axis 2');
const yAxis3Group = page.getByLabel('Y Axis 3');
await selectInspectorTab(page, 'Elements');
await page.getByRole('tab', { name: 'Elements' }).click();
// Drag swg b into Y Axis 3
await page
@@ -204,14 +203,14 @@ test.describe('Overlay Plot', () => {
.dragTo(page.locator('[aria-label="Element Item Group Y Axis 3"]'));
// Assert that all Y Axis property groups are visible
await selectInspectorTab(page, 'Config');
await page.getByRole('tab', { name: 'Config' }).click();
await expect(yAxis1PropertyGroup).toBeVisible();
await expect(yAxis2PropertyGroup).toBeVisible();
await expect(yAxis3PropertyGroup).toBeVisible();
// Verify that the elements are in the correct buckets and in the correct order
await selectInspectorTab(page, 'Elements');
await page.getByRole('tab', { name: 'Elements' }).click();
expect(yAxis1Group.getByRole('listitem', { name: swgD.name })).toBeTruthy();
expect(yAxis1Group.getByRole('listitem').nth(0).getByText(swgD.name)).toBeTruthy();
@@ -242,7 +241,7 @@ test.describe('Overlay Plot', () => {
await waitForPlotsToRender(page);
await page.click('button[title="Edit"]');
await selectInspectorTab(page, 'Elements');
await page.getByRole('tab', { name: 'Elements' }).click();
await page.locator(`#inspector-elements-tree >> text=${swgA.name}`).click();

View File

@@ -25,7 +25,7 @@
*/
const { test, expect } = require('../../../../pluginFixtures');
const { createDomainObjectWithDefaults, selectInspectorTab } = require('../../../../appActions');
const { createDomainObjectWithDefaults } = require('../../../../appActions');
const uuid = require('uuid').v4;
test.describe('Scatter Plot', () => {
@@ -54,7 +54,7 @@ test.describe('Scatter Plot', () => {
// the SWG appears in the elements pool
await page.goto(scatterPlot.url);
await editButton.click();
await selectInspectorTab(page, 'Elements');
await page.getByRole('tab', { name: 'Elements' }).click();
await expect.soft(page.locator(`#inspector-elements-tree >> text=${swg1.name}`)).toBeVisible();
await saveButton.click();
await page.locator('li[title="Save and Finish Editing"]').click();
@@ -82,7 +82,7 @@ test.describe('Scatter Plot', () => {
await editButton.click();
// Click the "Elements" tab
await selectInspectorTab(page, 'Elements');
await page.getByRole('tab', { name: 'Elements' }).click();
await expect.soft(page.locator(`#inspector-elements-tree >> text=${swg1.name}`)).toBeHidden();
await expect.soft(page.locator(`#inspector-elements-tree >> text=${swg2.name}`)).toBeVisible();
await saveButton.click();

View File

@@ -26,11 +26,7 @@ necessarily be used for reference when writing new tests in this area.
*/
const { test, expect } = require('../../../../pluginFixtures');
const {
createDomainObjectWithDefaults,
selectInspectorTab,
waitForPlotsToRender
} = require('../../../../appActions');
const { createDomainObjectWithDefaults, waitForPlotsToRender } = require('../../../../appActions');
test.describe('Stacked Plot', () => {
let stackedPlot;
@@ -75,7 +71,7 @@ test.describe('Stacked Plot', () => {
await page.click('button[title="Edit"]');
await selectInspectorTab(page, 'Elements');
await page.getByRole('tab', { name: 'Elements' }).click();
await swgBElementsPoolItem.click({ button: 'right' });
await page
@@ -107,7 +103,7 @@ test.describe('Stacked Plot', () => {
await page.click('button[title="Edit"]');
await selectInspectorTab(page, 'Elements');
await page.getByRole('tab', { name: 'Elements' }).click();
const stackedPlotItem1 = page.locator('.c-plot--stacked-container').nth(0);
const stackedPlotItem2 = page.locator('.c-plot--stacked-container').nth(1);
@@ -152,7 +148,7 @@ test.describe('Stacked Plot', () => {
}) => {
await page.goto(stackedPlot.url);
await selectInspectorTab(page, 'Config');
await page.getByRole('tab', { name: 'Config' }).click();
// Click on the 1st plot
await page.locator(`[aria-label="Stacked Plot Item ${swgA.name}"] canvas`).nth(1).click();
@@ -193,7 +189,7 @@ test.describe('Stacked Plot', () => {
// Go into edit mode
await page.click('button[title="Edit"]');
await selectInspectorTab(page, 'Config');
await page.getByRole('tab', { name: 'Config' }).click();
// Click on canvas for the 1st plot
await page.locator(`[aria-label="Stacked Plot Item ${swgA.name}"]`).click();
@@ -238,7 +234,7 @@ test.describe('Stacked Plot', () => {
// Go into edit mode
await page.click('button[title="Edit"]');
await selectInspectorTab(page, 'Config');
await page.getByRole('tab', { name: 'Config' }).click();
let legendProperties = await page.locator('[aria-label="Legend Properties"]');
await legendProperties.locator('[title="Display legends per sub plot."]~div input').uncheck();

View File

@@ -29,8 +29,7 @@ const {
createDomainObjectWithDefaults,
setRealTimeMode,
setFixedTimeMode,
waitForPlotsToRender,
selectInspectorTab
waitForPlotsToRender
} = require('../../../../appActions');
test.describe('Plot Tagging', () => {
@@ -42,7 +41,7 @@ test.describe('Plot Tagging', () => {
* @param {Number} yEnd a telemetry item with a plot
* @returns {Promise}
*/
async function createTags({ page, canvas, xEnd = 700, yEnd = 480 }) {
async function createTags({ page, canvas, xEnd = 700, yEnd = 520 }) {
await canvas.hover({ trial: true });
//Alt+Shift Drag Start to select some points to tag
@@ -150,7 +149,7 @@ test.describe('Plot Tagging', () => {
await waitForPlotsToRender(page);
await expect(page.getByRole('tab', { name: 'Annotations' })).not.toHaveClass(/is-current/);
await selectInspectorTab(page, 'Annotations');
await page.getByRole('tab', { name: 'Annotations' }).click();
await expect(page.getByRole('tab', { name: 'Annotations' })).toHaveClass(/is-current/);
await expect(page.getByText('No tags to display for this item')).toBeVisible();
@@ -285,7 +284,7 @@ test.describe('Plot Tagging', () => {
page,
canvas,
xEnd: 700,
yEnd: 215
yEnd: 240
});
await basicTagsTests(page);
await testTelemetryItem(page, alphaSineWave);

View File

@@ -153,7 +153,6 @@ test.describe('Time conductor input fields real-time mode', () => {
await expect(page.locator('.c-compact-tc__setting-value.icon-plus')).toContainText('00:00:01');
// Verify url parameters persist after mode switch
await page.waitForNavigation({ waitUntil: 'networkidle' });
expect(page.url()).toContain(`startDelta=${startDelta}`);
expect(page.url()).toContain(`endDelta=${endDelta}`);
});

View File

@@ -25,12 +25,15 @@ const {
openObjectTreeContextMenu,
createDomainObjectWithDefaults
} = require('../../../../appActions');
import { MISSION_TIME } from '../../../../constants';
test.describe('Timer', () => {
let timer;
test.beforeEach(async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' });
timer = await createDomainObjectWithDefaults(page, { type: 'timer' });
await assertTimerElements(page, timer);
});
test('Can perform actions on the Timer', async ({ page }) => {
@@ -63,6 +66,70 @@ test.describe('Timer', () => {
});
});
test.describe('Timer with target date', () => {
let timer;
test.beforeEach(async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' });
timer = await createDomainObjectWithDefaults(page, { type: 'timer' });
await assertTimerElements(page, timer);
});
// Override clock
test.use({
clockOptions: {
now: MISSION_TIME,
shouldAdvanceTime: true
}
});
test('Can count down to a target date', async ({ page }) => {
// Set the target date to 2024-11-24 03:30:00
await page.getByTitle('More options').click();
await page.getByRole('menuitem', { name: /Edit Properties.../ }).click();
await page.getByPlaceholder('YYYY-MM-DD').fill('2024-11-24');
await page.locator('input[name="hour"]').fill('3');
await page.locator('input[name="min"]').fill('30');
await page.locator('input[name="sec"]').fill('00');
await page.getByLabel('Save').click();
// Get the current timer seconds value
const timerSecValue = (await page.locator('.c-timer__value').innerText()).split(':').at(-1);
await expect(page.locator('.c-timer__direction')).toHaveClass(/icon-minus/);
// Wait for the timer to count down and assert
await expect
.poll(async () => {
const newTimerValue = (await page.locator('.c-timer__value').innerText()).split(':').at(-1);
return Number(newTimerValue);
})
.toBeLessThan(Number(timerSecValue));
});
test('Can count up from a target date', async ({ page }) => {
// Set the target date to 2020-11-23 03:30:00
await page.getByTitle('More options').click();
await page.getByRole('menuitem', { name: /Edit Properties.../ }).click();
await page.getByPlaceholder('YYYY-MM-DD').fill('2020-11-23');
await page.locator('input[name="hour"]').fill('3');
await page.locator('input[name="min"]').fill('30');
await page.locator('input[name="sec"]').fill('00');
await page.getByLabel('Save').click();
// Get the current timer seconds value
const timerSecValue = (await page.locator('.c-timer__value').innerText()).split(':').at(-1);
await expect(page.locator('.c-timer__direction')).toHaveClass(/icon-plus/);
// Wait for the timer to count up and assert
await expect
.poll(async () => {
const newTimerValue = (await page.locator('.c-timer__value').innerText()).split(':').at(-1);
return Number(newTimerValue);
})
.toBeGreaterThan(Number(timerSecValue));
});
});
/**
* Actions that can be performed on a timer from context menus.
* @typedef {'Start' | 'Stop' | 'Pause' | 'Restart at 0'} TimerAction
@@ -141,14 +208,17 @@ function buttonTitleFromAction(action) {
* @param {TimerAction} action
*/
async function assertTimerStateAfterAction(page, action) {
const timerValue = page.locator('.c-timer__value');
let timerStateClass;
switch (action) {
case 'Start':
case 'Restart at 0':
timerStateClass = 'is-started';
expect(await timerValue.innerText()).toBe('0D 00:00:00');
break;
case 'Stop':
timerStateClass = 'is-stopped';
expect(await timerValue.innerText()).toBe('--:--:--');
break;
case 'Pause':
timerStateClass = 'is-paused';
@@ -157,3 +227,25 @@ async function assertTimerStateAfterAction(page, action) {
await expect.soft(page.locator('.c-timer')).toHaveClass(new RegExp(timerStateClass));
}
/**
* Assert that all the major components of a timer are present in the DOM.
* @param {import('@playwright/test').Page} page
* @param {import('../../../../appActions').CreatedObjectInfo} timer
*/
async function assertTimerElements(page, timer) {
const timerElement = page.locator('.c-timer');
const resetButton = page.getByRole('button', { name: 'Reset' });
const pausePlayButton = page
.getByRole('button', { name: 'Pause' })
.or(page.getByRole('button', { name: 'Start' }));
const timerDirectionIcon = page.locator('.c-timer__direction');
const timerValue = page.locator('.c-timer__value');
expect(await page.locator('.l-browse-bar__object-name').innerText()).toBe(timer.name);
expect(timerElement).toBeAttached();
expect(resetButton).toBeAttached();
expect(pausePlayButton).toBeAttached();
expect(timerDirectionIcon).toBeAttached();
expect(timerValue).toBeAttached();
}

View File

@@ -24,7 +24,7 @@
*/
const { test, expect } = require('../../pluginFixtures');
const { createDomainObjectWithDefaults, selectInspectorTab } = require('../../appActions');
const { createDomainObjectWithDefaults } = require('../../appActions');
const { v4: uuid } = require('uuid');
test.describe('Grand Search', () => {
@@ -61,7 +61,7 @@ test.describe('Grand Search', () => {
`Clock D ${myItemsFolderName} Red Folder Blue Folder`
);
// Click the Elements pool to dismiss the search menu
await selectInspectorTab(page, 'Elements');
await page.getByRole('tab', { name: 'Elements' }).click();
await expect(page.locator('[aria-label="Search Result"] >> nth=0')).toBeHidden();
await page.locator('[aria-label="OpenMCT Search"] [aria-label="Search Input"]').click();
@@ -175,7 +175,8 @@ test.describe('Grand Search', () => {
let networkRequests = [];
page.on('request', (request) => {
const searchRequest = request.url().endsWith('_find');
const searchRequest =
request.url().endsWith('_find') || request.url().includes('by_keystring');
const fetchRequest = request.resourceType() === 'fetch';
if (searchRequest && fetchRequest) {
networkRequests.push(request);

View File

@@ -50,8 +50,6 @@ test('Verify that the create button appears and that the Folder Domain Object is
test('Verify that My Items Tree appears @ipad', async ({ page, openmctConfig }) => {
const { myItemsFolderName } = openmctConfig;
//Test.slow annotation is currently broken. Needs to be fixed in https://github.com/nasa/openmct/issues/5374
test.slow();
//Go to baseURL
await page.goto('./');

View File

@@ -131,8 +131,9 @@ test.describe('Performance tests', () => {
await page.evaluate(() => window.performance.mark('new-notebook-entry-created'));
// Enter Notebook Entry text
await page.locator('div.c-ne__text').last().fill('New Entry');
await page.keyboard.press('Enter');
await page.getByLabel('Notebook Entry').last().click();
await page.getByLabel('Notebook Entry Input').last().fill('New Entry');
await page.locator('.c-ne__save-button').click();
await page.evaluate(() => window.performance.mark('new-notebook-entry-filled'));
//Individual Notebook Entry Search

View File

@@ -1,121 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, 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.
*****************************************************************************/
/*
This test suite is an initial example for memory leak testing using performance. This configuration and execution must
be kept separate from the traditional performance measurements to avoid any "observer" effects associated with tracing
or profiling playwright and/or the browser.
Based on a pattern identified in https://github.com/trentmwillis/devtools-protocol-demos/blob/master/testing-demos/memory-leak-by-heap.js
and https://github.com/paulirish/automated-chrome-profiling/issues/3
Best path forward: https://github.com/cowchimp/headless-devtools/blob/master/src/Memory/example.js
*/
const { test, expect } = require('@playwright/test');
const filePath = 'e2e/test-data/PerformanceDisplayLayout.json';
// eslint-disable-next-line playwright/no-skipped-test
test.describe.skip('Memory Performance tests', () => {
test.beforeEach(async ({ page, browser }, testInfo) => {
// Go to baseURL
await page.goto('./', { waitUntil: 'networkidle' });
// Click a:has-text("My Items")
await page.locator('a:has-text("My Items")').click({
button: 'right'
});
// Click text=Import from JSON
await page.locator('text=Import from JSON').click();
// Upload Performance Display Layout.json
await page.setInputFiles('#fileElem', filePath);
// Click text=OK
await page.locator('text=OK').click();
await expect(
page.locator('a:has-text("Performance Display Layout Display Layout")')
).toBeVisible();
});
test('Embedded View Large for Imagery is performant in Fixed Time', async ({ page, browser }) => {
await page.goto('./', { waitUntil: 'networkidle' });
// To to Search Available after Launch
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
// Fill Search input
await page
.locator('[aria-label="OpenMCT Search"] input[type="search"]')
.fill('Performance Display Layout');
//Search Result Appears and is clicked
await Promise.all([
page.waitForNavigation(),
page.locator('a:has-text("Performance Display Layout")').first().click()
]);
//Time to Example Imagery Frame loads within Display Layout
await page.waitForSelector('.c-imagery__main-image__bg', { state: 'visible' });
//Time to Example Imagery object loads
await page.waitForSelector('.c-imagery__main-image__background-image', { state: 'visible' });
const client = await page.context().newCDPSession(page);
await client.send('HeapProfiler.enable');
await client.send('HeapProfiler.startSampling');
// await client.send('HeapProfiler.collectGarbage');
await client.send('Performance.enable');
let performanceMetricsBefore = await client.send('Performance.getMetrics');
console.log(performanceMetricsBefore.metrics);
//await client.send('Performance.disable');
//Open Large view
await page.locator('button:has-text("Large View")').click();
await client.send('HeapProfiler.takeHeapSnapshot');
//Time to Imagery Rendered in Large Frame
await page.waitForSelector('.c-imagery__main-image__bg', { state: 'visible' });
//Time to Example Imagery object loads
await page.waitForSelector('.c-imagery__main-image__background-image', { state: 'visible' });
// Click Close Icon
await page.locator('.c-click-icon').click();
//Time to Example Imagery Frame loads within Display Layout
await page.waitForSelector('.c-imagery__main-image__bg', { state: 'visible' });
//Time to Example Imagery object loads
await page.waitForSelector('.c-imagery__main-image__background-image', { state: 'visible' });
await client.send('HeapProfiler.collectGarbage');
//await client.send('Performance.enable');
let performanceMetricsAfter = await client.send('Performance.getMetrics');
console.log(performanceMetricsAfter.metrics);
//await client.send('Performance.disable');
});
});

View File

@@ -0,0 +1,299 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, 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.
*****************************************************************************/
const { test, expect } = require('@playwright/test');
const memoryLeakFilePath = 'e2e/test-data/memory-leak-detection.json';
/**
* Executes tests to verify that views are not leaking memory on navigation away. This sort of
* memory leak is generally caused by a failure to clean up registered listeners.
*
* These tests are executed on a set of pre-built displays loaded from ../test-data/memory-leak-detection.json.
*
* In order to modify the test data set:
* 1. Run Open MCT locally (npm start)
* 2. Right click on a folder in the tree, and select "Import From JSON"
* 3. In the subsequent dialog, select the file ../test-data/memory-leak-detection.json
* 4. Click "OK"
* 5. Modify test objects as desired
* 6. Right click on the "Memory Leak Detection" folder, and select "Export to JSON"
* 7. Copy the exported file to ../test-data/memory-leak-detection.json
*
*/
const NAV_LEAK_TIMEOUT = 10 * 1000; // 10s
test.describe('Navigation memory leak is not detected in', () => {
test.beforeEach(async ({ page }) => {
// Go to baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' });
await page.locator('a:has-text("My Items")').click({
button: 'right'
});
await page.locator('text=Import from JSON').click();
// Upload memory-leak-detection.json
await page.setInputFiles('#fileElem', memoryLeakFilePath);
await page.locator('text=OK').click();
await expect(page.locator('a:has-text("Memory Leak Detection")')).toBeVisible();
});
test('plot view', async ({ page }) => {
const result = await navigateToObjectAndDetectMemoryLeak(page, 'overlay-plot-single-1hz-swg', {
timeout: NAV_LEAK_TIMEOUT
});
// If we got here without timing out, then the root view object was garbage collected and no memory leak was detected.
expect(result).toBe(true);
});
test('stacked plot view', async ({ page }) => {
const result = await navigateToObjectAndDetectMemoryLeak(page, 'stacked-plot-single-1hz-swg', {
timeout: NAV_LEAK_TIMEOUT
});
// If we got here without timing out, then the root view object was garbage collected and no memory leak was detected.
expect(result).toBe(true);
});
test('LAD table view', async ({ page }) => {
const result = await navigateToObjectAndDetectMemoryLeak(page, 'lad-table-single-1hz-swg', {
timeout: NAV_LEAK_TIMEOUT
});
// If we got here without timing out, then the root view object was garbage collected and no memory leak was detected.
expect(result).toBe(true);
});
test('LAD table set', async ({ page }) => {
const result = await navigateToObjectAndDetectMemoryLeak(page, 'lad-table-set-single-1hz-swg', {
timeout: NAV_LEAK_TIMEOUT
});
// If we got here without timing out, then the root view object was garbage collected and no memory leak was detected.
expect(result).toBe(true);
});
//TODO: Figure out why using the `table-row` component inside the `table` component leaks TelemetryTableRow objects
test('telemetry table view', async ({ page }) => {
const result = await navigateToObjectAndDetectMemoryLeak(
page,
'telemetry-table-single-1hz-swg',
{
timeout: NAV_LEAK_TIMEOUT
}
);
// If we got here without timing out, then the root view object was garbage collected and no memory leak was detected.
expect(result).toBe(true);
});
//TODO: Figure out why using the `SideBar` component inside the leaks Notebook objects
test('notebook view', async ({ page }) => {
const result = await navigateToObjectAndDetectMemoryLeak(
page,
'notebook-memory-leak-detection-test',
{
timeout: NAV_LEAK_TIMEOUT
}
);
// If we got here without timing out, then the root view object was garbage collected and no memory leak was detected.
expect(result).toBe(true);
});
test('display layout of a single SWG alphanumeric', async ({ page }) => {
const result = await navigateToObjectAndDetectMemoryLeak(
page,
'display-layout-single-1hz-swg',
{
timeout: NAV_LEAK_TIMEOUT
}
);
// If we got here without timing out, then the root view object was garbage collected and no memory leak was detected.
expect(result).toBe(true);
});
test('display layout of a single SWG plot', async ({ page }) => {
const result = await navigateToObjectAndDetectMemoryLeak(
page,
'display-layout-single-overlay-plot',
{
timeout: NAV_LEAK_TIMEOUT
}
);
// If we got here without timing out, then the root view object was garbage collected and no memory leak was detected.
expect(result).toBe(true);
});
//TODO: Figure out why `svg` in the CompassRose component leaks imagery
test('example imagery view', async ({ page }) => {
const result = await navigateToObjectAndDetectMemoryLeak(
page,
'example-imagery-memory-leak-test',
{
timeout: NAV_LEAK_TIMEOUT * 6 // 1 min
}
);
// If we got here without timing out, then the root view object was garbage collected and no memory leak was detected.
expect(result).toBe(true);
});
test('display layout of example imagery views', async ({ page }) => {
const result = await navigateToObjectAndDetectMemoryLeak(
page,
'display-layout-images-memory-leak-test',
{
timeout: NAV_LEAK_TIMEOUT * 6 // 1 min
}
);
// If we got here without timing out, then the root view object was garbage collected and no memory leak was detected.
expect(result).toBe(true);
});
test('display layout with plots of swgs, alphanumerics, and condition sets, ', async ({
page
}) => {
const result = await navigateToObjectAndDetectMemoryLeak(
page,
'display-layout-simple-telemetry',
{
timeout: NAV_LEAK_TIMEOUT * 6 // 1 min
}
);
// If we got here without timing out, then the root view object was garbage collected and no memory leak was detected.
expect(result).toBe(true);
});
test('flexible layout with plots of swgs', async ({ page }) => {
const result = await navigateToObjectAndDetectMemoryLeak(
page,
'flexible-layout-plots-memory-leak-test',
{
timeout: NAV_LEAK_TIMEOUT
}
);
// If we got here without timing out, then the root view object was garbage collected and no memory leak was detected.
expect(result).toBe(true);
});
test('flexible layout of example imagery views', async ({ page }) => {
const result = await navigateToObjectAndDetectMemoryLeak(
page,
'flexible-layout-images-memory-leak-test',
{
timeout: NAV_LEAK_TIMEOUT * 6 // 1 min
}
);
// If we got here without timing out, then the root view object was garbage collected and no memory leak was detected.
expect(result).toBe(true);
});
test('tabbed view of display layouts and time strips', async ({ page }) => {
const result = await navigateToObjectAndDetectMemoryLeak(
page,
'tab-view-simple-memory-leak-test',
{
timeout: NAV_LEAK_TIMEOUT * 6 * 2 // 2 min
}
);
// If we got here without timing out, then the root view object was garbage collected and no memory leak was detected.
expect(result).toBe(true);
});
test('time strip view of telemetry', async ({ page }) => {
const result = await navigateToObjectAndDetectMemoryLeak(
page,
'time-strip-telemetry-memory-leak-test',
{
timeout: NAV_LEAK_TIMEOUT * 6 // 1 min
}
);
// If we got here without timing out, then the root view object was garbage collected and no memory leak was detected.
expect(result).toBe(true);
});
/**
*
* @param {import('@playwright/test').Page} page
* @param {*} objectName
* @returns
*/
async function navigateToObjectAndDetectMemoryLeak(page, objectName) {
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
// Fill Search input
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill(objectName);
//Search Result Appears and is clicked
await Promise.all([
page.locator(`div.c-gsearch-result__title:has-text("${objectName}")`).first().click(),
page.waitForNavigation()
]);
// Register a finalization listener on the root node for the view. This tends to be the last thing to be
// garbage collected since it has either direct or indirect references to all resources used by the view. Therefore it's a pretty good proxy
// 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,
'navigatedObject',
window.openmct.layout.$refs.browseObject.$refs.objectViewWrapper.firstChild
);
});
});
// Nav back to folder
await page.goto('./#/browse/mine', { waitUntil: 'networkidle' });
await page.waitForNavigation();
// This next code block blocks until the finalization listener is called and the gcPromise resolved. This means that the root node for the view has been garbage collected.
// In the event that the root node is not garbage collected, the gcPromise will never resolve and the test will time out.
await page.evaluate(() => {
const gcPromise = window.gcPromise;
window.gcPromise = null;
// Manually invoke the garbage collector once all references are removed.
window.gc();
return gcPromise;
});
// Clean up the finalization registry since we don't need it any more.
await page.evaluate(() => {
window.fr = null;
});
// If we get here without timing out, it means the garbage collection promise resolved and the test passed.
return true;
}
});

View File

@@ -32,7 +32,7 @@ const {
waitForPlotsToRender
} = require('../../appActions');
test.describe.fixme('Plot Tagging Performance', () => {
test.describe('Plot Tagging Performance', () => {
/**
* Given a canvas and a set of points, tags the points on the canvas.
* @param {import('@playwright/test').Page} page
@@ -41,7 +41,7 @@ test.describe.fixme('Plot Tagging Performance', () => {
* @param {Number} yEnd a telemetry item with a plot
* @returns {Promise}
*/
async function createTags({ page, canvas, xEnd = 700, yEnd = 480 }) {
async function createTags({ page, canvas, xEnd = 700, yEnd = 520 }) {
await canvas.hover({ trial: true });
//Alt+Shift Drag Start to select some points to tag
@@ -265,7 +265,7 @@ test.describe.fixme('Plot Tagging Performance', () => {
page,
canvas,
xEnd: 700,
yEnd: 215
yEnd: 240
});
await basicTagsTests(page);
await testTelemetryItem(page, alphaSineWave);

View File

@@ -26,11 +26,12 @@ Tests the branding associated with the default deployment. At least the about mo
const { test, expect } = require('../../../pluginFixtures');
const percySnapshot = require('@percy/playwright');
const VISUAL_URL = require('../../../constants').VISUAL_URL;
test.describe('Visual - Branding', () => {
test.beforeEach(async ({ page }) => {
//Go to baseURL and Hide Tree
await page.goto('./#/browse/mine?hideTree=true', { waitUntil: 'networkidle' });
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
});
test('Visual - About Modal', async ({ page, theme }) => {

View File

@@ -0,0 +1,56 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, 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.
*****************************************************************************/
const { test } = require('../../../pluginFixtures.js');
const { VISUAL_URL, MISSION_TIME } = require('../../../constants.js');
const percySnapshot = require('@percy/playwright');
//Declare the scope of the visual test
const inspectorPane = '.l-shell__pane-inspector';
test.describe('Visual - Controlled Clock', () => {
test.beforeEach(async ({ page }) => {
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
});
test.use({
storageState: './e2e/test-data/overlay_plot_with_delay_storage.json',
clockOptions: {
now: MISSION_TIME,
shouldAdvanceTime: true
}
});
test('Inspector from overlay_plot_with_delay_storage @localStorage', async ({ page, theme }) => {
//Expand the Inspector Pane
await page.getByRole('button', { name: 'Inspect' }).click();
await percySnapshot(page, `Inspector view of overlayPlot (theme: ${theme})`, {
scope: inspectorPane
});
//Open Annotations Tab
await page.getByRole('tab', { name: 'Annotations' }).click();
await percySnapshot(page, `Inspector view of Annotations Tab (theme: ${theme})`, {
scope: inspectorPane
});
});
});

View File

@@ -25,14 +25,21 @@ const {
expandTreePaneItemByName,
createDomainObjectWithDefaults
} = require('../../../appActions.js');
const VISUAL_URL = require('../../../constants.js').VISUAL_URL;
const percySnapshot = require('@percy/playwright');
test.describe('Visual - Tree Pane', () => {
test('Tree pane in various states @unstable', async ({ page, theme, openmctConfig }) => {
const { myItemsFolderName } = openmctConfig;
await page.goto('./#/browse/mine', { waitUntil: 'networkidle' });
//Declare the scope of the visual test
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' });
//Open Tree
await page.getByRole('button', { name: 'Browse' }).click();
//Create a Folder Structure
const foo = await createDomainObjectWithDefaults(page, {
type: 'Folder',
name: 'Foo Folder'
@@ -60,8 +67,6 @@ test.describe('Visual - Tree Pane', () => {
name: 'Z Clock'
});
const treePane = "[role=tree][aria-label='Main Tree']";
await percySnapshot(page, `Tree Pane w/ collapsed tree (theme: ${theme})`, {
scope: treePane
});

View File

@@ -25,35 +25,31 @@ Collection of Visual Tests set to run with browser clock manipulate made possibl
clockOptions plugin fixture.
*/
const { VISUAL_URL, MISSION_TIME } = require('../../constants');
const { test, expect } = require('../../pluginFixtures');
const percySnapshot = require('@percy/playwright');
test.describe('Visual - Controlled Clock', () => {
test.beforeEach(async ({ page }) => {
//Go to baseURL and Hide Tree
await page.goto('./#/browse/mine?hideTree=true', { waitUntil: 'networkidle' });
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
});
test.use({
storageState: './e2e/test-data/overlay_plot_with_delay_storage.json',
clockOptions: {
now: MISSION_TIME,
shouldAdvanceTime: false //Don't advance the clock
}
});
test('Overlay Plot Loading Indicator @localStorage', async ({ page, theme }) => {
// Go to baseURL
await page.goto('./#/browse/mine', { waitUntil: 'networkidle' });
await page.getByTitle('Collapse Browse Pane').click();
await page
.locator('a')
.filter({ hasText: 'Overlay Plot with Telemetry Object Overlay Plot' })
.click();
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
await page.locator('a').filter({ hasText: 'Overlay Plot with 5s Delay' }).click();
//Ensure that we're on the Unnamed Overlay Plot object
await expect(page.locator('.l-browse-bar__object-name')).toContainText(
'Overlay Plot with Telemetry Object'
'Overlay Plot with 5s Delay'
);
//Wait for canvas to be rendered and stop animating
//Wait for canvas to be rendered and stop animating, but plot should not be loaded. Cannot use waitForPlotsToRender
await page.locator('canvas >> nth=1').hover({ trial: true });
//Take snapshot of Sine Wave Generator within Overlay Plot

View File

@@ -29,11 +29,11 @@ are only meant to run against openmct's app.js started by `npm run start` within
const { test, expect } = require('../../pluginFixtures');
const percySnapshot = require('@percy/playwright');
const { createDomainObjectWithDefaults } = require('../../appActions');
const { VISUAL_URL } = require('../../constants');
test.describe('Visual - Default', () => {
test.beforeEach(async ({ page }) => {
//Go to baseURL and Hide Tree
await page.goto('./#/browse/mine?hideTree=true', { waitUntil: 'networkidle' });
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
});
test('Visual - Default Dashboard', async ({ page, theme }) => {

View File

@@ -23,13 +23,15 @@
const { expect, test } = require('../../pluginFixtures');
const percySnapshot = require('@percy/playwright');
const { createDomainObjectWithDefaults } = require('../../appActions');
const VISUAL_URL = require('../../constants').VISUAL_URL;
test.describe('Visual - LAD Table', () => {
/** @type {import('@playwright/test').Locator} */
let ladTable;
test.beforeEach(async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' });
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
// Create LAD Table
ladTable = await createDomainObjectWithDefaults(page, {
type: 'LAD Table',
@@ -55,9 +57,6 @@ test.describe('Visual - LAD Table', () => {
});
test('Toggled column widths behave accordingly', async ({ page, theme }) => {
await page.goto(ladTable.url);
//Close panes for visual consistency
await page.getByTitle('Collapse Inspect Pane').click();
await page.getByTitle('Collapse Browse Pane').click();
await expect(page.locator('button[title="Expand Columns"]')).toBeVisible();

View File

@@ -22,15 +22,12 @@
const { test } = require('../../pluginFixtures');
const percySnapshot = require('@percy/playwright');
const {
selectInspectorTab,
expandTreePaneItemByName,
createDomainObjectWithDefaults
} = require('../../appActions');
const { expandTreePaneItemByName, createDomainObjectWithDefaults } = require('../../appActions');
const {
startAndAddRestrictedNotebookObject,
enterTextEntry
} = require('../../helper/notebookUtils');
const { VISUAL_URL } = require('../../constants');
test.describe('Visual - Restricted Notebook', () => {
test.beforeEach(async ({ page }) => {
@@ -44,23 +41,26 @@ test.describe('Visual - Restricted Notebook', () => {
});
test.describe('Visual - Notebook', () => {
let notebook;
test.beforeEach(async ({ page }) => {
//Go to baseURL and Hide Tree
await page.goto('./#/browse/mine?hideTree=true', { waitUntil: 'networkidle' });
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
notebook = await createDomainObjectWithDefaults(page, {
type: 'Notebook',
name: 'Test Notebook'
});
});
test('Accepts dropped objects as embeds @unstable', async ({ page, theme, openmctConfig }) => {
test('Accepts dropped objects as embeds', async ({ page, theme, openmctConfig }) => {
const { myItemsFolderName } = openmctConfig;
const notebook = await createDomainObjectWithDefaults(page, {
type: 'Notebook',
name: 'Embed Test Notebook'
});
// Create Overlay Plot
await createDomainObjectWithDefaults(page, {
type: 'Overlay Plot',
name: 'Dropped Overlay Plot'
});
//Open Tree
await page.getByRole('button', { name: 'Browse' }).click();
await expandTreePaneItemByName(page, myItemsFolderName);
await page.goto(notebook.url);
@@ -70,18 +70,19 @@ test.describe('Visual - Notebook', () => {
await percySnapshot(page, `Notebook w/ dropped embed (theme: ${theme})`);
});
test("Blur 'Add tag' on Notebook", async ({ page, theme }) => {
await createDomainObjectWithDefaults(page, {
type: 'Notebook',
name: 'Add Tag Test Notebook'
});
await enterTextEntry(page, 'Entry 0');
// Click on Annotations tab
await selectInspectorTab(page, 'Annotations');
await percySnapshot(page, `Notebook Entry (theme: '${theme}')`);
// Open the Inspector
await page.getByRole('button', { name: 'Inspect' }).click();
// Open the Annotations tab
await page.getByRole('tab', { name: 'Annotations' }).click();
// Take snapshot of the notebook with the Annotations tab opened
await percySnapshot(page, `Notebook Annotation (theme: '${theme}')`);
// Add annotation
await page.locator('button:has-text("Add Tag")').click();
// Take snapshot of the notebook with the AutoComplete field visible

View File

@@ -27,11 +27,11 @@
const { test, expect } = require('../../pluginFixtures');
const percySnapshot = require('@percy/playwright');
const { createDomainObjectWithDefaults } = require('../../appActions');
const VISUAL_URL = require('../../constants').VISUAL_URL;
test.describe("Visual - Check Notification Info Banner of 'Save successful'", () => {
test.beforeEach(async ({ page }) => {
// Go to baseURL and Hide Tree
await page.goto('./', { waitUntil: 'networkidle' });
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
});
test("Create a clock, click on 'Save successful' banner and dismiss it", async ({
@@ -43,12 +43,6 @@ test.describe("Visual - Check Notification Info Banner of 'Save successful'", ()
type: 'Clock',
name: 'Default Clock'
});
// Verify there is a button with aria-label="Review 1 Notification"
expect(await page.locator('button[aria-label="Review 1 Notification"]').isVisible()).toBe(true);
// Verify there is a button with aria-label="Clear all notifications"
expect(await page.locator('button[aria-label="Clear all notifications"]').isVisible()).toBe(
true
);
// Click on the div with role="alert" that has "Save successful" text
await page.locator('div[role="alert"]:has-text("Save successful")').click();
// Verify there is a div with role="dialog"

View File

@@ -21,16 +21,20 @@
*****************************************************************************/
const { test } = require('../../pluginFixtures');
const { setBoundsToSpanAllActivities } = require('../../helper/planningUtils');
const {
setBoundsToSpanAllActivities,
setDraftStatusForPlan
} = require('../../helper/planningUtils');
const { createDomainObjectWithDefaults, createPlanFromJSON } = require('../../appActions');
const percySnapshot = require('@percy/playwright');
const VISUAL_URL = require('../../constants').VISUAL_URL;
const examplePlanSmall = require('../../test-data/examplePlans/ExamplePlan_Small2.json');
const snapshotScope = '.l-shell__pane-main .l-pane__contents';
test.describe('Visual - Planning', () => {
test.beforeEach(async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' });
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
});
test('Plan View', async ({ page, theme }) => {
@@ -50,8 +54,7 @@ test.describe('Visual - Planning', () => {
name: 'Plan Visual Test (Draft)',
json: examplePlanSmall
});
await page.goto('./#/browse/mine');
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
await setDraftStatusForPlan(page, plan);
await setBoundsToSpanAllActivities(page, examplePlanSmall, plan.url);
@@ -87,7 +90,7 @@ test.describe('Visual - Planning', () => {
await setDraftStatusForPlan(page, plan);
await page.goto('./#/browse/mine');
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
await setBoundsToSpanAllActivities(page, examplePlanSmall, ganttChart.url);
await percySnapshot(page, `Gantt Chart View w/ draft status (theme: ${theme})`, {
@@ -95,14 +98,3 @@ test.describe('Visual - Planning', () => {
});
});
});
/**
* Uses the Open MCT API to set the status of a plan to 'draft'.
* @param {import('@playwright/test').Page} page
* @param {import('../../appActions').CreatedObjectInfo} plan
*/
async function setDraftStatusForPlan(page, plan) {
await page.evaluate(async (planObject) => {
await window.openmct.status.set(planObject.uuid, 'draft');
}, plan);
}

View File

@@ -26,15 +26,15 @@ This test suite is dedicated to tests which verify search functionality.
const { test, expect } = require('../../pluginFixtures');
const { createDomainObjectWithDefaults } = require('../../appActions');
const { VISUAL_URL } = require('../../constants');
const percySnapshot = require('@percy/playwright');
test.describe('Grand Search', () => {
let clock;
let displayLayout;
test.beforeEach(async ({ page, theme }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' });
await page.getByTitle('Collapse Browse Pane').click();
test.beforeEach(async ({ page }) => {
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
displayLayout = await createDomainObjectWithDefaults(page, {
type: 'Display Layout',

View File

@@ -19,8 +19,8 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import EventTelemetryProvider from './EventTelemetryProvider';
import EventMetadataProvider from './EventMetadataProvider';
import EventTelemetryProvider from './EventTelemetryProvider';
export default function EventGeneratorPlugin(options) {
return function install(openmct) {

View File

@@ -19,8 +19,8 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import EventMessageGeneratorPlugin from './plugin.js';
import { createOpenMct, resetApplicationState } from '../../src/utils/testing';
import EventMessageGeneratorPlugin from './plugin.js';
describe('the plugin', () => {
let openmct;

View File

@@ -22,6 +22,7 @@
import EventEmitter from 'EventEmitter';
import { v4 as uuid } from 'uuid';
import createExampleUser from './exampleUserCreator';
const STATUSES = [

View File

@@ -20,11 +20,11 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import GeneratorMetadataProvider from './GeneratorMetadataProvider';
import GeneratorProvider from './GeneratorProvider';
import SinewaveLimitProvider from './SinewaveLimitProvider';
import SinewaveStalenessProvider from './SinewaveStalenessProvider';
import StateGeneratorProvider from './StateGeneratorProvider';
import GeneratorMetadataProvider from './GeneratorMetadataProvider';
export default function (openmct) {
openmct.types.addType('example.state-generator', {

View File

@@ -1,4 +1,5 @@
import Vue from 'vue';
import HelloWorld from './HelloWorld.vue';
function SimpleVuePlugin() {

View File

@@ -1,6 +1,6 @@
{
"name": "openmct",
"version": "3.1.0-next",
"version": "3.1.0",
"description": "The Open MCT core platform",
"devDependencies": {
"@babel/eslint-parser": "7.22.5",
@@ -12,23 +12,25 @@
"@types/eventemitter3": "1.2.0",
"@types/jasmine": "4.3.4",
"@types/lodash": "4.14.192",
"@vue/compat": "^3.1.0",
"@vue/compiler-sfc": "^3.1.0",
"@vue/compat": "3.3.4",
"@vue/compiler-sfc": "3.3.4",
"babel-loader": "9.1.0",
"babel-plugin-istanbul": "6.1.1",
"codecov": "3.8.3",
"comma-separated-values": "3.6.4",
"copy-webpack-plugin": "11.0.0",
"cspell": "6.31.2",
"cspell": "7.3.6",
"css-loader": "6.8.1",
"d3-axis": "3.0.0",
"d3-scale": "3.3.0",
"d3-selection": "3.0.0",
"eslint": "8.43.0",
"eslint-config-prettier": "8.8.0",
"eslint-plugin-compat": "4.1.4",
"eslint": "8.48.0",
"eslint-config-prettier": "9.0.0",
"eslint-plugin-compat": "4.2.0",
"eslint-plugin-playwright": "0.12.0",
"eslint-plugin-prettier": "4.2.1",
"eslint-plugin-simple-import-sort": "10.0.0",
"eslint-plugin-unicorn": "44.0.2",
"eslint-plugin-vue": "9.15.0",
"eslint-plugin-you-dont-need-lodash-underscore": "6.12.0",
"eventemitter3": "1.2.0",
@@ -50,10 +52,12 @@
"karma-webpack": "5.0.0",
"location-bar": "3.0.1",
"lodash": "4.17.21",
"marked": "9.0.3",
"mini-css-extract-plugin": "2.7.6",
"moment": "2.29.4",
"moment-duration-format": "2.3.2",
"moment-timezone": "0.5.41",
"npm-run-all2": "6.0.6",
"nyc": "15.1.0",
"painterro": "1.2.78",
"plotly.js-basic-dist": "2.20.0",
@@ -66,23 +70,25 @@
"sass-loader": "13.3.2",
"sinon": "15.1.0",
"style-loader": "3.3.3",
"typescript": "5.1.3",
"typescript": "5.2.2",
"uuid": "9.0.0",
"vue": "^3.1.0",
"vue": "3.3.4",
"vue-eslint-parser": "9.3.1",
"vue-loader": "16.8.3",
"webpack": "5.88.0",
"vue-loader": "^16.0.0",
"webpack-cli": "5.1.1",
"webpack-dev-server": "4.15.1",
"webpack-merge": "5.9.0"
},
"scripts": {
"clean": "rm -rf ./dist ./node_modules ./package-lock.json ./coverage ./html-test-results ./test-results ./.nyc_output ",
"clean-test-lint": "npm run clean; npm install; npm run test; npm run lint; npm run lint:spelling",
"start": "npx webpack serve --config ./.webpack/webpack.dev.js",
"start:prod": "npx webpack serve --config ./.webpack/webpack.prod.js",
"start:coverage": "npx webpack serve --config ./.webpack/webpack.coverage.js",
"lint": "eslint example src e2e --ext .js openmct.js --max-warnings=0 && eslint example src --ext .vue",
"lint:js": "eslint example src e2e --ext .js openmct.js --max-warnings=0",
"lint:vue": "eslint example src --ext .vue",
"lint:spelling": "cspell \"**/*.{js,md,vue}\" --show-context --gitignore",
"lint": "run-p \"lint:js -- {1}\" \"lint:vue -- {1}\" \"lint:spelling -- {1}\" --",
"lint:fix": "eslint example src e2e --ext .js,.vue openmct.js --fix",
"build:prod": "webpack --config ./.webpack/webpack.prod.js",
"build:dev": "webpack --config ./.webpack/webpack.dev.js",
@@ -102,7 +108,9 @@
"test:e2e:visual:full": "percy exec --config ./e2e/.percy.nightly.yml -- npx playwright test --config=e2e/playwright-visual.config.js --grep-invert @unstable",
"test:e2e:full": "npx playwright test --config=e2e/playwright-ci.config.js --grep-invert @couchdb",
"test:e2e:watch": "npx playwright test --ui --config=e2e/playwright-ci.config.js",
"test:perf": "npx playwright test --config=e2e/playwright-performance.config.js",
"test:perf:contract": "npx playwright test --config=e2e/playwright-performance-dev.config.js",
"test:perf:localhost": "npx playwright test --config=e2e/playwright-performance-prod.config.js --project=chrome",
"test:perf:memory": "npx playwright test --config=e2e/playwright-performance-prod.config.js --project=chrome-memory",
"update-about-dialog-copyright": "perl -pi -e 's/20\\d\\d\\-202\\d/2014\\-2023/gm' ./src/ui/layout/AboutDialog.vue",
"update-copyright-date": "npm run update-about-dialog-copyright && grep -lr --null --include=*.{js,scss,vue,ts,sh,html,md,frag} 'Copyright (c) 20' . | xargs -r0 perl -pi -e 's/Copyright\\s\\(c\\)\\s20\\d\\d\\-20\\d\\d/Copyright \\(c\\)\\ 2014\\-2023/gm'",
"cov:e2e:report": "nyc report --reporter=lcovonly --report-dir=./coverage/e2e",

View File

@@ -43,7 +43,6 @@ define([
'./plugins/duplicate/plugin',
'./plugins/importFromJSONAction/plugin',
'./plugins/exportAsJSONAction/plugin',
'./ui/components/components',
'vue'
], function (
EventEmitter,
@@ -68,7 +67,6 @@ define([
DuplicateActionPlugin,
ImportFromJSONAction,
ExportAsJSONAction,
components,
Vue
) {
/**
@@ -430,7 +428,6 @@ define([
};
MCT.prototype.plugins = plugins;
MCT.prototype.components = components.default;
return MCT;
});

View File

@@ -20,8 +20,8 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import ActionCollection from './ActionCollection';
import { createOpenMct, resetApplicationState } from '../../utils/testing';
import ActionCollection from './ActionCollection';
describe('The ActionCollection', () => {
let openmct;

View File

@@ -20,9 +20,10 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import EventEmitter from 'EventEmitter';
import ActionCollection from './ActionCollection';
import _ from 'lodash';
import ActionCollection from './ActionCollection';
class ActionsAPI extends EventEmitter {
constructor(openmct) {
super();
@@ -92,7 +93,7 @@ class ActionsAPI extends EventEmitter {
if (this._actionCollections.has(key)) {
let actionCollection = this._actionCollections.get(key);
actionCollection.off('destroy', this._updateCachedActionCollections);
delete actionCollection.applicableActions;
this._actionCollections.delete(key);
}
}

View File

@@ -20,9 +20,9 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import ActionsAPI from './ActionsAPI';
import { createOpenMct, resetApplicationState } from '../../utils/testing';
import ActionCollection from './ActionCollection';
import ActionsAPI from './ActionsAPI';
describe('The Actions API', () => {
let openmct;

View File

@@ -20,9 +20,9 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import { v4 as uuid } from 'uuid';
import EventEmitter from 'EventEmitter';
import _ from 'lodash';
import { v4 as uuid } from 'uuid';
/**
* @readonly
@@ -100,7 +100,7 @@ export default class AnnotationAPI extends EventEmitter {
creatable: false,
cssClass: 'icon-notebook',
initialize: function (domainObject) {
domainObject.targets = domainObject.targets || {};
domainObject.targets = domainObject.targets || [];
domainObject._deleted = domainObject._deleted || false;
domainObject.originalContextPath = domainObject.originalContextPath || '';
domainObject.tags = domainObject.tags || [];
@@ -117,10 +117,10 @@ export default class AnnotationAPI extends EventEmitter {
* @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 {Object<string, Object>} targets The targets ID keystrings and their specific properties.
* For plots, this will be a bounding box, e.g.: {maxY: 100, minY: 0, maxX: 100, minX: 0}
* @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"}
* @property {DomainObject>} targetDomainObjects the targets ID keystrings and the domain objects this annotation points to (e.g., telemetry objects for a plot)
* @property {DomainObject>[]} targetDomainObjects the domain objects this annotation points to (e.g., telemetry objects for a plot)
*/
/**
* @method create
@@ -141,11 +141,15 @@ export default class AnnotationAPI extends EventEmitter {
throw new Error(`Unknown annotation type: ${annotationType}`);
}
if (!Object.keys(targets).length) {
if (!targets.length) {
throw new Error(`At least one target is required to create an annotation`);
}
if (!Object.keys(targetDomainObjects).length) {
if (targets.some((target) => !target.keyString)) {
throw new Error(`All targets require a keyString to create an annotation`);
}
if (!targetDomainObjects.length) {
throw new Error(`At least one targetDomainObject is required to create an annotation`);
}
@@ -181,7 +185,7 @@ export default class AnnotationAPI extends EventEmitter {
const success = await this.openmct.objects.save(createdObject);
if (success) {
this.emit('annotationCreated', createdObject);
Object.values(targetDomainObjects).forEach((targetDomainObject) => {
targetDomainObjects.forEach((targetDomainObject) => {
this.#updateAnnotationModified(targetDomainObject);
});
@@ -321,7 +325,10 @@ export default class AnnotationAPI extends EventEmitter {
}
#addTagMetaInformationToTags(tags) {
return tags.map((tagKey) => {
// Convert to Set and back to Array to remove duplicates
const uniqueTags = [...new Set(tags)];
return uniqueTags.map((tagKey) => {
const tagModel = this.availableTags[tagKey];
tagModel.tagID = tagKey;
@@ -363,7 +370,8 @@ export default class AnnotationAPI extends EventEmitter {
const modelAddedToResults = await Promise.all(
results.map(async (result) => {
const targetModels = await Promise.all(
Object.keys(result.targets).map(async (targetID) => {
result.targets.map(async (target) => {
const targetID = target.keyString;
const targetModel = await this.openmct.objects.get(targetID);
const targetKeyString = this.openmct.objects.makeKeyString(targetModel.identifier);
const originalPathObjects = await this.openmct.objects.getOriginalPath(targetKeyString);
@@ -410,13 +418,12 @@ export default class AnnotationAPI extends EventEmitter {
#breakApartSeparateTargets(results) {
const separateResults = [];
results.forEach((result) => {
Object.keys(result.targets).forEach((targetID) => {
result.targets.forEach((target) => {
const targetID = target.keyString;
const separatedResult = {
...result
};
separatedResult.targets = {
[targetID]: result.targets[targetID]
};
separatedResult.targets = [target];
separatedResult.targetModels = result.targetModels.filter((targetModel) => {
const targetKeyString = this.openmct.objects.makeKeyString(targetModel.identifier);

View File

@@ -20,8 +20,8 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import { createOpenMct, resetApplicationState } from '../../utils/testing';
import ExampleTagsPlugin from '../../../example/exampleTags/plugin';
import { createOpenMct, resetApplicationState } from '../../utils/testing';
describe('The Annotation API', () => {
let openmct;
@@ -62,11 +62,12 @@ describe('The Annotation API', () => {
key: 'anAnnotationKey',
namespace: 'fooNameSpace'
},
targets: {
'fooNameSpace:some-object': {
targets: [
{
keyString: 'fooNameSpace:some-object',
entryId: 'fooBarEntry'
}
}
]
};
mockObjectProvider = jasmine.createSpyObj('mock provider', ['create', 'update', 'get']);
@@ -121,7 +122,7 @@ describe('The Annotation API', () => {
tags: ['sometag'],
contentText: 'fooContext',
targetDomainObjects: [mockDomainObject],
targets: { fooTarget: {} }
targets: [{ keyString: 'fooTarget' }]
};
const annotationObject = await openmct.annotation.create(annotationCreationArguments);
expect(annotationObject).toBeDefined();
@@ -136,7 +137,7 @@ describe('The Annotation API', () => {
tags: ['sometag'],
contentText: 'fooContext',
targetDomainObjects: [mockDomainObject],
targets: { fooTarget: {} }
targets: [{ keyString: 'fooTarget' }]
};
openmct.annotation.setNamespaceToSaveAnnotations('fooNameSpace');
const annotationObject = await openmct.annotation.create(annotationCreationArguments);
@@ -166,7 +167,7 @@ describe('The Annotation API', () => {
tags: ['sometag'],
contentText: 'fooContext',
targetDomainObjects: [mockDomainObject],
targets: { fooTarget: {} }
targets: [{ keyString: 'fooTarget' }]
};
openmct.annotation.setNamespaceToSaveAnnotations('namespaceThatDoesNotExist');
await openmct.annotation.create(annotationCreationArguments);
@@ -183,7 +184,7 @@ describe('The Annotation API', () => {
tags: ['sometag'],
contentText: 'fooContext',
targetDomainObjects: [mockDomainObject],
targets: { fooTarget: {} }
targets: [{ keyString: 'fooTarget' }]
};
openmct.annotation.setNamespaceToSaveAnnotations('immutableProvider');
await openmct.annotation.create(annotationCreationArguments);
@@ -202,7 +203,7 @@ describe('The Annotation API', () => {
annotationType: openmct.annotation.ANNOTATION_TYPES.NOTEBOOK,
tags: ['aWonderfulTag'],
contentText: 'fooContext',
targets: { 'fooNameSpace:some-object': { entryId: 'fooBarEntry' } },
targets: [{ keyString: 'fooNameSpace:some-object', entryId: 'fooBarEntry' }],
targetDomainObjects: [mockDomainObject]
};
});
@@ -272,17 +273,19 @@ describe('The Annotation API', () => {
let comparator;
beforeEach(() => {
targets = {
fooTarget: {
targets = [
{
keyString: 'fooTarget',
foo: 42
}
};
otherTargets = {
fooTarget: {
];
otherTargets = [
{
keyString: 'fooTarget',
bar: 42
}
};
comparator = (t1, t2) => t1.fooTarget.foo === t2.fooTarget.bar;
];
comparator = (t1, t2) => t1[0].foo === t2[0].bar;
});
it('can add a comparator function', () => {

View File

@@ -20,8 +20,8 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import DefaultCompositionProvider from './DefaultCompositionProvider';
import CompositionCollection from './CompositionCollection';
import DefaultCompositionProvider from './DefaultCompositionProvider';
/**
* @typedef {import('./CompositionProvider').default} CompositionProvider

View File

@@ -20,6 +20,7 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import _ from 'lodash';
import objectUtils from '../objects/object-utils';
/**
@@ -185,9 +186,10 @@ export default class CompositionProvider {
return;
}
this.#publicAPI.objects.eventEmitter.on('mutation', this.#onMutation.bind(this));
const onMutation = this.#onMutation.bind(this);
this.#publicAPI.objects.eventEmitter.on('mutation', onMutation);
this.topicListener = () => {
this.#publicAPI.objects.eventEmitter.off('mutation', this.#onMutation.bind(this));
this.#publicAPI.objects.eventEmitter.off('mutation', onMutation);
};
}

View File

@@ -19,9 +19,10 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import { toRaw } from 'vue';
import objectUtils from '../objects/object-utils';
import CompositionProvider from './CompositionProvider';
import { toRaw } from 'vue';
/**
* @typedef {import('../objects/ObjectAPI').DomainObject} DomainObject

View File

@@ -1,6 +1,8 @@
import mount from 'utils/mount';
import AutoCompleteField from './components/controls/AutoCompleteField.vue';
import ClockDisplayFormatField from './components/controls/ClockDisplayFormatField.vue';
import CheckBoxField from './components/controls/CheckBoxField.vue';
import ClockDisplayFormatField from './components/controls/ClockDisplayFormatField.vue';
import Datetime from './components/controls/Datetime.vue';
import FileInput from './components/controls/FileInput.vue';
import Locator from './components/controls/Locator.vue';
@@ -9,8 +11,6 @@ import SelectField from './components/controls/SelectField.vue';
import TextAreaField from './components/controls/TextAreaField.vue';
import TextField from './components/controls/TextField.vue';
import ToggleSwitchField from './components/controls/ToggleSwitchField.vue';
import mount from 'utils/mount';
export const DEFAULT_CONTROLS_MAP = {
autocomplete: AutoCompleteField,
checkbox: CheckBoxField,

View File

@@ -20,12 +20,12 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import FormController from './FormController';
import FormProperties from './components/FormProperties.vue';
import _ from 'lodash';
import mount from 'utils/mount';
import FormProperties from './components/FormProperties.vue';
import FormController from './FormController';
export default class FormsAPI {
constructor(openmct) {
this.openmct = openmct;

View File

@@ -73,9 +73,10 @@
</template>
<script>
import FormRow from '@/api/forms/components/FormRow.vue';
import { v4 as uuid } from 'uuid';
import FormRow from '@/api/forms/components/FormRow.vue';
export default {
components: {
FormRow

View File

@@ -123,7 +123,6 @@ export default {
formatDatetime(timestamp = this.model.value) {
if (!timestamp) {
this.resetValues();
return;
}
@@ -137,7 +136,7 @@ export default {
const data = {
model,
value: timestamp
value: new Date(timestamp).toISOString()
};
this.$emit('onChange', data);

View File

@@ -29,7 +29,7 @@
</template>
<script>
import MctTree from '@/ui/layout/mct-tree.vue';
import MctTree from '@/ui/layout/MctTree.vue';
export default {
components: {

View File

@@ -34,10 +34,11 @@
</template>
<script>
import toggleMixin from '../../toggle-check-box-mixin';
import { v4 as uuid } from 'uuid';
import ToggleSwitch from '@/ui/components/ToggleSwitch.vue';
import { v4 as uuid } from 'uuid';
import toggleMixin from '../../toggle-check-box-mixin';
export default {
components: {

View File

@@ -21,6 +21,7 @@
*****************************************************************************/
import EventEmitter from 'EventEmitter';
import SimpleIndicator from './SimpleIndicator';
class IndicatorAPI extends EventEmitter {

View File

@@ -21,9 +21,11 @@
*****************************************************************************/
import EventEmitter from 'EventEmitter';
import indicatorTemplate from './res/indicator-template.html';
import { convertTemplateToHTML } from '@/utils/template/templateHelpers';
import indicatorTemplate from './res/indicator-template.html';
const DEFAULT_ICON_CLASS = 'icon-info';
class SimpleIndicator extends EventEmitter {

View File

@@ -20,11 +20,12 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import MenuAPI from './MenuAPI';
import Menu from './menu';
import { createOpenMct, createMouseEvent, resetApplicationState } from '../../utils/testing';
import Vue from 'vue';
import { createMouseEvent, createOpenMct, resetApplicationState } from '../../utils/testing';
import Menu from './menu';
import MenuAPI from './MenuAPI';
describe('The Menu API', () => {
let openmct;
let appHolder;

View File

@@ -20,10 +20,11 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import EventEmitter from 'EventEmitter';
import mount from 'utils/mount';
import { h } from 'vue';
import MenuComponent from './components/Menu.vue';
import SuperMenuComponent from './components/SuperMenu.vue';
import { h } from 'vue';
import mount from 'utils/mount';
export const MENU_PLACEMENT = {
TOP: 'top',

View File

@@ -30,8 +30,8 @@
*
* @namespace platform/api/notifications
*/
import moment from 'moment';
import EventEmitter from 'eventemitter3';
import moment from 'moment';
/**
* @typedef {object} NotificationProperties
@@ -165,7 +165,7 @@ export default class NotificationAPI extends EventEmitter {
/**
* Create a new progress notification. These notifications will contain a progress bar.
* @param {string} message
* @param {number | 'unknown'} progressPerc A value between 0 and 100, or the string 'unknown'.
* @param {number | null} progressPerc A value between 0 and 100, or null.
* @param {string} [progressText] Text description of progress (eg. "10 of 20 objects copied").
*/
progress(message, progressPerc, progressText) {

View File

@@ -435,7 +435,8 @@ class InMemorySearchProvider {
}
localIndexAnnotation(objectToIndex, model) {
Object.keys(model.targets).forEach((targetID) => {
model.targets.forEach((target) => {
const targetID = target.keyString;
if (!this.localIndexedAnnotationsByDomainObject[targetID]) {
this.localIndexedAnnotationsByDomainObject[targetID] = [];
}

View File

@@ -57,7 +57,8 @@
};
function indexAnnotation(objectToIndex, model) {
Object.keys(model.targets).forEach((targetID) => {
model.targets.forEach((target) => {
const targetID = target.keyString;
if (!indexedAnnotationsByDomainObject[targetID]) {
indexedAnnotationsByDomainObject[targetID] = [];
}

View File

@@ -19,9 +19,10 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import _ from 'lodash';
import utils from './object-utils.js';
import EventEmitter from 'EventEmitter';
import _ from 'lodash';
import utils from './object-utils.js';
const ANY_OBJECT_EVENT = 'mutation';

View File

@@ -20,15 +20,16 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import utils from 'objectUtils';
import MutableDomainObject from './MutableDomainObject';
import RootRegistry from './RootRegistry';
import RootObjectProvider from './RootObjectProvider';
import EventEmitter from 'EventEmitter';
import InterceptorRegistry from './InterceptorRegistry';
import Transaction from './Transaction';
import utils from 'objectUtils';
import ConflictError from './ConflictError';
import InMemorySearchProvider from './InMemorySearchProvider';
import InterceptorRegistry from './InterceptorRegistry';
import MutableDomainObject from './MutableDomainObject';
import RootObjectProvider from './RootObjectProvider';
import RootRegistry from './RootRegistry';
import Transaction from './Transaction';
/**
* Uniquely identifies a domain object.

View File

@@ -1,5 +1,5 @@
import ObjectAPI from './ObjectAPI.js';
import { createOpenMct, resetApplicationState } from '../../utils/testing';
import ObjectAPI from './ObjectAPI.js';
describe('The Object API', () => {
let objectAPI;

View File

@@ -1,6 +1,7 @@
import Transaction from './Transaction';
import utils from 'objectUtils';
import Transaction from './Transaction';
let openmct = {};
let objectAPI;
let transaction;

View File

@@ -1,6 +1,7 @@
import mount from 'utils/mount';
import DialogComponent from './components/DialogComponent.vue';
import Overlay from './Overlay';
import mount from 'utils/mount';
class Dialog extends Overlay {
constructor({ iconClass, message, title, hint, timestamp, ...options }) {

View File

@@ -1,7 +1,8 @@
import OverlayComponent from './components/OverlayComponent.vue';
import EventEmitter from 'EventEmitter';
import mount from 'utils/mount';
import OverlayComponent from './components/OverlayComponent.vue';
const cssClasses = {
large: 'l-overlay-large',
small: 'l-overlay-small',

View File

@@ -20,8 +20,8 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import Overlay from './Overlay';
import Dialog from './Dialog';
import Overlay from './Overlay';
import ProgressDialog from './ProgressDialog';
import Selection from './Selection';
@@ -141,7 +141,7 @@ class OverlayAPI {
* @see NotificationService
*
* @typedef options
* @property {number} progressPerc the initial progress value (0-100) or {string} 'unknown' for anonymous progress
* @property {number | null} progressPerc the initial progress value (0-100) or null for anonymous progress
* @property {string} progressText the initial text to be shown under the progress bar
* @property {buttons[]} buttons a list of buttons with title and callback properties that will
* be added to the dialog.

View File

@@ -1,6 +1,7 @@
import mount from 'utils/mount';
import ProgressDialogComponent from './components/ProgressDialogComponent.vue';
import Overlay from './Overlay';
import mount from 'utils/mount';
let component;
class ProgressDialog extends Overlay {

View File

@@ -20,9 +20,10 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import mount from 'utils/mount';
import SelectionComponent from './components/SelectionComponent.vue';
import Overlay from './Overlay';
import mount from 'utils/mount';
class Selection extends Overlay {
constructor({

View File

@@ -1,5 +1,5 @@
import StatusAPI from './StatusAPI.js';
import { createOpenMct, resetApplicationState } from '../../utils/testing';
import StatusAPI from './StatusAPI.js';
describe('The Status API', () => {
let statusAPI;

View File

@@ -20,14 +20,15 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import TelemetryCollection from './TelemetryCollection';
import TelemetryRequestInterceptorRegistry from './TelemetryRequestInterceptor';
import CustomStringFormatter from '../../plugins/displayLayout/CustomStringFormatter';
import TelemetryMetadataManager from './TelemetryMetadataManager';
import TelemetryValueFormatter from './TelemetryValueFormatter';
import DefaultMetadataProvider from './DefaultMetadataProvider';
import objectUtils from 'objectUtils';
import CustomStringFormatter from '../../plugins/displayLayout/CustomStringFormatter';
import DefaultMetadataProvider from './DefaultMetadataProvider';
import TelemetryCollection from './TelemetryCollection';
import TelemetryMetadataManager from './TelemetryMetadataManager';
import TelemetryRequestInterceptorRegistry from './TelemetryRequestInterceptor';
import TelemetryValueFormatter from './TelemetryValueFormatter';
/**
* @typedef {import('../time/TimeContext').TimeContext} TimeContext
*/
@@ -784,6 +785,7 @@ export default class TelemetryAPI {
*/
getLimits(domainObject) {
const provider = this.#findLimitEvaluator(domainObject);
if (!provider || !provider.getLimits) {
return {
limits: function () {
@@ -792,7 +794,23 @@ export default class TelemetryAPI {
};
}
return provider.getLimits(domainObject);
const abortController = new AbortController();
const options = { signal: abortController.signal };
this.requestAbortControllers.add(abortController);
try {
return provider.getLimits(domainObject, options);
} catch (error) {
if (error.name !== 'AbortError') {
this.openmct.notifications.error(
'Error requesting telemetry data, see console for details'
);
}
throw new Error(error);
} finally {
this.requestAbortControllers.delete(abortController);
}
}
}

View File

@@ -20,6 +20,7 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import { createOpenMct, resetApplicationState } from 'utils/testing';
import TelemetryAPI from './TelemetryAPI';
import TelemetryCollection from './TelemetryCollection';

View File

@@ -20,10 +20,11 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import _ from 'lodash';
import EventEmitter from 'EventEmitter';
import { LOADED_ERROR, TIMESYSTEM_KEY_NOTIFICATION, TIMESYSTEM_KEY_WARNING } from './constants';
import _ from 'lodash';
import { TIME_CONTEXT_EVENTS } from '../time/constants';
import { LOADED_ERROR, TIMESYSTEM_KEY_NOTIFICATION, TIMESYSTEM_KEY_WARNING } from './constants';
/**
* @typedef {import('../objects/ObjectAPI').DomainObject} DomainObject

Some files were not shown because too many files have changed in this diff Show More