diff --git a/cypress/e2e/23-variables.cy.ts b/cypress/e2e/23-variables.cy.ts new file mode 100644 index 0000000000..8dc16bb8e9 --- /dev/null +++ b/cypress/e2e/23-variables.cy.ts @@ -0,0 +1,126 @@ +import { VariablesPage } from '../pages/variables'; +import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants'; +import { randFirstName, randLastName } from '@ngneat/falso'; + +const variablesPage = new VariablesPage(); + +const email = DEFAULT_USER_EMAIL; +const password = DEFAULT_USER_PASSWORD; +const firstName = randFirstName(); +const lastName = randLastName(); + +describe('Variables', () => { + before(() => { + cy.resetAll(); + cy.setup({ email, firstName, lastName, password }); + }); + + it('should show the unlicensed action box when the feature is disabled', () => { + cy.signin({ email, password }); + cy.visit(variablesPage.url); + + variablesPage.getters.unavailableResourcesList().should('be.visible'); + variablesPage.getters.resourcesList().should('not.exist'); + }); + + describe('licensed', () => { + before(() => { + cy.enableFeature('feat:variables'); + }); + + beforeEach(() => { + cy.signin({ email, password }); + cy.visit(variablesPage.url); + }); + + it('should show the licensed action box when the feature is enabled', () => { + variablesPage.getters.emptyResourcesList().should('be.visible'); + variablesPage.getters.createVariableButton().should('be.visible'); + }); + + it('should create a new variable using empty state row', () => { + const key = 'ENV_VAR'; + const value = 'value'; + + variablesPage.actions.createVariableFromEmptyState(key, value); + variablesPage.getters.variableRow(key).should('contain', value).should('be.visible'); + variablesPage.getters.variablesRows().should('have.length', 1); + }); + + it('should create a new variable using pre-existing state', () => { + const key = 'ENV_VAR_NEW'; + const value = 'value2'; + + variablesPage.actions.createVariable(key, value); + variablesPage.getters.variableRow(key).should('contain', value).should('be.visible'); + variablesPage.getters.variablesRows().should('have.length', 2); + + const otherKey = 'ENV_EXAMPLE'; + const otherValue = 'value3'; + + variablesPage.actions.createVariable(otherKey, otherValue); + variablesPage.getters + .variableRow(otherKey) + .should('contain', otherValue) + .should('be.visible'); + variablesPage.getters.variablesRows().should('have.length', 3); + }); + + it('should get validation errors and cancel variable creation', () => { + const key = 'ENV_VAR_NEW$'; + const value = 'value3'; + + variablesPage.getters.createVariableButton().click(); + const editingRow = variablesPage.getters.variablesEditableRows().eq(0); + variablesPage.actions.setRowValue(editingRow, 'key', key); + variablesPage.actions.setRowValue(editingRow, 'value', value); + editingRow.should('contain', 'This field may contain only letters'); + variablesPage.getters.editableRowSaveButton(editingRow).should('be.disabled'); + variablesPage.actions.cancelRowEditing(editingRow); + + variablesPage.getters.variablesRows().should('have.length', 3); + }); + + it('should edit a variable', () => { + const key = 'ENV_VAR_NEW'; + const newValue = 'value4'; + + variablesPage.actions.editRow(key); + const editingRow = variablesPage.getters.variablesEditableRows().eq(0); + variablesPage.actions.setRowValue(editingRow, 'value', newValue); + variablesPage.actions.saveRowEditing(editingRow); + + variablesPage.getters.variableRow(key).should('contain', newValue).should('be.visible'); + variablesPage.getters.variablesRows().should('have.length', 3); + }); + + it('should delete a variable', () => { + const key = 'TO_DELETE'; + const value = 'xxx'; + + variablesPage.actions.createVariable(key, value); + variablesPage.actions.deleteVariable(key); + }); + + it('should search for a variable', () => { + // One Result + variablesPage.getters.searchBar().type('NEW'); + variablesPage.getters.variablesRows().should('have.length', 1); + variablesPage.getters.variableRow('NEW').should('contain.text', 'ENV_VAR_NEW'); + + // Multiple Results + variablesPage.getters.searchBar().clear().type('ENV_VAR'); + variablesPage.getters.variablesRows().should('have.length', 2); + + // All Results + variablesPage.getters.searchBar().clear().type('ENV'); + variablesPage.getters.variablesRows().should('have.length', 3); + + // No Results + variablesPage.getters.searchBar().clear().type('Some non-existent variable'); + variablesPage.getters.variablesRows().should('not.exist'); + + cy.contains('No variables found').should('be.visible'); + }); + }); +}); diff --git a/cypress/pages/variables.ts b/cypress/pages/variables.ts new file mode 100644 index 0000000000..721d874351 --- /dev/null +++ b/cypress/pages/variables.ts @@ -0,0 +1,71 @@ +import { BasePage } from './base'; +import Chainable = Cypress.Chainable; + +export class VariablesPage extends BasePage { + url = '/variables'; + getters = { + unavailableResourcesList: () => cy.getByTestId('unavailable-resources-list'), + emptyResourcesList: () => cy.getByTestId('empty-resources-list'), + resourcesList: () => cy.getByTestId('resources-list'), + goToUpgrade: () => cy.getByTestId('go-to-upgrade'), + actionBox: () => cy.getByTestId('action-box'), + emptyResourcesListNewVariableButton: () => this.getters.emptyResourcesList().find('button'), + searchBar: () => cy.getByTestId('resources-list-search').find('input'), + createVariableButton: () => cy.getByTestId('resources-list-add'), + variablesRows: () => cy.getByTestId('variables-row'), + variablesEditableRows: () => + cy.getByTestId('variables-row').filter((index, row) => !!row.querySelector('input')), + variableRow: (key: string) => + this.getters.variablesRows().contains(key).parents('[data-test-id="variables-row"]'), + editableRowCancelButton: (row: Chainable>) => + row.getByTestId('variable-row-cancel-button'), + editableRowSaveButton: (row: Chainable>) => + row.getByTestId('variable-row-save-button'), + }; + + actions = { + createVariable: (key: string, value: string) => { + this.getters.createVariableButton().click(); + + const editingRow = this.getters.variablesEditableRows().eq(0); + this.actions.setRowValue(editingRow, 'key', key); + this.actions.setRowValue(editingRow, 'value', value); + this.actions.saveRowEditing(editingRow); + }, + deleteVariable: (key: string) => { + const row = this.getters.variableRow(key); + row.within(() => { + cy.getByTestId('variable-row-delete-button').click(); + }); + + const modal = cy.get('[role="dialog"]'); + modal.should('be.visible'); + modal.get('.btn--confirm').click(); + }, + createVariableFromEmptyState: (key: string, value: string) => { + this.getters.emptyResourcesListNewVariableButton().click(); + + const editingRow = this.getters.variablesEditableRows().eq(0); + this.actions.setRowValue(editingRow, 'key', key); + this.actions.setRowValue(editingRow, 'value', value); + this.actions.saveRowEditing(editingRow); + }, + editRow: (key: string) => { + const row = this.getters.variableRow(key); + row.within(() => { + cy.getByTestId('variable-row-edit-button').click(); + }); + }, + setRowValue: (row: Chainable>, field: 'key' | 'value', value: string) => { + row.within(() => { + cy.getByTestId(`variable-row-${field}-input`).type('{selectAll}{del}').type(value); + }); + }, + cancelRowEditing: (row: Chainable>) => { + this.getters.editableRowCancelButton(row).click(); + }, + saveRowEditing: (row: Chainable>) => { + this.getters.editableRowSaveButton(row).click(); + }, + }; +} diff --git a/packages/editor-ui/src/components/VariablesRow.vue b/packages/editor-ui/src/components/VariablesRow.vue index 4b1264659d..7b0861a2d9 100644 --- a/packages/editor-ui/src/components/VariablesRow.vue +++ b/packages/editor-ui/src/components/VariablesRow.vue @@ -130,7 +130,7 @@ function focusFirstInput() {