Compare commits
	
		
			6 Commits
		
	
	
		
			release/3.
			...
			release/2.
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					db495ea12e | ||
| 
						 | 
					9022d5d859 | ||
| 
						 | 
					8fed722f72 | ||
| 
						 | 
					9a64220db4 | ||
| 
						 | 
					eeba4703dd | ||
| 
						 | 
					1c00f1a731 | 
@@ -27,7 +27,7 @@ This test suite is dedicated to tests which verify the basic operations surround
 | 
			
		||||
const { test, expect } = require('../../../../baseFixtures');
 | 
			
		||||
const { createDomainObjectWithDefaults } = require('../../../../appActions');
 | 
			
		||||
 | 
			
		||||
test.describe('Notebook Network Request Inspection @couchdb', () => {
 | 
			
		||||
test.describe('Notebook Tests with CouchDB @couchdb', () => {
 | 
			
		||||
    let testNotebook;
 | 
			
		||||
    test.beforeEach(async ({ page }) => {
 | 
			
		||||
        //Navigate to baseURL
 | 
			
		||||
@@ -221,6 +221,45 @@ test.describe('Notebook Network Request Inspection @couchdb', () => {
 | 
			
		||||
 | 
			
		||||
        expect(filterNonFetchRequests(addingNotebookElementsRequests).length).toBeLessThanOrEqual(4);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('Search tests', async ({ page }) => {
 | 
			
		||||
        test.info().annotations.push({
 | 
			
		||||
            type: 'issue',
 | 
			
		||||
            description: 'https://github.com/akhenry/openmct-yamcs/issues/69'
 | 
			
		||||
        });
 | 
			
		||||
        await page.locator('text=To start a new entry, click here or drag and drop any object').click();
 | 
			
		||||
        await page.locator('[aria-label="Notebook Entry Input"]').click();
 | 
			
		||||
        await page.locator('[aria-label="Notebook Entry Input"]').fill(`First Entry`);
 | 
			
		||||
        await page.locator('[aria-label="Notebook Entry Input"]').press('Enter');
 | 
			
		||||
 | 
			
		||||
        // Add three tags
 | 
			
		||||
        await page.hover(`button:has-text("Add Tag")`);
 | 
			
		||||
        await page.locator(`button:has-text("Add Tag")`).click();
 | 
			
		||||
        await page.locator('[placeholder="Type to select tag"]').click();
 | 
			
		||||
        await page.locator('[aria-label="Autocomplete Options"] >> text=Science').click();
 | 
			
		||||
        await page.waitForSelector('[aria-label="Tag"]:has-text("Science")');
 | 
			
		||||
 | 
			
		||||
        await page.hover(`button:has-text("Add Tag")`);
 | 
			
		||||
        await page.locator(`button:has-text("Add Tag")`).click();
 | 
			
		||||
        await page.locator('[placeholder="Type to select tag"]').click();
 | 
			
		||||
        await page.locator('[aria-label="Autocomplete Options"] >> text=Drilling').click();
 | 
			
		||||
        await page.waitForSelector('[aria-label="Tag"]:has-text("Drilling")');
 | 
			
		||||
 | 
			
		||||
        await page.hover(`button:has-text("Add Tag")`);
 | 
			
		||||
        await page.locator(`button:has-text("Add Tag")`).click();
 | 
			
		||||
        await page.locator('[placeholder="Type to select tag"]').click();
 | 
			
		||||
        await page.locator('[aria-label="Autocomplete Options"] >> text=Driving').click();
 | 
			
		||||
        await page.waitForSelector('[aria-label="Tag"]:has-text("Driving")');
 | 
			
		||||
 | 
			
		||||
        await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
 | 
			
		||||
        await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Sc');
 | 
			
		||||
        await expect(page.locator('[aria-label="Search Result"]').first()).toContainText("Science");
 | 
			
		||||
        await expect(page.locator('[aria-label="Search Result"]').first()).not.toContainText("Driving");
 | 
			
		||||
 | 
			
		||||
        await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
 | 
			
		||||
        await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Xq');
 | 
			
		||||
        await expect(page.locator('text=No results found')).toBeVisible();
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// Try to reduce indeterminism of browser requests by only returning fetch requests.
 | 
			
		||||
 
 | 
			
		||||
@@ -39,7 +39,7 @@ async function createNotebookAndEntry(page, iterations = 1) {
 | 
			
		||||
    createDomainObjectWithDefaults(page, { type: 'Notebook' });
 | 
			
		||||
 | 
			
		||||
    for (let iteration = 0; iteration < iterations; iteration++) {
 | 
			
		||||
        // Click text=To start a new entry, click here or drag and drop any object
 | 
			
		||||
        // Create an entry
 | 
			
		||||
        await page.locator('text=To start a new entry, click here or drag and drop any object').click();
 | 
			
		||||
        const entryLocator = `[aria-label="Notebook Entry Input"] >> nth = ${iteration}`;
 | 
			
		||||
        await page.locator(entryLocator).click();
 | 
			
		||||
@@ -116,7 +116,7 @@ test.describe('Tagging in Notebooks @addInit', () => {
 | 
			
		||||
 | 
			
		||||
        await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
 | 
			
		||||
        await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Xq');
 | 
			
		||||
        await expect(page.locator('[aria-label="Search Result"]')).toBeHidden();
 | 
			
		||||
        await expect(page.locator('text=No results found')).toBeVisible();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('Can delete tags', async ({ page }) => {
 | 
			
		||||
@@ -133,6 +133,27 @@ test.describe('Tagging in Notebooks @addInit', () => {
 | 
			
		||||
        await expect(page.locator('[aria-label="Search Result"]')).not.toContainText("Driving");
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('Can delete entries without tags', async ({ page }) => {
 | 
			
		||||
        test.info().annotations.push({
 | 
			
		||||
            type: 'issue',
 | 
			
		||||
            description: 'https://github.com/nasa/openmct/issues/5823'
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        await createNotebookEntryAndTags(page);
 | 
			
		||||
 | 
			
		||||
        await page.locator('text=To start a new entry, click here or drag and drop any object').click();
 | 
			
		||||
        const entryLocator = `[aria-label="Notebook Entry Input"] >> nth = 1`;
 | 
			
		||||
        await page.locator(entryLocator).click();
 | 
			
		||||
        await page.locator(entryLocator).fill(`An entry without tags`);
 | 
			
		||||
        await page.locator('[aria-label="Notebook Entry Input"] >> nth=1').press('Enter');
 | 
			
		||||
 | 
			
		||||
        await page.hover('[aria-label="Notebook Entry Input"] >> nth=1');
 | 
			
		||||
        await page.locator('button[title="Delete this entry"]').last().click();
 | 
			
		||||
        await expect(page.locator('text=This action will permanently delete this entry. Do you wish to continue?')).toBeVisible();
 | 
			
		||||
        await page.locator('button:has-text("Ok")').click();
 | 
			
		||||
        await expect(page.locator('text=This action will permanently delete this entry. Do you wish to continue?')).toBeHidden();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('Can delete objects with tags and neither return in search', async ({ page }) => {
 | 
			
		||||
        await createNotebookEntryAndTags(page);
 | 
			
		||||
        // Delete Notebook
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "openmct",
 | 
			
		||||
  "version": "2.1.1-SNAPSHOT",
 | 
			
		||||
  "version": "2.1.1",
 | 
			
		||||
  "description": "The Open MCT core platform",
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@babel/eslint-parser": "7.18.9",
 | 
			
		||||
@@ -51,7 +51,7 @@
 | 
			
		||||
    "moment-timezone": "0.5.37",
 | 
			
		||||
    "nyc": "15.1.0",
 | 
			
		||||
    "painterro": "1.2.78",
 | 
			
		||||
    "playwright-core": "1.26.1",
 | 
			
		||||
    "playwright-core": "1.25.2",
 | 
			
		||||
    "plotly.js-basic-dist": "2.14.0",
 | 
			
		||||
    "plotly.js-gl2d-dist": "2.14.0",
 | 
			
		||||
    "printj": "1.3.1",
 | 
			
		||||
 
 | 
			
		||||
@@ -63,10 +63,9 @@ export default class Editor extends EventEmitter {
 | 
			
		||||
            .then(() => {
 | 
			
		||||
                this.editing = false;
 | 
			
		||||
                this.emit('isEditing', false);
 | 
			
		||||
                this.openmct.objects.endTransaction();
 | 
			
		||||
            }).catch(error => {
 | 
			
		||||
                throw error;
 | 
			
		||||
            }).finally(() => {
 | 
			
		||||
                this.openmct.objects.endTransaction();
 | 
			
		||||
            });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										80
									
								
								src/api/EditorSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								src/api/EditorSpec.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,80 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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 Editor API', () => {
 | 
			
		||||
    let openmct;
 | 
			
		||||
 | 
			
		||||
    beforeEach((done) => {
 | 
			
		||||
        openmct = createOpenMct();
 | 
			
		||||
        openmct.on('start', done);
 | 
			
		||||
 | 
			
		||||
        spyOn(openmct.objects, 'endTransaction');
 | 
			
		||||
 | 
			
		||||
        openmct.startHeadless();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    afterEach(() => {
 | 
			
		||||
        return resetApplicationState(openmct);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('opens a transaction on edit', () => {
 | 
			
		||||
        expect(
 | 
			
		||||
            openmct.objects.isTransactionActive()
 | 
			
		||||
        ).toBeFalse();
 | 
			
		||||
        openmct.editor.edit();
 | 
			
		||||
        expect(
 | 
			
		||||
            openmct.objects.isTransactionActive()
 | 
			
		||||
        ).toBeTrue();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('closes an open transaction on successful save', async () => {
 | 
			
		||||
        spyOn(openmct.objects, 'getActiveTransaction')
 | 
			
		||||
            .and.returnValue({
 | 
			
		||||
                commit: () => Promise.resolve(true)
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        openmct.editor.edit();
 | 
			
		||||
        await openmct.editor.save();
 | 
			
		||||
 | 
			
		||||
        expect(
 | 
			
		||||
            openmct.objects.endTransaction
 | 
			
		||||
        ).toHaveBeenCalled();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('does not close an open transaction on failed save', async () => {
 | 
			
		||||
        spyOn(openmct.objects, 'getActiveTransaction')
 | 
			
		||||
            .and.returnValue({
 | 
			
		||||
                commit: () => Promise.reject()
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        openmct.editor.edit();
 | 
			
		||||
        await openmct.editor.save().catch(() => {});
 | 
			
		||||
 | 
			
		||||
        expect(
 | 
			
		||||
            openmct.objects.endTransaction
 | 
			
		||||
        ).not.toHaveBeenCalled();
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
@@ -346,6 +346,10 @@ export default class AnnotationAPI extends EventEmitter {
 | 
			
		||||
    */
 | 
			
		||||
    async searchForTags(query, abortController) {
 | 
			
		||||
        const matchingTagKeys = this.#getMatchingTags(query);
 | 
			
		||||
        if (!matchingTagKeys.length) {
 | 
			
		||||
            return [];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const searchResults = (await Promise.all(this.openmct.objects.search(matchingTagKeys, abortController, this.openmct.objects.SEARCH_TYPES.TAGS))).flat();
 | 
			
		||||
        const filteredDeletedResults = searchResults.filter((result) => {
 | 
			
		||||
            return !(result._deleted);
 | 
			
		||||
 
 | 
			
		||||
@@ -185,5 +185,10 @@ describe("The Annotation API", () => {
 | 
			
		||||
            expect(results).toBeDefined();
 | 
			
		||||
            expect(results.length).toEqual(1);
 | 
			
		||||
        });
 | 
			
		||||
        it("returns no tags for empty search", async () => {
 | 
			
		||||
            const results = await openmct.annotation.searchForTags('q');
 | 
			
		||||
            expect(results).toBeDefined();
 | 
			
		||||
            expect(results.length).toEqual(0);
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -515,7 +515,9 @@ export default {
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        removeAnnotations(entryId) {
 | 
			
		||||
            this.openmct.annotation.deleteAnnotations(this.notebookAnnotations[entryId]);
 | 
			
		||||
            if (this.notebookAnnotations[entryId]) {
 | 
			
		||||
                this.openmct.annotation.deleteAnnotations(this.notebookAnnotations[entryId]);
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        checkEntryPos(entry) {
 | 
			
		||||
            const entryPos = getEntryPosById(entry.id, this.domainObject, this.selectedSection, this.selectedPage);
 | 
			
		||||
 
 | 
			
		||||
@@ -90,6 +90,10 @@ class CouchSearchProvider {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    searchForTags(tagsArray, abortSignal) {
 | 
			
		||||
        if (!tagsArray || !tagsArray.length) {
 | 
			
		||||
            return [];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const filter = {
 | 
			
		||||
            "selector": {
 | 
			
		||||
                "$and": [
 | 
			
		||||
 
 | 
			
		||||
@@ -442,7 +442,8 @@ export default {
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        removeSeries(plotSeries) {
 | 
			
		||||
        removeSeries(plotSeries, index) {
 | 
			
		||||
            this.seriesModels.splice(index, 1);
 | 
			
		||||
            this.checkSameRangeValue();
 | 
			
		||||
            this.stopListening(plotSeries);
 | 
			
		||||
        },
 | 
			
		||||
 
 | 
			
		||||
@@ -102,8 +102,8 @@ export default class Collection extends Model {
 | 
			
		||||
            throw new Error('model not found in collection.');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.emit('remove', model, index);
 | 
			
		||||
        this.models.splice(index, 1);
 | 
			
		||||
        this.emit('remove', model, index);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    destroy(model) {
 | 
			
		||||
 
 | 
			
		||||
@@ -26,6 +26,7 @@
 | 
			
		||||
>
 | 
			
		||||
    <div class="c-menu-button c-ctrl-wrapper c-ctrl-wrapper--menus-left">
 | 
			
		||||
        <button
 | 
			
		||||
            aria-label="Time Conductor History"
 | 
			
		||||
            class="c-button--menu c-history-button icon-history"
 | 
			
		||||
            @click.prevent.stop="showHistoryMenu"
 | 
			
		||||
        >
 | 
			
		||||
 
 | 
			
		||||
@@ -145,10 +145,10 @@ export default {
 | 
			
		||||
            const annotationsToDelete = this.annotations.filter((annotation) => {
 | 
			
		||||
                return annotation.tags.includes(tagToRemove);
 | 
			
		||||
            });
 | 
			
		||||
            const result = await this.openmct.annotation.deleteAnnotations(annotationsToDelete);
 | 
			
		||||
            this.$emit('tags-updated', annotationsToDelete);
 | 
			
		||||
 | 
			
		||||
            return result;
 | 
			
		||||
            if (annotationsToDelete) {
 | 
			
		||||
                await this.openmct.annotation.deleteAnnotations(annotationsToDelete);
 | 
			
		||||
                this.$emit('tags-updated', annotationsToDelete);
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        async tagAdded(newTag) {
 | 
			
		||||
            // Either undelete an annotation, or create one (1) new annotation
 | 
			
		||||
 
 | 
			
		||||
@@ -33,7 +33,8 @@
 | 
			
		||||
        class="c-tree__item c-elements-pool__item"
 | 
			
		||||
        :class="{
 | 
			
		||||
            'is-context-clicked': contextClickActive,
 | 
			
		||||
            'hover': hover
 | 
			
		||||
            'hover': hover,
 | 
			
		||||
            'is-alias': isAlias
 | 
			
		||||
        }"
 | 
			
		||||
    >
 | 
			
		||||
        <span
 | 
			
		||||
@@ -55,6 +56,7 @@ export default {
 | 
			
		||||
    components: {
 | 
			
		||||
        ObjectLabel
 | 
			
		||||
    },
 | 
			
		||||
    inject: ['openmct'],
 | 
			
		||||
    props: {
 | 
			
		||||
        index: {
 | 
			
		||||
            type: Number,
 | 
			
		||||
@@ -82,9 +84,12 @@ export default {
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    data() {
 | 
			
		||||
        const isAlias = this.elementObject.location !== this.openmct.objects.makeKeyString(this.parentObject.identifier);
 | 
			
		||||
 | 
			
		||||
        return {
 | 
			
		||||
            contextClickActive: false,
 | 
			
		||||
            hover: false
 | 
			
		||||
            hover: false,
 | 
			
		||||
            isAlias
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,15 @@
 | 
			
		||||
        margin-top: $interiorMargin;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__item {
 | 
			
		||||
        &.is-alias {
 | 
			
		||||
            // Object is an alias to an original.
 | 
			
		||||
            [class*='__type-icon'] {
 | 
			
		||||
                @include isAlias();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__search {
 | 
			
		||||
        flex: 0 0 auto;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -232,6 +232,8 @@ describe("GrandSearch", () => {
 | 
			
		||||
    it("should render an object search result if new object added", async () => {
 | 
			
		||||
        const composition = openmct.composition.get(mockFolderObject);
 | 
			
		||||
        composition.add(mockNewObject);
 | 
			
		||||
        // after adding, need to wait a beat for the folder to be indexed
 | 
			
		||||
        await Vue.nextTick();
 | 
			
		||||
        await grandSearchComponent.$children[0].searchEverything('apple');
 | 
			
		||||
        await Vue.nextTick();
 | 
			
		||||
        const searchResults = document.querySelectorAll('[aria-label="New Apple Test Folder folder result"]');
 | 
			
		||||
@@ -271,6 +273,13 @@ describe("GrandSearch", () => {
 | 
			
		||||
        expect(annotationResults[1].innerText).toContain('Driving');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("should render no annotation search results if no match", async () => {
 | 
			
		||||
        await grandSearchComponent.$children[0].searchEverything('Qbert');
 | 
			
		||||
        await Vue.nextTick();
 | 
			
		||||
        const annotationResults = document.querySelectorAll('[aria-label="Search Result"]');
 | 
			
		||||
        expect(annotationResults.length).toBe(0);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    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];
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user