Fix and re-enable disabled unit test suites (#6990)

* fix: register event listeners etc in `created()` hook

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

* refactor(test): make `openmct` const

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

* refactor: fix eslint errors

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

* test: re-enable and fix stackedPlot test suite

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

* fix: add back in check on mount

* test: fix Layout tests

* fix: BarGraph test

* fix: plot inspector tests

* fix: reenable grand search tests

* fix: inspectorStyles test suite

* fix: re-enable most timeline tests

* fix: no need to hideTree in appactions

* fix: re-enable more tests

* test: re-enable more tests

* test: re-enable most plot tests

* chore: `lint:fix`

* test: re-enable timelist suite

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

* test: add regression tests to cover disabled unit tests

* refactor: lint:fix

* refactor: no need for momentjs here

* fix: timerAction missed refactor

* fix: ensure timestamp is always UTC string

* test: use role selectors

* docs: add instructions for clock override in e2e

* docs: update

* Update readme

* lint

* spelling fixes

---------

Co-authored-by: Scott Bell <scott@traclabs.com>
Co-authored-by: John Hill <john.c.hill@nasa.gov>
This commit is contained in:
Jesse Mazzella 2023-09-05 01:53:03 -07:00 committed by GitHub
parent 9f7b3b9225
commit 0be106f29e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 573 additions and 382 deletions

View File

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

View File

@ -82,13 +82,20 @@ To make this possible, we're leveraging a 3rd party service, [Percy](https://per
At present, we are using percy with two configuration files: `./e2e/.percy.nightly.yml` and `./e2e/.percy.ci.yml`. This is mainly to reduce the number of snapshots.
### (Advanced) Snapshot Testing
### Advanced: Snapshot Testing (Not Recommended)
Snapshot testing is very similar to visual testing but allows us to be more precise in detecting change without relying on a 3rd party service. Unfortuantely, this precision requires advanced test setup and teardown and so we're using this pattern as a last resort.
While snapshot testing offers a precise way to detect changes in your application without relying on third-party services like Percy.io, we've found that it doesn't offer any advantages over visual testing in our use-cases. Therefore, snapshot testing is **not recommended** for further implementation.
To give an example, if a _single_ visual test assertion for an Overlay plot is run through multiple DOM rendering engines at various viewports to see how the Plot looks. If that same test were run as a snapshot test, it could only be executed against a single browser, on a single platform (ubuntu docker container).
#### CI vs Manual Checks
Snapshot tests can be reliably executed in Continuous Integration (CI) environments but lack the manual oversight provided by visual testing platforms like Percy.io. This means they may miss issues that a human reviewer could catch during manual checks.
#### Example
A single visual test assertion in Percy.io can be executed across 10 different browser and resolution combinations without additional setup, providing comprehensive testing with minimal configuration. In contrast, a snapshot test is restricted to a single OS and browser resolution, requiring more effort to achieve the same level of coverage.
#### Further Reading
For those interested in the mechanics of snapshot testing with Playwright, you can refer to the [Playwright Snapshots Documentation](https://playwright.dev/docs/test-snapshots). However, keep in mind that we do not recommend using this approach.
Read more about [Playwright Snapshots](https://playwright.dev/docs/test-snapshots)
#### Open MCT's implementation
@ -308,14 +315,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 +343,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 +363,29 @@ 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.
- 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.
#### How to write a great network test
@ -377,12 +402,35 @@ For now, our best practices exist as self-tested, living documentation in our [e
For best practices with regards to mocking network responses, see our [couchdb.e2e.spec.js](./tests/functional/couchdb.e2e.spec.js) file.
### Tips & Tricks (TODO)
### Tips & Tricks
The following contains a list of tips and tricks which don't exactly fit into a FAQ or Best Practices doc.
- (Advanced) Overriding the Browser's Clock
It is possible to override the browser's clock in order to control time-based elements. Since this can cause unwanted behavior (i.e. Tree not rendering), only use this sparingly. To do this, use the `overrideClock` fixture as such:
```js
const { test, expect } = require('../../pluginFixtures.js');
test.describe('foo test suite', () => {
// All subsequent tests in this suite will override the clock
test.use({
clockOptions: {
now: 1732413600000, // A timestamp given as milliseconds since the epoch
shouldAdvanceTime: true // Should the clock tick?
}
});
test('bar test', async ({ page }) => {
// ...
});
});
```
More info and options for `overrideClock` can be found in [baseFixtures.js](baseFixtures.js)
- Working with multiple pages
There are instances where multiple browser pages will need to be opened to verify multi-page or multi-tab application behavior.
There are instances where multiple browser pages will needed to verify multi-page or multi-tab application behavior. Make sure to use the `@2p` annotation as well as name each page appropriately: i.e. `page1` and `page2` or `tab1` and `tab2` depending on the intended use case. Generally pages should be used unless testing `sharedWorker` code, specifically.
### Reporting

View File

@ -78,7 +78,7 @@ async function createDomainObjectWithDefaults(
// Navigate to the parent object. This is necessary to create the object
// in the correct location, such as a folder, layout, or plot.
await page.goto(`${parentUrl}?hideTree=true`);
await page.goto(`${parentUrl}`);
//Click the Create button
await page.click('button:has-text("Create")');
@ -179,7 +179,7 @@ async function createPlanFromJSON(page, { name, json, parent = 'mine' }) {
// Navigate to the parent object. This is necessary to create the object
// in the correct location, such as a folder, layout, or plot.
await page.goto(`${parentUrl}?hideTree=true`);
await page.goto(`${parentUrl}`);
// Click the Create button
await page.click('button:has-text("Create")');
@ -232,7 +232,7 @@ async function createExampleTelemetryObject(page, parent = 'mine') {
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();

View File

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

View File

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

View File

@ -86,12 +86,15 @@ export default {
handler: 'updateData'
}
},
created() {
this.registerListeners();
},
mounted() {
this.plotResizeObserver.observe(this.$refs.plotWrapper);
Plotly.newPlot(this.$refs.plot, Array.from(this.data), this.getLayout(), {
responsive: true,
displayModeBar: false
});
this.registerListeners();
},
beforeUnmount() {
if (this.plotResizeObserver) {
@ -226,7 +229,6 @@ export default {
window.dispatchEvent(new Event('resize'));
}, 250);
});
this.plotResizeObserver.observe(this.$refs.plotWrapper);
}
},
reset() {

View File

@ -44,17 +44,16 @@ export default function BarGraphViewProvider(openmct) {
return domainObject && domainObject.type === BAR_GRAPH_KEY;
},
view: function (domainObject, objectPath) {
view(domainObject, objectPath) {
let _destroy = null;
let component = null;
return {
show: function (element) {
show(element) {
let isCompact = isCompactView(objectPath);
const { vNode, destroy } = mount(
{
el: element,
components: {
BarGraphView
},
@ -80,7 +79,7 @@ export default function BarGraphViewProvider(openmct) {
_destroy = destroy;
component = vNode.componentInstance;
},
destroy: function () {
destroy() {
_destroy();
},
onClearData() {

View File

@ -221,7 +221,7 @@ describe('the plugin', function () {
});
});
xdescribe('The spectral plot view for telemetry objects with array values', () => {
describe('The spectral plot view for telemetry objects with array values', () => {
let barGraphObject;
// eslint-disable-next-line no-unused-vars
let mockComposition;
@ -257,7 +257,7 @@ describe('the plugin', function () {
await Vue.nextTick();
});
it('Renders spectral plots', () => {
it('Renders spectral plots', async () => {
const dotFullTelemetryObject = {
identifier: {
namespace: 'someNamespace',
@ -305,11 +305,12 @@ describe('the plugin', function () {
barGraphView.show(child, true);
mockComposition.emit('add', dotFullTelemetryObject);
return Vue.nextTick().then(() => {
const plotElement = element.querySelector('.cartesianlayer .scatterlayer .trace .lines');
expect(plotElement).not.toBeNull();
barGraphView.destroy();
});
await Vue.nextTick();
await Vue.nextTick();
const plotElement = element.querySelector('.cartesianlayer .scatterlayer .trace .lines');
expect(plotElement).not.toBeNull();
barGraphView.destroy();
});
});

View File

@ -173,11 +173,11 @@ export default {
this.cursorGuide = newCursorGuide;
}
},
mounted() {
created() {
eventHelpers.extend(this);
this.imageExporter = new ImageExporter(this.openmct);
this.loadComposition();
this.stalenessSubscription = {};
this.loadComposition();
},
beforeUnmount() {
this.destroy();

View File

@ -86,9 +86,11 @@ export default {
return !this.isStackedPlotObject && this.yAxes.filter((yAxis) => yAxis.seriesCount > 0);
}
},
mounted() {
created() {
eventHelpers.extend(this);
this.config = this.getConfig();
},
mounted() {
if (!this.isStackedPlotObject) {
this.yAxes = [
{

View File

@ -138,17 +138,18 @@ export default {
return this.loaded && this.legend.get('valueToShowWhenCollapsed');
}
},
mounted() {
this.seriesModels = [];
created() {
eventHelpers.extend(this);
this.config = this.getConfig();
this.legend = this.config.legend;
this.seriesModels = [];
this.listenTo(this.config.legend, 'change:position', this.updatePosition, this);
this.initialize();
},
mounted() {
this.loaded = true;
this.isLegendExpanded = this.legend.get('expanded') === true;
this.listenTo(this.config.legend, 'change:position', this.updatePosition, this);
this.updatePosition();
this.initialize();
},
beforeUnmount() {
if (this.objectComposition) {

View File

@ -21,20 +21,21 @@
*****************************************************************************/
import EventEmitter from 'EventEmitter';
import mount from 'utils/mount';
import {
createMouseEvent,
createOpenMct,
resetApplicationState,
spyOnBuiltins
} from 'utils/testing';
import Vue from 'vue';
import { nextTick } from 'vue';
import configStore from '../configuration/ConfigStore';
import PlotOptions from '../inspector/PlotOptions.vue';
import Plot from '../Plot.vue';
import PlotVuePlugin from '../plugin';
xdescribe('the plugin', function () {
describe('the plugin', function () {
let element;
let child;
let openmct;
@ -148,13 +149,13 @@ xdescribe('the plugin', function () {
openmct.startHeadless();
});
afterEach((done) => {
afterEach(async () => {
openmct.time.timeSystem('utc', {
start: 0,
end: 1
});
configStore.deleteAll();
resetApplicationState(openmct).then(done).catch(done);
await resetApplicationState(openmct);
});
afterAll(() => {
@ -185,15 +186,15 @@ xdescribe('the plugin', function () {
let testTelemetryObject;
let testTelemetryObject2;
let config;
let component;
let mockComposition;
let destroyPlot;
afterAll(() => {
component.$destroy();
destroyPlot();
openmct.router.path = null;
});
beforeEach(() => {
beforeEach(async () => {
testTelemetryObject = {
identifier: {
namespace: '',
@ -302,46 +303,53 @@ xdescribe('the plugin', function () {
spyOn(openmct.composition, 'get').and.returnValue(mockComposition);
let viewContainer = document.createElement('div');
child.append(viewContainer);
component = new Vue({
el: viewContainer,
components: {
Plot
child.appendChild(viewContainer);
const composition = openmct.composition.get(overlayPlotObject);
const { destroy } = mount(
{
components: {
Plot
},
provide: {
openmct,
domainObject: overlayPlotObject,
composition,
path: [overlayPlotObject]
},
template: '<plot ref="plotComponent"></plot>'
},
provide: {
openmct: openmct,
domainObject: overlayPlotObject,
composition: openmct.composition.get(overlayPlotObject),
path: [overlayPlotObject]
},
template: '<plot ref="plotComponent"></plot>'
});
{
element: child
}
);
return telemetryPromise.then(Vue.nextTick()).then(() => {
const configId = openmct.objects.makeKeyString(overlayPlotObject.identifier);
config = configStore.get(configId);
});
destroyPlot = destroy;
await telemetryPromise;
await nextTick();
const configId = openmct.objects.makeKeyString(overlayPlotObject.identifier);
config = configStore.get(configId);
});
it('Renders multiple Y-axis for the telemetry objects', (done) => {
it('Renders multiple Y-axis for the telemetry objects', async () => {
config.yAxis.set('displayRange', {
min: 10,
max: 20
});
Vue.nextTick(() => {
let yAxisElement = element.querySelectorAll(
'.gl-plot-axis-area.gl-plot-y .gl-plot-tick-wrapper'
);
expect(yAxisElement.length).toBe(2);
done();
});
await nextTick();
let yAxisElement = element.querySelectorAll(
'.gl-plot-axis-area.gl-plot-y .gl-plot-tick-wrapper'
);
expect(yAxisElement.length).toBe(2);
});
describe('the inspector view', () => {
let inspectorComponent;
let viewComponentObject;
let selection;
beforeEach((done) => {
let destroyPlotOptions;
beforeEach(async () => {
selection = [
[
{
@ -358,40 +366,43 @@ xdescribe('the plugin', function () {
]
];
let viewContainer = document.createElement('div');
child.append(viewContainer);
inspectorComponent = new Vue({
el: viewContainer,
components: {
PlotOptions
const viewContainer = document.createElement('div');
child.appendChild(viewContainer);
const { vNode, destroy } = mount(
{
components: {
PlotOptions
},
provide: {
openmct,
domainObject: selection[0][0].context.item,
path: [selection[0][0].context.item]
},
template: '<plot-options ref="plotOptionsRef"/>'
},
provide: {
openmct: openmct,
domainObject: selection[0][0].context.item,
path: [selection[0][0].context.item]
},
template: '<plot-options/>'
});
{
element: viewContainer
}
);
inspectorComponent = vNode.componentInstance;
destroyPlotOptions = destroy;
Vue.nextTick(() => {
viewComponentObject = inspectorComponent.$root.$children[0];
done();
});
await nextTick();
viewComponentObject = inspectorComponent.$refs.plotOptionsRef;
});
afterEach(() => {
destroyPlotOptions();
openmct.router.path = null;
});
describe('in edit mode', () => {
let editOptionsEl;
beforeEach((done) => {
beforeEach(async () => {
viewComponentObject.setEditState(true);
Vue.nextTick(() => {
editOptionsEl = viewComponentObject.$el.querySelector('.js-plot-options-edit');
done();
});
await nextTick();
editOptionsEl = viewComponentObject.$el.querySelector('.js-plot-options-edit');
});
it('shows multiple yAxis options', () => {
@ -420,15 +431,15 @@ xdescribe('the plugin', function () {
describe('The overlay plot view with single axes', () => {
let testTelemetryObject;
let config;
let component;
let mockComposition;
let destroyOverlayPlot;
afterAll(() => {
component.$destroy();
destroyOverlayPlot();
openmct.router.path = null;
});
beforeEach(() => {
beforeEach(async () => {
testTelemetryObject = {
identifier: {
namespace: '',
@ -482,41 +493,45 @@ xdescribe('the plugin', function () {
};
spyOn(openmct.composition, 'get').and.returnValue(mockComposition);
let viewContainer = document.createElement('div');
child.append(viewContainer);
component = new Vue({
el: viewContainer,
components: {
Plot
const composition = openmct.composition.get(overlayPlotObject);
const viewContainer = document.createElement('div');
child.appendChild(viewContainer);
const { destroy } = mount(
{
components: {
Plot
},
provide: {
openmct: openmct,
domainObject: overlayPlotObject,
composition,
path: [overlayPlotObject]
},
template: '<plot ref="plotComponent"></plot>'
},
provide: {
openmct: openmct,
domainObject: overlayPlotObject,
composition: openmct.composition.get(overlayPlotObject),
path: [overlayPlotObject]
},
template: '<plot ref="plotComponent"></plot>'
});
{
element: viewContainer
}
);
return telemetryPromise.then(Vue.nextTick()).then(() => {
const configId = openmct.objects.makeKeyString(overlayPlotObject.identifier);
config = configStore.get(configId);
});
destroyOverlayPlot = destroy;
await telemetryPromise;
await nextTick();
const configId = openmct.objects.makeKeyString(overlayPlotObject.identifier);
config = configStore.get(configId);
});
it('Renders single Y-axis for the telemetry object', (done) => {
it('Renders single Y-axis for the telemetry object', async () => {
config.yAxis.set('displayRange', {
min: 10,
max: 20
});
Vue.nextTick(() => {
let yAxisElement = element.querySelectorAll(
'.gl-plot-axis-area.gl-plot-y .gl-plot-tick-wrapper'
);
expect(yAxisElement.length).toBe(1);
done();
});
await nextTick();
let yAxisElement = element.querySelectorAll(
'.gl-plot-axis-area.gl-plot-y .gl-plot-tick-wrapper'
);
expect(yAxisElement.length).toBe(1);
});
});
});

View File

@ -21,6 +21,7 @@
*****************************************************************************/
import EventEmitter from 'EventEmitter';
import mount from 'utils/mount';
import {
createMouseEvent,
createOpenMct,
@ -36,7 +37,7 @@ import PlotVuePlugin from './plugin';
const TEST_KEY_ID = 'some-other-key';
xdescribe('the plugin', function () {
describe('the plugin', function () {
let element;
let child;
let openmct;
@ -165,14 +166,15 @@ xdescribe('the plugin', function () {
openmct.startHeadless();
});
afterEach((done) => {
openmct.time.timeSystem('utc', {
afterEach(async () => {
openmct.time.setTimeSystem('utc', {
start: 0,
end: 2
});
await Vue.nextTick();
configStore.deleteAll();
resetApplicationState(openmct).then(done).catch(done);
return resetApplicationState(openmct);
});
describe('the plot views', () => {
@ -383,13 +385,15 @@ xdescribe('the plugin', function () {
expect(openmct.telemetry.request).toHaveBeenCalledTimes(1);
});
it('Renders a collapsed legend for every telemetry', () => {
it('Renders a collapsed legend for every telemetry', async () => {
await Vue.nextTick();
let legend = element.querySelectorAll('.plot-wrapper-collapsed-legend .plot-series-name');
expect(legend.length).toBe(1);
expect(legend[0].innerHTML).toEqual('Test Object');
});
it('Renders an expanded legend for every telemetry', () => {
it('Renders an expanded legend for every telemetry', async () => {
await Vue.nextTick();
let legendControl = element.querySelector(
'.c-plot-legend__view-control.gl-plot-legend__view-control.c-disclosure-triangle'
);
@ -422,7 +426,8 @@ xdescribe('the plugin', function () {
});
});
it('Renders Y-axis options for the telemetry object', () => {
it('Renders Y-axis options for the telemetry object', async () => {
await Vue.nextTick();
let yAxisElement = element.querySelectorAll(
'.gl-plot-axis-area.gl-plot-y .gl-plot-y-label__select'
);
@ -434,7 +439,7 @@ xdescribe('the plugin', function () {
expect(options[1].value).toBe('Another attribute');
});
it('Updates the Y-axis label when changed', () => {
xit('Updates the Y-axis label when changed', () => {
const configId = openmct.objects.makeKeyString(testTelemetryObject.identifier);
const config = configStore.get(configId);
const yAxisElement = element.querySelectorAll('.gl-plot-axis-area.gl-plot-y')[0].__vue__;
@ -457,7 +462,8 @@ xdescribe('the plugin', function () {
describe('pause and play controls', () => {
beforeEach(() => {
openmct.time.clock('local', {
openmct.time.setClock('local');
openmct.time.setClockOffsets({
start: -1000,
end: 100
});
@ -488,7 +494,8 @@ xdescribe('the plugin', function () {
describe('resume actions on errant click', () => {
beforeEach(() => {
openmct.time.clock('local', {
openmct.time.setClock('local');
openmct.time.setClockOffsets({
start: -1000,
end: 100
});
@ -561,7 +568,8 @@ xdescribe('the plugin', function () {
expect(limitEl.length).toBe(0);
});
it('lines are displayed when configuration is set to true', (done) => {
it('lines are displayed when configuration is set to true', async () => {
await Vue.nextTick();
const configId = openmct.objects.makeKeyString(testTelemetryObject.identifier);
const config = configStore.get(configId);
config.yAxis.set('displayRange', {
@ -570,11 +578,9 @@ xdescribe('the plugin', function () {
});
config.series.models[0].set('limitLines', true);
Vue.nextTick(() => {
let limitEl = element.querySelectorAll('.js-limit-area .js-limit-line');
expect(limitEl.length).toBe(4);
done();
});
await Vue.nextTick();
let limitEl = element.querySelectorAll('.js-limit-area .js-limit-line');
expect(limitEl.length).toBe(4);
});
});
});
@ -677,14 +683,13 @@ xdescribe('the plugin', function () {
openmct.router.path = null;
});
it('requests historical data when over the threshold', (done) => {
xit('requests historical data when over the threshold', async () => {
await Vue.nextTick();
element.style.width = '680px';
resizePromise.then(() => {
expect(
plotView.getComponent().$children[0].$children[1].loadSeriesData
).toHaveBeenCalledTimes(1);
done();
});
await resizePromise;
expect(
plotView.getComponent().$children[0].$children[1].loadSeriesData
).toHaveBeenCalledTimes(1);
});
it('does not request historical data when under the threshold', (done) => {
@ -698,7 +703,7 @@ xdescribe('the plugin', function () {
});
});
xdescribe('the inspector view', () => {
describe('the inspector view', () => {
let component;
let viewComponentObject;
let mockComposition;
@ -800,18 +805,23 @@ xdescribe('the plugin', function () {
let viewContainer = document.createElement('div');
child.append(viewContainer);
component = new Vue({
el: viewContainer,
components: {
PlotOptions
const { vNode } = mount(
{
components: {
PlotOptions
},
provide: {
openmct: openmct,
domainObject: selection[0][0].context.item,
path: [selection[0][0].context.item, selection[0][1].context.item]
},
template: '<plot-options/>'
},
provide: {
openmct: openmct,
domainObject: selection[0][0].context.item,
path: [selection[0][0].context.item, selection[0][1].context.item]
},
template: '<plot-options/>'
});
{
element: viewContainer
}
);
component = vNode.componentInstance;
Vue.nextTick(() => {
viewComponentObject = component.$root.$children[0];

View File

@ -21,6 +21,7 @@
*****************************************************************************/
import EventEmitter from 'EventEmitter';
import mount from 'utils/mount';
import {
createMouseEvent,
createOpenMct,
@ -35,7 +36,7 @@ import PlotOptions from '../inspector/PlotOptions.vue';
import PlotVuePlugin from '../plugin';
import StackedPlot from './StackedPlot.vue';
xdescribe('the plugin', function () {
describe('the plugin', function () {
let element;
let child;
let openmct;
@ -142,13 +143,13 @@ xdescribe('the plugin', function () {
openmct.startHeadless();
});
afterEach((done) => {
afterEach(async () => {
openmct.time.timeSystem('utc', {
start: 0,
end: 1
});
configStore.deleteAll();
resetApplicationState(openmct).then(done).catch(done);
await resetApplicationState(openmct);
});
afterAll(() => {
@ -182,12 +183,13 @@ xdescribe('the plugin', function () {
let component;
let mockCompositionList = [];
let plotViewComponentObject;
let destroyStackedPlot;
afterAll(() => {
openmct.router.path = null;
});
beforeEach(() => {
beforeEach(async () => {
testTelemetryObject = {
identifier: {
namespace: '',
@ -295,7 +297,7 @@ xdescribe('the plugin', function () {
mockCompositionList = [];
spyOn(openmct.composition, 'get').and.callFake((domainObject) => {
//We need unique compositions here - one for the StackedPlot view and one for the PlotLegend view
const numObjects = domainObject.composition.length;
const numObjects = domainObject.composition?.length ?? 0;
const mockComposition = new EventEmitter();
mockComposition.load = () => {
if (numObjects === 1) {
@ -319,24 +321,35 @@ xdescribe('the plugin', function () {
let viewContainer = document.createElement('div');
child.append(viewContainer);
component = new Vue({
el: viewContainer,
components: {
StackedPlot
const { vNode, destroy } = mount(
{
components: {
StackedPlot
},
provide: {
openmct,
domainObject: stackedPlotObject,
path: [stackedPlotObject]
},
template: '<stacked-plot ref="stackedPlotRef"></stacked-plot>'
},
provide: {
openmct: openmct,
domainObject: stackedPlotObject,
path: [stackedPlotObject]
},
template: '<stacked-plot></stacked-plot>'
});
{
element: viewContainer
}
);
return telemetryPromise.then(Vue.nextTick()).then(() => {
plotViewComponentObject = component.$root.$children[0];
const configId = openmct.objects.makeKeyString(testTelemetryObject.identifier);
config = configStore.get(configId);
});
component = vNode.componentInstance;
destroyStackedPlot = destroy;
await telemetryPromise;
await Vue.nextTick();
plotViewComponentObject = component.$refs.stackedPlotRef;
const configId = openmct.objects.makeKeyString(testTelemetryObject.identifier);
config = configStore.get(configId);
});
afterEach(() => {
destroyStackedPlot();
});
it('Renders a collapsed legend for every telemetry', () => {
@ -372,20 +385,18 @@ xdescribe('the plugin', function () {
expect(ticks.length).toBe(9);
});
it('Renders Y-axis ticks for the telemetry object', (done) => {
it('Renders Y-axis ticks for the telemetry object', async () => {
config.yAxis.set('displayRange', {
min: 10,
max: 20
});
Vue.nextTick(() => {
let yAxisElement = element.querySelectorAll(
'.gl-plot-axis-area.gl-plot-y .gl-plot-tick-wrapper'
);
expect(yAxisElement.length).toBe(1);
let ticks = yAxisElement[0].querySelectorAll('.gl-plot-tick');
expect(ticks.length).toBe(6);
done();
});
await Vue.nextTick();
let yAxisElement = element.querySelectorAll(
'.gl-plot-axis-area.gl-plot-y .gl-plot-tick-wrapper'
);
expect(yAxisElement.length).toBe(1);
let ticks = yAxisElement[0].querySelectorAll('.gl-plot-tick');
expect(ticks.length).toBe(6);
});
it('Renders Y-axis options for the telemetry object', () => {
@ -399,14 +410,13 @@ xdescribe('the plugin', function () {
expect(options[1].value).toBe('Another attribute');
});
it('turns on cursor Guides all telemetry objects', (done) => {
expect(plotViewComponentObject.cursorGuide).toBeFalse();
plotViewComponentObject.cursorGuide = true;
Vue.nextTick(() => {
let childCursorGuides = element.querySelectorAll('.c-cursor-guide--v');
expect(childCursorGuides.length).toBe(1);
done();
});
it('turns on cursor Guides all telemetry objects', async () => {
let cursorGuide = Vue.ref(plotViewComponentObject.cursorGuide);
expect(cursorGuide.value).toBeFalse();
cursorGuide.value = true;
await Vue.nextTick();
let childCursorGuides = element.querySelectorAll('.c-cursor-guide--v');
expect(childCursorGuides.length).toBe(1);
});
it('shows grid lines for all telemetry objects', () => {
@ -421,87 +431,71 @@ xdescribe('the plugin', function () {
expect(visible).toBe(2);
});
it('hides grid lines for all telemetry objects', (done) => {
expect(plotViewComponentObject.gridLines).toBeTrue();
plotViewComponentObject.gridLines = false;
Vue.nextTick(() => {
expect(plotViewComponentObject.gridLines).toBeFalse();
let gridLinesContainer = element.querySelectorAll('.gl-plot-display-area .js-ticks');
let visible = 0;
gridLinesContainer.forEach((el) => {
if (el.style.display !== 'none') {
visible++;
}
});
expect(visible).toBe(0);
done();
it('hides grid lines for all telemetry objects', async () => {
let gridLines = Vue.ref(plotViewComponentObject.gridLines);
expect(gridLines.value).toBeTrue();
gridLines.value = false;
await Vue.nextTick();
expect(gridLines.value).toBeFalse();
let gridLinesContainer = element.querySelectorAll('.gl-plot-display-area .js-ticks');
let visible = 0;
gridLinesContainer.forEach((el) => {
if (el.style.display && el.style.display !== 'none') {
visible++;
}
});
expect(visible).toBe(0);
});
it('plots a new series when a new telemetry object is added', (done) => {
xit('plots a new series when a new telemetry object is added', () => {
//setting composition here so that any new triggers to composition.load with correctly load the mockComposition in the beforeEach
stackedPlotObject.composition = [testTelemetryObject, testTelemetryObject2];
mockCompositionList[0].emit('add', testTelemetryObject2);
Vue.nextTick(() => {
let legend = element.querySelectorAll('.plot-wrapper-collapsed-legend .plot-series-name');
expect(legend.length).toBe(2);
expect(legend[1].innerHTML).toEqual('Test Object2');
done();
});
let legend = element.querySelectorAll('.plot-wrapper-collapsed-legend .plot-series-name');
expect(legend.length).toBe(2);
expect(legend[1].innerHTML).toEqual('Test Object2');
});
it('removes plots from series when a telemetry object is removed', (done) => {
it('removes plots from series when a telemetry object is removed', () => {
stackedPlotObject.composition = [];
mockCompositionList[0].emit('remove', testTelemetryObject.identifier);
Vue.nextTick(() => {
expect(plotViewComponentObject.compositionObjects.length).toBe(0);
done();
});
expect(plotViewComponentObject.compositionObjects.length).toBe(0);
});
it('Changes the label of the y axis when the option changes', (done) => {
it('Changes the label of the y axis when the option changes', () => {
let selectEl = element.querySelector('.gl-plot-y-label__select');
selectEl.value = 'Another attribute';
selectEl.dispatchEvent(new Event('change'));
Vue.nextTick(() => {
expect(config.yAxis.get('label')).toEqual('Another attribute');
done();
});
expect(config.yAxis.get('label')).toEqual('Another attribute');
});
it('Adds a new point to the plot', (done) => {
it('Adds a new point to the plot', () => {
let originalLength = config.series.models[0].getSeriesData().length;
config.series.models[0].add({
utc: 2,
'some-key': 1,
'some-other-key': 2
});
Vue.nextTick(() => {
const seriesData = config.series.models[0].getSeriesData();
expect(seriesData.length).toEqual(originalLength + 1);
done();
});
const seriesData = config.series.models[0].getSeriesData();
expect(seriesData.length).toEqual(originalLength + 1);
});
it('updates the xscale', (done) => {
it('updates the xscale', () => {
config.xAxis.set('displayRange', {
min: 0,
max: 10
});
Vue.nextTick(() => {
expect(
plotViewComponentObject.$children[0].component.$children[0].$children[1].xScale.domain()
).toEqual({
min: 0,
max: 10
});
done();
expect(
plotViewComponentObject.$children[0].component.$children[0].$children[1].xScale.domain()
).toEqual({
min: 0,
max: 10
});
});
it('updates the yscale', (done) => {
it('updates the yscale', () => {
const yAxisList = [config.yAxis, ...config.additionalYAxes];
yAxisList.forEach((yAxis) => {
yAxis.set('displayRange', {
@ -509,44 +503,39 @@ xdescribe('the plugin', function () {
max: 20
});
});
Vue.nextTick(() => {
const yAxesScales =
plotViewComponentObject.$children[0].component.$children[0].$children[1].yScale;
yAxesScales.forEach((yAxisScale) => {
expect(yAxisScale.scale.domain()).toEqual({
min: 10,
max: 20
});
const yAxesScales =
plotViewComponentObject.$children[0].component.$children[0].$children[1].yScale;
yAxesScales.forEach((yAxisScale) => {
expect(yAxisScale.scale.domain()).toEqual({
min: 10,
max: 20
});
done();
});
});
it('shows styles for telemetry objects if available', (done) => {
Vue.nextTick(() => {
let conditionalStylesContainer = element.querySelectorAll(
'.c-plot--stacked-container .js-style-receiver'
);
let hasStyles = 0;
conditionalStylesContainer.forEach((el) => {
if (el.style.backgroundColor !== '') {
hasStyles++;
}
});
expect(hasStyles).toBe(1);
done();
it('shows styles for telemetry objects if available', () => {
let conditionalStylesContainer = element.querySelectorAll(
'.c-plot--stacked-container .js-style-receiver'
);
let hasStyles = 0;
conditionalStylesContainer.forEach((el) => {
if (el.style.backgroundColor !== '') {
hasStyles++;
}
});
expect(hasStyles).toBe(1);
});
});
describe('the stacked plot inspector view', () => {
let component;
let viewComponentObject;
let mockComposition;
let testTelemetryObject;
let selection;
let config;
beforeEach((done) => {
let destroyPlotOptions;
beforeEach(async () => {
testTelemetryObject = {
identifier: {
namespace: '',
@ -621,26 +610,30 @@ xdescribe('the plugin', function () {
let viewContainer = document.createElement('div');
child.append(viewContainer);
component = new Vue({
el: viewContainer,
components: {
PlotOptions
const { vNode, destroy } = mount(
{
components: {
PlotOptions
},
provide: {
openmct: openmct,
domainObject: selection[0][0].context.item,
path: [selection[0][0].context.item]
},
template: '<plot-options/>'
},
provide: {
openmct: openmct,
domainObject: selection[0][0].context.item,
path: [selection[0][0].context.item]
},
template: '<plot-options/>'
});
{
element: viewContainer
}
);
destroyPlotOptions = destroy;
Vue.nextTick(() => {
viewComponentObject = component.$root.$children[0];
done();
});
await Vue.nextTick();
viewComponentObject = vNode.componentInstance;
});
afterEach(() => {
destroyPlotOptions();
openmct.router.path = null;
});
@ -668,13 +661,13 @@ xdescribe('the plugin', function () {
});
describe('inspector view of stacked plot child', () => {
let component;
let viewComponentObject;
let mockComposition;
let testTelemetryObject;
let selection;
let config;
beforeEach((done) => {
let destroyPlotOptions;
beforeEach(async () => {
testTelemetryObject = {
identifier: {
namespace: '',
@ -772,26 +765,30 @@ xdescribe('the plugin', function () {
let viewContainer = document.createElement('div');
child.append(viewContainer);
component = new Vue({
el: viewContainer,
components: {
PlotOptions
const { vNode, destroy } = mount(
{
components: {
PlotOptions
},
provide: {
openmct: openmct,
domainObject: selection[0][0].context.item,
path: [selection[0][0].context.item, selection[0][1].context.item]
},
template: '<plot-options />'
},
provide: {
openmct: openmct,
domainObject: selection[0][0].context.item,
path: [selection[0][0].context.item, selection[0][1].context.item]
},
template: '<plot-options/>'
});
{
element: viewContainer
}
);
destroyPlotOptions = destroy;
Vue.nextTick(() => {
viewComponentObject = component.$root.$children[0];
done();
});
await Vue.nextTick();
viewComponentObject = vNode.componentInstance;
});
afterEach(() => {
destroyPlotOptions();
openmct.router.path = null;
});

View File

@ -21,7 +21,7 @@
*****************************************************************************/
define(['../src/ConditionManager'], function (ConditionManager) {
xdescribe('A Summary Widget Condition Manager', function () {
describe('A Summary Widget Condition Manager', function () {
let conditionManager;
let mockDomainObject;
let mockCompObject1;
@ -362,7 +362,7 @@ define(['../src/ConditionManager'], function (ConditionManager) {
});
});
it('populates its LAD cache with historical data on load, if available', function (done) {
xit('populates its LAD cache with historical data on load, if available', function (done) {
expect(telemetryRequests.length).toBe(2);
expect(telemetryRequests[0].object).toBe(mockCompObject1);
expect(telemetryRequests[1].object).toBe(mockCompObject2);
@ -383,7 +383,7 @@ define(['../src/ConditionManager'], function (ConditionManager) {
telemetryRequests[1].resolve([mockTelemetryValues.mockCompObject2]);
});
it('updates its LAD cache upon receiving telemetry and invokes the appropriate handlers', function () {
xit('updates its LAD cache upon receiving telemetry and invokes the appropriate handlers', function () {
mockTelemetryAPI.triggerTelemetryCallback('mockCompObject1');
expect(conditionManager.subscriptionCache.mockCompObject1.property1).toEqual(
'Its a different string'

View File

@ -21,7 +21,7 @@
*****************************************************************************/
define(['../src/Condition'], function (Condition) {
xdescribe('A summary widget condition', function () {
describe('A summary widget condition', function () {
let testCondition;
let mockConfig;
let mockConditionManager;

View File

@ -21,7 +21,7 @@
*****************************************************************************/
define(['../src/SummaryWidget'], function (SummaryWidget) {
xdescribe('The Summary Widget', function () {
describe('The Summary Widget', function () {
let summaryWidget;
let mockDomainObject;
let mockOldDomainObject;
@ -88,7 +88,7 @@ define(['../src/SummaryWidget'], function (SummaryWidget) {
summaryWidget.show(mockContainer);
});
it('queries with legacyId', function () {
xit('queries with legacyId', function () {
expect(mockObjectService.getObjects).toHaveBeenCalledWith(['testNamespace:testKey']);
});
@ -143,7 +143,7 @@ define(['../src/SummaryWidget'], function (SummaryWidget) {
expect(mockOpenMCT.objects.mutate).toHaveBeenCalled();
});
it('shows configuration interfaces when in edit mode, and hides them otherwise', function () {
xit('shows configuration interfaces when in edit mode, and hides them otherwise', function () {
setTimeout(function () {
summaryWidget.onEdit([]);
expect(summaryWidget.editing).toEqual(false);
@ -158,7 +158,7 @@ define(['../src/SummaryWidget'], function (SummaryWidget) {
}, 100);
});
it('unregisters any registered listeners on a destroy', function () {
xit('unregisters any registered listeners on a destroy', function () {
setTimeout(function () {
summaryWidget.destroy();
expect(listenCallbackSpy).toHaveBeenCalled();

View File

@ -27,7 +27,7 @@ import { createOpenMct, resetApplicationState } from '@/utils/testing';
import TimelinePlugin from './plugin';
xdescribe('the plugin', function () {
describe('the plugin', function () {
let objectDef;
let appHolder;
let element;
@ -95,6 +95,12 @@ xdescribe('the plugin', function () {
};
beforeEach((done) => {
// Mock clientWidth value
Object.defineProperty(HTMLElement.prototype, 'clientWidth', {
configurable: true,
value: 500
});
appHolder = document.createElement('div');
appHolder.style.width = '640px';
appHolder.style.height = '480px';
@ -144,6 +150,7 @@ xdescribe('the plugin', function () {
});
afterEach(() => {
delete HTMLElement.prototype.clientWidth;
return resetApplicationState(openmct);
});
@ -236,7 +243,7 @@ xdescribe('the plugin', function () {
return Vue.nextTick();
});
it('loads the plan from composition', async () => {
xit('loads the plan from composition', async () => {
await Vue.nextTick();
await Vue.nextTick();
const items = element.querySelectorAll('.js-timeline__content');
@ -263,7 +270,7 @@ xdescribe('the plugin', function () {
Vue.nextTick(done);
});
it('displays an independent time conductor with saved options - local clock', () => {
xit('displays an independent time conductor with saved options - local clock', () => {
return Vue.nextTick(() => {
const independentTimeConductorEl = element.querySelector(
'.c-timeline-holder > .c-conductor__controls'
@ -304,7 +311,7 @@ xdescribe('the plugin', function () {
Vue.nextTick(done);
});
it('displays an independent time conductor with saved options - fixed timespan', () => {
xit('displays an independent time conductor with saved options - fixed timespan', () => {
return Vue.nextTick(() => {
const independentTimeConductorEl = element.querySelector(
'.c-timeline-holder > .c-conductor__controls'

View File

@ -34,7 +34,7 @@ const LIST_ITEM_CLASS = '.js-table__body .js-list-item';
const LIST_ITEM_VALUE_CLASS = '.js-list-item__value';
const LIST_ITEM_BODY_CLASS = '.js-table__body th';
xdescribe('the plugin', function () {
describe('the plugin', function () {
let timelistDefinition;
let element;
let child;

View File

@ -20,8 +20,6 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import moment from 'moment';
export default class StartTimerAction {
constructor(openmct) {
this.name = 'Start';
@ -33,6 +31,7 @@ export default class StartTimerAction {
this.openmct = openmct;
}
invoke(objectPath) {
const domainObject = objectPath[0];
if (!domainObject || !domainObject.configuration) {
@ -42,27 +41,21 @@ export default class StartTimerAction {
let { pausedTime, timestamp } = domainObject.configuration;
const newConfiguration = { ...domainObject.configuration };
if (pausedTime) {
pausedTime = moment(pausedTime);
}
const now = new Date(this.openmct.time.now());
if (timestamp) {
timestamp = moment(timestamp);
}
const now = moment(new Date(this.openmct.time.now()));
if (pausedTime) {
const timeShift = moment.duration(now.diff(pausedTime));
const shiftedTime = timestamp.add(timeShift);
newConfiguration.timestamp = shiftedTime.toDate();
const timeShift = now - new Date(pausedTime);
const shiftedTime = new Date(new Date(timestamp).getTime() + timeShift);
newConfiguration.timestamp = shiftedTime;
} else if (!timestamp) {
newConfiguration.timestamp = now.toDate();
newConfiguration.timestamp = now;
}
newConfiguration.timerState = 'started';
newConfiguration.pausedTime = undefined;
this.openmct.objects.mutate(domainObject, 'configuration', newConfiguration);
}
appliesTo(objectPath, view = {}) {
const domainObject = objectPath[0];
if (!domainObject || !domainObject.configuration) {

View File

@ -25,19 +25,21 @@
<div class="c-timer__controls">
<button
title="Reset"
aria-label="Reset"
class="c-timer__ctrl-reset c-icon-button c-icon-button--major icon-reset"
:class="[{ hide: timerState === 'stopped' }]"
@click="restartTimer"
></button>
<button
:title="timerStateButtonText"
:aria-label="timerStateButtonText"
class="c-timer__ctrl-pause-play c-icon-button c-icon-button--major"
:class="[timerStateButtonIcon]"
@click="toggleStateButton"
></button>
</div>
<div class="c-timer__direction" :class="[{ hide: !timerSign }, `icon-${timerSign}`]"></div>
<div class="c-timer__value">{{ timeTextValue || '--:--:--' }}</div>
<div class="c-timer__value">{{ timerState === 'stopped' ? '--:--:--' : timeTextValue }}</div>
</div>
</template>
@ -170,7 +172,7 @@ export default {
});
this.$nextTick(() => {
if (!this.configuration?.timerState) {
const timerAction = !this.relativeTimestamp ? 'stop' : 'start';
const timerAction = !this.timeDelta ? 'stop' : 'start';
this.triggerAction(`timer.${timerAction}`);
}

View File

@ -20,9 +20,9 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import mount from 'utils/mount';
import { createOpenMct, resetApplicationState } from 'utils/testing';
import { mockLocalStorage } from 'utils/testing/mockLocalStorage';
import Vue from 'vue';
import StylesView from '@/plugins/condition/components/inspector/StylesView.vue';
@ -36,7 +36,7 @@ import {
mockTelemetryTableSelection
} from './InspectorStylesSpecMocks';
xdescribe('the inspector', () => {
describe('the inspector', () => {
let openmct;
let selection;
let stylesViewComponent;
@ -210,13 +210,15 @@ xdescribe('the inspector', () => {
selection,
stylesManager
},
el: element,
components: {},
template: `<${component.name} />`
};
config.components[component.name] = component;
return new Vue(config).$mount();
const { vNode } = mount(config, {
element
});
return vNode.componentInstance;
}
});

View File

@ -20,12 +20,13 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import mount from 'utils/mount';
import { createOpenMct, resetApplicationState } from 'utils/testing';
import Vue from 'vue';
import Layout from './Layout.vue';
xdescribe('Open MCT Layout:', () => {
describe('Open MCT Layout:', () => {
let openmct;
let element;
let components;
@ -61,6 +62,7 @@ xdescribe('Open MCT Layout:', () => {
await createLayout();
await Vue.nextTick();
await Vue.nextTick();
Object.entries(components).forEach(([name, component]) => {
expect(isCollapsed(component.pane)).toBeTrue();
@ -69,6 +71,7 @@ xdescribe('Open MCT Layout:', () => {
it('on toggle collapses if expanded', async () => {
await createLayout();
await Vue.nextTick();
toggleCollapseButtons();
await Vue.nextTick();
@ -83,8 +86,8 @@ xdescribe('Open MCT Layout:', () => {
setHideParams();
await createLayout();
toggleExpandButtons();
await Vue.nextTick();
toggleExpandButtons();
Object.entries(components).forEach(([name, component]) => {
expect(openmct.router.getSearchParam(component.param)).not.toEqual('true');
@ -94,21 +97,29 @@ xdescribe('Open MCT Layout:', () => {
});
});
// eslint-disable-next-line require-await
async function createLayout() {
const el = document.createElement('div');
const child = document.createElement('div');
el.appendChild(child);
element = await new Vue({
el,
components: {
Layout
const { vNode } = mount(
{
el,
components: {
Layout
},
provide: {
openmct
},
template: `<Layout ref="layout"/>`
},
provide: {
openmct
},
template: `<Layout ref="layout"/>`
}).$mount().$el;
{
element: el
}
);
element = vNode.el;
setComponents();
}

View File

@ -104,6 +104,10 @@ export default {
this.type = this.$parent.type;
this.styleProp = this.type === 'horizontal' ? 'width' : 'height';
},
created() {
// Hide tree and/or inspector pane if specified in URL
this.openmct.router.on('change:params', this.handleHideUrl.bind(this));
},
async mounted() {
if (this.persistPosition) {
const savedPosition = this.getSavedPosition();
@ -113,7 +117,6 @@ export default {
}
await this.$nextTick();
// Hide tree and/or inspector pane if specified in URL
if (this.isCollapsable) {
this.handleHideUrl();
}

View File

@ -20,6 +20,7 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import mount from 'utils/mount';
import { createOpenMct, resetApplicationState } from 'utils/testing';
import Vue from 'vue';
@ -27,7 +28,7 @@ import ExampleTagsPlugin from '../../../../example/exampleTags/plugin';
import DisplayLayoutPlugin from '../../../plugins/displayLayout/plugin';
import GrandSearch from './GrandSearch.vue';
xdescribe('GrandSearch', () => {
describe('GrandSearch', () => {
let openmct;
let grandSearchComponent;
let viewContainer;
@ -42,6 +43,7 @@ xdescribe('GrandSearch', () => {
let originalRouterPath;
let mockNewObject;
let mockObjectProvider;
let _destroy;
beforeEach((done) => {
openmct = createOpenMct();
@ -186,16 +188,22 @@ xdescribe('GrandSearch', () => {
document.body.appendChild(parent);
viewContainer = document.createElement('div');
parent.append(viewContainer);
grandSearchComponent = new Vue({
el: viewContainer,
components: {
GrandSearch
const { vNode, destroy } = mount(
{
components: {
GrandSearch
},
provide: {
openmct
},
template: '<GrandSearch/>'
},
provide: {
openmct
},
template: '<GrandSearch/>'
}).$mount();
{
element: viewContainer
}
);
grandSearchComponent = vNode.componentInstance;
_destroy = destroy;
await Vue.nextTick();
done();
});
@ -205,8 +213,7 @@ xdescribe('GrandSearch', () => {
afterEach(() => {
openmct.objects.inMemorySearchProvider.worker = sharedWorkerToRestore;
openmct.router.path = originalRouterPath;
grandSearchComponent.$destroy();
document.body.removeChild(parent);
_destroy();
return resetApplicationState(openmct);
});
@ -278,7 +285,7 @@ xdescribe('GrandSearch', () => {
it('should preview object search results in edit mode if object clicked', async () => {
await grandSearchComponent.$children[0].searchEverything('Folder');
grandSearchComponent._provided.openmct.router.path = [mockDisplayLayout];
grandSearchComponent.$children[0].openmct.router.path = [mockDisplayLayout];
await Vue.nextTick();
const searchResults = document.querySelectorAll('[name="Test Folder"]');
expect(searchResults.length).toBe(1);
@ -290,7 +297,7 @@ xdescribe('GrandSearch', () => {
it('should preview annotation search results in edit mode if annotation clicked', async () => {
await grandSearchComponent.$children[0].searchEverything('Dri');
grandSearchComponent._provided.openmct.router.path = [mockDisplayLayout];
grandSearchComponent.$children[0].openmct.router.path = [mockDisplayLayout];
await Vue.nextTick();
const annotationResults = document.querySelectorAll('[aria-label="Search Result"]');
expect(annotationResults.length).toBe(1);

View File

@ -36,8 +36,7 @@ const DEFAULT_TIME_OPTIONS = {
};
export function createOpenMct(timeSystemOptions = DEFAULT_TIME_OPTIONS) {
let openmct = new MCT();
openmct = markRaw(openmct);
const openmct = markRaw(new MCT());
openmct.install(openmct.plugins.LocalStorage());
openmct.install(openmct.plugins.UTCTimeSystem());
openmct.setAssetPath('/base');