/* * Copyright 2018-2022 Elyra Authors * * 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. */ describe('Code Snippet tests', () => { const snippetName = 'test-code-snippet'; beforeEach(() => { cy.resetJupyterLab(); openCodeSnippetExtension(); }); afterEach(() => { // delete code-snippet used for testing cy.exec(`elyra-metadata remove code-snippets --name=${snippetName}`, { failOnNonZeroExit: false }); }); it('should open the Code Snippet extension', () => { // make sure it is rendered properly cy.get('.elyra-metadata .elyra-metadataHeader').contains('Code Snippets'); // and code-snippet create new button is visible cy.findByRole('button', { name: /create new code snippet/i }).should( 'be.visible' ); }); it('should provide warnings when required fields are not entered properly', () => { createInvalidCodeSnippet(snippetName); // Metadata editor should not close cy.get('.lm-TabBar-tabLabel') .contains('New Code Snippet') .should('be.visible'); // Fields marked as required should be highlighted cy.get('.error-detail li.text-danger').as('required-warnings'); cy.get('@required-warnings').should('have.length', 2); }); it('should create valid code-snippet', () => { createValidCodeSnippet(snippetName); // Metadata editor tab should not be visible cy.get('.lm-TabBar-tabLabel') .contains('New Code Snippet') .should('not.exist'); // Check new code snippet is displayed getSnippetByName(snippetName); }); it('should fail to create duplicate Code Snippet', () => { // create code snippet createValidCodeSnippet(snippetName); // tries to create duplicate code snippet createValidCodeSnippet(snippetName); // Should display dialog cy.get('.jp-Dialog-header').contains('Error making request'); // Close dialog cy.get('button.jp-mod-accept').click(); }); it('should trigger save / submit on pressing enter', () => { populateCodeSnippetFields(snippetName); cy.get('.elyra-formEditor-form-display_name').type('{enter}'); // Metadata editor tab should not be visible cy.get('.lm-TabBar-tabLabel') .contains('New Code Snippet') .should('not.exist'); // Check new code snippet is displayed getSnippetByName(snippetName); }); // Delete snippet it('should delete existing Code Snippet', () => { createValidCodeSnippet(snippetName); cy.wait(500); getSnippetByName(snippetName); deleteSnippet(snippetName); }); // Duplicate snippet it('should duplicate existing Code Snippet', () => { createValidCodeSnippet(snippetName); cy.wait(500); let snippetRef = getSnippetByName(snippetName); expect(snippetRef).to.not.be.null; // create a duplicate of this snippet duplicateSnippet(snippetName); cy.wait(100); snippetRef = getSnippetByName(`${snippetName}-Copy1`); expect(snippetRef).to.not.be.null; // create another duplicate of this snippet duplicateSnippet(snippetName); cy.wait(100); snippetRef = getSnippetByName(`${snippetName}-Copy2`); expect(snippetRef).to.not.be.null; // cleanup deleteSnippet(snippetName); deleteSnippet(`${snippetName}-Copy1`); deleteSnippet(`${snippetName}-Copy2`); }); it('should have visible action buttons for existing code snippet', () => { createValidCodeSnippet(snippetName); const actionButtons = getActionButtonsElement(snippetName); const buttonTitles = [ 'Copy to clipboard', 'Insert', 'Edit', 'Duplicate', 'Delete' ]; // Check expected buttons to be visible buttonTitles.forEach((title: string) => { actionButtons.within(() => { cy.get(`button[title="${title}"]`).should('be.visible'); }); }); }); it('should display/hide code snippet content on expand/collapse button', () => { createValidCodeSnippet(snippetName); // Check new code snippet is displayed const item = getSnippetByName(snippetName); // Click on expand button item .parentsUntil('.elyra-metadata-item') .first() .find('button') .first() .click(); // Check code mirror is visible cy.get('.elyra-expandableContainer-details-visible').should('exist'); // Click on collapse button item .parentsUntil('.elyra-metadata-item') .first() .find('button') .first() .click(); // Check code mirror is not visible cy.get('.elyra-expandableContainer-details-visible').should('not.exist'); }); it('should update code snippet name after editing it', () => { createValidCodeSnippet(snippetName); // Find new snippet in display and click on edit button getActionButtonsElement(snippetName).within(() => { cy.get('button[title="Edit"]').click(); }); // Edit snippet name const newSnippetName = 'new-name'; cy.get('.elyra-formEditor-form-display_name') .find('input') .clear() .type(newSnippetName); saveAndCloseMetadataEditor(); cy.wait(500); // Check new snippet name is displayed const updatedSnippetItem = getSnippetByName(newSnippetName); // Check old snippet name does not exist expect(updatedSnippetItem.innerText).not.to.eq(snippetName); // Delete updated code snippet deleteSnippet(newSnippetName); }); it('should fail to insert a code snippet into unsupported widget', () => { createValidCodeSnippet(snippetName); // Insert snippet into launcher widget insert(snippetName); // Check if insertion failed and dismiss dialog cy.get('.jp-Dialog-header').contains('Error'); cy.get('button.jp-mod-accept').click(); cy.wait(100); }); it('should insert a python code snippet into python editor', () => { createValidCodeSnippet(snippetName); // Open blank python file cy.createNewScriptEditor('Python'); cy.wait(1500); // Insert snippet into python editor insert(snippetName); // Check if editor has the new code cy.get('.CodeMirror:visible'); cy.get('span.cm-string').contains(/test/i); }); it('should fail to insert a java code snippet into python editor', () => { createValidCodeSnippet(snippetName, 'Java'); // Open blank python file cy.createNewScriptEditor('Python'); cy.wait(500); // Insert snippet into python editor insert(snippetName); // Check for language mismatch warning cy.get('.jp-Dialog-header').contains(/warning/i); // Dismiss the dialog cy.findByRole('button', { name: /cancel/i }).click(); // Check it did not insert the code cy.get('.CodeMirror:visible'); cy.get('span.cm-string').should('not.exist'); }); // DEV NOTE: Uncomment the tests below to run them locally // TODO: Investigate tests below only failing on CI // Steps: checkCodeMirror, closeTabWithoutSaving // it('Test inserting a code snippet into a notebook', () => { // openCodeSnippetExtension(); // clickCreateNewSnippetButton(); // const snippetName = 'test-code-snippet'; // fillMetadaEditorForm(snippetName); // cy.wait(500); // // Open blank notebook file // cy.get( // '.jp-LauncherCard[data-category="Notebook"][title="Python 3"]:visible' // ).click(); // cy.wait(500); // // Check widget is loaded // cy.get('.CodeMirror:visible'); // insert(snippetName); // // Check if notebook cell has the new code // checkCodeMirror(); // // NOTE: Notebook cell is still empty when this test runs on CI // closeTabWithoutSaving(); // // NOTE: Save dialog isn't visible when this test runs on CI // }); // it('Test inserting a code snippet into a markdown file', () => { // openCodeSnippetExtension(); // clickCreateNewSnippetButton(); // const snippetName = 'test-code-snippet'; // fillMetadaEditorForm(snippetName); // cy.wait(500); // // Open blank notebook file // cy.get( // '.jp-LauncherCard[title="Create a new markdown file"]:visible' // ).click(); // cy.wait(500); // // Check widget is loaded // cy.get('.CodeMirror:visible'); // insert(snippetName); // // Check if notebook cell has the new code // checkCodeMirror(); // // Check for language decoration // cy.get('span.cm-comment') // .first() // .contains('Python'); // closeTabWithoutSaving(); // }); }); // ------------------------------ // ----- Utility Functions ------ // ------------------------------ const openCodeSnippetExtension = (): void => { cy.get('.jp-SideBar [title="Code Snippets"]').click(); cy.get('.jp-SideBar .lm-mod-current[title="Code Snippets"]'); }; const getSnippetByName = (snippetName: string): any => { return cy.get(`[data-item-id="${snippetName}"]`); }; const createInvalidCodeSnippet = (snippetName: string): any => { clickCreateNewSnippetButton(); // Name code snippet cy.get('.elyra-formEditor-form-display_name').type(snippetName); saveAndCloseMetadataEditor(); }; const populateCodeSnippetFields = ( snippetName: string, language?: string ): any => { clickCreateNewSnippetButton(); // Name code snippet cy.get('.elyra-formEditor-form-display_name').type(snippetName); // Select python language from dropdown list editSnippetLanguage(snippetName, language ?? 'Python'); // Add snippet code cy.get('.CodeMirror .CodeMirror-scroll:visible').type( 'print("Code Snippet Test")' ); }; const createValidCodeSnippet = ( snippetName: string, language?: string ): any => { populateCodeSnippetFields(snippetName, language); saveAndCloseMetadataEditor(); cy.wait(1000); }; const clickCreateNewSnippetButton = (): void => { cy.findByRole('button', { name: /create new code snippet/i }).click(); }; const saveAndCloseMetadataEditor = (): void => { cy.get('.elyra-metadataEditor-saveButton > button:visible').click(); }; const deleteSnippet = (snippetName: string): void => { // Find element by name const item = getSnippetByName(snippetName); // Click on delete button item.find('button[title="Delete"]').click(); // Confirm action in dialog cy.get('.jp-Dialog-header').contains(`Delete snippet '${snippetName}'?`); cy.get('button.jp-mod-accept').click(); }; const duplicateSnippet = (snippetName: string): void => { // Find element by name const item = getSnippetByName(snippetName); // Click duplicate button item.find('button[title="Duplicate"]').click(); }; const getActionButtonsElement = (snippetName: string): any => { const actionButtonsElement = getSnippetByName(snippetName).find( '.elyra-expandableContainer-action-buttons' ); return actionButtonsElement; }; const insert = (snippetName: string): void => { getActionButtonsElement(snippetName).within(() => { cy.get('button[title="Insert"]').click(); }); cy.wait(500); }; const editSnippetLanguage = (snippetName: string, lang: string): void => { cy.get('.elyra-formEditor') .find('.elyra-form-DropDown-item option') .then(list => Cypress._.map(list, 'value')) .should('include', lang); cy.get('.elyra-formEditor') .find('.elyra-formEditor-form-language input') .type(lang); cy.get('.elyra-formEditor-form-language input') .should('have.value', lang) .click(); };