Compare commits

..

57 Commits

Author SHA1 Message Date
Andrew Henry
5de5ff347c Merge branch 'release/2.0.5' into persistence-errors 2022-07-12 11:44:21 -07:00
John Hill
2bfe632e7e Fix all of the e2e tests (#5477)
* Fix timer test

* be explicit about the warnings text

* add full suite to CI to enable CircleCI Checks

* add back in devtool=false for CI env so firefox tests run

* add framework suite

* Don't install webpack HMR in CI

* Fix playwright version installs

* exclude HMR if running tests in any environment

- use NODE_ENV=TEST to exclude webpack HMR

- deparameterize some of the playwright configs

* use lower-case 'test'

* timer hover fix

* conditionally skip for firefox due to missing console events

* increase timeouts to give time for mutation

* no need to close save banner

* remove devtool setting

* revert

* update snapshots

* disable video to save some resources

* use one worker

* more timeouts :)

* Remove `browser.close()` and `page.close()` as it was breaking other tests

* Remove unnecessary awaits and fix func call syntax

* Fix image reset test

* fix restrictedNotebook tests

* revert playwright-ci.config settings

* increase timeout for polling imagery test

* remove unnecessary waits

* disable notebook lock test for chrome-beta as its unreliable

- remove some unnecessary 'wait for save banner' logic

- remove unused await

- mark imagery test as slow in chrome-beta

* LINT!! *shakes fist*

* don't run full e2e suite per commit

* disable video in all configs

* add flakey zoom comment

* exclude webpack HMR in non-development modes

Co-authored-by: Jesse Mazzella <jesse.d.mazzella@nasa.gov>
Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>
2022-07-12 11:29:38 -07:00
Andrew Henry
4ac39a3990 Do not pass onPartialResponse option on to upstream telemetry (#5486) 2022-07-12 11:23:30 -07:00
Andrew Henry
169d148c58 Revert some changes to minimize risk 2022-07-12 09:08:59 -07:00
Andrew Henry
40d2f3295f Better handling of persistence errors in general, and conflict errors specifically 2022-07-12 09:04:34 -07:00
Scott Bell
0e707150e0 get rid of root (#5483) 2022-07-11 20:02:40 -05:00
Shefali Joshi
2540d96617 Clear data when time bounds are changed (#5482)
* Clear data when time bounds are changed
Also react to clear data action
Ensure that the yKey is set to 'none' if there is no range with array Values

* Refactor trace updates to a method
2022-07-11 17:29:59 -05:00
Alize Nguyen
1c8784fec5 Stacked plot interceptor rename (#5468)
* Rename stacked plot interceptor and move to folder

Co-authored-by: Andrew Henry <akhenry@gmail.com>
2022-07-11 17:30:26 +00:00
Charles Hacskaylo
2943d2b6ec Release 2.0.5 UI and Gauge fixes (#5470)
* Various UI fixes
- Tweak to Gauge properties form for clarity and usability.
- Fix Gauge 'dial' type not obeying "Show units" property setting, closes #5325.
- Tweaks to Operator Status UI label and layout for clarity.
- Changed name and description of Graph object for clarity and consistency.
- Fixed CSS classing that was coloring Export menu items text incorrectly.
- Fixed icon-to-text vertical alignment in `.c-object-label`.
- Fix for broken layout in imagery local controls (brightness, layers, magnification).

Co-authored-by: Andrew Henry <akhenry@gmail.com>
2022-07-11 10:26:10 -07:00
Andrew Henry
4246a597a9 Fix shelved alarms (#5479)
* Fix the logic around shelved alarms

* Remove application router listener
2022-07-11 10:08:34 -07:00
Shefali Joshi
0af7965021 Fix couchdb no response (#5474)
* Update the creation date only when the document is created for the first time

* If there is no response from a bulk get, couch db has issues

* Check the response - if it's null, don't apply interceptors
2022-07-10 10:50:23 -07:00
Shefali Joshi
e9c0909415 Use timeKey for time comparison (#5471) 2022-07-08 22:05:31 +00:00
Andrew Henry
0f0a3dc48f Remove performance marks (#5465)
* Remove performance marks

* Retain performance mark in view large. It doesn't happen very often and it's needed for an automated performance test
2022-07-08 18:27:55 +00:00
Alize Nguyen
4c82680b87 Added plot interceptor for missing series config (#5422)
Co-authored-by: Andrew Henry <akhenry@gmail.com>
Co-authored-by: Shefali Joshi <simplyrender@gmail.com>
2022-07-08 10:47:05 -07:00
Andrew Henry
c4734b8ad6 Lock model (#5457)
* Lock event Model to prevent reactification

* de-reactify all the things

* Make API properties writable to allow test mocks to override them

* Fix merge conflict
2022-07-08 09:29:53 -07:00
Jesse Mazzella
9786ff5de4 [Remote Clock] Fix requestInterceptor typo (#5462)
* Fix typo in telemetry request interceptor

Co-authored-by: Jamie V <jamie.j.vigliotta@nasa.gov>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2022-07-08 09:20:35 -07:00
Jamie V
437154a5c0 removing the call for default import now that TelemetryAPI is an ES6 class (#5461) 2022-07-08 09:16:17 -07:00
Alize Nguyen
2bd38dab9f Fix for missing object for LADTableSet (#5458)
* Handle missing object errors for display layouts

Co-authored-by: Andrew Henry <akhenry@gmail.com>
2022-07-08 14:05:34 +00:00
Jesse Mazzella
063df721ae [Remote Clock] Wait for first tick and recalculate historical request bounds (#5433)
* Updated to ES6 class
* added request intercept functionality to telemetry api, added a request interceptor for remote clock
* add remoteClock e2e test stub

Co-authored-by: Jamie V <jamie.j.vigliotta@nasa.gov>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2022-07-07 23:51:12 +00:00
Shefali Joshi
a09db30b32 Allow endpoints with a single enum metadata value in Bar/Line graphs (#5443)
* If there is only 1 metadata value, set yKey to none. Also, fix bug for determining the name of a metadata value
* Update tests for enum metadata values

Co-authored-by: John Hill <john.c.hill@nasa.gov>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2022-07-07 16:44:09 -07:00
Khalid Adil
9d89bdd6d3 [Static Root] Static Root Plugin not loading (#5455)
* Log if hitting falsy leafValue

* Add some logging

* Remove logs and specify null/undefined

Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>
2022-07-07 15:00:33 -07:00
John Hill
ed9ca2829b fix pathing (#5452)
Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>
2022-07-07 14:33:05 -07:00
rukmini-bose
eacbac6aad Fix for Fault Management Visual Bugs (#5376)
* Closes #5365
* General visual improvements

Co-authored-by: Charles Hacskaylo <charlesh88@gmail.com>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2022-07-07 20:56:54 +00:00
Jesse Mazzella
69153fe8f0 [CouchDB] Always subscribe to the CouchDB changes feed (#5434)
* Add unknown state, remove maintenance state

* Handle all CouchDB status codes

- Set unknown status if we receive an unhandled code

* Include status code in error messages

* SharedWorker can send unknown status

* Add test for unknown status

* Always subscribe to CouchDB changes feed

- Always subscribe to the CouchDB changes feed, even if there are no observable objects, since we are also checking the status of CouchDB via this feed.

* Update indicator status if not using SharedWorker

* Start listening to changes feed on first request

* fix test

* adjust test to hopefully avoid race condition

* lint

Co-authored-by: John Hill <john.c.hill@nasa.gov>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
Co-authored-by: Scott Bell <scott@traclabs.com>
2022-07-07 14:30:30 -05:00
John Hill
51196530fd No gauge (#5451)
* Installed gauge plugin by default
* Make gauge part of standard install in e2e suite and add restrictednotebook

Co-authored-by: Andrew Henry <akhenry@gmail.com>
2022-07-07 11:04:50 -07:00
Andrew Henry
fefa46ce7e Debounce status summary (#5448)
Co-authored-by: John Hill <john.c.hill@nasa.gov>
2022-07-07 09:34:31 -07:00
Scott Bell
e08ab8ef24 fix sourcemaps (#5373)
Co-authored-by: John Hill <john.c.hill@nasa.gov>
2022-07-07 08:19:35 -07:00
Jamie V
7011877e64 [Telemetry Collections] Respect "Latest" Strategy Option (#5421)
* Respect latest strategy in Telemetry Collections to limit potential memory growth.
2022-07-06 16:53:41 -07:00
John Hill
34ecc08238 Backmerge e2e code coverage changes and fixes into release/2.0.5 (#5431) 2022-07-06 00:12:45 +00:00
Alize Nguyen
a07c043a29 Handle missing objects gracefully (#5399)
* Handle missing object errors for display layouts
* Handle missing object errors for Overlay Plots
* Add check for this.config
* Add try/catch statement & check if obj is missing
* Changed console.error to console.warn
* Lint fix
* Fix for this.metadata.value is undefined
* Add e2e test
* Update comment text
* Add reload check and @private, verify console.warn
* Redid assignment and metadata check
* Fix typo
* Changed assignment and metadata check
* Redid checks for isMissing(object)
* Lint fix
2022-07-05 08:58:03 -07:00
Scott Bell
2999a5135e 5361 Tags not persisting when several notebook entries are created at once (#5428)
* add end to end test to catch multiple entry errors

* click expansion triangle instead

* fix race condition between annotation creation and mutation

* make sure notebook tags run in e2e

* address PR comments
2022-07-05 17:45:39 +02:00
Andrew Henry
2766452b38 Show a better default poll question (#5425) 2022-07-01 15:56:03 -07:00
Khalid Adil
f3cdf69288 [Static Root] Return leafValue if null/undefined/false (#5416)
* Return leafValue if null/undefined/false

* Added a null to the test json
2022-07-01 13:07:13 -07:00
Charles Hacskaylo
a040bb30c2 Gauge fixes for Firefox and units display (#5369)
* Closes #5323, #5325. Parent branch is release/2.0.5.
- Significant work refactoring SVG markup and CSS for dial gauge;
- Fixed missing `v-if` to control display of units for #5325;
- Fixed bad `.length` test for limit properties;

* Closes #5323, #5325
- Add 'value out of range' indicator

* Closes #5323, #5325
- More accurate element naming;
- Fix cross-browser problems with current value display in dial gauge;
- Refinements to "out of range" indicator approach;
- Fixed size of "Amplitude" input in Sine Wave Generator;

* Closes #5323, #5325
- Styles and stubbed in code to support needle meter type;

* Closes #5323, #5325
- Stubbed in markup and CSS for needle-style meter;

* Closes #5323, #5325
- Fixed missing `js-*` classes that were failing npm run test;

* Closes #5323, #5325
- Fix to not display meter value bar unless a data value is expected;

* Addressing PR comments
- Renamed method for clarity;
- Added null value check in method `valueExpected`;
2022-06-30 21:11:16 +02:00
Jesse Mazzella
0a2e0a4e65 [CouchDB] Better determination of indicator status (#5415)
* Add unknown state, remove maintenance state

* Handle all CouchDB status codes

- Set unknown status if we receive an unhandled code

* Include status code in error messages

* SharedWorker can send unknown status

* Add test for unknown status
2022-06-30 16:30:32 +00:00
Shefali Joshi
e8df2bd437 Make plans non editable. (#5377)
* Make plans non editable.

* Add unit test for fix
2022-06-29 12:51:40 -07:00
Alize Nguyen
ccd2a8b64c Plot progress bar fix for 2.0.5 (#5386)
* Add .bind(this) to stopLoading() in loadMoreData()

* Replace load spinner with progress bar for plots

* Add loading delay prop to swg

* fix linting errors

* match load order

* Update accessibility

* Add Math.max to timeout to handle negative inputs

* Moved math.max to load delay variable

* Add loading fix for stacked plots

* Move loadingUpdate func into plot item for update

* Merge conflict resolve

* Check if delay is 0 and send, put post in a func

* Put obj directly to model, removed computed prop

* Lint fix

* Fix template where legend was not displayed

* Remove commented out template

* Fixed failing test

Co-authored-by: unlikelyzero <jchill2@gmail.com>
2022-06-29 12:41:00 -07:00
Scott Bell
2bd35bb2a5 5361 tags not persisting locally (#5408)
* fixed typo

* remove unneeded lookup

* fix tags adding and deleting

* more reliable way to remove tags

* break tests up for parallel execution

* fixed notebook tagging test

* enable e2e tests

* made schedule index comment more clear and fix uppercase/lowercase issue

* address e2e changes

* add unit test to bump coverage

* fix typo

* need to check on annotation creation if provider exists or not

* added fixtures

* undo silly couchdb commit
2022-06-29 19:30:18 +02:00
Scott Bell
28dbd724d6 5391 Add preview and drag support to Grand Search (#5394)
* add preview and drag actions

* added unit test, simplified remove action

* do not hide search results in preview mode when clicking outside search results

* add semantic aria labels to enable e2e tests

* readd preview

* add e2e test

* remove commented out url

* add percy snapshot and add search to ci

* make percy stuff work

* linting

* fix percy again

* move percy snapshots to a visual test

* added separate visual test and changed test to fixtures

* fix fixtures path

* addressing review comments
2022-06-29 08:12:45 -07:00
Jesse Mazzella
5a1c329c66 [Timer] Update 3dot menu actions appropriately (#5387)
* Call `removeAllListeners()` after emit

* Manually show/hide actions if within a view

* remove sneaky `console.log()`

* Add Timer e2e test

* Add to comments

* Avoid hard waits in Timer e2e test

- Assert against timer view state instead of menu options

* Let's also test actions from the Timer view
2022-06-28 19:39:46 +02:00
John Hill
00a5cbd2fd Cherrypicked commits (#5390)
Co-authored-by: unlikelyzero <jchill2@gmail.com>
2022-06-26 06:40:50 -07:00
Scott Bell
a2d698d5c1 Imagery View does not discard old images when they fall out of bounds (#5351)
* change to using telemetry collection

* fix tests

* added more unit tests
2022-06-23 16:04:40 -07:00
Andrew Henry
5685a5b393 Fix naming of method (#5368) 2022-06-23 20:52:12 +00:00
Jesse Mazzella
164f39695e Remove workarounds for chrome 'scrollTop' issue (#5375) 2022-06-21 15:17:47 -07:00
Shefali Joshi
c384cf67da Include objectStyles reference to conditionSetIdentifier in imports (#5354)
* Include objectStyles reference to conditionSetIdentifier in imports

* Add tests for export

* Refactored some code and removed console log
2022-06-21 14:34:45 -04:00
Shefali Joshi
417b225505 Restrict timestrip composition to time based plots, plans and imagery (#5161)
* Restrict timestrip composition to time based plots, plans and imagery

* Adds unit tests for timeline composition policy

* Addresses review comments
Improves tests

* Reuse test objects

Co-authored-by: Jamie V <jamie.j.vigliotta@nasa.gov>
2022-06-21 13:15:23 -04:00
Shefali Joshi
e5e93f311c Port grid icons and imagery test to release 2.0.5 from master (#5360)
* Port grid icons to release 2.0.5 from master

* Port imagery test to release/2.0.5
2022-06-17 08:41:30 -07:00
Shefali Joshi
39e6d9c90c Dont' mutate a stacked plot unless its user initiated (#5357) 2022-06-17 14:20:18 +00:00
Jesse Mazzella
60d021ef82 Fix imagery filter slider drag in flexible layouts (#5326) (#5350) 2022-06-16 12:25:29 -07:00
Joshi
59880955a2 Remove snapshot 2022-06-08 19:11:40 -07:00
Joshi
b51ed7e844 Merge branch 'master' of https://github.com/nasa/openmct 2022-06-08 19:11:13 -07:00
Joshi
7bbaec4006 Merge branch 'master' of https://github.com/nasa/openmct 2022-06-07 14:02:58 -07:00
Joshi
c0f24b3925 Merge branch 'master' of https://github.com/nasa/openmct 2022-05-31 11:06:55 -07:00
Joshi
4e79725897 Merge branch 'master' of https://github.com/nasa/openmct 2022-05-24 15:05:16 -07:00
Joshi
0674c9fc33 Merge branch 'master' of https://github.com/nasa/openmct 2022-05-20 09:25:39 -07:00
Joshi
de1b877954 Merge branch 'master' of https://github.com/nasa/openmct 2022-05-09 14:00:43 -07:00
dependabot[bot]
4db2f547d9 Bump d3-selection from 1.3.2 to 3.0.0
Bumps [d3-selection](https://github.com/d3/d3-selection) from 1.3.2 to 3.0.0.
- [Release notes](https://github.com/d3/d3-selection/releases)
- [Commits](https://github.com/d3/d3-selection/compare/v1.3.2...v3.0.0)

---
updated-dependencies:
- dependency-name: d3-selection
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-06 15:45:20 +00:00
22 changed files with 165 additions and 551 deletions

View File

@@ -3,7 +3,7 @@
{
"name": "descriptionRegexp",
"config": {
"regexp": "[x|X]] Testing instructions",
"regexp": "x] Testing instructions",
"errorMessage": ":police_officer: PR Description does not confirm that associated issue(s) contain Testing instructions"
}
},

3
app.js
View File

@@ -12,7 +12,7 @@ const express = require('express');
const app = express();
const fs = require('fs');
const request = require('request');
const __DEV__ = !process.env.NODE_ENV || process.env.NODE_ENV === 'development';
const __DEV__ = process.env.NODE_ENV === 'development';
// Defaults
options.port = options.port || options.p || 8080;
@@ -89,4 +89,3 @@ app.get('/', function (req, res) {
app.listen(options.port, options.host, function () {
console.log('Open MCT application running at %s:%s', options.host, options.port);
});

View File

@@ -1,122 +0,0 @@
# e2e testing
This document captures information specific to the e2e testing of Open MCT. For general information about testing, please see [the Open MCT README](https://github.com/nasa/openmct/blob/master/README.md#tests).
## Overview
This document is designed to capture on the What, Why, and How's of writing and running e2e tests in Open MCT.
### About e2e testing
e2e testing is an industry-standard approach to automating the testing of web-based UIs such as Open MCT. Broadly speaking, e2e tests differentiate themselves from unit tests by preferring replication of real user interactions over execution of raw JavaScript functions.
Historically, the abstraction necessary to replicate real user behavior meant that:
- e2e tests were "expensive" due to how much code each test executed. The closer a test replicates the user, the more code is needed run during test execution. Unit tests could run smaller units of code more effeciently.
- e2e tests were flaky due to network conditions or the underlying protocols associated with testing a browser.
- e2e frameworks relied on a browser communication standard which lacked the observability and controls necessary needed to reach the code paths possible with unit and integration tests.
- e2e frameworks provided insufficient debug information on test failure
However, as the web ecosystem has matured to the point where mission-critical UIs can be written for the web (Open MCT), the e2e testing tools have matured as well. There are now fewer "trade-offs" when choosing to write an e2e test over any other type of test.
Modern e2e frameworks:
- Bypass the surface layer of the web-application-under-test and use a raw debugging protocol to observe and control application and browser state.
- These new browser-internal protocols enable near-instant, bi-directional communication between test code and the browser, speeding up test execution and making the tests as reliable as the application itself.
- Provide test debug tooling which enables developers to pinpoint failure
Furthermore, the abstraction necessary to run e2e tests as a user enables them to be extended to run within a variety of contexts. This matches the extensible design of Open MCT.
A single e2e test in Open MCT is extended to run:
- Against a matrix of browser versions.
- Against a matrix of OS platforms.
- Against a local development version of Open MCT.
- A version of Open MCT loaded as a dependency (VIPER, VISTA, etc)
- Against a variety of data sources or telemetry endpoints.
### Why Playwright?
[Playwright](https://playwright.dev/) was chosen as our e2e framework because it solves a few VIPER Mission needs:
1. First-class support for Automated Performance Testing
2. Official Chrome, Chrome Canary, and iPad Capabilities
3. Support for Browserless.io
4. Ability to generate code coverage reports
## Getting Started
### Getting started with Playwright
### Getting started with Open MCT's implementation of Playwright
## Types of Testing
### (TBD) Visual Testing
- Visual tests leverage [Percy](https://percy.io/).
- Visual tests should be written within the `./tests/visual` folder so that they can be ignored during git clones to avoid leaking credentials when executing percy cli
#### (TBD) How to write a good visual test
### (TBD) Snapshot Testing
<https://playwright.dev/docs/test-snapshots>
### (TBD) Mobile Testing
### (TBD) Performance Testing
### (FUTURE) Component Testing
- Component testing is currrently possible in Playwright but not enabled on this project. For more, please see: <https://playwright.dev/docs/test-components>
## Architecture, Test Design and Best Practices
### (TBD) Architecture
#### (TBD) Continuous Integration
- Test maturation
- Difference between full and e2e-ci suites
- Platforms
### (TBD) Multi-browser and Multi-operating system
- Where is it tested
- What's supported
### (TBD) Test Design
- Re-usable tests for VISTA, VIPER, etc.
#### Annotations
- Annotations are a great way of organizing tests outside of a file structure.
- Current list of annotations:
- `@ipad` - Mobile execution possible with Playwright's iPad support.
- `@gds` - Executes a GDS Test Case. Used to track in VIPER Mission.
- `@addInit` - Initializes the browser with an injected and artificial state. Useful for non-default plugins.
- `@localStorage` - Captures or generates session storage to manipulate browser state. Useful for excluding in tests which require a persistent backend (i.e. CouchDB).
- `@snapshot` - Uses Playwright's snapshot functionality to record a copy of the DOM for direct comparison. Must be run inside of a container.
### (TBD) Best Practices
### (TBD) Reporting
### (TBD) Code Coverage
Code coverage is collected during test execution and reported with [nyc](https://github.com/istanbuljs/nyc) and [codecov.io](https://about.codecov.io/)
## Other
### FAQ
- How does this help NASA missions?
- When should I write an e2e test instead of a unit test?
- When should I write a functional vs visual test?
- How is Open MCT extending default Playwright functionality?
### Troubleshooting
- Why is my test failing on CI and not locally?
- How can I view the failing tests on CI?

View File

@@ -1,18 +0,0 @@
/**
* Wait for all animations within the given element and subtrees to finish
* See: https://github.com/microsoft/playwright/issues/15660#issuecomment-1184911658
* @param {import('@playwright/test').Locator} locator
*/
function waitForAnimations(locator) {
return locator
.evaluate((element) =>
Promise.all(
element
.getAnimations({ subtree: true })
.map((animation) => animation.finished)));
}
// eslint-disable-next-line no-undef
module.exports = {
waitForAnimations
};

View File

@@ -60,7 +60,6 @@ test.describe.serial('Condition Set CRUD Operations on @localStorage', () => {
getConditionSetIdentifierFromUrl = conditionSetUrl.split('/').pop().split('?')[0];
console.debug('getConditionSetIdentifierFromUrl ' + getConditionSetIdentifierFromUrl);
await page.close();
});
//Load localStorage for subsequent tests

View File

@@ -28,7 +28,6 @@ but only assume that example imagery is present.
const { test } = require('../../../fixtures.js');
const { expect } = require('@playwright/test');
const { waitForAnimations } = require('../../../commonActions.js');
const backgroundImageSelector = '.c-imagery__main-image__background-image';
@@ -82,16 +81,7 @@ test.describe('Example Imagery Object', () => {
expect(imageMouseZoomedIn.width).toBeGreaterThan(originalImageDimensions.width);
expect(imageMouseZoomedOut.height).toBeLessThan(imageMouseZoomedIn.height);
expect(imageMouseZoomedOut.width).toBeLessThan(imageMouseZoomedIn.width);
});
test('Can adjust image brightness/contrast by dragging the sliders', async ({ page, browserName }) => {
test.fixme(browserName === 'firefox', 'This test needs to be updated to work with firefox');
// Open the image filter menu
await page.locator('[role=toolbar] button[title="Brightness and contrast"]').click();
// Drag the brightness and contrast sliders around and assert filter values
await dragBrightnessSliderAndAssertFilterValues(page);
await dragContrastSliderAndAssertFilterValues(page);
});
test('Can use alt+drag to move around image once zoomed in', async ({ page }) => {
@@ -160,65 +150,78 @@ test.describe('Example Imagery Object', () => {
});
test('Can use + - buttons to zoom on the image', async ({ page }) => {
// Get initial image dimensions
await page.locator(backgroundImageSelector).hover({trial: true});
const zoomInBtn = page.locator('.t-btn-zoom-in').nth(0);
const zoomOutBtn = page.locator('.t-btn-zoom-out').nth(0);
const initialBoundingBox = await page.locator(backgroundImageSelector).boundingBox();
// Zoom in twice via button
await zoomIntoImageryByButton(page);
await zoomIntoImageryByButton(page);
// Get and assert zoomed in image dimensions
await zoomInBtn.click();
await zoomInBtn.click();
// wait for zoom animation to finish
await page.locator(backgroundImageSelector).hover({trial: true});
const zoomedInBoundingBox = await page.locator(backgroundImageSelector).boundingBox();
expect(zoomedInBoundingBox.height).toBeGreaterThan(initialBoundingBox.height);
expect(zoomedInBoundingBox.width).toBeGreaterThan(initialBoundingBox.width);
// Zoom out once via button
await zoomOutOfImageryByButton(page);
// Get and assert zoomed out image dimensions
await zoomOutBtn.click();
// wait for zoom animation to finish
await page.locator(backgroundImageSelector).hover({trial: true});
const zoomedOutBoundingBox = await page.locator(backgroundImageSelector).boundingBox();
expect(zoomedOutBoundingBox.height).toBeLessThan(zoomedInBoundingBox.height);
expect(zoomedOutBoundingBox.width).toBeLessThan(zoomedInBoundingBox.width);
// Zoom out again via button, assert against the initial image dimensions
await zoomOutOfImageryByButton(page);
const finalBoundingBox = await page.locator(backgroundImageSelector).boundingBox();
expect(finalBoundingBox).toEqual(initialBoundingBox);
});
test('Can use the reset button to reset the image', async ({ page }, testInfo) => {
test.slow(testInfo.project === 'chrome-beta', "This test is slow in chrome-beta");
// Get initial image dimensions
// wait for zoom animation to finish
await page.locator(backgroundImageSelector).hover({trial: true});
const zoomInBtn = page.locator('.t-btn-zoom-in').nth(0);
const zoomResetBtn = page.locator('.t-btn-zoom-reset').nth(0);
const initialBoundingBox = await page.locator(backgroundImageSelector).boundingBox();
// Zoom in twice via button
await zoomIntoImageryByButton(page);
await zoomIntoImageryByButton(page);
await zoomInBtn.click();
// wait for zoom animation to finish
await page.locator(backgroundImageSelector).hover({trial: true});
await zoomInBtn.click();
// wait for zoom animation to finish
await page.locator(backgroundImageSelector).hover({trial: true});
// Get and assert zoomed in image dimensions
const zoomedInBoundingBox = await page.locator(backgroundImageSelector).boundingBox();
expect.soft(zoomedInBoundingBox.height).toBeGreaterThan(initialBoundingBox.height);
expect.soft(zoomedInBoundingBox.width).toBeGreaterThan(initialBoundingBox.width);
// Reset pan and zoom and assert against initial image dimensions
await resetImageryPanAndZoom(page);
const finalBoundingBox = await page.locator(backgroundImageSelector).boundingBox();
expect(finalBoundingBox).toEqual(initialBoundingBox);
// wait for zoom animation to finish
// FIXME: The zoom is flakey, sometimes not returning to original dimensions
// https://github.com/nasa/openmct/issues/5491
await expect.poll(async () => {
await zoomResetBtn.click();
const boundingBox = await page.locator(backgroundImageSelector).boundingBox();
return boundingBox;
}, {
timeout: 10 * 1000
}).toEqual(initialBoundingBox);
});
test('Using the zoom features does not pause telemetry', async ({ page }) => {
const pausePlayButton = page.locator('.c-button.pause-play');
// wait for zoom animation to finish
await page.locator(backgroundImageSelector).hover({trial: true});
// open the time conductor drop down
await page.locator('button:has-text("Fixed Timespan")').click();
// Click local clock
await page.locator('[data-testid="conductor-modeOption-realtime"]').click();
await expect.soft(pausePlayButton).not.toHaveClass(/is-paused/);
// Zoom in via button
await zoomIntoImageryByButton(page);
await expect(pausePlayButton).not.toHaveClass(/is-paused/);
await expect.soft(pausePlayButton).not.toHaveClass(/is-paused/);
const zoomInBtn = page.locator('.t-btn-zoom-in').nth(0);
await zoomInBtn.click();
// wait for zoom animation to finish
await page.locator(backgroundImageSelector).hover({trial: true});
return expect(pausePlayButton).not.toHaveClass(/is-paused/);
});
});
@@ -246,8 +249,10 @@ test('Example Imagery in Display layout', async ({ page, browserName }) => {
await page.click('text=Example Imagery');
// Clear and set Image load delay to minimum value
// FIXME: Update the value to 5000 ms when this bug is fixed.
// See: https://github.com/nasa/openmct/issues/5265
await page.locator('input[type="number"]').fill('');
await page.locator('input[type="number"]').fill('5000');
await page.locator('input[type="number"]').fill('0');
// Click text=OK
await Promise.all([
@@ -331,19 +336,6 @@ test('Example Imagery in Display layout', async ({ page, browserName }) => {
// Verify selected image is still displayed
await expect(selectedImage).toBeVisible();
// Unpause imagery
await page.locator('.pause-play').click();
//Get background-image url from background-image css prop
await assertBackgroundImageUrlFromBackgroundCss(page);
// Open the image filter menu
await page.locator('[role=toolbar] button[title="Brightness and contrast"]').click();
// Drag the brightness and contrast sliders around and assert filter values
await dragBrightnessSliderAndAssertFilterValues(page);
await dragContrastSliderAndAssertFilterValues(page);
});
test.describe('Example imagery thumbnails resize in display layouts', () => {
@@ -434,11 +426,6 @@ test.describe('Example imagery thumbnails resize in display layouts', () => {
});
});
// test.fixme('Can use Mouse Wheel to zoom in and out of previous image');
// test.fixme('Can use alt+drag to move around image once zoomed in');
// test.fixme('Clicking on the left arrow should pause the imagery and go to previous image');
// test.fixme('If the imagery view is in pause mode, images still come in');
// test.fixme('If the imagery view is not in pause mode, it should be updated when new images come in');
test.describe('Example Imagery in Flexible layout', () => {
test('Example Imagery in Flexible layout', async ({ page, browserName }) => {
test.fixme(browserName === 'firefox', 'This test needs to be updated to work with firefox');
@@ -649,6 +636,22 @@ async function assertBackgroundImageBrightness(page, expected) {
expect(actual).toBe(expected);
}
/**
* Gets the filter:contrast value of the current background-image and
* asserts against an expected value
* @param {import('@playwright/test').Page} page
* @param {String} expected The expected contrast value
*/
async function assertBackgroundImageContrast(page, expected) {
const backgroundImage = page.locator('.c-imagery__main-image__background-image');
// Get the contrast filter value (i.e: filter: contrast(500%) => "500")
const actual = await backgroundImage.evaluate((el) => {
return el.style.filter.match(/contrast\((\d{1,3})%\)/)[1];
});
expect(actual).toBe(expected);
}
/**
* @param {import('@playwright/test').Page} page
*/
@@ -748,70 +751,3 @@ async function mouseZoomIn(page) {
expect(imageMouseZoomedIn.height).toBeGreaterThan(originalImageDimensions.height);
expect(imageMouseZoomedIn.width).toBeGreaterThan(originalImageDimensions.width);
}
/**
* Gets the filter:contrast value of the current background-image and
* asserts against an expected value
* @param {import('@playwright/test').Page} page
* @param {String} expected The expected contrast value
*/
async function assertBackgroundImageContrast(page, expected) {
const backgroundImage = page.locator('.c-imagery__main-image__background-image');
// Get the contrast filter value (i.e: filter: contrast(500%) => "500")
const actual = await backgroundImage.evaluate((el) => {
return el.style.filter.match(/contrast\((\d{1,3})%\)/)[1];
});
expect(actual).toBe(expected);
}
/**
* Use the '+' button to zoom in. Hovers first if the toolbar is not visible
* and waits for the zoom animation to finish afterwards.
* @param {import('@playwright/test').Page} page
*/
async function zoomIntoImageryByButton(page) {
// FIXME: There should only be one set of imagery buttons, but there are two?
const zoomInBtn = page.locator("[role='toolbar'][aria-label='Image controls'] .t-btn-zoom-in").nth(0);
const backgroundImage = page.locator(backgroundImageSelector);
if (!(await zoomInBtn.isVisible())) {
await backgroundImage.hover({trial: true});
}
await zoomInBtn.click();
await waitForAnimations(backgroundImage);
}
/**
* Use the '-' button to zoom out. Hovers first if the toolbar is not visible
* and waits for the zoom animation to finish afterwards.
* @param {import('@playwright/test').Page} page
*/
async function zoomOutOfImageryByButton(page) {
// FIXME: There should only be one set of imagery buttons, but there are two?
const zoomOutBtn = page.locator("[role='toolbar'][aria-label='Image controls'] .t-btn-zoom-out").nth(0);
const backgroundImage = page.locator(backgroundImageSelector);
if (!(await zoomOutBtn.isVisible())) {
await backgroundImage.hover({trial: true});
}
await zoomOutBtn.click();
await waitForAnimations(backgroundImage);
}
/**
* Use the reset button to reset image pan and zoom. Hovers first if the toolbar is not visible
* and waits for the zoom animation to finish afterwards.
* @param {import('@playwright/test').Page} page
*/
async function resetImageryPanAndZoom(page) {
// FIXME: There should only be one set of imagery buttons, but there are two?
const panZoomResetBtn = page.locator("[role='toolbar'][aria-label='Image controls'] .t-btn-zoom-reset").nth(0);
const backgroundImage = page.locator(backgroundImageSelector);
if (!(await panZoomResetBtn.isVisible())) {
await backgroundImage.hover({trial: true});
}
await panZoomResetBtn.click();
await waitForAnimations(backgroundImage);
}

View File

@@ -31,7 +31,7 @@ to "fail" on assertions. Instead, they should be used to detect changes between
Note: Larger testsuite sizes are OK due to the setup time associated with these tests.
*/
const { test } = require('../../fixtures.js');
const { test } = require('@playwright/test');
const percySnapshot = require('@percy/playwright');
const path = require('path');
const sinon = require('sinon');

View File

@@ -32,8 +32,7 @@ to "fail" on assertions. Instead, they should be used to detect changes between
Note: Larger testsuite sizes are OK due to the setup time associated with these tests.
*/
const { test } = require('../../fixtures.js');
const { expect } = require('@playwright/test');
const { test, expect } = require('@playwright/test');
const percySnapshot = require('@percy/playwright');
const path = require('path');
const sinon = require('sinon');

View File

@@ -24,8 +24,7 @@
This test suite is dedicated to tests which verify search functionality.
*/
const { test } = require('../../fixtures.js');
const { expect } = require('@playwright/test');
const { test, expect } = require('@playwright/test');
const percySnapshot = require('@percy/playwright');
/**

View File

@@ -1,6 +1,6 @@
{
"name": "openmct",
"version": "2.1.0-SNAPSHOT",
"version": "2.0.5",
"description": "The Open MCT core platform",
"devDependencies": {
"@babel/eslint-parser": "7.18.2",
@@ -13,7 +13,7 @@
"@types/karma": "^6.3.2",
"@types/lodash": "^4.14.178",
"@types/mocha": "^9.1.0",
"babel-loader": "8.2.5",
"babel-loader": "8.2.3",
"babel-plugin-istanbul": "6.1.1",
"comma-separated-values": "3.6.4",
"codecov":"3.8.3",
@@ -23,10 +23,10 @@
"d3-axis": "3.0.0",
"d3-scale": "3.3.0",
"d3-selection": "3.0.0",
"eslint": "8.18.0",
"eslint": "8.13.0",
"eslint-plugin-compat": "4.0.2",
"eslint-plugin-playwright": "0.9.0",
"eslint-plugin-vue": "9.1.1",
"eslint-plugin-vue": "9.1.0",
"eslint-plugin-you-dont-need-lodash-underscore": "6.12.0",
"eventemitter3": "1.2.0",
"express": "4.13.1",
@@ -34,7 +34,7 @@
"git-rev-sync": "3.0.2",
"html2canvas": "1.4.1",
"imports-loader": "0.8.0",
"jasmine-core": "4.2.0",
"jasmine-core": "4.1.1",
"jsdoc": "3.5.5",
"karma": "6.3.20",
"karma-chrome-launcher": "3.1.1",
@@ -42,7 +42,7 @@
"karma-coverage": "2.2.0",
"karma-coverage-istanbul-reporter": "3.0.3",
"karma-firefox-launcher": "2.1.2",
"karma-jasmine": "5.1.0",
"karma-jasmine": "4.0.1",
"karma-junit-reporter": "2.0.1",
"karma-sourcemap-loader": "0.3.8",
"karma-spec-reporter": "0.0.34",
@@ -50,20 +50,20 @@
"lighthouse": "9.6.1",
"location-bar": "3.0.1",
"lodash": "4.17.21",
"mini-css-extract-plugin": "2.6.1",
"moment": "2.29.4",
"mini-css-extract-plugin": "2.6.0",
"moment": "2.29.3",
"moment-duration-format": "2.3.2",
"moment-timezone": "0.5.34",
"node-bourbon": "4.2.3",
"painterro": "1.2.56",
"nyc":"15.1.0",
"painterro": "1.2.78",
"plotly.js-basic-dist": "2.12.0",
"plotly.js-gl2d-dist": "2.12.0",
"printj": "1.3.1",
"request": "2.88.2",
"resolve-url-loader": "5.0.0",
"sass": "1.52.2",
"sass-loader": "13.0.2",
"sass-loader": "12.6.0",
"sinon": "14.0.0",
"style-loader": "^1.0.1",
"uuid": "8.3.2",
@@ -72,7 +72,7 @@
"vue-loader": "15.9.8",
"vue-template-compiler": "2.6.14",
"webpack": "5.68.0",
"webpack-cli": "4.10.0",
"webpack-cli": "4.9.2",
"webpack-dev-middleware": "5.3.3",
"webpack-hot-middleware": "2.25.1",
"webpack-merge": "5.8.0"
@@ -92,7 +92,7 @@
"test:firefox": "cross-env NODE_ENV=test NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --single-run --browsers=FirefoxHeadless",
"test:debug": "cross-env NODE_ENV=debug karma start --no-single-run",
"test:e2e": "npx playwright test",
"test:e2e:ci": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome visual smoke branding default condition timeConductor clock persistence performance grandsearch tags",
"test:e2e:ci": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome smoke branding default condition timeConductor clock exampleImagery persistence performance grandsearch notebook/tags",
"test:e2e:local": "npx playwright test --config=e2e/playwright-local.config.js --project=chrome",
"test:e2e:updatesnapshots": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome --grep @snapshot --update-snapshots",
"test:e2e:visual": "percy exec --config ./e2e/.percy.yml -- npx playwright test --config=e2e/playwright-visual.config.js",

View File

@@ -52,8 +52,8 @@ class MutableDomainObject {
// Property should not be serialized
enumerable: false
},
_callbacksForPaths: {
value: {},
_observers: {
value: [],
// Property should not be serialized
enumerable: false
},
@@ -64,31 +64,15 @@ class MutableDomainObject {
}
});
}
/**
* BRAND new approach
* - Register a listener on $_synchronize_model
* - The $_synchronize_model event provides the path. Figure out whether the mutated path is equal to, or a parent of the observed path.
* - If so, trigger callback with new value
* - As an optimization, ONLY trigger if value has actually changed. Could be deferred until later?
*/
$observe(path, callback) {
let callbacksForPath = this._callbacksForPaths[path];
if (callbacksForPath === undefined) {
callbacksForPath = [];
this._callbacksForPaths[path] = callbacksForPath;
}
let fullPath = qualifiedEventName(this, path);
let eventOff =
this._globalEventEmitter.off.bind(this._globalEventEmitter, fullPath, callback);
callbacksForPath.push(callback);
return function unlisten() {
let index = callbacksForPath.indexOf(callback);
callbacksForPath.splice(index, 1);
if (callbacksForPath.length === 0) {
delete this._callbacksForPaths[path];
}
}.bind(this);
this._globalEventEmitter.on(fullPath, callback);
this._observers.push(eventOff);
return eventOff;
}
$set(path, value) {
_.set(this, path, value);
@@ -104,14 +88,25 @@ class MutableDomainObject {
this._globalEventEmitter.emit(ANY_OBJECT_EVENT, this);
//Emit wildcard event, with path so that callback knows what changed
this._globalEventEmitter.emit(qualifiedEventName(this, '*'), this, path, value);
//Emit events specific to properties affected
let parentPropertiesList = path.split('.');
for (let index = parentPropertiesList.length; index > 0; index--) {
let parentPropertyPath = parentPropertiesList.slice(0, index).join('.');
this._globalEventEmitter.emit(qualifiedEventName(this, parentPropertyPath), _.get(this, parentPropertyPath));
}
//TODO: Emit events for listeners of child properties when parent changes.
// Do it at observer time - also register observers for parent attribute path.
}
$refresh(model) {
const clone = JSON.parse(JSON.stringify(this));
//TODO: Currently we are updating the entire object.
// In the future we could update a specific property of the object using the 'path' parameter.
this._globalEventEmitter.emit(qualifiedEventName(this, '$_synchronize_model'), model);
//Emit wildcard event
this._globalEventEmitter.emit(qualifiedEventName(this, '*'), this, '*', this, clone);
//Emit wildcard event, with path so that callback knows what changed
this._globalEventEmitter.emit(qualifiedEventName(this, '*'), this);
}
$on(event, callback) {
@@ -119,53 +114,23 @@ class MutableDomainObject {
return () => this._instanceEventEmitter.off(event, callback);
}
$updateListenersOnPath(updatedModel, mutatedPath, newValue, oldModel) {
const isRefresh = mutatedPath === '*';
Object.entries(this._callbacksForPaths).forEach(([observedPath, callbacks]) => {
if (isChildOf(observedPath, mutatedPath)
|| isParentOf(observedPath, mutatedPath)) {
let newValueOfObservedPath;
if (observedPath === '*') {
newValueOfObservedPath = updatedModel;
} else {
newValueOfObservedPath = _.get(updatedModel, observedPath);
}
if (isRefresh && observedPath !== '*') {
const oldValueOfObservedPath = _.get(oldModel, observedPath);
if (!_.isEqual(newValueOfObservedPath, oldValueOfObservedPath)) {
callbacks.forEach(callback => callback(newValueOfObservedPath));
}
} else {
//Assumed to be different if result of mutation.
callbacks.forEach(callback => callback(newValueOfObservedPath));
}
}
});
}
$synchronizeModel(updatedObject) {
let clone = JSON.parse(JSON.stringify(updatedObject));
utils.refresh(this, clone);
}
$destroy() {
Object.keys(this._callbacksForPaths).forEach(key => delete this._callbacksForPaths[key]);
while (this._observers.length > 0) {
const observer = this._observers.pop();
observer();
}
this._instanceEventEmitter.emit('$_destroy');
this._globalEventEmitter.off(qualifiedEventName(this, '$_synchronize_model'), this.$synchronizeModel);
this._globalEventEmitter.off(qualifiedEventName(this, '*'), this.$updateListenersOnPath);
}
static createMutable(object, mutationTopic) {
let mutable = Object.create(new MutableDomainObject(mutationTopic));
Object.assign(mutable, object);
mutable.$updateListenersOnPath = mutable.$updateListenersOnPath.bind(mutable);
mutable.$synchronizeModel = mutable.$synchronizeModel.bind(mutable);
mutable._globalEventEmitter.on(qualifiedEventName(mutable, '$_synchronize_model'), mutable.$synchronizeModel);
mutable._globalEventEmitter.on(qualifiedEventName(mutable, '*'), mutable.$updateListenersOnPath);
mutable.$observe('$_synchronize_model', (updatedObject) => {
let clone = JSON.parse(JSON.stringify(updatedObject));
utils.refresh(mutable, clone);
});
return mutable;
}
@@ -182,12 +147,4 @@ function qualifiedEventName(object, eventName) {
return [keystring, eventName].join(':');
}
function isChildOf(observedPath, mutatedPath) {
return Boolean(mutatedPath === '*' || observedPath?.startsWith(mutatedPath));
}
function isParentOf(observedPath, mutatedPath) {
return Boolean(observedPath === '*' || mutatedPath?.startsWith(observedPath));
}
export default MutableDomainObject;

View File

@@ -230,6 +230,7 @@ export default class ObjectAPI {
return result;
}).catch((result) => {
console.warn(`Failed to retrieve ${keystring}:`, result);
this.openmct.notifications.error(`Failed to retrieve object ${keystring}`);
delete this.cache[keystring];
@@ -387,7 +388,13 @@ export default class ObjectAPI {
}
}
return result;
return result.catch((error) => {
if (error instanceof this.errors.Conflict) {
this.openmct.notifications.error(`Conflict detected while saving ${this.makeKeyString(domainObject.identifier)}`);
}
throw error;
});
}
/**

View File

@@ -1,7 +1,7 @@
import ObjectAPI from './ObjectAPI.js';
import { createOpenMct, resetApplicationState } from '../../utils/testing';
fdescribe("The Object API", () => {
describe("The Object API", () => {
let objectAPI;
let typeRegistry;
let openmct = {};
@@ -287,167 +287,53 @@ fdescribe("The Object API", () => {
mutableSecondInstance.$destroy();
});
describe('on mutation', () => {
it('to stay synchronized', function () {
objectAPI.mutate(mutable, 'otherAttribute', 'new-attribute-value');
expect(mutableSecondInstance.otherAttribute).toBe('new-attribute-value');
});
it('to stay synchronized when mutated', function () {
objectAPI.mutate(mutable, 'otherAttribute', 'new-attribute-value');
expect(mutableSecondInstance.otherAttribute).toBe('new-attribute-value');
});
it('to indicate when a property changes', function () {
let mutationCallback = jasmine.createSpy('mutation-callback');
let unlisten;
it('to indicate when a property changes', function () {
let mutationCallback = jasmine.createSpy('mutation-callback');
let unlisten;
return new Promise(function (resolve) {
mutationCallback.and.callFake(resolve);
unlisten = objectAPI.observe(mutableSecondInstance, 'otherAttribute', mutationCallback);
objectAPI.mutate(mutable, 'otherAttribute', 'some-new-value');
}).then(function () {
expect(mutationCallback).toHaveBeenCalledWith('some-new-value');
unlisten();
});
});
it('to indicate when a child property has changed', function () {
let embeddedKeyCallback = jasmine.createSpy('embeddedKeyCallback');
let embeddedObjectCallback = jasmine.createSpy('embeddedObjectCallback');
let objectAttributeCallback = jasmine.createSpy('objectAttribute');
let listeners = [];
return new Promise(function (resolve) {
objectAttributeCallback.and.callFake(resolve);
listeners.push(objectAPI.observe(mutableSecondInstance, 'objectAttribute.embeddedObject.embeddedKey', embeddedKeyCallback));
listeners.push(objectAPI.observe(mutableSecondInstance, 'objectAttribute.embeddedObject', embeddedObjectCallback));
listeners.push(objectAPI.observe(mutableSecondInstance, 'objectAttribute', objectAttributeCallback));
objectAPI.mutate(mutable, 'objectAttribute.embeddedObject.embeddedKey', 'updated-embedded-value');
}).then(function () {
expect(embeddedKeyCallback).toHaveBeenCalledWith('updated-embedded-value');
expect(embeddedObjectCallback).toHaveBeenCalledWith({
embeddedKey: 'updated-embedded-value'
});
expect(objectAttributeCallback).toHaveBeenCalledWith({
embeddedObject: {
embeddedKey: 'updated-embedded-value'
}
});
listeners.forEach(listener => listener());
});
});
it('to indicate when a parent property has changed', function () {
let embeddedKeyCallback = jasmine.createSpy('embeddedKeyCallback');
let embeddedObjectCallback = jasmine.createSpy('embeddedObjectCallback');
let objectAttributeCallback = jasmine.createSpy('objectAttribute');
let listeners = [];
return new Promise(function (resolve) {
objectAttributeCallback.and.callFake(resolve);
listeners.push(objectAPI.observe(mutableSecondInstance, 'objectAttribute.embeddedObject.embeddedKey', embeddedKeyCallback));
listeners.push(objectAPI.observe(mutableSecondInstance, 'objectAttribute.embeddedObject', embeddedObjectCallback));
listeners.push(objectAPI.observe(mutableSecondInstance, 'objectAttribute', objectAttributeCallback));
objectAPI.mutate(mutable, 'objectAttribute.embeddedObject', 'updated-embedded-value');
}).then(function () {
expect(embeddedKeyCallback).toHaveBeenCalledWith(undefined);
expect(embeddedObjectCallback).toHaveBeenCalledWith('updated-embedded-value');
expect(objectAttributeCallback).toHaveBeenCalledWith({
embeddedObject: 'updated-embedded-value'
});
listeners.forEach(listener => listener());
});
return new Promise(function (resolve) {
mutationCallback.and.callFake(resolve);
unlisten = objectAPI.observe(mutableSecondInstance, 'otherAttribute', mutationCallback);
objectAPI.mutate(mutable, 'otherAttribute', 'some-new-value');
}).then(function () {
expect(mutationCallback).toHaveBeenCalledWith('some-new-value');
unlisten();
});
});
describe('on refresh', () => {
let refreshModel;
it('to indicate when a child property has changed', function () {
let embeddedKeyCallback = jasmine.createSpy('embeddedKeyCallback');
let embeddedObjectCallback = jasmine.createSpy('embeddedObjectCallback');
let objectAttributeCallback = jasmine.createSpy('objectAttribute');
let listeners = [];
beforeEach(() => {
refreshModel = JSON.parse(JSON.stringify(mutable));
});
return new Promise(function (resolve) {
objectAttributeCallback.and.callFake(resolve);
it('to stay synchronized', function () {
refreshModel.otherAttribute = 'new-attribute-value';
mutable.$refresh(refreshModel);
expect(mutableSecondInstance.otherAttribute).toBe('new-attribute-value');
});
listeners.push(objectAPI.observe(mutableSecondInstance, 'objectAttribute.embeddedObject.embeddedKey', embeddedKeyCallback));
listeners.push(objectAPI.observe(mutableSecondInstance, 'objectAttribute.embeddedObject', embeddedObjectCallback));
listeners.push(objectAPI.observe(mutableSecondInstance, 'objectAttribute', objectAttributeCallback));
it('to indicate when a property changes', function () {
let mutationCallback = jasmine.createSpy('mutation-callback');
let unlisten;
return new Promise(function (resolve) {
mutationCallback.and.callFake(resolve);
unlisten = objectAPI.observe(mutableSecondInstance, 'otherAttribute', mutationCallback);
refreshModel.otherAttribute = 'some-new-value';
mutable.$refresh(refreshModel);
}).then(function () {
expect(mutationCallback).toHaveBeenCalledWith('some-new-value');
unlisten();
objectAPI.mutate(mutable, 'objectAttribute.embeddedObject.embeddedKey', 'updated-embedded-value');
}).then(function () {
expect(embeddedKeyCallback).toHaveBeenCalledWith('updated-embedded-value');
expect(embeddedObjectCallback).toHaveBeenCalledWith({
embeddedKey: 'updated-embedded-value'
});
});
it('to indicate when a child property has changed', function () {
let embeddedKeyCallback = jasmine.createSpy('embeddedKeyCallback');
let embeddedObjectCallback = jasmine.createSpy('embeddedObjectCallback');
let objectAttributeCallback = jasmine.createSpy('objectAttribute');
let listeners = [];
return new Promise(function (resolve) {
objectAttributeCallback.and.callFake(resolve);
listeners.push(objectAPI.observe(mutableSecondInstance, 'objectAttribute.embeddedObject.embeddedKey', embeddedKeyCallback));
listeners.push(objectAPI.observe(mutableSecondInstance, 'objectAttribute.embeddedObject', embeddedObjectCallback));
listeners.push(objectAPI.observe(mutableSecondInstance, 'objectAttribute', objectAttributeCallback));
refreshModel.objectAttribute.embeddedObject.embeddedKey = 'updated-embedded-value';
mutable.$refresh(refreshModel);
}).then(function () {
expect(embeddedKeyCallback).toHaveBeenCalledWith('updated-embedded-value');
expect(embeddedObjectCallback).toHaveBeenCalledWith({
expect(objectAttributeCallback).toHaveBeenCalledWith({
embeddedObject: {
embeddedKey: 'updated-embedded-value'
});
expect(objectAttributeCallback).toHaveBeenCalledWith({
embeddedObject: {
embeddedKey: 'updated-embedded-value'
}
});
listeners.forEach(listener => listener());
}
});
});
it('to indicate when a parent property has changed', function () {
let embeddedKeyCallback = jasmine.createSpy('embeddedKeyCallback');
let embeddedObjectCallback = jasmine.createSpy('embeddedObjectCallback');
let objectAttributeCallback = jasmine.createSpy('objectAttribute');
let listeners = [];
return new Promise(function (resolve) {
objectAttributeCallback.and.callFake(resolve);
listeners.push(objectAPI.observe(mutableSecondInstance, 'objectAttribute.embeddedObject.embeddedKey', embeddedKeyCallback));
listeners.push(objectAPI.observe(mutableSecondInstance, 'objectAttribute.embeddedObject', embeddedObjectCallback));
listeners.push(objectAPI.observe(mutableSecondInstance, 'objectAttribute', objectAttributeCallback));
refreshModel.objectAttribute.embeddedObject = 'updated-embedded-value';
mutable.$refresh(refreshModel);
}).then(function () {
expect(embeddedKeyCallback).toHaveBeenCalledWith(undefined);
expect(embeddedObjectCallback).toHaveBeenCalledWith('updated-embedded-value');
expect(objectAttributeCallback).toHaveBeenCalledWith({
embeddedObject: 'updated-embedded-value'
});
listeners.forEach(listener => listener());
});
listeners.forEach(listener => listener());
});
});
});
});

View File

@@ -155,7 +155,7 @@ describe("The LAD Table", () => {
// add another telemetry object as composition in lad table to test multi rows
mockObj.ladTable.composition.push(anotherTelemetryObj.identifier);
beforeEach(async () => {
beforeEach(async (done) => {
let telemetryRequestResolve;
let telemetryObjectResolve;
let anotherTelemetryObjectResolve;
@@ -204,6 +204,8 @@ describe("The LAD Table", () => {
await Promise.all([telemetryRequestPromise, telemetryObjectPromise, anotherTelemetryObjectPromise]);
await Vue.nextTick();
done();
});
it("should show one row per object in the composition", () => {

View File

@@ -49,7 +49,6 @@ describe("the plugin", () => {
let parentObject;
let parentObjectPath;
let changedParentObject;
let unobserve;
beforeEach((done) => {
parentObject = {
name: 'mock folder',
@@ -74,7 +73,7 @@ describe("the plugin", () => {
});
});
unobserve = openmct.objects.observe(parentObject, '*', (newObject) => {
openmct.objects.observe(parentObject, '*', (newObject) => {
changedParentObject = newObject;
done();
@@ -82,9 +81,6 @@ describe("the plugin", () => {
newFolderAction.invoke(parentObjectPath);
});
afterEach(() => {
unobserve();
});
it('creates a new folder object', () => {
expect(openmct.objects.save).toHaveBeenCalled();

View File

@@ -296,17 +296,12 @@ export default {
window.addEventListener('orientationchange', this.formatSidebar);
window.addEventListener('hashchange', this.setSectionAndPageFromUrl);
this.filterAndSortEntries();
this.unlistenToEntryChanges = this.openmct.objects.observe(this.domainObject, "configuration.entries", () => this.filterAndSortEntries());
},
beforeDestroy() {
if (this.unlisten) {
this.unlisten();
}
if (this.unlistenToEntryChanges) {
this.unlistenToEntryChanges();
}
window.removeEventListener('orientationchange', this.formatSidebar);
window.removeEventListener('hashchange', this.setSectionAndPageFromUrl);
},

View File

@@ -233,13 +233,6 @@ export default {
},
mounted() {
this.dropOnEntry = this.dropOnEntry.bind(this);
this.$on('tags-updated', async () => {
const user = await this.openmct.user.getCurrentUser();
this.entry.modified = Date.now();
this.entry.modifiedBy = user.getId();
this.$emit('updateEntry', this.entry);
});
},
methods: {
async addNewEmbed(objectPath) {

View File

@@ -217,9 +217,11 @@ class CouchObjectProvider {
this.indicator.setIndicatorToState(DISCONNECTED);
console.error(error.message);
throw new Error(`CouchDB Error - No response"`);
}
} else {
console.error(error.message);
console.error(error.message);
throw error;
}
}
}
@@ -653,7 +655,6 @@ class CouchObjectProvider {
let document = new CouchDocument(key, queued.model);
document.metadata.created = Date.now();
this.request(key, "PUT", document).then((response) => {
console.log('create check response', key);
this.#checkResponse(response, queued.intermediateResponse, key);
}).catch(error => {
queued.intermediateResponse.reject(error);

View File

@@ -71,7 +71,7 @@ describe("the RemoteClock plugin", () => {
parse: (datum) => datum.key
};
beforeEach(async () => {
beforeEach((done) => {
openmct.install(openmct.plugins.RemoteClock(TIME_TELEMETRY_ID));
let clocks = openmct.time.getAllClocks();
@@ -113,7 +113,9 @@ describe("the RemoteClock plugin", () => {
end: OFFSET_END
});
await Promise.all([objectPromiseResolve, requestPromise]);
Promise.all([objectPromiseResolve, requestPromise])
.then(done)
.catch(done);
});
it('is available and sets up initial values and listeners', () => {

View File

@@ -78,15 +78,13 @@ describe("the plugin", () => {
describe('when invoked', () => {
beforeEach(() => {
beforeEach((done) => {
openmct.overlays.overlay = function (options) {};
spyOn(openmct.overlays, 'overlay');
viewDatumAction.invoke(mockObjectPath, mockView);
});
it('creates an overlay', () => {
expect(openmct.overlays.overlay).toHaveBeenCalled();
});
});

View File

@@ -133,11 +133,8 @@ export default {
this.addedTags.push(newTagValue);
this.userAddingTag = true;
},
async tagRemoved(tagToRemove) {
const result = await this.openmct.annotation.removeAnnotationTag(this.annotation, tagToRemove);
this.$emit('tags-updated');
return result;
tagRemoved(tagToRemove) {
return this.openmct.annotation.removeAnnotationTag(this.annotation, tagToRemove);
},
async tagAdded(newTag) {
const annotationWasCreated = this.annotation === null || this.annotation === undefined;
@@ -149,7 +146,6 @@ export default {
this.tagsChanged(this.annotation.tags);
this.userAddingTag = false;
this.$emit('tags-updated');
}
}
};

View File

@@ -6,17 +6,6 @@ const webpack = require('webpack');
module.exports = merge(common, {
mode: 'development',
watchOptions: {
// Since we use require.context, webpack is watching the entire directory.
// We need to exclude any files we don't want webpack to watch.
// See: https://webpack.js.org/configuration/watch/#watchoptions-exclude
ignored: [
'**/{node_modules,dist,docs,e2e}', // All files in node_modules, dist, docs, e2e,
'**/{*.yml,Procfile,webpack*.js,babel*.js,package*.json,tsconfig.json,jsdoc.json}', // Config files
'**/*.{sh,md,png,ttf,woff,svg}', // Non source files
'**/.*' // dotfiles and dotfolders
]
},
resolve: {
alias: {
"vue": path.join(__dirname, "node_modules/vue/dist/vue.js")