New forms code needs tests #4539 (#4758)

* New forms code needs tests #4539

Co-authored-by: Jamie V <jamie.j.vigliotta@nasa.gov>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
Co-authored-by: unlikelyzero <jchill2@gmail.com>
Co-authored-by: Joshi <simplyrender@gmail.com>
This commit is contained in:
Nikhil 2022-05-18 09:25:11 -07:00 committed by GitHub
parent b8ff5c7f33
commit 95299336d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 717 additions and 100 deletions

View File

@ -0,0 +1,79 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*
This test suite is dedicated to tests which verify form functionality.
*/
const { test, expect } = require('@playwright/test');
const TEST_FOLDER = 'test folder';
test.describe('forms set', () => {
test('New folder form has title as required field', async ({ page }) => {
//Go to baseURL
await page.goto('/', { waitUntil: 'networkidle' });
// Click button:has-text("Create")
await page.click('button:has-text("Create")');
// Click :nth-match(:text("Folder"), 2)
await page.click(':nth-match(:text("Folder"), 2)');
// Click text=Properties Title Notes >> input[type="text"]
await page.click('text=Properties Title Notes >> input[type="text"]');
// Fill text=Properties Title Notes >> input[type="text"]
await page.fill('text=Properties Title Notes >> input[type="text"]', '');
// Press Tab
await page.press('text=Properties Title Notes >> input[type="text"]', 'Tab');
// Click text=OK Cancel
await page.click('text=OK', { force: true });
const okButton = page.locator('text=OK');
await expect(okButton).toBeDisabled();
await expect(page.locator('.c-form-row__state-indicator').first()).toHaveClass(/invalid/);
// Click text=Properties Title Notes >> input[type="text"]
await page.click('text=Properties Title Notes >> input[type="text"]');
// Fill text=Properties Title Notes >> input[type="text"]
await page.fill('text=Properties Title Notes >> input[type="text"]', TEST_FOLDER);
// Press Tab
await page.press('text=Properties Title Notes >> input[type="text"]', 'Tab');
await expect(page.locator('.c-form-row__state-indicator').first()).not.toHaveClass(/invalid/);
// Click text=OK
await Promise.all([
page.waitForNavigation(),
page.click('text=OK')
]);
await expect(page.locator('.l-browse-bar__object-name')).toContainText(TEST_FOLDER);
});
test.fixme('Create all object types and verify correctness', async ({ page }) => {
//Create the following Domain Objects with their unique Object Types
// Sine Wave Generator (number object)
// Timer Object
// Plan View Object
// Clock Object
// Hyperlink
});
});

View File

@ -23,10 +23,13 @@
import FormController from './FormController';
import FormProperties from './components/FormProperties.vue';
import EventEmitter from 'EventEmitter';
import Vue from 'vue';
export default class FormsAPI {
export default class FormsAPI extends EventEmitter {
constructor(openmct) {
super();
this.openmct = openmct;
this.formController = new FormController(openmct);
}
@ -107,6 +110,8 @@ export default class FormsAPI {
let onDismiss;
let onSave;
const self = this;
const promise = new Promise((resolve, reject) => {
onSave = onFormSave(resolve);
onDismiss = onFormDismiss(reject);
@ -115,7 +120,7 @@ export default class FormsAPI {
const vm = new Vue({
components: { FormProperties },
provide: {
openmct: this.openmct
openmct: self.openmct
},
data() {
return {
@ -132,7 +137,7 @@ export default class FormsAPI {
if (element) {
element.append(formElement);
} else {
overlay = this.openmct.overlays.overlay({
overlay = self.openmct.overlays.overlay({
element: vm.$el,
size: 'small',
onDestroy: () => vm.$destroy()
@ -140,6 +145,7 @@ export default class FormsAPI {
}
function onFormPropertyChange(data) {
self.emit('onFormPropertyChange', data);
if (onChange) {
onChange(data);
}

View File

@ -0,0 +1,157 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import { createOpenMct, resetApplicationState } from '../../utils/testing';
describe('The Forms API', () => {
let openmct;
let element;
beforeEach((done) => {
element = document.createElement('div');
element.style.display = 'block';
element.style.width = '1920px';
element.style.height = '1080px';
openmct = createOpenMct();
openmct.on('start', done);
openmct.startHeadless(element);
});
afterEach(() => {
return resetApplicationState(openmct);
});
it('openmct supports form API', () => {
expect(openmct.forms).not.toBe(null);
});
describe('check default form controls exists', () => {
it('autocomplete', () => {
const control = openmct.forms.getFormControl('autocomplete');
expect(control).not.toBe(null);
});
it('clock', () => {
const control = openmct.forms.getFormControl('composite');
expect(control).not.toBe(null);
});
it('datetime', () => {
const control = openmct.forms.getFormControl('datetime');
expect(control).not.toBe(null);
});
it('file-input', () => {
const control = openmct.forms.getFormControl('file-input');
expect(control).not.toBe(null);
});
it('locator', () => {
const control = openmct.forms.getFormControl('locator');
expect(control).not.toBe(null);
});
it('numberfield', () => {
const control = openmct.forms.getFormControl('numberfield');
expect(control).not.toBe(null);
});
it('select', () => {
const control = openmct.forms.getFormControl('select');
expect(control).not.toBe(null);
});
it('textarea', () => {
const control = openmct.forms.getFormControl('textarea');
expect(control).not.toBe(null);
});
it('textfield', () => {
const control = openmct.forms.getFormControl('textfield');
expect(control).not.toBe(null);
});
});
it('supports user defined form controls', () => {
const newFormControl = {
show: () => {
console.log('show new control');
},
destroy: () => {
console.log('destroy');
}
};
openmct.forms.addNewFormControl('newFormControl', newFormControl);
const control = openmct.forms.getFormControl('newFormControl');
expect(control).not.toBe(null);
expect(control.show).not.toBe(null);
expect(control.destroy).not.toBe(null);
});
describe('show form on UI', () => {
let formStructure;
beforeEach(() => {
formStructure = {
title: 'Test Show Form',
sections: [
{
rows: [
{
key: 'name',
control: 'textfield',
name: 'Title',
pattern: '\\S+',
required: false,
cssClass: 'l-input-lg',
value: 'Test Name'
}
]
}
]
};
});
it('when container element is provided', (done) => {
openmct.forms.showForm(formStructure, { element }).catch(() => {
done();
});
const titleElement = element.querySelector('.c-overlay__dialog-title');
expect(titleElement.textContent).toBe(formStructure.title);
element.querySelector('.js-cancel-button').click();
});
it('when container element is not provided', (done) => {
openmct.forms.showForm(formStructure).catch(() => {
done();
});
const titleElement = document.querySelector('.c-overlay__dialog-title');
const title = titleElement.textContent;
expect(title).toBe(formStructure.title);
document.querySelector('.js-cancel-button').click();
});
});
});

View File

@ -21,9 +21,9 @@
*****************************************************************************/
<template>
<div class="c-form">
<div class="c-form js-form">
<div class="c-overlay__top-bar c-form__top-bar">
<div class="c-overlay__dialog-title">{{ model.title }}</div>
<div class="c-overlay__dialog-title js-form-title">{{ model.title }}</div>
<div class="c-overlay__dialog-hint hint">All fields marked <span class="req icon-asterisk"></span> are required.</div>
</div>
<form
@ -70,7 +70,7 @@
</button>
<button
tabindex="0"
class="c-button"
class="c-button js-cancel-button"
@click="onDismiss"
>
{{ cancelLabel }}

View File

@ -26,29 +26,31 @@ import { createOpenMct, createMouseEvent, resetApplicationState } from '../../ut
describe ('The Menu API', () => {
let openmct;
let element;
let appHolder;
let menuAPI;
let actionsArray;
let x;
let y;
let result;
let onDestroy;
let menuElement;
const x = 8;
const y = 16;
const menuOptions = {
onDestroy: () => {
console.log('default onDestroy');
}
};
beforeEach((done) => {
const appHolder = document.createElement('div');
appHolder = document.createElement('div');
appHolder.style.display = 'block';
appHolder.style.width = '1920px';
appHolder.style.height = '1080px';
openmct = createOpenMct();
element = document.createElement('div');
element.style.display = 'block';
element.style.width = '1920px';
element.style.height = '1080px';
openmct.on('start', done);
openmct.startHeadless(appHolder);
openmct.startHeadless();
menuAPI = new MenuAPI(openmct);
actionsArray = [
@ -56,7 +58,7 @@ describe ('The Menu API', () => {
key: 'test-css-class-1',
name: 'Test Action 1',
cssClass: 'icon-clock',
description: 'This is a test action',
description: 'This is a test action 1',
onItemClicked: () => {
result = 'Test Action 1 Invoked';
}
@ -65,149 +67,165 @@ describe ('The Menu API', () => {
key: 'test-css-class-2',
name: 'Test Action 2',
cssClass: 'icon-clock',
description: 'This is a test action',
description: 'This is a test action 2',
onItemClicked: () => {
result = 'Test Action 2 Invoked';
}
}
];
x = 8;
y = 16;
});
afterEach(() => {
return resetApplicationState(openmct);
});
describe("showMenu method", () => {
it("creates an instance of Menu when invoked", () => {
menuAPI.showMenu(x, y, actionsArray);
expect(menuAPI.menuComponent).toBeInstanceOf(Menu);
describe('showMenu method', () => {
beforeAll(() => {
spyOn(menuOptions, 'onDestroy').and.callThrough();
});
describe("creates a menu component", () => {
let menuComponent;
let vueComponent;
it('creates an instance of Menu when invoked', (done) => {
menuOptions.onDestroy = done;
beforeEach(() => {
onDestroy = jasmine.createSpy('onDestroy');
menuAPI.showMenu(x, y, actionsArray, menuOptions);
const menuOptions = {
onDestroy
};
expect(menuAPI.menuComponent).toBeInstanceOf(Menu);
document.body.click();
});
describe('creates a menu component', () => {
it('with all the actions passed in', (done) => {
menuOptions.onDestroy = done;
menuAPI.showMenu(x, y, actionsArray, menuOptions);
vueComponent = menuAPI.menuComponent.component;
menuComponent = document.querySelector(".c-menu");
menuElement = document.querySelector('.c-menu');
expect(menuElement).toBeDefined();
spyOn(vueComponent, '$destroy');
});
it("renders a menu component in the expected x and y coordinates", () => {
let boundingClientRect = menuComponent.getBoundingClientRect();
let left = boundingClientRect.left;
let top = boundingClientRect.top;
expect(left).toEqual(x);
expect(top).toEqual(y);
});
it("with all the actions passed in", () => {
expect(menuComponent).toBeDefined();
let listItems = menuComponent.children[0].children;
const listItems = menuElement.children[0].children;
expect(listItems.length).toEqual(actionsArray.length);
document.body.click();
});
it("with click-able menu items, that will invoke the correct callBacks", () => {
let listItem1 = menuComponent.children[0].children[0];
it('with click-able menu items, that will invoke the correct callBack', (done) => {
menuOptions.onDestroy = done;
menuAPI.showMenu(x, y, actionsArray, menuOptions);
menuElement = document.querySelector('.c-menu');
const listItem1 = menuElement.children[0].children[0];
listItem1.click();
expect(result).toEqual("Test Action 1 Invoked");
expect(result).toEqual('Test Action 1 Invoked');
});
it("dismisses the menu when action is clicked on", () => {
let listItem1 = menuComponent.children[0].children[0];
it('dismisses the menu when action is clicked on', (done) => {
menuOptions.onDestroy = done;
menuAPI.showMenu(x, y, actionsArray, menuOptions);
menuElement = document.querySelector('.c-menu');
const listItem1 = menuElement.children[0].children[0];
listItem1.click();
let menu = document.querySelector('.c-menu');
menuElement = document.querySelector('.c-menu');
expect(menu).toBeNull();
expect(menuElement).toBeNull();
});
it("invokes the destroy method when menu is dismissed", () => {
it('invokes the destroy method when menu is dismissed', (done) => {
menuOptions.onDestroy = done;
menuAPI.showMenu(x, y, actionsArray, menuOptions);
const vueComponent = menuAPI.menuComponent.component;
spyOn(vueComponent, '$destroy');
document.body.click();
expect(vueComponent.$destroy).toHaveBeenCalled();
});
it("invokes the onDestroy callback if passed in", () => {
document.body.click();
it('invokes the onDestroy callback if passed in', (done) => {
let count = 0;
menuOptions.onDestroy = () => {
count++;
expect(count).toEqual(1);
done();
};
expect(onDestroy).toHaveBeenCalled();
menuAPI.showMenu(x, y, actionsArray, menuOptions);
document.body.click();
});
});
});
describe("superMenu method", () => {
it("creates a superMenu", () => {
menuAPI.showSuperMenu(x, y, actionsArray);
describe('superMenu method', () => {
it('creates a superMenu', (done) => {
menuOptions.onDestroy = done;
const superMenu = document.querySelector('.c-super-menu__menu');
menuAPI.showSuperMenu(x, y, actionsArray, menuOptions);
menuElement = document.querySelector('.c-super-menu__menu');
expect(superMenu).not.toBeNull();
expect(menuElement).not.toBeNull();
document.body.click();
});
it("Mouse over a superMenu shows correct description", (done) => {
menuAPI.showSuperMenu(x, y, actionsArray);
it('Mouse over a superMenu shows correct description', (done) => {
menuOptions.onDestroy = done;
const superMenu = document.querySelector('.c-super-menu__menu');
const superMenuItem = superMenu.querySelector('li');
menuAPI.showSuperMenu(x, y, actionsArray, menuOptions);
menuElement = document.querySelector('.c-super-menu__menu');
const superMenuItem = menuElement.querySelector('li');
const mouseOverEvent = createMouseEvent('mouseover');
superMenuItem.dispatchEvent(mouseOverEvent);
const itemDescription = document.querySelector('.l-item-description__description');
setTimeout(() => {
menuAPI.menuComponent.component.$nextTick(() => {
expect(menuElement).not.toBeNull();
expect(itemDescription.innerText).toEqual(actionsArray[0].description);
expect(superMenu).not.toBeNull();
done();
}, 300);
document.body.click();
});
});
});
describe("Menu Placements", () => {
it("default menu position BOTTOM_RIGHT", () => {
menuAPI.showMenu(x, y, actionsArray);
const menu = document.querySelector('.c-menu');
const boundingClientRect = menu.getBoundingClientRect();
const left = boundingClientRect.left;
const top = boundingClientRect.top;
expect(left).toEqual(x);
expect(top).toEqual(y);
});
it("menu position BOTTOM_RIGHT", () => {
const menuOptions = {
placement: openmct.menus.menuPlacement.BOTTOM_RIGHT
};
describe('Menu Placements', () => {
it('default menu position BOTTOM_RIGHT', (done) => {
menuOptions.onDestroy = done;
menuAPI.showMenu(x, y, actionsArray, menuOptions);
menuElement = document.querySelector('.c-menu');
const menu = document.querySelector('.c-menu');
const boundingClientRect = menu.getBoundingClientRect();
const boundingClientRect = menuElement.getBoundingClientRect();
const left = boundingClientRect.left;
const top = boundingClientRect.top;
expect(left).toEqual(x);
expect(top).toEqual(y);
document.body.click();
});
it('menu position BOTTOM_RIGHT', (done) => {
menuOptions.onDestroy = done;
menuOptions.placement = openmct.menus.menuPlacement.BOTTOM_RIGHT;
menuAPI.showMenu(x, y, actionsArray, menuOptions);
menuElement = document.querySelector('.c-menu');
const boundingClientRect = menuElement.getBoundingClientRect();
const left = boundingClientRect.left;
const top = boundingClientRect.top;
expect(left).toEqual(x);
expect(top).toEqual(y);
document.body.click();
});
});
});

View File

@ -40,11 +40,13 @@ describe("The User API", () => {
});
afterEach(() => {
const activeOverlays = openmct.overlays.activeOverlays;
activeOverlays.forEach(overlay => overlay.dismiss());
return resetApplicationState(openmct);
});
describe('with regard to user providers', () => {
it('allows you to specify a user provider', () => {
openmct.user.on('providerAdded', (provider) => {
expect(provider).toBeInstanceOf(ExampleUserProvider);

View File

@ -0,0 +1,128 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import CreateAction from './CreateAction';
import {
createOpenMct,
resetApplicationState
} from 'utils/testing';
import { debounce } from 'lodash';
let parentObject;
let parentObjectPath;
let unObserve;
describe("The create action plugin", () => {
let openmct;
const TYPES = [
'clock',
'conditionWidget',
'conditionWidget',
'example.imagery',
'example.state-generator',
'flexible-layout',
'folder',
'generator',
'hyperlink',
'LadTable',
'LadTableSet',
'layout',
'mmgis',
'notebook',
'plan',
'table',
'tabs',
'telemetry-mean',
'telemetry.plot.bar-graph',
'telemetry.plot.overlay',
'telemetry.plot.stacked',
'time-strip',
'timer',
'webpage'
];
beforeEach((done) => {
openmct = createOpenMct();
openmct.on('start', done);
openmct.startHeadless();
});
afterEach(() => {
return resetApplicationState(openmct);
});
describe('creates new objects for a', () => {
beforeEach(() => {
parentObject = {
name: 'mock folder',
type: 'folder',
identifier: {
key: 'mock-folder',
namespace: ''
},
composition: []
};
parentObjectPath = [parentObject];
spyOn(openmct.objects, 'save');
openmct.objects.save.and.callThrough();
spyOn(openmct.forms, 'showForm');
openmct.forms.showForm.and.callFake(formStructure => {
return Promise.resolve({
name: 'test',
notes: 'test notes',
location: parentObjectPath
});
});
});
afterEach(() => {
parentObject = null;
unObserve();
});
TYPES.forEach(type => {
it(`type ${type}`, (done) => {
function callback(newObject) {
const composition = newObject.composition;
openmct.objects.get(composition[0])
.then(object => {
expect(object.type).toEqual(type);
expect(object.location).toEqual(openmct.objects.makeKeyString(parentObject.identifier));
done();
});
}
const deBouncedCallback = debounce(callback, 300);
unObserve = openmct.objects.observe(parentObject, '*', deBouncedCallback);
const createAction = new CreateAction(openmct, type, parentObject);
createAction.invoke();
});
});
});
});

View File

@ -45,7 +45,7 @@ export default class EditPropertiesAction extends PropertiesAction {
}
invoke(objectPath) {
this._showEditForm(objectPath);
return this._showEditForm(objectPath);
}
/**
@ -86,7 +86,7 @@ export default class EditPropertiesAction extends PropertiesAction {
const formStructure = createWizard.getFormStructure(false);
formStructure.title = 'Edit ' + this.domainObject.name;
this.openmct.forms.showForm(formStructure)
return this.openmct.forms.showForm(formStructure)
.then(this._onSave.bind(this));
}
}

View File

@ -0,0 +1,222 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import {
createMouseEvent,
createOpenMct,
resetApplicationState
} from 'utils/testing';
import { debounce } from 'lodash';
describe('EditPropertiesAction plugin', () => {
let editPropertiesAction;
let openmct;
let element;
beforeEach((done) => {
element = document.createElement('div');
element.style.display = 'block';
element.style.width = '1920px';
element.style.height = '1080px';
openmct = createOpenMct();
openmct.on('start', done);
openmct.startHeadless(element);
editPropertiesAction = openmct.actions.getAction('properties');
});
afterEach(() => {
editPropertiesAction = null;
return resetApplicationState(openmct);
});
it('editPropertiesAction exists', () => {
expect(editPropertiesAction.key).toEqual('properties');
});
it('edit properties action applies to only persistable objects', () => {
spyOn(openmct.objects, 'isPersistable').and.returnValue(true);
const domainObject = {
name: 'mock folder',
type: 'folder',
identifier: {
key: 'mock-folder',
namespace: ''
},
composition: []
};
const isApplicableTo = editPropertiesAction.appliesTo([domainObject]);
expect(isApplicableTo).toBe(true);
});
it('edit properties action does not apply to non persistable objects', () => {
spyOn(openmct.objects, 'isPersistable').and.returnValue(false);
const domainObject = {
name: 'mock folder',
type: 'folder',
identifier: {
key: 'mock-folder',
namespace: ''
},
composition: []
};
const isApplicableTo = editPropertiesAction.appliesTo([domainObject]);
expect(isApplicableTo).toBe(false);
});
it('edit properties action when invoked shows form', (done) => {
const domainObject = {
name: 'mock folder',
notes: 'mock notes',
type: 'folder',
identifier: {
key: 'mock-folder',
namespace: ''
},
modified: 1643065068597,
persisted: 1643065068600,
composition: []
};
const deBouncedFormChange = debounce(handleFormPropertyChange, 500);
openmct.forms.on('onFormPropertyChange', deBouncedFormChange);
function handleFormPropertyChange(data) {
const form = document.querySelector('.js-form');
const title = form.querySelector('input');
expect(title.value).toEqual(domainObject.name);
const notes = form.querySelector('textArea');
expect(notes.value).toEqual(domainObject.notes);
const buttons = form.querySelectorAll('button');
expect(buttons[0].textContent.trim()).toEqual('OK');
expect(buttons[1].textContent.trim()).toEqual('Cancel');
const clickEvent = createMouseEvent('click');
buttons[1].dispatchEvent(clickEvent);
openmct.forms.off('onFormPropertyChange', deBouncedFormChange);
}
editPropertiesAction.invoke([domainObject])
.catch(() => {
done();
});
});
it('edit properties action saves changes', (done) => {
const oldName = 'mock folder';
const newName = 'renamed mock folder';
const domainObject = {
name: oldName,
notes: 'mock notes',
type: 'folder',
identifier: {
key: 'mock-folder',
namespace: ''
},
modified: 1643065068597,
persisted: 1643065068600,
composition: []
};
let unObserve;
function callback(newObject) {
expect(newObject.name).not.toEqual(oldName);
expect(newObject.name).toEqual(newName);
unObserve();
done();
}
const deBouncedCallback = debounce(callback, 300);
unObserve = openmct.objects.observe(domainObject, '*', deBouncedCallback);
let changed = false;
const deBouncedFormChange = debounce(handleFormPropertyChange, 500);
openmct.forms.on('onFormPropertyChange', deBouncedFormChange);
function handleFormPropertyChange(data) {
const form = document.querySelector('.js-form');
const title = form.querySelector('input');
const notes = form.querySelector('textArea');
const buttons = form.querySelectorAll('button');
expect(buttons[0].textContent.trim()).toEqual('OK');
expect(buttons[1].textContent.trim()).toEqual('Cancel');
if (!changed) {
expect(title.value).toEqual(domainObject.name);
expect(notes.value).toEqual(domainObject.notes);
// change input field value and dispatch event for it
title.focus();
title.value = newName;
title.dispatchEvent(new Event('input'));
title.blur();
changed = true;
} else {
// click ok to save form changes
const clickEvent = createMouseEvent('click');
buttons[0].dispatchEvent(clickEvent);
openmct.forms.off('onFormPropertyChange', deBouncedFormChange);
}
}
editPropertiesAction.invoke([domainObject]);
});
it('edit properties action discards changes', (done) => {
const name = 'mock folder';
const domainObject = {
name,
notes: 'mock notes',
type: 'folder',
identifier: {
key: 'mock-folder',
namespace: ''
},
modified: 1643065068597,
persisted: 1643065068600,
composition: []
};
editPropertiesAction.invoke([domainObject])
.catch(() => {
expect(domainObject.name).toEqual(name);
done();
});
const form = document.querySelector('.js-form');
const buttons = form.querySelectorAll('button');
const clickEvent = createMouseEvent('click');
buttons[1].dispatchEvent(clickEvent);
});
});

View File

@ -91,6 +91,7 @@ describe("The Move Action plugin", () => {
});
describe("when moving an object to a new parent and removing from the old parent", () => {
let unObserve;
beforeEach((done) => {
openmct.router.path = [];
@ -104,7 +105,7 @@ describe("The Move Action plugin", () => {
});
});
openmct.objects.observe(parentObject, '*', (newObject) => {
unObserve = openmct.objects.observe(parentObject, '*', (newObject) => {
done();
});
@ -113,6 +114,10 @@ describe("The Move Action plugin", () => {
moveAction.invoke([childObject, parentObject]);
});
afterEach(() => {
unObserve();
});
it("the child object's identifier should be in the new parent's composition", () => {
let newParentChild = anotherParentObject.composition[0];
expect(newParentChild).toEqual(childObject.identifier);