Compare commits
43 Commits
dave/inspe
...
release/3.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c1dd82cf5f | ||
|
|
e43ddf6a4a | ||
|
|
018a64eb86 | ||
|
|
b1785da838 | ||
|
|
ce59c0f50a | ||
|
|
d53d8d562e | ||
|
|
d973140906 | ||
|
|
b1169ffd7d | ||
|
|
ede93d881c | ||
|
|
6947c912a7 | ||
|
|
2243381d52 | ||
|
|
3c7d3397d6 | ||
|
|
9f8338124f | ||
|
|
ab0e2d2c96 | ||
|
|
c3b5e4e1e3 | ||
|
|
8fc5aa6ed5 | ||
|
|
5592d20ab1 | ||
|
|
ce2305455a | ||
|
|
ff2c8b35b0 | ||
|
|
482f1f68dd | ||
|
|
05c7a81630 | ||
|
|
b8949db767 | ||
|
|
61e7050391 | ||
|
|
3f51516da9 | ||
|
|
541a022f36 | ||
|
|
c7b5ecbd68 | ||
|
|
6776cc308f | ||
|
|
95ac919ddf | ||
|
|
2a1064cbab | ||
|
|
8e917b2679 | ||
|
|
0be106f29e | ||
|
|
9f7b3b9225 | ||
|
|
e9b94c308b | ||
|
|
64740e133f | ||
|
|
c27ad469f6 | ||
|
|
e09a7aebae | ||
|
|
b87459dfd7 | ||
|
|
ca06a6a047 | ||
|
|
95e3ab25a4 | ||
|
|
f9db6433a1 | ||
|
|
cbac2c1c82 | ||
|
|
a8678aa739 | ||
|
|
244e3b7938 |
@@ -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:
|
||||
|
||||
@@ -479,7 +479,11 @@
|
||||
"mediump",
|
||||
"sinonjs",
|
||||
"generatedata",
|
||||
"grandsearch"
|
||||
"grandsearch",
|
||||
"websockets",
|
||||
"swgs",
|
||||
"memlab",
|
||||
"devmode"
|
||||
],
|
||||
"dictionaries": ["npm", "softwareTerms", "node", "html", "css", "bash", "en_US"],
|
||||
"ignorePaths": [
|
||||
|
||||
15
.eslintrc.js
15
.eslintrc.js
@@ -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
23
.github/release.yml
vendored
Normal 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:
|
||||
- '*'
|
||||
2
.github/workflows/e2e-couchdb.yml
vendored
2
.github/workflows/e2e-couchdb.yml
vendored
@@ -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 }}
|
||||
|
||||
10
.github/workflows/pr-platform.yml
vendored
10
.github/workflows/pr-platform.yml
vendored
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
176
API.md
@@ -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
|
||||
|
||||
|
||||
40
README.md
40
README.md
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
136
e2e/README.md
136
e2e/README.md
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
60
e2e/playwright-performance-prod.config.js
Normal file
60
e2e/playwright-performance-prod.config.js
Normal 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;
|
||||
1
e2e/test-data/memory-leak-detection.json
Normal file
1
e2e/test-data/memory-leak-detection.json
Normal file
File diff suppressed because one or more lines are too long
@@ -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}}]"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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')
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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}`);
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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('./');
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
299
e2e/tests/performance/memory/navigation.memory.perf.spec.js
Normal file
299
e2e/tests/performance/memory/navigation.memory.perf.spec.js
Normal 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;
|
||||
}
|
||||
});
|
||||
@@ -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);
|
||||
|
||||
@@ -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 }) => {
|
||||
|
||||
56
e2e/tests/visual/components/inspector.visual.spec.js
Normal file
56
e2e/tests/visual/components/inspector.visual.spec.js
Normal 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
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }) => {
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
import EventEmitter from 'EventEmitter';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import createExampleUser from './exampleUserCreator';
|
||||
|
||||
const STATUSES = [
|
||||
|
||||
@@ -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', {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import Vue from 'vue';
|
||||
|
||||
import HelloWorld from './HelloWorld.vue';
|
||||
|
||||
function SimpleVuePlugin() {
|
||||
|
||||
34
package.json
34
package.json
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MctTree from '@/ui/layout/mct-tree.vue';
|
||||
import MctTree from '@/ui/layout/MctTree.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
*****************************************************************************/
|
||||
|
||||
import EventEmitter from 'EventEmitter';
|
||||
|
||||
import SimpleIndicator from './SimpleIndicator';
|
||||
|
||||
class IndicatorAPI extends EventEmitter {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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] = [];
|
||||
}
|
||||
|
||||
@@ -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] = [];
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import Transaction from './Transaction';
|
||||
import utils from 'objectUtils';
|
||||
|
||||
import Transaction from './Transaction';
|
||||
|
||||
let openmct = {};
|
||||
let objectAPI;
|
||||
let transaction;
|
||||
|
||||
@@ -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 }) {
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user