From 2bd35bb2a5d53eab62b544c76bbe5bed69c9712b Mon Sep 17 00:00:00 2001 From: Scott Bell Date: Wed, 29 Jun 2022 19:30:18 +0200 Subject: [PATCH] 5361 tags not persisting locally (#5408) * fixed typo * remove unneeded lookup * fix tags adding and deleting * more reliable way to remove tags * break tests up for parallel execution * fixed notebook tagging test * enable e2e tests * made schedule index comment more clear and fix uppercase/lowercase issue * address e2e changes * add unit test to bump coverage * fix typo * need to check on annotation creation if provider exists or not * added fixtures * undo silly couchdb commit --- e2e/tests/plugins/notebook/tags.e2e.spec.js | 146 ++++++++++++++++++ package.json | 2 +- src/api/objects/InMemorySearchProvider.js | 34 ++-- src/api/objects/InMemorySearchWorker.js | 21 ++- src/plugins/notebook/components/Notebook.vue | 1 + .../notebook/components/NotebookEntry.vue | 1 + .../persistence/couch/CouchSearchProvider.js | 2 +- src/ui/components/tags/TagEditor.vue | 19 +-- src/ui/layout/search/GrandSearch.vue | 2 - src/ui/layout/search/GrandSearchSpec.js | 3 +- 10 files changed, 185 insertions(+), 46 deletions(-) create mode 100644 e2e/tests/plugins/notebook/tags.e2e.spec.js diff --git a/e2e/tests/plugins/notebook/tags.e2e.spec.js b/e2e/tests/plugins/notebook/tags.e2e.spec.js new file mode 100644 index 0000000000..422820fef6 --- /dev/null +++ b/e2e/tests/plugins/notebook/tags.e2e.spec.js @@ -0,0 +1,146 @@ +/***************************************************************************** + * 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 { expect } = require('@playwright/test'); +const { test } = require('../../../fixtures'); + +/** + * Creates a notebook object and adds an entry. + * @param {import('@playwright/test').Page} page + */ +async function createNotebookAndEntry(page) { + //Go to baseURL + await page.goto('/', { waitUntil: 'networkidle' }); + + // Click button:has-text("Create") + await page.locator('button:has-text("Create")').click(); + // Click li:has-text("Notebook") + await page.locator('li:has-text("Notebook")').click(); + // Click button:has-text("OK") + await Promise.all([ + page.waitForNavigation(), + page.locator('button:has-text("OK")').click() + ]); + + // Click text=To start a new entry, click here or drag and drop any object + await page.locator('text=To start a new entry, click here or drag and drop any object').click(); +} + +/** + * Creates a notebook object, adds an entry, and adds a tag. + * @param {import('@playwright/test').Page} page + */ +async function createNotebookEntryAndTags(page) { + await createNotebookAndEntry(page); + // Click text=To start a new entry, click here or drag and drop any object + await page.locator('button:has-text("Add Tag")').click(); + + // Click [placeholder="Type to select tag"] + await page.locator('[placeholder="Type to select tag"]').click(); + // Click text=Driving + await page.locator('text=Driving').click(); + + // Click button:has-text("Add Tag") + await page.locator('button:has-text("Add Tag")').click(); + // Click [placeholder="Type to select tag"] + await page.locator('[placeholder="Type to select tag"]').click(); + // Click text=Science + await page.locator('text=Science').click(); +} + +test.describe('Tagging in Notebooks', () => { + test('Can load tags', async ({ page }) => { + await createNotebookAndEntry(page); + // Click text=To start a new entry, click here or drag and drop any object + await page.locator('button:has-text("Add Tag")').click(); + + // Click [placeholder="Type to select tag"] + await page.locator('[placeholder="Type to select tag"]').click(); + + await expect(page.locator('[aria-label="Autocomplete Options"]')).toContainText("Science"); + await expect(page.locator('[aria-label="Autocomplete Options"]')).toContainText("Drilling"); + await expect(page.locator('[aria-label="Autocomplete Options"]')).toContainText("Driving"); + }); + test('Can add tags', async ({ page }) => { + await createNotebookEntryAndTags(page); + + await expect(page.locator('[aria-label="Notebook Entry"]')).toContainText("Science"); + await expect(page.locator('[aria-label="Notebook Entry"]')).toContainText("Driving"); + + // Click button:has-text("Add Tag") + await page.locator('button:has-text("Add Tag")').click(); + // Click [placeholder="Type to select tag"] + await page.locator('[placeholder="Type to select tag"]').click(); + + await expect(page.locator('[aria-label="Autocomplete Options"]')).not.toContainText("Science"); + await expect(page.locator('[aria-label="Autocomplete Options"]')).not.toContainText("Driving"); + await expect(page.locator('[aria-label="Autocomplete Options"]')).toContainText("Drilling"); + }); + test('Can search for tags', async ({ page }) => { + await createNotebookEntryAndTags(page); + // Click [aria-label="OpenMCT Search"] input[type="search"] + await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click(); + // Fill [aria-label="OpenMCT Search"] input[type="search"] + await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('sc'); + await expect(page.locator('[aria-label="Search Result"]')).toContainText("Science"); + await expect(page.locator('[aria-label="Search Result"]')).toContainText("Driving"); + + // Click [aria-label="OpenMCT Search"] input[type="search"] + await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click(); + // Fill [aria-label="OpenMCT Search"] input[type="search"] + await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Sc'); + await expect(page.locator('[aria-label="Search Result"]')).toContainText("Science"); + await expect(page.locator('[aria-label="Search Result"]')).toContainText("Driving"); + + // Click [aria-label="OpenMCT Search"] input[type="search"] + await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click(); + // Fill [aria-label="OpenMCT Search"] input[type="search"] + await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Xq'); + await expect(page.locator('[aria-label="Search Result"]')).not.toBeVisible(); + await expect(page.locator('[aria-label="Search Result"]')).not.toBeVisible(); + }); + + test('Can delete tags', async ({ page }) => { + await createNotebookEntryAndTags(page); + await page.locator('[aria-label="Notebook Entries"]').click(); + // Delete Driving + await page.locator('text=Science Driving Add Tag >> button').nth(1).click(); + + await expect(page.locator('[aria-label="Notebook Entry"]')).toContainText("Science"); + await expect(page.locator('[aria-label="Notebook Entry"]')).not.toContainText("Driving"); + + // Fill [aria-label="OpenMCT Search"] input[type="search"] + await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('sc'); + await expect(page.locator('[aria-label="Search Result"]')).not.toContainText("Driving"); + }); + test('Tags persist across reload', async ({ page }) => { + await createNotebookEntryAndTags(page); + //Go to baseURL + await page.reload(); + await expect(page.locator('[aria-label="Notebook Entry"]')).toContainText("Science"); + await expect(page.locator('[aria-label="Notebook Entry"]')).toContainText("Driving"); + }); +}); diff --git a/package.json b/package.json index a053839873..fb1094f23c 100644 --- a/package.json +++ b/package.json @@ -89,7 +89,7 @@ "test": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --single-run", "test:firefox": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --single-run --browsers=FirefoxHeadless", "test:debug": "cross-env NODE_ENV=debug karma start --no-single-run", - "test:e2e:ci": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome smoke branding default condition timeConductor clock exampleImagery persistence performance grandsearch", + "test:e2e:ci": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome smoke branding default condition timeConductor clock exampleImagery persistence performance grandsearch notebook/tags", "test:e2e:local": "npx playwright test --config=e2e/playwright-local.config.js --project=chrome", "test:e2e:updatesnapshots": "npx playwright test --config=e2e/playwright-local.config.js --project=chrome --grep @snapshot --update-snapshots", "test:e2e:visual": "percy exec --config ./e2e/.percy.yml -- npx playwright test --config=e2e/playwright-visual.config.js", diff --git a/src/api/objects/InMemorySearchProvider.js b/src/api/objects/InMemorySearchProvider.js index bbfa01b092..840b316187 100644 --- a/src/api/objects/InMemorySearchProvider.js +++ b/src/api/objects/InMemorySearchProvider.js @@ -224,7 +224,8 @@ class InMemorySearchProvider { /** * Schedule an id to be indexed at a later date. If there are less - * pending requests then allowed, will kick off an indexing request. + * pending requests than the maximum allowed, this will kick off an indexing request. + * This is done only when indexing first begins and we need to index a lot of objects. * * @private * @param {identifier} id to be indexed. @@ -258,8 +259,12 @@ class InMemorySearchProvider { } onAnnotationCreation(annotationObject) { - const provider = this; - provider.index(annotationObject); + + const objectProvider = this.openmct.objects.getProvider(annotationObject.identifier); + if (objectProvider === undefined || objectProvider.search === undefined) { + const provider = this; + provider.index(annotationObject); + } } onNameMutation(domainObject, name) { @@ -270,7 +275,6 @@ class InMemorySearchProvider { } onTagMutation(domainObject, newTags) { - domainObject.oldTags = domainObject.tags; domainObject.tags = newTags; const provider = this; @@ -404,20 +408,16 @@ class InMemorySearchProvider { } }); - // remove old tags - if (model.oldTags) { - model.oldTags.forEach(tagIDToRemove => { - const existsInNewModel = model.tags.includes(tagIDToRemove); - if (!existsInNewModel && this.localIndexedAnnotationsByTag[tagIDToRemove]) { - this.localIndexedAnnotationsByTag[tagIDToRemove] = this.localIndexedAnnotationsByTag[tagIDToRemove]. - filter(annotationToRemove => { - const shouldKeep = annotationToRemove.keyString !== keyString; + const tagsToRemoveFromIndex = Object.keys(this.localIndexedAnnotationsByTag).filter(indexedTag => { + return !(model.tags.includes(indexedTag)); + }); + tagsToRemoveFromIndex.forEach(tagToRemoveFromIndex => { + this.localIndexedAnnotationsByTag[tagToRemoveFromIndex] = this.localIndexedAnnotationsByTag[tagToRemoveFromIndex].filter(indexedAnnotation => { + const shouldKeep = indexedAnnotation.keyString !== keyString; - return shouldKeep; - }); - } + return shouldKeep; }); - } + }); } localIndexAnnotation(objectToIndex, model) { @@ -449,7 +449,7 @@ class InMemorySearchProvider { keyString }; if (model && (model.type === 'annotation')) { - if (model.targets && model.targets) { + if (model.targets) { this.localIndexAnnotation(objectToIndex, model); } diff --git a/src/api/objects/InMemorySearchWorker.js b/src/api/objects/InMemorySearchWorker.js index d970052587..a2bb53a026 100644 --- a/src/api/objects/InMemorySearchWorker.js +++ b/src/api/objects/InMemorySearchWorker.js @@ -94,19 +94,16 @@ }); // remove old tags - if (model.oldTags) { - model.oldTags.forEach(tagIDToRemove => { - const existsInNewModel = model.tags.includes(tagIDToRemove); - if (!existsInNewModel && indexedAnnotationsByTag[tagIDToRemove]) { - indexedAnnotationsByTag[tagIDToRemove] = indexedAnnotationsByTag[tagIDToRemove]. - filter(annotationToRemove => { - const shouldKeep = annotationToRemove.keyString !== keyString; + const tagsToRemoveFromIndex = Object.keys(indexedAnnotationsByTag).filter(indexedTag => { + return !(model.tags.includes(indexedTag)); + }); + tagsToRemoveFromIndex.forEach(tagToRemoveFromIndex => { + indexedAnnotationsByTag[tagToRemoveFromIndex] = indexedAnnotationsByTag[tagToRemoveFromIndex].filter(indexedAnnotation => { + const shouldKeep = indexedAnnotation.keyString !== keyString; - return shouldKeep; - }); - } + return shouldKeep; }); - } + }); } function indexItem(keyString, model) { @@ -116,7 +113,7 @@ keyString }; if (model && (model.type === 'annotation')) { - if (model.targets && model.targets) { + if (model.targets) { indexAnnotation(objectToIndex, model); } diff --git a/src/plugins/notebook/components/Notebook.vue b/src/plugins/notebook/components/Notebook.vue index 9dbead306b..4d493525ac 100644 --- a/src/plugins/notebook/components/Notebook.vue +++ b/src/plugins/notebook/components/Notebook.vue @@ -144,6 +144,7 @@ v-if="selectedSection && selectedPage" ref="notebookEntries" class="c-notebook__entries" + aria-label="Notebook Entries" >
{ let parent; let sharedWorkerToRestore; let mockDomainObject; + let mockAnnotationObject; let mockDisplayLayout; let mockFolderObject; - let mockAnnotationObject; let originalRouterPath; beforeEach((done) => { @@ -107,7 +107,6 @@ describe("GrandSearch", () => { }; openmct.router.isNavigatedObject = jasmine.createSpy().and.returnValue(false); - const mockObjectProvider = jasmine.createSpyObj("mock object provider", [ "create", "update",