diff --git a/e2e/tests/api/forms/forms.e2e.spec.js b/e2e/tests/api/forms/forms.e2e.spec.js new file mode 100644 index 0000000000..cb36ce4546 --- /dev/null +++ b/e2e/tests/api/forms/forms.e2e.spec.js @@ -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 + }); +}); diff --git a/src/api/forms/FormsAPI.js b/src/api/forms/FormsAPI.js index e2bce03049..902d3473c2 100644 --- a/src/api/forms/FormsAPI.js +++ b/src/api/forms/FormsAPI.js @@ -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); } diff --git a/src/api/forms/FormsAPISpec.js b/src/api/forms/FormsAPISpec.js new file mode 100644 index 0000000000..ac7f0fc9fb --- /dev/null +++ b/src/api/forms/FormsAPISpec.js @@ -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(); + }); + }); +}); diff --git a/src/api/forms/components/FormProperties.vue b/src/api/forms/components/FormProperties.vue index 830fd0753b..c43dca08d4 100644 --- a/src/api/forms/components/FormProperties.vue +++ b/src/api/forms/components/FormProperties.vue @@ -21,9 +21,9 @@ *****************************************************************************/