* clock, timeConductor and appActions fixes
* Ensure realtime uses upstream context when available
Eliminate ambiguity when looking for time conductor locator
* Fix log plot e2e tests
* Fix displayLayout e2e tests
* Specify global time conductor to fix issues with duplicate selectors with independent time contexts
* a11y: ARIA for conductor and independent time conductor
* a11y: fix label collisions, specify 'Menu' in label
* Add watch mode
* fix(e2e): update appActions and tests to use a11y locators for ITC
* Don't remove the itc popup from the DOM. Just show/hide it once it's added the first time.
* test(e2e): disable one imagery test due to known bug
* Add fixme to tagging tests, issue described in 6822
* Fix locator for time conductor popups
* Improve how time bounds are set in independent time conductor.
Fix tests for flexible layout and timestrip
* Fix some tests for itc for display layouts
* Fix Inspector tabs remounting on change
* fix autoscale test and snapshot
* Fix telemetry table test
* Fix timestrip test
* e2e: move test info annotations to within test
* 6826: Fixes padStart error due to using it on a number rather than a string
* fix(e2e): update snapshots
* fix(e2e): fix restricted notebook locator
* fix(restrictedNotebook): fix issue causing sections not to update on lock
* fix(restrictedNotebook): fix issue causing snapshots to not be able to be deleted from a locked page
- Using `this.$delete(arr, index)` does not update the `length` property on the underlying target object, so it can lead to bizarre issues where your array is of length 4 but it has 3 objects in it.
* fix: replace all instances of `$delete` with `Array.splice()` or `delete`
* fix(e2e): fix grand search test
* fix(#3117): can remove item from displayLayout via tree context menu while viewing another item
* fix: remove typo 
* Wait for background image to load
* fix(#6832): timelist events can tick down
* fix: ensure that menuitems have the raw objects so emits work
* fix: assign new arrays instead of editing state in-place
* refactor(timelist): use `getClock()` instead of `clock()`
* Revert "refactor(timelist): use `getClock()` instead of `clock()`"
This reverts commit d888553112.
* refactor(timelist): use new timeAPI
* Stop ticking when the independent time context is disabled (#6833)
* Turn off the clock ticket for independent time conductor when it is disabled
* Fix linting issues
---------
Co-authored-by: Khalid Adil <khalidadil29@gmail.com>
* test: update couchdb notebook test
* fix: codeQL warnings
* fix(tree-item): infinite spinner issue
- Using `indexOf()` with an object was failing due to some items in the tree being Proxy-wrapped and others not. So instead, use `findIndex()` with a predicate that compares the navigationPaths of both objects
* [Timer] Remove "refresh" call, it is not needed (#6841)
* removing an unneccessary refresh that waas causing many get requests
* lets just pretend this never happened
* fix(mct-tree): maintain reactivity of all tree items
* Hide change role button in the indicator in cases where there is only… (#6840)
Hide change role button in the indicator in cases where there is only a single role available for the current user
---------
Co-authored-by: Shefali <simplyrender@gmail.com>
Co-authored-by: Khalid Adil <khalidadil29@gmail.com>
Co-authored-by: John Hill <john.c.hill@nasa.gov>
Co-authored-by: David Tsay <david.e.tsay@nasa.gov>
Co-authored-by: Jamie V <jamie.j.vigliotta@nasa.gov>
		
	
		
			
				
	
	
		
			359 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			359 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/*****************************************************************************
 | 
						|
 * Open MCT, Copyright (c) 2014-2023, 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.
 | 
						|
 *****************************************************************************/
 | 
						|
 | 
						|
const { test, expect } = require('../../pluginFixtures.js');
 | 
						|
const { createDomainObjectWithDefaults } = require('../../appActions.js');
 | 
						|
const { waitForAnimations } = require('../../baseFixtures.js');
 | 
						|
 | 
						|
test.describe('Recent Objects', () => {
 | 
						|
  /** @type {import('@playwright/test').Locator} */
 | 
						|
  let recentObjectsList;
 | 
						|
  /** @type {import('@playwright/test').Locator} */
 | 
						|
  let clock;
 | 
						|
  /** @type {import('@playwright/test').Locator} */
 | 
						|
  let folderA;
 | 
						|
  test.beforeEach(async ({ page }) => {
 | 
						|
    await page.goto('./', { waitUntil: 'domcontentloaded' });
 | 
						|
 | 
						|
    // Set Recent Objects List locator for subsequent tests
 | 
						|
    recentObjectsList = page.getByRole('list', {
 | 
						|
      name: 'Recent Objects'
 | 
						|
    });
 | 
						|
 | 
						|
    // Create a folder and nest a Clock within it
 | 
						|
    folderA = await createDomainObjectWithDefaults(page, {
 | 
						|
      type: 'Folder'
 | 
						|
    });
 | 
						|
    clock = await createDomainObjectWithDefaults(page, {
 | 
						|
      type: 'Clock',
 | 
						|
      parent: folderA.uuid
 | 
						|
    });
 | 
						|
 | 
						|
    // Drag the Recent Objects panel up a bit
 | 
						|
    await page
 | 
						|
      .locator('.l-pane.l-pane--vertical-handle-before', {
 | 
						|
        hasText: 'Recently Viewed'
 | 
						|
      })
 | 
						|
      .locator('.l-pane__handle')
 | 
						|
      .hover();
 | 
						|
    await page.mouse.down();
 | 
						|
    await page.mouse.move(0, 100);
 | 
						|
    await page.mouse.up();
 | 
						|
  });
 | 
						|
  test.fixme(
 | 
						|
    'Navigated objects show up in recents, object renames and deletions are reflected',
 | 
						|
    async ({ page }) => {
 | 
						|
      test.info().annotations.push({
 | 
						|
        type: 'issue',
 | 
						|
        description: 'https://github.com/nasa/openmct/issues/6818'
 | 
						|
      });
 | 
						|
 | 
						|
      // Verify that both created objects appear in the list and are in the correct order
 | 
						|
      await assertInitialRecentObjectsListState();
 | 
						|
 | 
						|
      // Navigate to the folder by clicking on the main object name in the recent objects list item
 | 
						|
      await page.getByRole('listitem', { name: folderA.name }).getByText(folderA.name).click();
 | 
						|
      await page.waitForURL(`**/${folderA.uuid}?*`);
 | 
						|
      expect(recentObjectsList.getByRole('listitem').nth(0).getByText(folderA.name)).toBeTruthy();
 | 
						|
 | 
						|
      // Rename
 | 
						|
      folderA.name = `${folderA.name}-NEW!`;
 | 
						|
      await page.locator('.l-browse-bar__object-name').fill('');
 | 
						|
      await page.locator('.l-browse-bar__object-name').fill(folderA.name);
 | 
						|
      await page.keyboard.press('Enter');
 | 
						|
 | 
						|
      // Verify rename has been applied in recent objects list item and objects paths
 | 
						|
      expect(
 | 
						|
        await page
 | 
						|
          .getByRole('navigation', {
 | 
						|
            name: clock.name
 | 
						|
          })
 | 
						|
          .locator('a')
 | 
						|
          .filter({
 | 
						|
            hasText: folderA.name
 | 
						|
          })
 | 
						|
          .count()
 | 
						|
      ).toBeGreaterThan(0);
 | 
						|
      expect(recentObjectsList.getByRole('listitem', { name: folderA.name })).toBeTruthy();
 | 
						|
 | 
						|
      // Delete
 | 
						|
      await page.click('button[title="Show selected item in tree"]');
 | 
						|
      // Delete the folder via the left tree pane treeitem context menu
 | 
						|
      await page
 | 
						|
        .getByRole('treeitem', { name: new RegExp(folderA.name) })
 | 
						|
        .locator('a')
 | 
						|
        .click({
 | 
						|
          button: 'right'
 | 
						|
        });
 | 
						|
      await page.getByRole('menuitem', { name: /Remove/ }).click();
 | 
						|
      await page.getByRole('button', { name: 'OK' }).click();
 | 
						|
 | 
						|
      // Verify that the folder and clock are no longer in the recent objects list
 | 
						|
      await expect(recentObjectsList.getByRole('listitem', { name: folderA.name })).toBeHidden();
 | 
						|
      await expect(recentObjectsList.getByRole('listitem', { name: clock.name })).toBeHidden();
 | 
						|
    }
 | 
						|
  );
 | 
						|
 | 
						|
  test('Clicking on an object in the path of a recent object navigates to the object', async ({
 | 
						|
    page,
 | 
						|
    openmctConfig
 | 
						|
  }) => {
 | 
						|
    const { myItemsFolderName } = openmctConfig;
 | 
						|
    test.info().annotations.push({
 | 
						|
      type: 'issue',
 | 
						|
      description: 'https://github.com/nasa/openmct/issues/6151'
 | 
						|
    });
 | 
						|
    await page.goto('./#/browse/mine');
 | 
						|
 | 
						|
    // Navigate to the folder by clicking on its entry in the Clock's breadcrumb
 | 
						|
    const waitForFolderNavigation = page.waitForURL(`**/${folderA.uuid}?*`);
 | 
						|
    await page
 | 
						|
      .getByRole('navigation', {
 | 
						|
        name: clock.name
 | 
						|
      })
 | 
						|
      .locator('a')
 | 
						|
      .filter({
 | 
						|
        hasText: folderA.name
 | 
						|
      })
 | 
						|
      .click();
 | 
						|
 | 
						|
    // Verify that the hash URL updates correctly
 | 
						|
    await waitForFolderNavigation;
 | 
						|
    expect(page.url()).toMatch(new RegExp(`.*${folderA.uuid}?.*`));
 | 
						|
 | 
						|
    // Navigate to My Items by clicking on its entry in the Clock's breadcrumb
 | 
						|
    const waitForMyItemsNavigation = page.waitForURL(`**/mine?*`);
 | 
						|
    await page
 | 
						|
      .getByRole('navigation', {
 | 
						|
        name: clock.name
 | 
						|
      })
 | 
						|
      .locator('a')
 | 
						|
      .filter({
 | 
						|
        hasText: myItemsFolderName
 | 
						|
      })
 | 
						|
      .click();
 | 
						|
 | 
						|
    // Verify that the hash URL updates correctly
 | 
						|
    await waitForMyItemsNavigation;
 | 
						|
    expect(page.url()).toMatch(new RegExp(`.*mine?.*`));
 | 
						|
  });
 | 
						|
  test("Clicking on the 'target button' scrolls the object into view in the tree and highlights it", async ({
 | 
						|
    page
 | 
						|
  }) => {
 | 
						|
    const clockTreeItem = page
 | 
						|
      .getByRole('tree', { name: 'Main Tree' })
 | 
						|
      .getByRole('treeitem', { name: clock.name });
 | 
						|
    const folderTreeItem = page.getByRole('tree', { name: 'Main Tree' }).getByRole('treeitem', {
 | 
						|
      name: folderA.name,
 | 
						|
      expanded: true
 | 
						|
    });
 | 
						|
 | 
						|
    // Click the "Target" button for the Clock which is nested in a folder
 | 
						|
    await page.getByRole('button', { name: `Open and scroll to ${clock.name}` }).click();
 | 
						|
 | 
						|
    // Assert that the Clock parent folder has expanded and the Clock is visible)
 | 
						|
    await expect(folderTreeItem.locator('.c-disclosure-triangle')).toHaveClass(/--expanded/);
 | 
						|
    await expect(clockTreeItem).toBeVisible();
 | 
						|
 | 
						|
    // Assert that the Clock treeitem is highlighted
 | 
						|
    await expect(clockTreeItem.locator('.c-tree__item')).toHaveClass(/is-targeted-item/);
 | 
						|
 | 
						|
    // Wait for highlight animation to end
 | 
						|
    await waitForAnimations(clockTreeItem.locator('.c-tree__item'));
 | 
						|
 | 
						|
    // Assert that the Clock treeitem is no longer highlighted
 | 
						|
    await expect(clockTreeItem.locator('.c-tree__item')).not.toHaveClass(/is-targeted-item/);
 | 
						|
  });
 | 
						|
  test('Persists on refresh', async ({ page }) => {
 | 
						|
    await assertInitialRecentObjectsListState();
 | 
						|
    await page.reload();
 | 
						|
    await assertInitialRecentObjectsListState();
 | 
						|
  });
 | 
						|
  test('Displays objects and aliases uniquely', async ({ page }) => {
 | 
						|
    const mainTree = page.getByRole('tree', { name: 'Main Tree' });
 | 
						|
 | 
						|
    // Navigate to the clock and reveal it in the tree
 | 
						|
    await page.goto(clock.url);
 | 
						|
    await page.getByTitle('Show selected item in tree').click();
 | 
						|
 | 
						|
    // Right click the clock and create an alias using the "link" context menu action
 | 
						|
    const clockTreeItem = page
 | 
						|
      .getByRole('tree', {
 | 
						|
        name: 'Main Tree'
 | 
						|
      })
 | 
						|
      .getByRole('treeitem', {
 | 
						|
        name: clock.name
 | 
						|
      });
 | 
						|
    await clockTreeItem.click({
 | 
						|
      button: 'right'
 | 
						|
    });
 | 
						|
    await page
 | 
						|
      .getByRole('menuitem', {
 | 
						|
        name: /Create Link/
 | 
						|
      })
 | 
						|
      .click();
 | 
						|
    await page
 | 
						|
      .getByRole('tree', { name: 'Create Modal Tree' })
 | 
						|
      .getByRole('treeitem')
 | 
						|
      .first()
 | 
						|
      .click();
 | 
						|
    await page.getByRole('button', { name: 'Save' }).click();
 | 
						|
 | 
						|
    // Click the newly created object alias in the tree
 | 
						|
    await mainTree
 | 
						|
      .getByRole('treeitem', {
 | 
						|
        name: new RegExp(clock.name)
 | 
						|
      })
 | 
						|
      .filter({
 | 
						|
        has: page.locator('.is-alias')
 | 
						|
      })
 | 
						|
      .click();
 | 
						|
 | 
						|
    // Assert that two recent objects are displayed and one of them is an alias
 | 
						|
    expect(await recentObjectsList.getByRole('listitem', { name: clock.name }).count()).toBe(2);
 | 
						|
    expect(await recentObjectsList.locator('.is-alias').count()).toBe(1);
 | 
						|
 | 
						|
    // Assert that the alias and the original's breadcrumbs are different
 | 
						|
    const clockBreadcrumbs = recentObjectsList
 | 
						|
      .getByRole('listitem', { name: clock.name })
 | 
						|
      .getByRole('navigation');
 | 
						|
    expect(await clockBreadcrumbs.count()).toBe(2);
 | 
						|
    expect(await clockBreadcrumbs.nth(0).innerText()).not.toEqual(
 | 
						|
      await clockBreadcrumbs.nth(1).innerText()
 | 
						|
    );
 | 
						|
  });
 | 
						|
  test('Enforces a limit of 20 recent objects and clears the recent objects', async ({ page }) => {
 | 
						|
    // Creating 21 objects takes a while, so increase the timeout
 | 
						|
    test.slow();
 | 
						|
 | 
						|
    // Assert that the list initially contains 3 objects (clock, folder, my items)
 | 
						|
    expect(await recentObjectsList.locator('.c-recentobjects-listitem').count()).toBe(3);
 | 
						|
 | 
						|
    let lastFolder;
 | 
						|
    let lastClock;
 | 
						|
    // Create 19 more objects (3 in beforeEach() + 18 new = 21 total)
 | 
						|
    for (let i = 0; i < 9; i++) {
 | 
						|
      lastFolder = await createDomainObjectWithDefaults(page, {
 | 
						|
        type: 'Folder',
 | 
						|
        parent: lastFolder?.uuid
 | 
						|
      });
 | 
						|
      lastClock = await createDomainObjectWithDefaults(page, {
 | 
						|
        type: 'Clock',
 | 
						|
        parent: lastFolder?.uuid
 | 
						|
      });
 | 
						|
    }
 | 
						|
 | 
						|
    // Assert that the list contains 20 objects
 | 
						|
    expect(await recentObjectsList.locator('.c-recentobjects-listitem').count()).toBe(20);
 | 
						|
 | 
						|
    // Collapse the tree
 | 
						|
    await page.getByTitle('Collapse all tree items').click();
 | 
						|
    const lastFolderTreeItem = page.getByRole('tree', { name: 'Main Tree' }).getByRole('treeitem', {
 | 
						|
      name: lastFolder.name,
 | 
						|
      expanded: true
 | 
						|
    });
 | 
						|
    const lastClockTreeItem = page.getByRole('tree', { name: 'Main Tree' }).getByRole('treeitem', {
 | 
						|
      name: lastClock.name
 | 
						|
    });
 | 
						|
 | 
						|
    // Test "Open and Scroll To" in a deeply nested tree, while we're here
 | 
						|
    await page.getByRole('button', { name: `Open and scroll to ${lastClock.name}` }).click();
 | 
						|
 | 
						|
    // Assert that the Clock parent folder has expanded and the Clock is visible)
 | 
						|
    await expect(lastFolderTreeItem.locator('.c-disclosure-triangle')).toHaveClass(/--expanded/);
 | 
						|
    await expect(lastClockTreeItem).toBeVisible();
 | 
						|
 | 
						|
    // Assert that the Clock treeitem is highlighted
 | 
						|
    await expect(lastClockTreeItem.locator('.c-tree__item')).toHaveClass(/is-targeted-item/);
 | 
						|
 | 
						|
    // Wait for highlight animation to end
 | 
						|
    await waitForAnimations(lastClockTreeItem.locator('.c-tree__item'));
 | 
						|
 | 
						|
    // Assert that the Clock treeitem is no longer highlighted
 | 
						|
    await expect(lastClockTreeItem.locator('.c-tree__item')).not.toHaveClass(/is-targeted-item/);
 | 
						|
 | 
						|
    // Click the aria-label="Clear Recently Viewed" button
 | 
						|
    await page.getByRole('button', { name: 'Clear Recently Viewed' }).click();
 | 
						|
 | 
						|
    // Click on the "OK" button in the confirmation dialog
 | 
						|
    await page.getByRole('button', { name: 'OK' }).click();
 | 
						|
 | 
						|
    // Assert that the list is empty
 | 
						|
    expect(await recentObjectsList.locator('.c-recentobjects-listitem').count()).toBe(0);
 | 
						|
  });
 | 
						|
  test('Ensure clear recent objects button is active or inactive', async ({ page }) => {
 | 
						|
    // Assert that the list initially contains 3 objects (clock, folder, my items)
 | 
						|
    expect(await recentObjectsList.locator('.c-recentobjects-listitem').count()).toBe(3);
 | 
						|
 | 
						|
    // Assert that the button is enabled
 | 
						|
    expect(await page.getByRole('button', { name: 'Clear Recently Viewed' }).isEnabled()).toBe(
 | 
						|
      true
 | 
						|
    );
 | 
						|
 | 
						|
    // Click the aria-label="Clear Recently Viewed" button
 | 
						|
    await page.getByRole('button', { name: 'Clear Recently Viewed' }).click();
 | 
						|
 | 
						|
    // Click on the "OK" button in the confirmation dialog
 | 
						|
    await page.getByRole('button', { name: 'OK' }).click();
 | 
						|
 | 
						|
    // Assert that the list is empty
 | 
						|
    expect(await recentObjectsList.locator('.c-recentobjects-listitem').count()).toBe(0);
 | 
						|
 | 
						|
    // Assert that the button is disabled
 | 
						|
    expect(await page.getByRole('button', { name: 'Clear Recently Viewed' }).isEnabled()).toBe(
 | 
						|
      false
 | 
						|
    );
 | 
						|
 | 
						|
    // Navigate to folder object
 | 
						|
    await page.goto(folderA.url);
 | 
						|
 | 
						|
    // Assert that the list contains 1 object
 | 
						|
    expect(await recentObjectsList.locator('.c-recentobjects-listitem').count()).toBe(1);
 | 
						|
 | 
						|
    // Assert that the button is enabled
 | 
						|
    expect(await page.getByRole('button', { name: 'Clear Recently Viewed' }).isEnabled()).toBe(
 | 
						|
      true
 | 
						|
    );
 | 
						|
  });
 | 
						|
 | 
						|
  function assertInitialRecentObjectsListState() {
 | 
						|
    return Promise.all([
 | 
						|
      expect(recentObjectsList.getByRole('listitem', { name: clock.name })).toBeVisible(),
 | 
						|
      expect(recentObjectsList.getByRole('listitem', { name: folderA.name })).toBeVisible(),
 | 
						|
      expect(
 | 
						|
        recentObjectsList
 | 
						|
          .getByRole('listitem', { name: clock.name })
 | 
						|
          .locator('a')
 | 
						|
          .getByText(folderA.name)
 | 
						|
      ).toBeVisible(),
 | 
						|
      expect(recentObjectsList.getByRole('listitem').nth(0).getByText(clock.name)).toBeVisible(),
 | 
						|
      expect(
 | 
						|
        recentObjectsList
 | 
						|
          .getByRole('listitem', { name: clock.name })
 | 
						|
          .locator('a')
 | 
						|
          .getByText(folderA.name)
 | 
						|
      ).toBeVisible(),
 | 
						|
      expect(recentObjectsList.getByRole('listitem').nth(3).getByText(folderA.name)).toBeVisible()
 | 
						|
    ]);
 | 
						|
  }
 | 
						|
});
 |