diff --git a/apps/studio-next/cypress.config.ts b/apps/studio-next/cypress.config.ts new file mode 100644 index 000000000..fcabd0349 --- /dev/null +++ b/apps/studio-next/cypress.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from 'cypress'; + +export default defineConfig({ + e2e: { + baseUrl: 'http://localhost:3001', + retries: { + runMode: 1, + openMode: 1, + }, + }, +}); diff --git a/apps/studio-next/cypress/e2e/studio-ui.spec.cy.ts b/apps/studio-next/cypress/e2e/studio-ui.spec.cy.ts new file mode 100644 index 000000000..84ab495e6 --- /dev/null +++ b/apps/studio-next/cypress/e2e/studio-ui.spec.cy.ts @@ -0,0 +1,105 @@ +const isV2 = false; +const isV3 = true; + +/* Testing commented hovers is impossible even with `cypress-real-events` so +testing of these hovers is postponed until either Cypress has better supportĀ for +`mouseover`/`mouseenter` events or the architecture of `Studio` is changed to +allow testing those. */ + +describe('Studio UI spec', () => { + beforeEach(() => { + cy.visit('/'); + }); + + it('Logo should be visible in the UI', () => { + cy.get('[data-test="logo"]').should('be.visible'); + }); + + // it('Logo should display tooltip "AsyncAPI Logo" on hover', () => { + // cy.get('[data-test="logo"]').trigger('mouseenter'); + // cy.contains('AsyncAPI Logo').should('be.visible'); + // }); + + it('Button "AsyncAPI Website" should be visible in the UI', () => { + cy.get('[data-test="button-website"]').should('be.visible'); + }); + + // it('Button "AsyncAPI Website" should display tooltip "AsyncAPI Website" on hover', () => { + // cy.get('[data-test="button-website"]').trigger('mouseenter'); + // cy.contains('AsyncAPI Website').should('be.visible'); + // }); + + it('Button "AsyncAPI Github Organization" should be visible in the UI', () => { + cy.get('[data-test="button-github"]').should('be.visible'); + }); + + // it('Button "AsyncAPI Github Organization" should display tooltip "AsyncAPI Github Organization" on hover', () => { + // cy.get('[data-test="button-github"]').trigger('mouseenter'); + // cy.contains('AsyncAPI Github Organization').should('be.visible'); + // }); + + it('Button "AsyncAPI Slack Workspace" should be visible in the UI', () => { + cy.get('[data-test="button-slack"]').should('be.visible'); + }); + + // it('Button "AsyncAPI Slack Workspace" should display tooltip "AsyncAPI Slack Workspace" on hover', () => { + // cy.get('[data-test="button-slack"]').trigger('mouseenter'); + // cy.contains('AsyncAPI Slack Workspace').should('be.visible'); + // }); + + it('Button "Navigation" should be visible in the UI', () => { + cy.get('[data-test="button-navigation"]').should('be.visible'); + }); + + it('Button "Navigation" should display tooltip "Navigation" on hover', () => { + cy.get('[data-test="button-navigation"]').trigger('mouseenter'); + cy.contains('Navigation').should('be.visible'); + }); + + it('Button "Editor" should be visible in the UI', () => { + cy.get('[data-test="button-editor"]').should('be.visible'); + }); + + it('Button "Editor" should display tooltip "Editor" on hover', () => { + cy.get('[data-test="button-editor"]').trigger('mouseenter'); + cy.contains('Editor').should('be.visible'); + }); + + it('Button "Template preview" should be visible in the UI', () => { + cy.get('[data-test="button-template-preview"]').should('be.visible'); + }); + + it('Button "Template preview" should display tooltip "Template preview" on hover', () => { + cy.get('[data-test="button-template-preview"]').trigger('mouseenter'); + cy.contains('Template preview').should('be.visible'); + }); + + if (isV2) { + it('Button "Blocks visualiser" should be visible in the UI', () => { + cy.get('[data-test="button-blocks-visualiser"]').should('be.visible'); + }); + + it('Button "Blocks visualiser" should display tooltip "Blocks visualiser" on hover', () => { + cy.get('[data-test="button-blocks-visualiser"]').trigger('mouseenter'); + cy.contains('Blocks visualiser').should('be.visible'); + }); + } + + it('Button "New file" should be visible in the UI', () => { + cy.get('[data-test="button-new-file"]').should('be.visible'); + }); + + it('Button "New file" should display tooltip "New file" on hover', () => { + cy.get('[data-test="button-new-file"]').trigger('mouseenter'); + cy.contains('New file').should('be.visible'); + }); + + it('Button "Studio settings" should be visible in the UI', () => { + cy.get('[data-test="button-studio-settings"]').should('be.visible'); + }); + + it('Button "Studio settings" should display tooltip "Studio settings" on hover', () => { + cy.get('[data-test="button-studio-settings"]').trigger('mouseenter'); + cy.contains('Studio settings').should('be.visible'); + }); +}); diff --git a/apps/studio-next/cypress/fixtures/example.json b/apps/studio-next/cypress/fixtures/example.json new file mode 100644 index 000000000..02e425437 --- /dev/null +++ b/apps/studio-next/cypress/fixtures/example.json @@ -0,0 +1,5 @@ +{ + "name": "Using fixtures to represent data", + "email": "hello@cypress.io", + "body": "Fixtures are a great way to mock data for responses to routes" +} diff --git a/apps/studio-next/cypress/support/commands.ts b/apps/studio-next/cypress/support/commands.ts new file mode 100644 index 000000000..698b01a42 --- /dev/null +++ b/apps/studio-next/cypress/support/commands.ts @@ -0,0 +1,37 @@ +/// +// *********************************************** +// This example commands.ts shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** +// +// +// -- This is a parent command -- +// Cypress.Commands.add('login', (email, password) => { ... }) +// +// +// -- This is a child command -- +// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) +// +// +// -- This is a dual command -- +// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) +// +// +// -- This will overwrite an existing command -- +// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) +// +// declare global { +// namespace Cypress { +// interface Chainable { +// login(email: string, password: string): Chainable +// drag(subject: string, options?: Partial): Chainable +// dismiss(subject: string, options?: Partial): Chainable +// visit(originalFn: CommandOriginalFn, url: string, options: Partial): Chainable +// } +// } +// } \ No newline at end of file diff --git a/apps/studio-next/cypress/support/e2e.ts b/apps/studio-next/cypress/support/e2e.ts new file mode 100644 index 000000000..f80f74f8e --- /dev/null +++ b/apps/studio-next/cypress/support/e2e.ts @@ -0,0 +1,20 @@ +// *********************************************************** +// This example support/e2e.ts is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// Import commands.js using ES2015 syntax: +import './commands' + +// Alternatively you can use CommonJS syntax: +// require('./commands') \ No newline at end of file diff --git a/apps/studio-next/package.json b/apps/studio-next/package.json index b34f52e34..e47f91a0b 100644 --- a/apps/studio-next/package.json +++ b/apps/studio-next/package.json @@ -6,7 +6,13 @@ "dev": "next dev -p 3001", "build": "next build", "start": "next start", - "lint": "echo 'No linting configured'" + "lint": "next lint", + "cy:e2e:chrome": "cypress run --e2e --browser chrome", + "cy:e2e:chromium": "cypress run --e2e --browser chromium", + "cy:e2e:edge": "cypress run --e2e --browser edge", + "cy:e2e:electron": "cypress run --e2e --browser electron", + "cy:e2e:firefox": "cypress run --e2e --browser firefox", + "cy:open": "cypress open" }, "dependencies": { "@asyncapi/avro-schema-parser": "^3.0.19", @@ -72,6 +78,7 @@ "autoprefixer": "^10.4.13", "browserify-zlib": "^0.2.0", "buffer": "^6.0.3", + "cypress": "^13.8.1", "eslint-config-next": "14.1.4", "eslint-plugin-security": "^1.5.0", "eslint-plugin-sonarjs": "^0.16.0", diff --git a/apps/studio-next/scripts/template-parameters.ts b/apps/studio-next/scripts/template-parameters.ts index 35a8db0a8..1aba2b808 100644 --- a/apps/studio-next/scripts/template-parameters.ts +++ b/apps/studio-next/scripts/template-parameters.ts @@ -6,7 +6,8 @@ import { JSONSchema7 } from 'json-schema'; const DESTINATION_JSON = path.join(__dirname, '../src/components/Modals/Generator/template-parameters.json'); const TEMPLATES: Record = { - '@asyncapi/dotnet-nats-template': '.NET Nats Project', + '@asyncapi/dotnet-nats-template': '.NET NATS Project', + '@asyncapi/dotnet-rabbitmq-template': '.NET C# RabbitMQ Project', '@asyncapi/go-watermill-template': 'GO Lang Watermill Project', '@asyncapi/html-template': 'HTML website', '@asyncapi/java-spring-cloud-stream-template': 'Java Spring Cloud Stream Project', @@ -14,9 +15,10 @@ const TEMPLATES: Record = { '@asyncapi/java-template': 'Java Project', '@asyncapi/markdown-template': 'Markdown Documentation', '@asyncapi/nodejs-template': 'NodeJS Project', - '@asyncapi/nodejs-ws-template': 'NodeJS WebSocket Project', + '@asyncapi/nodejs-ws-template': 'NodeJS WebSocket Projects', + '@asyncapi/php-template': 'PHP RabbitMQ Project', '@asyncapi/python-paho-template': 'Python Paho Project', - '@asyncapi/ts-nats-template': 'Typescript Nats Project', + '@asyncapi/ts-nats-template': 'Typescript NATS Project', }; const SUPPORTED_TEMPLATES = Object.keys(TEMPLATES); diff --git a/apps/studio-next/src/components/Modals/Generator/GeneratorModal.tsx b/apps/studio-next/src/components/Modals/Generator/GeneratorModal.tsx index 013184a32..b638aa858 100644 --- a/apps/studio-next/src/components/Modals/Generator/GeneratorModal.tsx +++ b/apps/studio-next/src/components/Modals/Generator/GeneratorModal.tsx @@ -15,15 +15,14 @@ import templates from './template-parameters.json'; const unsupportedGenerators = [ '@asyncapi/dotnet-nats-template', - '@asyncapi/ts-nats-template', - '@asyncapi/python-paho-template', - '@asyncapi/nodejs-ws-template', - '@asyncapi/java-spring-cloud-stream-template', '@asyncapi/go-watermill-template', + '@asyncapi/java-spring-cloud-stream-template', '@asyncapi/java-spring-template', '@asyncapi/nodejs-template', - '@asyncapi/java-template', - '@asyncapi/php-template' + '@asyncapi/nodejs-ws-template', + '@asyncapi/php-template', + '@asyncapi/python-paho-template', + '@asyncapi/ts-nats-template', ]; const renderOptions = (actualVersion: string) => { diff --git a/apps/studio-next/src/components/Sidebar.tsx b/apps/studio-next/src/components/Sidebar.tsx index 00cb27fb6..4f704f3aa 100644 --- a/apps/studio-next/src/components/Sidebar.tsx +++ b/apps/studio-next/src/components/Sidebar.tsx @@ -46,6 +46,7 @@ interface NavItem { icon: ReactNode; tooltip: ReactNode; enabled: boolean; + dataTest: string; } interface SidebarProps {} @@ -53,8 +54,8 @@ interface SidebarProps {} export const Sidebar: FunctionComponent = () => { const { show, secondaryPanelType } = usePanelsState(); const document = useDocumentsState(state => state.documents['asyncapi']?.document) || null; - const isV3 = document?.version() === '3.0.0'; - + const isV3 = document?.version().startsWith('3.'); + if (show.activityBar === false) { return null; } @@ -68,7 +69,8 @@ export const Sidebar: FunctionComponent = () => { onClick: () => updateState('primarySidebar'), icon: , tooltip: 'Navigation', - enabled: true + enabled: true, + dataTest: 'button-navigation', }, // editor { @@ -78,27 +80,30 @@ export const Sidebar: FunctionComponent = () => { onClick: () => updateState('primaryPanel'), icon: , tooltip: 'Editor', - enabled: true + enabled: true, + dataTest: 'button-editor', }, // template { name: 'template', - title: 'Template', + title: 'Template preview', isActive: show.secondaryPanel && secondaryPanelType === 'template', onClick: () => updateState('secondaryPanel', 'template'), icon: , - tooltip: 'HTML preview', - enabled: true + tooltip: 'Template preview', + enabled: true, + dataTest: 'button-template-preview', }, // visuliser { name: 'visualiser', - title: 'Visualiser', + title: 'Blocks visualiser', isActive: show.secondaryPanel && secondaryPanelType === 'visualiser', onClick: () => updateState('secondaryPanel', 'visualiser'), icon: , tooltip: 'Blocks visualiser', - enabled: !isV3 + enabled: !isV3, + dataTest: 'button-blocks-visualiser', }, // newFile { @@ -108,7 +113,8 @@ export const Sidebar: FunctionComponent = () => { onClick: () => showModal(ConfirmNewFileModal), icon: , tooltip: 'New file', - enabled: true + enabled: true, + dataTest: 'button-new-file', }, ]; @@ -120,10 +126,10 @@ export const Sidebar: FunctionComponent = () => { {navigation.map(item => ( diff --git a/apps/studio-next/src/components/Toolbar.tsx b/apps/studio-next/src/components/Toolbar.tsx index 4c0f85a27..fcf821aca 100644 --- a/apps/studio-next/src/components/Toolbar.tsx +++ b/apps/studio-next/src/components/Toolbar.tsx @@ -6,12 +6,12 @@ export function Toolbar() {
-
+
AsyncAPI Logo beta @@ -19,17 +19,17 @@ export function Toolbar() {