Compare commits
	
		
			18 Commits
		
	
	
		
			v2.0.4
			...
			sprint-2.0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | aedf7c6aee | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 0a0826f87e | ||
|   | efb5d270d9 | ||
|   | de1b877954 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | e063442d8c | ||
|   | 6a5823ab5c | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 0493e5ae3c | ||
|   | 24f13b6249 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 4db2f547d9 | ||
|   | 221fb4d6bf | ||
|   | 257742b45b | ||
|   | 44edec4f04 | ||
|   | ab4d0dd37f | ||
|   | c089a4760d | ||
|   | b77a4066f2 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 20d7e80502 | ||
|   | d63fec51a7 | ||
|   | d30c4fcb53 | 
| @@ -2,7 +2,7 @@ version: 2.1 | ||||
| executors: | ||||
|   pw-focal-development: | ||||
|     docker: | ||||
|       - image: mcr.microsoft.com/playwright:v1.19.2-focal | ||||
|       - image: mcr.microsoft.com/playwright:v1.21.1-focal | ||||
|     environment: | ||||
|       NODE_ENV: development # Needed to ensure 'dist' folder created and devDependencies installed | ||||
| parameters: | ||||
| @@ -64,7 +64,7 @@ commands: | ||||
|         - run: curl -Os https://uploader.codecov.io/latest/linux/codecov;chmod +x codecov;./codecov  | ||||
| orbs: | ||||
|   node: circleci/node@4.9.0 | ||||
|   browser-tools: circleci/browser-tools@1.2.3 | ||||
|   browser-tools: circleci/browser-tools@1.3.0 | ||||
| jobs: | ||||
|   npm-audit: | ||||
|     parameters: | ||||
|   | ||||
							
								
								
									
										6
									
								
								.github/workflows/codeql-analysis.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/workflows/codeql-analysis.yml
									
									
									
									
										vendored
									
									
								
							| @@ -32,12 +32,12 @@ jobs: | ||||
|  | ||||
|     # Initializes the CodeQL tools for scanning. | ||||
|     - name: Initialize CodeQL | ||||
|       uses: github/codeql-action/init@v1 | ||||
|       uses: github/codeql-action/init@v2 | ||||
|       with: | ||||
|         languages: javascript | ||||
|  | ||||
|     - name: Autobuild | ||||
|       uses: github/codeql-action/autobuild@v1 | ||||
|       uses: github/codeql-action/autobuild@v2 | ||||
|  | ||||
|     - name: Perform CodeQL Analysis | ||||
|       uses: github/codeql-action/analyze@v1 | ||||
|       uses: github/codeql-action/analyze@v2 | ||||
|   | ||||
							
								
								
									
										2
									
								
								.github/workflows/e2e-pr.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/e2e-pr.yml
									
									
									
									
										vendored
									
									
								
							| @@ -30,7 +30,7 @@ jobs: | ||||
|       - uses: actions/setup-node@v3 | ||||
|         with: | ||||
|           node-version: '16' | ||||
|       - run: npx playwright@1.19.2 install | ||||
|       - run: npx playwright@1.21.1 install | ||||
|       - run: npm install | ||||
|       - run: npm run test:e2e:full | ||||
|       - name: Archive test results | ||||
|   | ||||
							
								
								
									
										2
									
								
								.github/workflows/e2e-visual.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/e2e-visual.yml
									
									
									
									
										vendored
									
									
								
							| @@ -17,7 +17,7 @@ jobs: | ||||
|       - uses: actions/setup-node@v3 | ||||
|         with: | ||||
|           node-version: '16' | ||||
|       - run: npx playwright@1.19.2 install | ||||
|       - run: npx playwright@1.21.1 install | ||||
|       - run: npm install | ||||
|       - name: Run the e2e visual tests | ||||
|         run: npm run test:e2e:visual | ||||
|   | ||||
| @@ -6,9 +6,9 @@ const { devices } = require('@playwright/test'); | ||||
|  | ||||
| /** @type {import('@playwright/test').PlaywrightTestConfig} */ | ||||
| const config = { | ||||
|     retries: 2, | ||||
|     retries: 1, | ||||
|     testDir: 'tests', | ||||
|     timeout: 90 * 1000, | ||||
|     timeout: 60 * 1000, | ||||
|     webServer: { | ||||
|         command: 'npm run start', | ||||
|         port: 8080, | ||||
| @@ -28,12 +28,12 @@ const config = { | ||||
|         { | ||||
|             name: 'chrome', | ||||
|             use: { | ||||
|                 browserName: 'chromium', | ||||
|                 ...devices['Desktop Chrome'] | ||||
|                 browserName: 'chromium' | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             name: 'MMOC', | ||||
|             grepInvert: /@snapshot/, | ||||
|             use: { | ||||
|                 browserName: 'chromium', | ||||
|                 viewport: { | ||||
|   | ||||
| @@ -29,12 +29,12 @@ const config = { | ||||
|         { | ||||
|             name: 'chrome', | ||||
|             use: { | ||||
|                 browserName: 'chromium', | ||||
|                 ...devices['Desktop Chrome'] | ||||
|                 browserName: 'chromium' | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             name: 'MMOC', | ||||
|             grepInvert: /@snapshot/, | ||||
|             use: { | ||||
|                 browserName: 'chromium', | ||||
|                 viewport: { | ||||
|   | ||||
| @@ -27,16 +27,115 @@ This test suite is dedicated to tests which verify the basic operations surround | ||||
| const { test, expect } = require('@playwright/test'); | ||||
|  | ||||
| test.describe('Move item tests', () => { | ||||
|     test.fixme('Create a basic object and verify that it can be moved to another Folder', async ({ page }) => { | ||||
|         //Create and save Folder | ||||
|         //Create and save Domain Object | ||||
|         //Verify that the newly created domain object can be moved to Folder from Step 1. | ||||
|         //Verify that newly moved object appears in the correct point in Tree | ||||
|         //Verify that newly moved object appears correctly in Inspector panel | ||||
|     test('Create a basic object and verify that it can be moved to another folder', async ({ page }) => { | ||||
|         // Go to Open MCT | ||||
|         await page.goto('/'); | ||||
|  | ||||
|         // Create a new folder in the root my items folder | ||||
|         let folder1 = "Folder1"; | ||||
|         await page.locator('button:has-text("Create")').click(); | ||||
|         await page.locator('li.icon-folder').click(); | ||||
|  | ||||
|         await page.locator('text=Properties Title Notes >> input[type="text"]').click(); | ||||
|         await page.locator('text=Properties Title Notes >> input[type="text"]').fill(folder1); | ||||
|         await Promise.all([ | ||||
|             page.waitForNavigation(), | ||||
|             page.locator('text=OK').click(), | ||||
|             page.waitForSelector('.c-message-banner__message') | ||||
|         ]); | ||||
|         //Wait until Save Banner is gone | ||||
|         await page.locator('.c-message-banner__close-button').click(); | ||||
|         await page.waitForSelector('.c-message-banner__message', { state: 'detached'}); | ||||
|  | ||||
|         // Create another folder with a new name at default location, which is currently inside Folder 1 | ||||
|         let folder2 = "Folder2"; | ||||
|         await page.locator('button:has-text("Create")').click(); | ||||
|         await page.locator('li.icon-folder').click(); | ||||
|         await page.locator('text=Properties Title Notes >> input[type="text"]').click(); | ||||
|         await page.locator('text=Properties Title Notes >> input[type="text"]').fill(folder2); | ||||
|         await Promise.all([ | ||||
|             page.waitForNavigation(), | ||||
|             page.locator('text=OK').click(), | ||||
|             page.waitForSelector('.c-message-banner__message') | ||||
|         ]); | ||||
|         //Wait until Save Banner is gone | ||||
|         await page.locator('.c-message-banner__close-button').click(); | ||||
|         await page.waitForSelector('.c-message-banner__message', { state: 'detached'}); | ||||
|  | ||||
|         // Move Folder 2 from Folder 1 to My Items | ||||
|         await page.locator('text=Open MCT My Items >> span').nth(3).click(); | ||||
|         await page.locator('.c-tree__scrollable div div:nth-child(2) .c-tree__item .c-tree__item__view-control').click(); | ||||
|  | ||||
|         await page.locator(`a:has-text("${folder2}")`).click({ | ||||
|             button: 'right' | ||||
|         }); | ||||
|         await page.locator('li.icon-move').click(); | ||||
|         await page.locator('form[name="mctForm"] >> text=My Items').click(); | ||||
|         await Promise.all([ | ||||
|             page.waitForNavigation(), | ||||
|             page.locator('text=OK').click() | ||||
|         ]); | ||||
|  | ||||
|         // Expect that Folder 2 is in My Items, the root folder | ||||
|         expect(page.locator(`text=My Items >> nth=0:has(text=${folder2})`)).toBeTruthy(); | ||||
|     }); | ||||
|     test.fixme('Create a basic object and verify that it cannot be moved to object without Composition Provider', async ({ page }) => { | ||||
|         //Create and save Telemetry Object | ||||
|         //Create and save Domain Object | ||||
|         //Verify that the newly created domain object cannot be moved to Telemetry Object from step 1. | ||||
|     test('Create a basic object and verify that it cannot be moved to telemetry object without Composition Provider', async ({ page }) => { | ||||
|         // Go to Open MCT | ||||
|         await page.goto('/'); | ||||
|  | ||||
|         // Create Telemetry Table | ||||
|         let telemetryTable = 'Test Telemetry Table'; | ||||
|         await page.locator('button:has-text("Create")').click(); | ||||
|         await page.locator('li:has-text("Telemetry Table")').click(); | ||||
|         await page.locator('text=Properties Title Notes >> input[type="text"]').click(); | ||||
|         await page.locator('text=Properties Title Notes >> input[type="text"]').fill(telemetryTable); | ||||
|         await Promise.all([ | ||||
|             page.waitForNavigation(), | ||||
|             page.locator('text=OK').click() | ||||
|         ]); | ||||
|  | ||||
|         // Finish editing and save Telemetry Table | ||||
|         await page.locator('.c-button--menu.c-button--major.icon-save').click(); | ||||
|         await page.locator('text=Save and Finish Editing').click(); | ||||
|  | ||||
|         // Create New Folder Basic Domain Object | ||||
|         let folder = 'Test Folder'; | ||||
|         await page.locator('button:has-text("Create")').click(); | ||||
|         await page.locator('li:has-text("Folder")').click(); | ||||
|         await page.locator('text=Properties Title Notes >> input[type="text"]').click(); | ||||
|         await page.locator('text=Properties Title Notes >> input[type="text"]').fill(folder); | ||||
|  | ||||
|         // See if it's possible to put the folder in the Telemetry object during creation (Soft Assert) | ||||
|         await page.locator(`form[name="mctForm"] >> text=${telemetryTable}`).click(); | ||||
|         let okButton = await page.locator('button.c-button.c-button--major:has-text("OK")'); | ||||
|         let okButtonStateDisabled = await okButton.isDisabled(); | ||||
|         expect.soft(okButtonStateDisabled).toBeTruthy(); | ||||
|  | ||||
|         // Continue test regardless of assertion and create it in My Items | ||||
|         await page.locator('form[name="mctForm"] >> text=My Items').click(); | ||||
|         await Promise.all([ | ||||
|             page.waitForNavigation(), | ||||
|             page.locator('text=OK').click() | ||||
|         ]); | ||||
|  | ||||
|         // Open My Items | ||||
|         await page.locator('text=Open MCT My Items >> span').nth(3).click(); | ||||
|  | ||||
|         // Select Folder Object and select Move from context menu | ||||
|         await Promise.all([ | ||||
|             page.waitForNavigation(), | ||||
|             page.locator(`a:has-text("${folder}")`).click() | ||||
|         ]); | ||||
|         await page.locator('.c-tree__item.is-navigated-object .c-tree__item__label .c-tree__item__type-icon').click({ | ||||
|             button: 'right' | ||||
|         }); | ||||
|         await page.locator('li.icon-move').click(); | ||||
|  | ||||
|         // See if it's possible to put the folder in the Telemetry object after creation | ||||
|         await page.locator('text=Location Open MCT My Items >> span').nth(3).click(); | ||||
|         await page.locator(`form[name="mctForm"] >> text=${telemetryTable}`).click(); | ||||
|         let okButton2 = await page.locator('button.c-button.c-button--major:has-text("OK")'); | ||||
|         let okButtonStateDisabled2 = await okButton2.isDisabled(); | ||||
|         expect(okButtonStateDisabled2).toBeTruthy(); | ||||
|     }); | ||||
| }); | ||||
|   | ||||
| @@ -24,13 +24,14 @@ | ||||
| This test suite is dedicated to tests which verify the basic operations surrounding imagery, | ||||
| but only assume that example imagery is present. | ||||
| */ | ||||
| /* globals process */ | ||||
|  | ||||
| const { test, expect } = require('@playwright/test'); | ||||
|  | ||||
| test.describe('Example Imagery', () => { | ||||
|  | ||||
|     test.beforeEach(async ({ page }) => { | ||||
|         page.on('console', msg => console.log(msg.text())) | ||||
|         page.on('console', msg => console.log(msg.text())); | ||||
|         //Go to baseURL | ||||
|         await page.goto('/', { waitUntil: 'networkidle' }); | ||||
|  | ||||
| @@ -42,10 +43,13 @@ test.describe('Example Imagery', () => { | ||||
|  | ||||
|         // Click text=OK | ||||
|         await Promise.all([ | ||||
|             page.waitForNavigation(/*{ url: 'http://localhost:8080/#/browse/mine/dab945d4-5a84-480e-8180-222b4aa730fa?tc.mode=fixed&tc.startBound=1639696164435&tc.endBound=1639697964435&tc.timeSystem=utc&view=conditionSet.view' }*/), | ||||
|             page.click('text=OK') | ||||
|             page.waitForNavigation({waitUntil: 'networkidle'}), | ||||
|             page.click('text=OK'), | ||||
|             //Wait for Save Banner to appear | ||||
|             page.waitForSelector('.c-message-banner__message') | ||||
|         ]); | ||||
|  | ||||
|         //Wait until Save Banner is gone | ||||
|         await page.waitForSelector('.c-message-banner__message', { state: 'detached'}); | ||||
|         await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Example Imagery'); | ||||
|     }); | ||||
|  | ||||
| @@ -77,9 +81,11 @@ test.describe('Example Imagery', () => { | ||||
|  | ||||
|     test('Can use alt+drag to move around image once zoomed in', async ({ page }) => { | ||||
|         const deltaYStep = 100; //equivalent to 1x zoom | ||||
|         const panHotkey = process.platform === 'linux' ? ['Control', 'Alt'] : ['Alt']; | ||||
|  | ||||
|         const bgImageLocator = await page.locator(backgroundImageSelector); | ||||
|         await bgImageLocator.hover(); | ||||
|  | ||||
|         // zoom in | ||||
|         await page.mouse.wheel(0, deltaYStep * 2); | ||||
|         await bgImageLocator.hover(); | ||||
| @@ -91,40 +97,49 @@ test.describe('Example Imagery', () => { | ||||
|         // center the mouse pointer | ||||
|         await page.mouse.move(imageCenterX, imageCenterY); | ||||
|  | ||||
|         //Get Diagnostic info about process environment | ||||
|         console.log('process.platform is ' + process.platform); | ||||
|         const getUA = await page.evaluate(() => navigator.userAgent); | ||||
|         console.log('navigator.userAgent ' + getUA); | ||||
|         // Pan Imagery Hints | ||||
|         const expectedAltText = process.platform === 'linux' ? 'Ctrl+Alt drag to pan' : 'Alt drag to pan'; | ||||
|         const imageryHintsText = await page.locator('.c-imagery__hints').innerText(); | ||||
|         expect(expectedAltText).toEqual(imageryHintsText); | ||||
|  | ||||
|         // pan right | ||||
|         await page.keyboard.down('Alt'); | ||||
|         await Promise.all(panHotkey.map(x => page.keyboard.down(x))); | ||||
|         await page.mouse.down(); | ||||
|         await page.mouse.move(imageCenterX - 200, imageCenterY, 10); | ||||
|         await page.mouse.up(); | ||||
|         await page.keyboard.up('Alt'); | ||||
|         await Promise.all(panHotkey.map(x => page.keyboard.up(x))); | ||||
|         const afterRightPanBoundingBox = await bgImageLocator.boundingBox(); | ||||
|         expect(zoomedBoundingBox.x).toBeGreaterThan(afterRightPanBoundingBox.x); | ||||
|  | ||||
|         // pan left | ||||
|         await page.keyboard.down('Alt'); | ||||
|         await Promise.all(panHotkey.map(x => page.keyboard.down(x))); | ||||
|         await page.mouse.down(); | ||||
|         await page.mouse.move(imageCenterX, imageCenterY, 10); | ||||
|         await page.mouse.up(); | ||||
|         await page.keyboard.up('Alt'); | ||||
|         await Promise.all(panHotkey.map(x => page.keyboard.up(x))); | ||||
|         const afterLeftPanBoundingBox = await bgImageLocator.boundingBox(); | ||||
|         expect(afterRightPanBoundingBox.x).toBeLessThan(afterLeftPanBoundingBox.x); | ||||
|  | ||||
|         // pan up | ||||
|         await page.mouse.move(imageCenterX, imageCenterY); | ||||
|         await page.keyboard.down('Alt'); | ||||
|         await Promise.all(panHotkey.map(x => page.keyboard.down(x))); | ||||
|         await page.mouse.down(); | ||||
|         await page.mouse.move(imageCenterX, imageCenterY + 200, 10); | ||||
|         await page.mouse.up(); | ||||
|         await page.keyboard.up('Alt'); | ||||
|         await Promise.all(panHotkey.map(x => page.keyboard.up(x))); | ||||
|         const afterUpPanBoundingBox = await bgImageLocator.boundingBox(); | ||||
|         expect(afterUpPanBoundingBox.y).toBeGreaterThan(afterLeftPanBoundingBox.y); | ||||
|  | ||||
|         // pan down | ||||
|         await page.keyboard.down('Alt'); | ||||
|         await Promise.all(panHotkey.map(x => page.keyboard.down(x))); | ||||
|         await page.mouse.down(); | ||||
|         await page.mouse.move(imageCenterX, imageCenterY - 200, 10); | ||||
|         await page.mouse.up(); | ||||
|         await page.keyboard.up('Alt'); | ||||
|         await Promise.all(panHotkey.map(x => page.keyboard.up(x))); | ||||
|         const afterDownPanBoundingBox = await bgImageLocator.boundingBox(); | ||||
|         expect(afterDownPanBoundingBox.y).toBeLessThan(afterUpPanBoundingBox.y); | ||||
|  | ||||
| @@ -156,20 +171,26 @@ test.describe('Example Imagery', () => { | ||||
|  | ||||
|     test('Can use the reset button to reset the image', async ({ page }) => { | ||||
|         const bgImageLocator = await page.locator(backgroundImageSelector); | ||||
|         // wait for zoom animation to finish | ||||
|         await bgImageLocator.hover(); | ||||
|  | ||||
|         const zoomInBtn = await page.locator('.t-btn-zoom-in'); | ||||
|         const zoomResetBtn = await page.locator('.t-btn-zoom-reset'); | ||||
|         const initialBoundingBox = await bgImageLocator.boundingBox(); | ||||
|  | ||||
|         await zoomInBtn.click(); | ||||
|         // wait for zoom animation to finish | ||||
|         await bgImageLocator.hover(); | ||||
|         await zoomInBtn.click(); | ||||
|         // wait for zoom animation to finish | ||||
|         await bgImageLocator.hover(); | ||||
|  | ||||
|         const zoomedInBoundingBox = await bgImageLocator.boundingBox(); | ||||
|         expect.soft(zoomedInBoundingBox.height).toBeGreaterThan(initialBoundingBox.height); | ||||
|         expect.soft(zoomedInBoundingBox.width).toBeGreaterThan(initialBoundingBox.width); | ||||
|  | ||||
|         await zoomResetBtn.click(); | ||||
|         // wait for zoom animation to finish | ||||
|         await bgImageLocator.hover(); | ||||
|  | ||||
|         const resetBoundingBox = await bgImageLocator.boundingBox(); | ||||
| @@ -180,38 +201,38 @@ test.describe('Example Imagery', () => { | ||||
|         expect(resetBoundingBox.width).toEqual(initialBoundingBox.width); | ||||
|     }); | ||||
|  | ||||
|     //test('Can use Mouse Wheel to zoom in and out of previous image'); | ||||
|     //test('Can zoom into the latest image and the real-time/fixed-time imagery will pause'); | ||||
|     //test.skip('Can zoom into a previous image from thumbstrip in real-time or fixed-time'); | ||||
|     //test.skip('Clicking on the left arrow should pause the imagery and go to previous image'); | ||||
|     //test.skip('If the imagery view is in pause mode, it should not be updated when new images come in'); | ||||
|     //test.skip('If the imagery view is not in pause mode, it should be updated when new images come in'); | ||||
|     //test.fixme('Can use Mouse Wheel to zoom in and out of previous image'); | ||||
|     //test.fixme('Can zoom into the latest image and the real-time/fixed-time imagery will pause'); | ||||
|     //test.fixme('Can zoom into a previous image from thumbstrip in real-time or fixed-time'); | ||||
|     //test.fixme('Clicking on the left arrow should pause the imagery and go to previous image'); | ||||
|     //test.fixme('If the imagery view is in pause mode, it should not be updated when new images come in'); | ||||
|     //test.fixme('If the imagery view is not in pause mode, it should be updated when new images come in'); | ||||
| }); | ||||
|  | ||||
| test.describe('Example Imagery in Display layout', () => { | ||||
|     test.skip('Can use Mouse Wheel to zoom in and out of previous image'); | ||||
|     test.skip('Can use alt+drag to move around image once zoomed in'); | ||||
|     test.skip('Can zoom into the latest image and the real-time/fixed-time imagery will pause'); | ||||
|     test.skip('Clicking on the left arrow should pause the imagery and go to previous image'); | ||||
|     test.skip('If the imagery view is in pause mode, it should not be updated when new images come in'); | ||||
|     test.skip('If the imagery view is not in pause mode, it should be updated when new images come in'); | ||||
|     test.fixme('Can use Mouse Wheel to zoom in and out of previous image'); | ||||
|     test.fixme('Can use alt+drag to move around image once zoomed in'); | ||||
|     test.fixme('Can zoom into the latest image and the real-time/fixed-time imagery will pause'); | ||||
|     test.fixme('Clicking on the left arrow should pause the imagery and go to previous image'); | ||||
|     test.fixme('If the imagery view is in pause mode, it should not be updated when new images come in'); | ||||
|     test.fixme('If the imagery view is not in pause mode, it should be updated when new images come in'); | ||||
| }); | ||||
|  | ||||
| test.describe('Example Imagery in Flexible layout', () => { | ||||
|     test.skip('Can use Mouse Wheel to zoom in and out of previous image'); | ||||
|     test.skip('Can use alt+drag to move around image once zoomed in'); | ||||
|     test.skip('Can zoom into the latest image and the real-time/fixed-time imagery will pause'); | ||||
|     test.skip('Clicking on the left arrow should pause the imagery and go to previous image'); | ||||
|     test.skip('If the imagery view is in pause mode, it should not be updated when new images come in'); | ||||
|     test.skip('If the imagery view is not in pause mode, it should be updated when new images come in'); | ||||
|     test.fixme('Can use Mouse Wheel to zoom in and out of previous image'); | ||||
|     test.fixme('Can use alt+drag to move around image once zoomed in'); | ||||
|     test.fixme('Can zoom into the latest image and the real-time/fixed-time imagery will pause'); | ||||
|     test.fixme('Clicking on the left arrow should pause the imagery and go to previous image'); | ||||
|     test.fixme('If the imagery view is in pause mode, it should not be updated when new images come in'); | ||||
|     test.fixme('If the imagery view is not in pause mode, it should be updated when new images come in'); | ||||
| }); | ||||
|  | ||||
| test.describe('Example Imagery in Tabs view', () => { | ||||
|     test.skip('Can use Mouse Wheel to zoom in and out of previous image'); | ||||
|     test.skip('Can use alt+drag to move around image once zoomed in'); | ||||
|     test.skip('Can zoom into the latest image and the real-time/fixed-time imagery will pause'); | ||||
|     test.skip('Can zoom into a previous image from thumbstrip in real-time or fixed-time'); | ||||
|     test.skip('Clicking on the left arrow should pause the imagery and go to previous image'); | ||||
|     test.skip('If the imagery view is in pause mode, it should not be updated when new images come in'); | ||||
|     test.skip('If the imagery view is not in pause mode, it should be updated when new images come in'); | ||||
|     test.fixme('Can use Mouse Wheel to zoom in and out of previous image'); | ||||
|     test.fixme('Can use alt+drag to move around image once zoomed in'); | ||||
|     test.fixme('Can zoom into the latest image and the real-time/fixed-time imagery will pause'); | ||||
|     test.fixme('Can zoom into a previous image from thumbstrip in real-time or fixed-time'); | ||||
|     test.fixme('Clicking on the left arrow should pause the imagery and go to previous image'); | ||||
|     test.fixme('If the imagery view is in pause mode, it should not be updated when new images come in'); | ||||
|     test.fixme('If the imagery view is not in pause mode, it should be updated when new images come in'); | ||||
| }); | ||||
|   | ||||
| @@ -21,7 +21,7 @@ | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| /* | ||||
| Test for plot autoscale. | ||||
| Testsuite for plot autoscale. | ||||
| */ | ||||
|  | ||||
| const { test: _test, expect } = require('@playwright/test'); | ||||
| @@ -47,7 +47,7 @@ test.use({ | ||||
| }); | ||||
|  | ||||
| test.describe('ExportAsJSON', () => { | ||||
|     test('autoscale off causes no error from undefined user range', async ({ page }) => { | ||||
|     test.slow('User can set autoscale with a valid range @snapshot', async ({ page }) => { | ||||
|         await page.goto('/', { waitUntil: 'networkidle' }); | ||||
|  | ||||
|         await setTimeRange(page); | ||||
| @@ -68,14 +68,6 @@ test.describe('ExportAsJSON', () => { | ||||
|                 .then(shot => expect(shot).toMatchSnapshot('autoscale-canvas-prepan.png', { maxDiffPixels: 40 })) | ||||
|         ]); | ||||
|  | ||||
|         let errorCount = 0; | ||||
|  | ||||
|         function onError() { | ||||
|             errorCount++; | ||||
|         } | ||||
|  | ||||
|         page.on('pageerror', onError); | ||||
|  | ||||
|         await page.keyboard.down('Alt'); | ||||
|  | ||||
|         await canvas.dragTo(canvas, { | ||||
| @@ -91,12 +83,6 @@ test.describe('ExportAsJSON', () => { | ||||
|  | ||||
|         await page.keyboard.up('Alt'); | ||||
|  | ||||
|         page.off('pageerror', onError); | ||||
|  | ||||
|         // There would have been an error at this point. So if there isn't, then | ||||
|         // we fixed it. | ||||
|         expect(errorCount).toBe(0); | ||||
|  | ||||
|         // Ensure the drag worked. | ||||
|         await Promise.all([ | ||||
|             testYTicks(page, ['0.00', '0.50', '1.00', '1.50', '2.00']), | ||||
| @@ -134,9 +120,14 @@ async function createSinewaveOverlayPlot(page) { | ||||
|     // add overlay plot with defaults | ||||
|     await page.locator('li:has-text("Overlay Plot")').click(); | ||||
|     await Promise.all([ | ||||
|         page.waitForNavigation(/*{ url: 'http://localhost:8080/#/browse/mine/a9268c6f-45cc-4bcd-a6a0-50ac4036e396?tc.mode=fixed&tc.startBound=1649305424163&tc.endBound=1649307224163&tc.timeSystem=utc&view=plot-overlay' }*/), | ||||
|         page.locator('text=OK').click() | ||||
|         page.waitForNavigation(), | ||||
|         page.locator('text=OK').click(), | ||||
|         //Wait for Save Banner to appear1 | ||||
|         page.waitForSelector('.c-message-banner__message') | ||||
|     ]); | ||||
|     //Wait until Save Banner is gone | ||||
|     await page.locator('.c-message-banner__close-button').click(); | ||||
|     await page.waitForSelector('.c-message-banner__message', { state: 'detached'}); | ||||
|  | ||||
|     // save (exit edit mode) | ||||
|     await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click(); | ||||
| @@ -148,14 +139,19 @@ async function createSinewaveOverlayPlot(page) { | ||||
|     // add sine wave generator with defaults | ||||
|     await page.locator('li:has-text("Sine Wave Generator")').click(); | ||||
|     await Promise.all([ | ||||
|         page.waitForNavigation(/*{ url: 'http://localhost:8080/#/browse/mine/a9268c6f-45cc-4bcd-a6a0-50ac4036e396/5cfa5c69-17bc-4a99-9545-4da8125380c5?tc.mode=fixed&tc.startBound=1649305424163&tc.endBound=1649307224163&tc.timeSystem=utc&view=plot-single' }*/), | ||||
|         page.locator('text=OK').click() | ||||
|         page.waitForNavigation(), | ||||
|         page.locator('text=OK').click(), | ||||
|         //Wait for Save Banner to appear1 | ||||
|         page.waitForSelector('.c-message-banner__message') | ||||
|     ]); | ||||
|     //Wait until Save Banner is gone | ||||
|     await page.locator('.c-message-banner__close-button').click(); | ||||
|     await page.waitForSelector('.c-message-banner__message', { state: 'detached'}); | ||||
|  | ||||
|     // focus the overlay plot | ||||
|     await page.locator('text=Open MCT My Items >> span').nth(3).click(); | ||||
|     await Promise.all([ | ||||
|         page.waitForNavigation(/*{ url: 'http://localhost:8080/#/browse/mine/a9268c6f-45cc-4bcd-a6a0-50ac4036e396?tc.mode=fixed&tc.startBound=1649305424163&tc.endBound=1649307224163&tc.timeSystem=utc&view=plot-overlay' }*/), | ||||
|         page.waitForNavigation(), | ||||
|         page.locator('text=Unnamed Overlay Plot').first().click() | ||||
|     ]); | ||||
| } | ||||
| @@ -168,11 +164,18 @@ async function turnOffAutoscale(page) { | ||||
|     await page.locator('text=Unnamed Overlay Plot Snapshot >> button').nth(3).click(); | ||||
|  | ||||
|     // uncheck autoscale | ||||
|     await page.locator('text=Y Axis Scaling Auto scale Padding >> input[type="checkbox"]').uncheck(); | ||||
|     await page.locator('text=Y Axis Label Log mode Auto scale Padding >> input[type="checkbox"] >> nth=1').uncheck(); | ||||
|  | ||||
|     // save | ||||
|     await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click(); | ||||
|     await page.locator('text=Save and Finish Editing').click(); | ||||
|     await Promise.all([ | ||||
|         page.locator('text=Save and Finish Editing').click(), | ||||
|         //Wait for Save Banner to appear | ||||
|         page.waitForSelector('.c-message-banner__message') | ||||
|     ]); | ||||
|     //Wait until Save Banner is gone | ||||
|     await page.locator('.c-message-banner__close-button').click(); | ||||
|     await page.waitForSelector('.c-message-banner__message', { state: 'detached'}); | ||||
| } | ||||
|  | ||||
| /** | ||||
| @@ -180,6 +183,7 @@ async function turnOffAutoscale(page) { | ||||
|  */ | ||||
| async function testYTicks(page, values) { | ||||
|     const yTicks = page.locator('.gl-plot-y-tick-label'); | ||||
|     await page.locator('canvas >> nth=1').hover(); | ||||
|     let promises = [yTicks.count().then(c => expect(c).toBe(values.length))]; | ||||
|  | ||||
|     for (let i = 0, l = values.length; i < l; i += 1) { | ||||
|   | ||||
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 15 KiB | 
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 18 KiB | 
| @@ -21,13 +21,14 @@ | ||||
|  *****************************************************************************/ | ||||
| 
 | ||||
| /* | ||||
| Tests to verify log plot functionality. | ||||
| Tests to verify log plot functionality. Note this test suite if very much under active development and should not | ||||
| necessarily be used for reference when writing new tests in this area. | ||||
| */ | ||||
| 
 | ||||
| const { test, expect } = require('@playwright/test'); | ||||
| 
 | ||||
| test.describe('Log plot tests', () => { | ||||
|     test.only('Can create a log plot.', async ({ page }) => { | ||||
|     test.slow('Log Plot ticks are functionally correct in regular and log mode and after refresh', async ({ page }) => { | ||||
|         await makeOverlayPlot(page); | ||||
|         await testRegularTicks(page); | ||||
|         await enableEditMode(page); | ||||
| @@ -39,17 +40,20 @@ test.describe('Log plot tests', () => { | ||||
|         await testLogTicks(page); | ||||
|         await saveOverlayPlot(page); | ||||
|         await testLogTicks(page); | ||||
|         await testLogPlotPixels(page); | ||||
|         //await testLogPlotPixels(page);
 | ||||
| 
 | ||||
|         // refresh page
 | ||||
|         await page.reload(); | ||||
|         // refresh page and wait for charts and ticks to load
 | ||||
|         await page.waitForTimeout(1 * 1000); | ||||
|         await page.reload({ waitUntil: 'networkidle'}); | ||||
|         await page.waitForSelector('.gl-plot-chart-area'); | ||||
|         await page.waitForSelector('.gl-plot-y-tick-label'); | ||||
| 
 | ||||
|         // test log ticks hold up after refresh
 | ||||
|         await testLogTicks(page); | ||||
|         await testLogPlotPixels(page); | ||||
|         //await testLogPlotPixels(page);
 | ||||
|     }); | ||||
| 
 | ||||
|     test.only('Verify that log mode option is reflected in import/export JSON', async ({ page }) => { | ||||
|     test.skip('Verify that log mode option is reflected in import/export JSON', async ({ page }) => { | ||||
|         await makeOverlayPlot(page); | ||||
|         await enableEditMode(page); | ||||
|         await enableLogMode(page); | ||||
| @@ -57,7 +61,7 @@ test.describe('Log plot tests', () => { | ||||
| 
 | ||||
|         // TODO ...export, delete the overlay, then import it...
 | ||||
| 
 | ||||
|         await testLogTicks(page); | ||||
|         //await testLogTicks(page);
 | ||||
| 
 | ||||
|         // TODO, the plot is slightly at different position that in the other test, so this fails.
 | ||||
|         // ...We can fix it by copying all steps from the first test...
 | ||||
| @@ -88,14 +92,18 @@ async function makeOverlayPlot(page) { | ||||
|     await page.locator('button.c-create-button').click(); | ||||
|     await page.locator('li:has-text("Overlay Plot")').click(); | ||||
|     await Promise.all([ | ||||
|         page.waitForNavigation(/*{ url: 'http://localhost:8080/#/browse/mine/8caf7072-535b-4af6-8394-edd86e3ea35f?tc.mode=fixed&tc.startBound=1648590633191&tc.endBound=1648592433191&tc.timeSystem=utc&view=plot-overlay' }*/), | ||||
|         page.locator('text=OK').click() | ||||
|         page.waitForNavigation({ waitUntil: 'networkidle'}), | ||||
|         page.locator('text=OK').click(), | ||||
|         //Wait for Save Banner to appear
 | ||||
|         page.waitForSelector('.c-message-banner__message') | ||||
|     ]); | ||||
|     //Wait until Save Banner is gone
 | ||||
|     await page.locator('.c-message-banner__close-button').click(); | ||||
|     await page.waitForSelector('.c-message-banner__message', { state: 'detached'}); | ||||
| 
 | ||||
|     // save the overlay plot
 | ||||
| 
 | ||||
|     await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click(); | ||||
|     await page.locator('text=Save and Finish Editing').click(); | ||||
|     await saveOverlayPlot(page); | ||||
| 
 | ||||
|     // create a sinewave generator
 | ||||
| 
 | ||||
| @@ -116,15 +124,20 @@ async function makeOverlayPlot(page) { | ||||
|     // Click OK to make generator
 | ||||
| 
 | ||||
|     await Promise.all([ | ||||
|         page.waitForNavigation(/*{ url: 'http://localhost:8080/#/browse/mine/8caf7072-535b-4af6-8394-edd86e3ea35f/6e58b26a-8a73-4df6-b3a6-918decc0bbfa?tc.mode=fixed&tc.startBound=1648590633191&tc.endBound=1648592433191&tc.timeSystem=utc&view=plot-single' }*/), | ||||
|         page.locator('text=OK').click() | ||||
|         page.waitForNavigation({ waitUntil: 'networkidle'}), | ||||
|         page.locator('text=OK').click(), | ||||
|         //Wait for Save Banner to appear
 | ||||
|         page.waitForSelector('.c-message-banner__message') | ||||
|     ]); | ||||
|     //Wait until Save Banner is gone
 | ||||
|     await page.locator('.c-message-banner__close-button').click(); | ||||
|     await page.waitForSelector('.c-message-banner__message', { state: 'detached'}); | ||||
| 
 | ||||
|     // click on overlay plot
 | ||||
| 
 | ||||
|     await page.locator('text=Open MCT My Items >> span').nth(3).click(); | ||||
|     await Promise.all([ | ||||
|         page.waitForNavigation(/*{ url: 'http://localhost:8080/#/browse/mine/8caf7072-535b-4af6-8394-edd86e3ea35f?tc.mode=fixed&tc.startBound=1648590633191&tc.endBound=1648592433191&tc.timeSystem=utc&view=plot-overlay' }*/), | ||||
|         page.waitForNavigation(), | ||||
|         page.locator('text=Unnamed Overlay Plot').first().click() | ||||
|     ]); | ||||
| } | ||||
| @@ -133,7 +146,7 @@ async function makeOverlayPlot(page) { | ||||
|  * @param {import('@playwright/test').Page} page | ||||
|  */ | ||||
| async function testRegularTicks(page) { | ||||
|     const yTicks = page.locator('.gl-plot-y-tick-label'); | ||||
|     const yTicks = await page.locator('.gl-plot-y-tick-label'); | ||||
|     expect(await yTicks.count()).toBe(7); | ||||
|     await expect(yTicks.nth(0)).toHaveText('-2'); | ||||
|     await expect(yTicks.nth(1)).toHaveText('0'); | ||||
| @@ -148,7 +161,7 @@ async function testRegularTicks(page) { | ||||
|  * @param {import('@playwright/test').Page} page | ||||
|  */ | ||||
| async function testLogTicks(page) { | ||||
|     const yTicks = page.locator('.gl-plot-y-tick-label'); | ||||
|     const yTicks = await page.locator('.gl-plot-y-tick-label'); | ||||
|     expect(await yTicks.count()).toBe(28); | ||||
|     await expect(yTicks.nth(0)).toHaveText('-2.98'); | ||||
|     await expect(yTicks.nth(1)).toHaveText('-2.50'); | ||||
| @@ -186,6 +199,7 @@ async function testLogTicks(page) { | ||||
| async function enableEditMode(page) { | ||||
|     // turn on edit mode
 | ||||
|     await page.locator('text=Unnamed Overlay Plot Snapshot >> button').nth(3).click(); | ||||
|     await expect(await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1)).toBeVisible(); | ||||
| } | ||||
| 
 | ||||
| /** | ||||
| @@ -210,7 +224,15 @@ async function disableLogMode(page) { | ||||
| async function saveOverlayPlot(page) { | ||||
|     // save overlay plot
 | ||||
|     await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click(); | ||||
|     await page.locator('text=Save and Finish Editing').click(); | ||||
| 
 | ||||
|     await Promise.all([ | ||||
|         page.locator('text=Save and Finish Editing').click(), | ||||
|         //Wait for Save Banner to appear
 | ||||
|         page.waitForSelector('.c-message-banner__message') | ||||
|     ]); | ||||
|     //Wait until Save Banner is gone
 | ||||
|     await page.locator('.c-message-banner__close-button').click(); | ||||
|     await page.waitForSelector('.c-message-banner__message', { state: 'detached' }); | ||||
| } | ||||
| 
 | ||||
| /** | ||||
| @@ -220,7 +242,7 @@ async function testLogPlotPixels(page) { | ||||
|     const pixelsMatch = await page.evaluate(async () => { | ||||
|         // TODO get canvas pixels at a few locations to make sure they're the correct color, to test that the plot comes out as expected.
 | ||||
| 
 | ||||
|         await new Promise((r) => setTimeout(r, 50)); | ||||
|         await new Promise((r) => setTimeout(r, 5 * 1000)); | ||||
| 
 | ||||
|         // These are some pixels that should be blue points in the log plot.
 | ||||
|         // If the plot changes shape to an unexpected shape, this will
 | ||||
| @@ -67,3 +67,46 @@ test.describe('Time counductor operations', () => { | ||||
|         expect(endDateValidityStatus).not.toBeTruthy(); | ||||
|     }); | ||||
| }); | ||||
|  | ||||
|  | ||||
| // Testing instructions: | ||||
| // Try to change the realtime offsets when in realtime (local clock) mode. | ||||
| test.describe('Time conductor input fields real-time mode', () => { | ||||
|     test('validate input fields in real-time mode', async ({ page }) => { | ||||
|         //Go to baseURL | ||||
|         await page.goto('/', { waitUntil: 'networkidle' }); | ||||
|  | ||||
|         // Set realtime "local clock" mode offsets | ||||
|         const timeInputs = page.locator('input.c-input--datetime'); | ||||
|  | ||||
|         // Click fixed timespan button | ||||
|         await page.locator('.c-button__label >> text=Fixed Timespan').click(); | ||||
|  | ||||
|         // Click local clock  | ||||
|         await page.locator('.icon-clock >> text=Local Clock').click(); | ||||
|  | ||||
|         // Click time offset button | ||||
|         await page.locator('.c-conductor__delta-button >> text=00:30:00').click(); | ||||
|  | ||||
|         // Input start time offset | ||||
|         await page.fill('.pr-time-controls__secs', '23'); | ||||
|  | ||||
|         // Click the check button | ||||
|         await page.locator('.icon-check').click(); | ||||
|  | ||||
|         // Verify time was updated on time offset button | ||||
|         await expect(page.locator('.c-conductor__delta-button').first()).toContainText('00:30:23'); | ||||
|  | ||||
|         // Click time offset set preceding now button | ||||
|         await page.locator('.c-conductor__delta-button >> text=00:00:30').click(); | ||||
|  | ||||
|         // Input preceding time offset | ||||
|         await page.fill('.pr-time-controls__secs', '31') | ||||
|  | ||||
|         // Click the check buttons | ||||
|         await page.locator('.icon-check').click(); | ||||
|  | ||||
|         // Verify time was updated on preceding time offset button | ||||
|         await expect(page.locator('.c-conductor__delta-button').nth(1)).toContainText('00:00:31'); | ||||
|     }); | ||||
| }); | ||||
|   | ||||
							
								
								
									
										22
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										22
									
								
								package.json
									
									
									
									
									
								
							| @@ -1,13 +1,13 @@ | ||||
| { | ||||
|   "name": "openmct", | ||||
|   "version": "2.0.4", | ||||
|   "version": "2.0.4-SNAPSHOT", | ||||
|   "description": "The Open MCT core platform", | ||||
|   "devDependencies": { | ||||
|     "@babel/eslint-parser": "7.16.3", | ||||
|     "@braintree/sanitize-url": "6.0.0", | ||||
|     "@percy/cli": "1.0.4", | ||||
|     "@percy/playwright": "1.0.2", | ||||
|     "@playwright/test": "1.19.2", | ||||
|     "@playwright/test": "1.21.1", | ||||
|     "@types/eventemitter3": "^1.0.0", | ||||
|     "@types/jasmine": "^4.0.1", | ||||
|     "@types/karma": "^6.3.2", | ||||
| @@ -20,12 +20,12 @@ | ||||
|     "copy-webpack-plugin": "10.2.0", | ||||
|     "cross-env": "7.0.3", | ||||
|     "css-loader": "4.0.0", | ||||
|     "d3-axis": "1.0.x", | ||||
|     "d3-scale": "1.0.x", | ||||
|     "d3-selection": "1.3.x", | ||||
|     "d3-axis": "3.0.0", | ||||
|     "d3-scale": "3.3.0", | ||||
|     "d3-selection": "3.0.0", | ||||
|     "eslint": "8.13.0", | ||||
|     "eslint-plugin-compat": "4.0.2", | ||||
|     "eslint-plugin-playwright": "0.8.0", | ||||
|     "eslint-plugin-playwright": "0.9.0", | ||||
|     "eslint-plugin-vue": "8.5.0", | ||||
|     "eslint-plugin-you-dont-need-lodash-underscore": "6.12.0", | ||||
|     "eventemitter3": "1.2.0", | ||||
| @@ -52,13 +52,13 @@ | ||||
|     "location-bar": "3.0.1", | ||||
|     "lodash": "4.17.21", | ||||
|     "mini-css-extract-plugin": "2.6.0", | ||||
|     "moment": "2.29.1", | ||||
|     "moment": "2.29.3", | ||||
|     "moment-duration-format": "2.3.2", | ||||
|     "moment-timezone": "0.5.34", | ||||
|     "node-bourbon": "4.2.3", | ||||
|     "painterro": "1.2.56", | ||||
|     "plotly.js-basic-dist": "2.5.0", | ||||
|     "plotly.js-gl2d-dist": "2.5.0", | ||||
|     "plotly.js-basic-dist": "2.12.0", | ||||
|     "plotly.js-gl2d-dist": "2.12.0", | ||||
|     "printj": "1.3.1", | ||||
|     "request": "2.88.2", | ||||
|     "resolve-url-loader": "5.0.0", | ||||
| @@ -92,9 +92,9 @@ | ||||
|     "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 default condition timeConductor", | ||||
|     "test:e2e:ci": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome smoke default condition timeConductor branding clock", | ||||
|     "test:e2e:local": "npx playwright test --config=e2e/playwright-local.config.js --project=chrome", | ||||
|     "test:e2e:debug": "npm run test:e2e:local -- --debug", | ||||
|     "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 default", | ||||
|     "test:e2e:full": "npx playwright test --config=e2e/playwright-ci.config.js", | ||||
|     "test:watch": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --no-single-run", | ||||
|   | ||||
| @@ -60,7 +60,7 @@ | ||||
|         > | ||||
|             <div class="c-image-controls__input icon-brightness"> | ||||
|                 <input | ||||
|                     v-model="filters.brightness" | ||||
|                     v-model="filters.contrast" | ||||
|                     type="range" | ||||
|                     min="0" | ||||
|                     max="500" | ||||
| @@ -69,7 +69,7 @@ | ||||
|             </div> | ||||
|             <div class="c-image-controls__input icon-contrast"> | ||||
|                 <input | ||||
|                     v-model="filters.contrast" | ||||
|                     v-model="filters.brightness" | ||||
|                     type="range" | ||||
|                     min="0" | ||||
|                     max="500" | ||||
| @@ -174,7 +174,7 @@ export default { | ||||
|             this.$emit('filtersUpdated', this.filters); | ||||
|         }, | ||||
|         handleResetFilters() { | ||||
|             this.filters = {...DEFAULT_FILTER_VALUES}; | ||||
|             this.filters = DEFAULT_FILTER_VALUES; | ||||
|             this.notifyFiltersChanged(); | ||||
|         }, | ||||
|         limitZoomRange(factor) { | ||||
|   | ||||
| @@ -55,7 +55,7 @@ | ||||
|             <div | ||||
|                 v-if="zoomFactor > 1" | ||||
|                 class="c-imagery__hints" | ||||
|             >Alt-drag to pan</div> | ||||
|             >{{formatImageAltText}}</div> | ||||
|             <div | ||||
|                 ref="focusedImageWrapper" | ||||
|                 class="image-wrapper" | ||||
| @@ -97,7 +97,8 @@ | ||||
|                         'transform': `scale(${zoomFactor}) translate(${imageTranslateX}px, ${imageTranslateY}px)`, | ||||
|                         'transition': `${!pan && animateZoom ? 'transform 250ms ease-in' : 'initial'}`, | ||||
|                         'width': `${sizedImageWidth}px`, | ||||
|                         'height': `${sizedImageHeight}px` | ||||
|                         'height': `${sizedImageHeight}px`, | ||||
|  | ||||
|                     }" | ||||
|                 ></div> | ||||
|                 <Compass | ||||
| @@ -381,9 +382,6 @@ export default { | ||||
|         formattedDuration() { | ||||
|             let result = 'N/A'; | ||||
|             let negativeAge = -1; | ||||
|             if (!Number.isInteger(this.numericDuration)) { | ||||
|                 return result; | ||||
|             } | ||||
|  | ||||
|             if (this.numericDuration > TWENTYFOUR_HOURS) { | ||||
|                 negativeAge *= (this.numericDuration / TWENTYFOUR_HOURS); | ||||
| @@ -490,6 +488,16 @@ export default { | ||||
|                 width: this.sizedImageWidth, | ||||
|                 height: this.sizedImageHeight | ||||
|             }; | ||||
|         }, | ||||
|         formatImageAltText() { | ||||
|             const regexLinux = /Linux/; | ||||
|             const navigator = window.navigator.userAgent; | ||||
|  | ||||
|             if (regexLinux.test(navigator)) { | ||||
|                 return 'Ctrl+Alt drag to pan'; | ||||
|             } | ||||
|  | ||||
|             return 'Alt drag to pan'; | ||||
|         } | ||||
|     }, | ||||
|     watch: { | ||||
| @@ -836,10 +844,8 @@ export default { | ||||
|             let currentTime = this.timeContext.clock() && this.timeContext.clock().currentValue(); | ||||
|             if (currentTime === undefined) { | ||||
|                 this.numericDuration = currentTime; | ||||
|             } else if (Number.isInteger(this.parsedSelectedTime)) { | ||||
|                 this.numericDuration = currentTime - this.parsedSelectedTime; | ||||
|             } else { | ||||
|                 this.numericDuration = undefined; | ||||
|                 this.numericDuration = currentTime - this.parsedSelectedTime; | ||||
|             } | ||||
|         }, | ||||
|         resetAgeCSS() { | ||||
|   | ||||
| @@ -63,7 +63,6 @@ | ||||
|             background-position: center; | ||||
|             background-repeat: no-repeat; | ||||
|             background-size: contain; | ||||
|             height: 100%; //fallback value | ||||
|         } | ||||
|         &__image { | ||||
|             height: 100%; | ||||
|   | ||||
| @@ -46,6 +46,7 @@ export default { | ||||
|  | ||||
|         // kickoff | ||||
|         this.subscribe(); | ||||
|         this.requestHistory(); | ||||
|     }, | ||||
|     beforeDestroy() { | ||||
|         if (this.unsubscribe) { | ||||
| @@ -70,18 +71,22 @@ export default { | ||||
|                 this.timeContext.off('timeSystem', this.timeSystemChange); | ||||
|             } | ||||
|         }, | ||||
|         isDatumValid(datum) { | ||||
|             //TODO: Add a check to see if there are duplicate images (identical image timestamp and url subsequently) | ||||
|             if (!datum) { | ||||
|         datumIsNotValid(datum) { | ||||
|             if (this.imageHistory.length === 0) { | ||||
|                 return false; | ||||
|             } | ||||
|  | ||||
|             const datumURL = this.formatImageUrl(datum); | ||||
|             const lastHistoryURL = this.formatImageUrl(this.imageHistory.slice(-1)[0]); | ||||
|  | ||||
|             // datum is not valid if it matches the last datum in history, | ||||
|             // or it is before the last datum in the history | ||||
|             const datumTimeCheck = this.parseTime(datum); | ||||
|             const bounds = this.timeContext.bounds(); | ||||
|             const historyTimeCheck = this.parseTime(this.imageHistory.slice(-1)[0]); | ||||
|             const matchesLast = (datumTimeCheck === historyTimeCheck) && (datumURL === lastHistoryURL); | ||||
|             const isStale = datumTimeCheck < historyTimeCheck; | ||||
|  | ||||
|             const isOutOfBounds = datumTimeCheck < bounds.start || datumTimeCheck > bounds.end; | ||||
|  | ||||
|             return !isOutOfBounds; | ||||
|             return matchesLast || isStale; | ||||
|         }, | ||||
|         formatImageUrl(datum) { | ||||
|             if (!datum) { | ||||
| @@ -128,19 +133,25 @@ export default { | ||||
|             return this.requestHistory(); | ||||
|         }, | ||||
|         async requestHistory() { | ||||
|             let bounds = this.timeContext.bounds(); | ||||
|             this.requestCount++; | ||||
|             const requestId = this.requestCount; | ||||
|             const bounds = this.timeContext.bounds(); | ||||
|             this.imageHistory = []; | ||||
|  | ||||
|             const data = await this.openmct.telemetry | ||||
|             let data = await this.openmct.telemetry | ||||
|                 .request(this.domainObject, bounds) || []; | ||||
|             // wait until new request resolves to do comparison | ||||
|             if (this.requestCount !== requestId) { | ||||
|                 return this.imageHistory = []; | ||||
|             } | ||||
|  | ||||
|             const imagery = data.filter(this.isDatumValid).map(this.normalizeDatum); | ||||
|             this.imageHistory = imagery; | ||||
|             if (this.requestCount === requestId) { | ||||
|                 let imagery = []; | ||||
|                 data.forEach((datum) => { | ||||
|                     let image = this.normalizeDatum(datum); | ||||
|                     if (image) { | ||||
|                         imagery.push(image); | ||||
|                     } | ||||
|                 }); | ||||
|                 //this is to optimize anything that reacts to imageHistory length | ||||
|                 this.imageHistory = imagery; | ||||
|             } | ||||
|         }, | ||||
|         clearData(domainObjectToClear) { | ||||
|             // global clearData button is accepted therefore no truthy check on inputted param | ||||
| @@ -172,29 +183,27 @@ export default { | ||||
|                 .subscribe(this.domainObject, (datum) => { | ||||
|                     let parsedTimestamp = this.parseTime(datum); | ||||
|                     let bounds = this.timeContext.bounds(); | ||||
|                     if (!(parsedTimestamp >= bounds.start && parsedTimestamp <= bounds.end)) { | ||||
|                         return; | ||||
|                     } | ||||
|  | ||||
|                     if (this.isDatumValid(datum)) { | ||||
|                         this.imageHistory.push(this.normalizeDatum(datum)); | ||||
|                     if (parsedTimestamp >= bounds.start && parsedTimestamp <= bounds.end) { | ||||
|                         let image = this.normalizeDatum(datum); | ||||
|                         if (image) { | ||||
|                             this.imageHistory.push(image); | ||||
|                         } | ||||
|                     } | ||||
|                 }); | ||||
|         }, | ||||
|         normalizeDatum(datum) { | ||||
|             if (this.datumIsNotValid(datum)) { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             const formattedTime = this.formatTime(datum); | ||||
|             const url = this.formatImageUrl(datum); | ||||
|             const time = this.parseTime(formattedTime); | ||||
|             const imageDownloadName = this.getImageDownloadName(datum); | ||||
|             let image = { ...datum }; | ||||
|             image.formattedTime = this.formatTime(datum); | ||||
|             image.url = this.formatImageUrl(datum); | ||||
|             image.time = this.parseTime(image.formattedTime); | ||||
|             image.imageDownloadName = this.getImageDownloadName(datum); | ||||
|  | ||||
|             return { | ||||
|                 ...datum, | ||||
|                 formattedTime, | ||||
|                 url, | ||||
|                 time, | ||||
|                 imageDownloadName | ||||
|             }; | ||||
|             return image; | ||||
|         }, | ||||
|         getFormatter(key) { | ||||
|             let metadataValue = this.metadata.value(key) || { format: key }; | ||||
|   | ||||
| @@ -84,6 +84,7 @@ describe("The Imagery View Layouts", () => { | ||||
|     let telemetryPromise; | ||||
|     let telemetryPromiseResolve; | ||||
|     let cleanupFirst; | ||||
|     let isClearDataTriggered; | ||||
|  | ||||
|     let openmct; | ||||
|     let parent; | ||||
| @@ -192,12 +193,20 @@ describe("The Imagery View Layouts", () => { | ||||
|         cleanupFirst = []; | ||||
|  | ||||
|         openmct = createOpenMct(); | ||||
|         openmct.time.timeSystem('utc', { | ||||
|             start: START - (5 * ONE_MINUTE), | ||||
|             end: START + (5 * ONE_MINUTE) | ||||
|         }); | ||||
|  | ||||
|         telemetryPromise = new Promise((resolve) => { | ||||
|             telemetryPromiseResolve = resolve; | ||||
|         }); | ||||
|  | ||||
|         spyOn(openmct.telemetry, 'request').and.callFake(() => { | ||||
|             if (isClearDataTriggered) { | ||||
|                 return []; | ||||
|             } | ||||
|  | ||||
|             telemetryPromiseResolve(imageTelemetry); | ||||
|  | ||||
|             return telemetryPromise; | ||||
| @@ -316,93 +325,44 @@ describe("The Imagery View Layouts", () => { | ||||
|         expect(imageryView).toBeDefined(); | ||||
|     }); | ||||
|  | ||||
|     describe("Clear data action for imagery", () => { | ||||
|     describe("imagery view", () => { | ||||
|         let applicableViews; | ||||
|         let imageryViewProvider; | ||||
|         let imageryView; | ||||
|         let componentView; | ||||
|         let clearDataPlugin; | ||||
|         let clearDataAction; | ||||
|  | ||||
|         beforeEach(() => { | ||||
|             openmct.time.timeSystem('utc', { | ||||
|                 start: START - (5 * ONE_MINUTE), | ||||
|                 end: START + (5 * ONE_MINUTE) | ||||
|             }); | ||||
|  | ||||
|             applicableViews = openmct.objectViews.get(imageryObject, [imageryObject]); | ||||
|             imageryViewProvider = applicableViews.find(viewProvider => viewProvider.key === imageryKey); | ||||
|             imageryView = imageryViewProvider.view(imageryObject, [imageryObject]); | ||||
|             imageryView.show(child); | ||||
|             componentView = imageryView._getInstance().$children[0]; | ||||
|  | ||||
|             clearDataPlugin = new ClearDataPlugin( | ||||
|                 ['example.imagery'], | ||||
|                 {indicator: true} | ||||
|             ); | ||||
|             openmct.install(clearDataPlugin); | ||||
|             clearDataAction = openmct.actions.getAction('clear-data-action'); | ||||
|  | ||||
|             return Vue.nextTick(); | ||||
|         }); | ||||
|  | ||||
|         it('clear data action is installed', () => { | ||||
|             expect(clearDataAction).toBeDefined(); | ||||
|         }); | ||||
|  | ||||
|         it('on clearData action should clear data for object is selected', (done) => { | ||||
|             // force show the thumbnails | ||||
|             componentView.forceShowThumbnails = true; | ||||
|             Vue.nextTick(() => { | ||||
|                 let clearDataResolve; | ||||
|                 let telemetryRequestPromise = new Promise((resolve) => { | ||||
|                     clearDataResolve = resolve; | ||||
|                 }); | ||||
|                 expect(parent.querySelectorAll('.c-imagery__thumb').length).not.toBe(0); | ||||
|  | ||||
|                 openmct.objectViews.on('clearData', (_domainObject) => { | ||||
|                     return Vue.nextTick(() => { | ||||
|                         expect(parent.querySelectorAll('.c-imagery__thumb').length).toBe(0); | ||||
|  | ||||
|                         clearDataResolve(); | ||||
|                     }); | ||||
|                 }); | ||||
|                 clearDataAction.invoke(imageryObject); | ||||
|  | ||||
|                 telemetryRequestPromise.then(() => { | ||||
|                     done(); | ||||
|                 }); | ||||
|             }); | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     describe("imagery view", () => { | ||||
|         let applicableViews; | ||||
|         let imageryViewProvider; | ||||
|         let imageryView; | ||||
|  | ||||
|         beforeEach(() => { | ||||
|             openmct.time.timeSystem('utc', { | ||||
|                 start: START - (5 * ONE_MINUTE), | ||||
|                 end: START + (5 * ONE_MINUTE) | ||||
|             }); | ||||
|  | ||||
|             applicableViews = openmct.objectViews.get(imageryObject, [imageryObject]); | ||||
|             imageryViewProvider = applicableViews.find(viewProvider => viewProvider.key === imageryKey); | ||||
|             imageryView = imageryViewProvider.view(imageryObject, [imageryObject]); | ||||
|             imageryView.show(child); | ||||
|  | ||||
|             imageryView._getInstance().$children[0].forceShowThumbnails = true; | ||||
|  | ||||
|             return Vue.nextTick(); | ||||
|         }); | ||||
|         afterEach(() => { | ||||
|             isClearDataTriggered = false; | ||||
|             // openmct.time.stopClock(); | ||||
|             // openmct.router.removeListener('change:hash', resolveFunction); | ||||
|             // imageryView.destroy(); | ||||
|         }); | ||||
|  | ||||
|         it("on mount should show the the most recent image", () => { | ||||
|         it("on mount should show the the most recent image", (done) => { | ||||
|             //Looks like we need Vue.nextTick here so that computed properties settle down | ||||
|             return Vue.nextTick(() => { | ||||
|             Vue.nextTick(() => { | ||||
|                 const imageInfo = getImageInfo(parent); | ||||
|  | ||||
|                 expect(imageInfo.url.indexOf(imageTelemetry[COUNT - 1].timeId)).not.toEqual(-1); | ||||
|                 done(); | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
| @@ -438,7 +398,7 @@ describe("The Imagery View Layouts", () => { | ||||
|  | ||||
|         it("should show that an image is not new", (done) => { | ||||
|             Vue.nextTick(() => { | ||||
|                 const target = imageTelemetry[4].url; | ||||
|                 const target = imageTelemetry[2].url; | ||||
|                 parent.querySelectorAll(`img[src='${target}']`)[0].click(); | ||||
|  | ||||
|                 Vue.nextTick(() => { | ||||
| @@ -560,6 +520,25 @@ describe("The Imagery View Layouts", () => { | ||||
|             expect(imageSizeAfter.width).toBeLessThan(imageSizeBefore.width); | ||||
|             done(); | ||||
|         }); | ||||
|  | ||||
|         it('clear data action is installed', () => { | ||||
|             expect(clearDataAction).toBeDefined(); | ||||
|         }); | ||||
|  | ||||
|         it('on clearData action should clear data for object is selected', async (done) => { | ||||
|             // force show the thumbnails | ||||
|             imageryView._getInstance().$children[0].forceShowThumbnails = true; | ||||
|             await Vue.nextTick(); | ||||
|             expect(parent.querySelectorAll('.c-imagery__thumb').length).not.toBe(0); | ||||
|             openmct.objectViews.on('clearData', async (_domainObject) => { | ||||
|                 await Vue.nextTick(); | ||||
|                 expect(parent.querySelectorAll('.c-imagery__thumb').length).toBe(0); | ||||
|                 done(); | ||||
|             }); | ||||
|             // stubbed telemetry data will return empty array when true | ||||
|             isClearDataTriggered = true; | ||||
|             clearDataAction.invoke(imageryObject); | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     describe("imagery time strip view", () => { | ||||
|   | ||||
| @@ -32,7 +32,7 @@ Add a line to install the CouchDB plugin for OpenMCT: | ||||
| ``` | ||||
| openmct.install(openmct.plugins.CouchDB("http://localhost:5984/openmct")); | ||||
| ``` | ||||
| 6. Enable cors in CouchDB by editing `/usr/local/etc/local.ini` and add: ` | ||||
| 6. Enable cors in CouchDB by editing `~/homebrew/etc/local.ini` and add: ` | ||||
| ``` | ||||
| [chttpd] | ||||
| enable_cors = true | ||||
| @@ -45,4 +45,4 @@ origins = http://localhost:8080 | ||||
| 9. Navigate to http://localhost:8080/ and create a random object in OpenMCT (e.g., a `Clock`) and save. You may get an error saying that the objects failed to persist. This is a known error that you can ignore, and will only happen the first time you save. | ||||
| 10. Navigate to: http://127.0.0.1:5984/_utils/#database/openmct/_all_docs | ||||
| 11. Look at the `JSON` tab and ensure you can see the `Clock` object you created above. | ||||
| 12. All done! 🏆 | ||||
| 12. All done! 🏆 | ||||
|   | ||||
| @@ -123,7 +123,7 @@ export default { | ||||
|             } | ||||
|         }, | ||||
|         drawAxis(bounds, timeSystem) { | ||||
|             let viewBounds = Object.assign({}, bounds); | ||||
|             let viewBounds = Object.create(bounds); | ||||
|  | ||||
|             this.setScale(viewBounds, timeSystem); | ||||
|             this.setAxis(viewBounds); | ||||
|   | ||||
| @@ -26,10 +26,9 @@ const config = { | ||||
|         maelstromTheme: './src/plugins/themes/maelstrom-theme.scss' | ||||
|     }, | ||||
|     output: { | ||||
|         globalObject: 'this', | ||||
|         globalObject: "this", | ||||
|         filename: '[name].js', | ||||
|         path: path.resolve(__dirname, 'dist'), | ||||
|         library: 'openmct', | ||||
|         library: '[name]', | ||||
|         libraryTarget: 'umd', | ||||
|         publicPath: '', | ||||
|         hashFunction: 'xxhash64', | ||||
| @@ -45,7 +44,7 @@ const config = { | ||||
|             "bourbon": "bourbon.scss", | ||||
|             "plotly-basic": "plotly.js-basic-dist", | ||||
|             "plotly-gl2d": "plotly.js-gl2d-dist", | ||||
|             "d3-scale": path.join(__dirname, "node_modules/d3-scale/build/d3-scale.min.js"), | ||||
|             "d3-scale": path.join(__dirname, "node_modules/d3-scale/dist/d3-scale.min.js"), | ||||
|             "printj": path.join(__dirname, "node_modules/printj/dist/printj.min.js"), | ||||
|             "styles": path.join(__dirname, "src/styles"), | ||||
|             "MCT": path.join(__dirname, "src/MCT"), | ||||
|   | ||||
| @@ -16,5 +16,5 @@ module.exports = merge(common, { | ||||
|             __OPENMCT_ROOT_RELATIVE__: '""' | ||||
|         }) | ||||
|     ], | ||||
|     devtool: 'eval-source-map' | ||||
|     devtool: 'source-map' | ||||
| }); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user