/***************************************************************************** * Open MCT, Copyright (c) 2014-2024, 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 { createDomainObjectWithDefaults, createPlanFromJSON } from '../appActions.js'; import { expect } from '../pluginFixtures.js'; /** * Asserts that the number of activities in the plan view matches the number of * activities in the plan data within the specified time bounds. Performs an assertion * for each activity in the plan data per group, using the earliest activity's * start time as the start bound and the current activity's end time as the end bound. * @param {import('@playwright/test').Page} page the page * @param {Object} plan The raw plan json to assert against * @param {string} planObjectUrl The URL of the object to assert against (plan or gantt chart) */ export async function assertPlanActivities(page, plan, planObjectUrl) { const groups = Object.keys(plan); for (const group of groups) { for (let i = 0; i < plan[group].length; i++) { // Set the startBound to the start time of the first activity in the group const startBound = plan[group][0].start; // Set the endBound to the end time of the current activity let endBound = plan[group][i].end; if (endBound === startBound) { // Prevent oddities with setting start and end bound equal // via URL params endBound += 1; } // Switch to fixed time mode with all plan events within the bounds await page.goto( `${planObjectUrl}?tc.mode=fixed&tc.startBound=${startBound}&tc.endBound=${endBound}&tc.timeSystem=utc&view=plan.view` ); // Assert that the number of activities in the plan view matches the number of // activities in the plan data within the specified time bounds await expect(page.locator('.activity-bounds')).toHaveCount( Object.values(plan) .flat() .filter((event) => activitiesWithinTimeBounds(event.start, event.end, startBound, endBound) ).length ); } } } /** * Returns true if the activities time bounds overlap, false otherwise. * @param {number} start1 the start time of the first activity * @param {number} end1 the end time of the first activity * @param {number} start2 the start time of the second activity * @param {number} end2 the end time of the second activity * @returns {boolean} true if the activities overlap, false otherwise */ function activitiesWithinTimeBounds(start1, end1, start2, end2) { return ( (start1 >= start2 && start1 <= end2) || (end1 >= start2 && end1 <= end2) || (start2 >= start1 && start2 <= end1) || (end2 >= start1 && end2 <= end1) ); } /** * Asserts that the swim lanes / groups in the plan view matches the order of * groups in the plan data. * @param {import('@playwright/test').Page} page the page * @param {Object} plan The raw plan json to assert against * @param {string} objectUrl The URL of the object to assert against (plan or gantt chart) */ export async function assertPlanOrderedSwimLanes(page, plan, objectUrl) { // Switch to the plan view await page.goto(`${objectUrl}?view=plan.view`); const planGroups = await page .locator('.c-plan__contents > div > .c-swimlane__lane-label .c-object-label__name') .all(); const groups = plan.Groups; for (let i = 0; i < groups.length; i++) { // Assert that the order of groups in the plan view matches the order of // groups in the plan data const groupName = planGroups[i]; await expect(groupName).toHaveText(groups[i].name); } } /** * Navigate to the plan view, switch to fixed time mode, * and set the bounds to span all activities. * @param {import('@playwright/test').Page} page * @param {Object} planJson * @param {string} planObjectUrl */ export async function setBoundsToSpanAllActivities(page, planJson, planObjectUrl) { // Get the earliest start value const start = getEarliestStartTime(planJson); // Get the latest end value const end = getLatestEndTime(planJson); // Set the start and end bounds to the earliest start and latest end await page.goto( `${planObjectUrl}?tc.mode=fixed&tc.startBound=${start}&tc.endBound=${end}&tc.timeSystem=utc&view=plan.view` ); } /** * @param {Object} planJson * @returns {number} */ export function getEarliestStartTime(planJson) { const activities = Object.values(planJson).flat(); return Math.min(...activities.map((activity) => activity.start)); } /** * * @param {Object} planJson * @returns {number} */ export function getLatestEndTime(planJson) { const activities = Object.values(planJson).flat(); return Math.max(...activities.map((activity) => activity.end)); } /** * * @param {object} planJson * @returns {object} */ export function getFirstActivity(planJson) { const groups = Object.keys(planJson); const firstGroupKey = groups[0]; const firstGroupItems = planJson[firstGroupKey]; return firstGroupItems[0]; } /** * Uses the Open MCT API to set the status of a plan to 'draft'. * @param {import('@playwright/test').Page} page * @param {import('../../appActions').CreatedObjectInfo} plan */ export async function setDraftStatusForPlan(page, plan) { await page.evaluate(async (planObject) => { await window.openmct.status.set(planObject.uuid, 'draft'); }, plan); } export async function addPlanGetInterceptor(page) { await page.waitForLoadState('load'); await page.evaluate(async () => { await window.openmct.objects.addGetInterceptor({ appliesTo: (identifier, domainObject) => { return domainObject && domainObject.type === 'plan'; }, invoke: (identifier, object) => { if (object) { object.sourceMap = { orderedGroups: 'Groups' }; } return object; } }); }); } /** * Create a Plan from JSON and add it to a Timelist and Navigate to the Plan view * @param {import('@playwright/test').Page} page */ export async function createTimelistWithPlanAndSetActivityInProgress(page, planJson) { await page.goto('./', { waitUntil: 'domcontentloaded' }); const timelist = await createDomainObjectWithDefaults(page, { name: 'Time List', type: 'Time List' }); await createPlanFromJSON(page, { name: 'Test Plan', json: planJson, parent: timelist.uuid }); // Ensure that all activities are shown in the expanded view const groups = Object.keys(planJson); const firstGroupKey = groups[0]; const firstGroupItems = planJson[firstGroupKey]; const firstActivityForPlan = firstGroupItems[0]; const lastActivity = firstGroupItems[firstGroupItems.length - 1]; const startBound = firstActivityForPlan.start; const endBound = lastActivity.end; // Switch to fixed time mode with all plan events within the bounds await page.goto( `${timelist.url}?tc.mode=fixed&tc.startBound=${startBound}&tc.endBound=${endBound}&tc.timeSystem=utc&view=timelist.view` ); // Change the object to edit mode await page.getByRole('button', { name: 'Edit Object' }).click(); // Find the display properties section in the inspector await page.getByRole('tab', { name: 'View Properties' }).click(); // Switch to expanded view and save the setting await page.getByLabel('Display Style').selectOption({ label: 'Expanded' }); // Click on the "Save" button await page.getByRole('button', { name: 'Save' }).click(); await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); const anActivity = page.getByRole('row').nth(0); // Set the activity to in progress await anActivity.click(); await page.getByRole('tab', { name: 'Activity' }).click(); await page.getByLabel('Activity Status', { exact: true }).selectOption({ label: 'In progress' }); }