diff --git a/.vscode/cspell.json b/.vscode/cspell.json index 5649488074b9..17e85156931c 100644 --- a/.vscode/cspell.json +++ b/.vscode/cspell.json @@ -37,6 +37,7 @@ "topnav", "unconfigured", "unplugin", + "unref", "unrunnable", "unstaged", "urql", @@ -48,4 +49,4 @@ ], "ignoreWords": [], "import": [] -} \ No newline at end of file +} diff --git a/graphql-codegen.yml b/graphql-codegen.yml index 496238eb8d1d..e2b11c51214b 100644 --- a/graphql-codegen.yml +++ b/graphql-codegen.yml @@ -18,7 +18,7 @@ vueOperations: &vueOperations - 'typescript-operations' - 'typed-document-node': # Intentionally specified under typed-document-node rather than top level config, - # becuase we don't want it flattening the types for the operations + # because we don't want it flattening the types for the operations flattenGeneratedTypes: true vueTesting: &vueTesting @@ -122,9 +122,10 @@ generates: './packages/app/src/generated/graphql-test.ts': documents: - './packages/app/src/**/*.{vue,ts,tsx,js,jsx}' - - './packages/frontend-shared/src/**/*.{vue,ts,tsx,js,jsx}' + - './packages/frontend-shared/src/gql-components/**/*.{vue,ts,tsx,js,jsx}' <<: *vueTesting './packages/frontend-shared/src/generated/graphql-test.ts': - documents: './packages/frontend-shared/src/gql-components/**/*.{vue,ts,tsx,js,jsx}' + documents: + - './packages/frontend-shared/src/gql-components/**/*.{vue,ts,tsx,js,jsx}' <<: *vueTesting diff --git a/packages/app/cypress/e2e/runs.cy.ts b/packages/app/cypress/e2e/runs.cy.ts index c03d84a13489..263a65218663 100644 --- a/packages/app/cypress/e2e/runs.cy.ts +++ b/packages/app/cypress/e2e/runs.cy.ts @@ -116,6 +116,10 @@ describe('App: Runs', { viewportWidth: 1200 }, () => { obj.result.data.cloudViewer.organizations.nodes = [] } + if (obj.result.data?.cloudViewer?.firstOrganization?.nodes) { + obj.result.data.cloudViewer.firstOrganization.nodes = [] + } + return obj.result }) @@ -150,8 +154,14 @@ describe('App: Runs', { viewportWidth: 1200 }, () => { cy.startAppServer('component') cy.remoteGraphQLIntercept(async (obj) => { - if (obj?.result?.data?.cloudViewer?.organizations?.nodes) { - obj.result.data.cloudViewer.organizations.nodes = [] + if ((obj.operationName !== 'CreateCloudOrgModal_CloudOrganizationsCheck_refreshOrganizations_cloudViewer')) { + if (obj.result.data?.cloudViewer?.organizations?.nodes) { + obj.result.data.cloudViewer.organizations.nodes = [] + } + + if (obj.result.data?.cloudViewer?.firstOrganization?.nodes) { + obj.result.data.cloudViewer.firstOrganization.nodes = [] + } } return obj.result @@ -181,7 +191,7 @@ describe('App: Runs', { viewportWidth: 1200 }, () => { cy.startAppServer('component') cy.remoteGraphQLIntercept(async (obj, testState) => { - if (obj.operationName === 'CloudConnectModals_RefreshCloudViewer_refreshCloudViewer_cloudViewer') { + if (obj.operationName === 'LoginConnectModals_LoginConnectModalsQuery_cloudViewer') { testState.refetchCalled = true } @@ -312,7 +322,7 @@ describe('App: Runs', { viewportWidth: 1200 }, () => { moveToRunsPage() cy.findByText(defaultMessages.runs.connect.buttonProject).click() - cy.get('button').contains(defaultMessages.runs.connect.modal.selectProject.createProject).click() + cy.contains('button', defaultMessages.runs.connect.modal.selectProject.createProject).click() cy.findByText(defaultMessages.runs.connectSuccessAlert.title).should('be.visible') cy.withCtx(async (ctx) => { @@ -351,7 +361,7 @@ describe('App: Runs', { viewportWidth: 1200 }, () => { cy.get('[href="#/runs"]').click() cy.findByText(defaultMessages.runs.connect.buttonProject).click() - cy.get('button').contains(defaultMessages.runs.connect.modal.selectProject.createProject).click() + cy.contains('button', defaultMessages.runs.connect.modal.selectProject.createProject).click() cy.get('[data-cy="alert"]').within(() => { cy.contains(defaultMessages.runs.connect.errors.baseError.title) @@ -386,7 +396,7 @@ describe('App: Runs', { viewportWidth: 1200 }, () => { cy.get('[href="#/runs"]').click() cy.findByText(defaultMessages.runs.connect.buttonProject).click() - cy.get('button').contains(defaultMessages.runs.connect.modal.selectProject.createProject).click() + cy.contains('button', defaultMessages.runs.connect.modal.selectProject.createProject).click() cy.get('[data-cy="alert"]').within(() => { cy.contains(defaultMessages.runs.connect.errors.internalServerError.title) @@ -417,7 +427,7 @@ describe('App: Runs', { viewportWidth: 1200 }, () => { } if (obj.result.data?.cloudViewer?.organizations?.nodes) { - const projectNodes = obj.result.data?.cloudViewer.organizations.nodes[0].projects.nodes + const projectNodes = obj.result.data.cloudViewer.organizations.nodes[0].projects.nodes projectNodes.push({ __typename: 'CloudProject', @@ -440,9 +450,10 @@ describe('App: Runs', { viewportWidth: 1200 }, () => { }) it('opens Connect Project modal after clicking Reconnect Project button', () => { - cy.findByText(defaultMessages.runs.errors.notFound.button).should('be.visible').click() + cy.findByText(defaultMessages.runs.errors.notFound.button).click() + cy.get('[aria-modal="true"]').should('exist') - cy.get('[data-cy="selectProject"] button').should('have.text', 'Mock Project') + cy.contains('[data-cy="selectProject"] button', 'Mock Project') cy.get('[data-cy="connect-project"]').click() cy.get('[data-cy="runs"]', { timeout: 7500 }) }) @@ -510,12 +521,13 @@ describe('App: Runs', { viewportWidth: 1200 }, () => { it('updates the button text when the request access button is clicked', () => { cy.remoteGraphQLIntercept(async (obj, testState) => { - if (obj.operationName === 'Runs_currentProject_cloudProject_cloudProjectBySlug') { + if (obj.operationName?.includes('cloudProject_cloudProjectBySlug')) { const proj = obj!.result!.data!.cloudProjectBySlug proj.__typename = 'CloudProjectUnauthorized' proj.message = 'Cloud Project Unauthorized' proj.hasRequestedAccess = false + testState.project = proj } else if (obj.operationName === 'RunsErrorRenderer_RequestAccess_cloudProjectRequestAccess') { obj!.result!.data!.cloudProjectRequestAccess = { @@ -696,7 +708,7 @@ describe('App: Runs', { viewportWidth: 1200 }, () => { cy.loginUser() cy.remoteGraphQLIntercept((obj) => { - if (obj.operationName === 'Runs_currentProject_cloudProject_cloudProjectBySlug') { + if (obj.operationName?.includes('cloudProject_cloudProjectBySlug')) { cloudData = obj.result obj.result = {} @@ -714,7 +726,7 @@ describe('App: Runs', { viewportWidth: 1200 }, () => { // cy.percySnapshot() // TODO: restore when Percy CSS is fixed. See https://github.com/cypress-io/cypress/issues/23435 cy.remoteGraphQLIntercept((obj) => { - if (obj.operationName === 'Runs_currentProject_cloudProject_cloudProjectBySlug') { + if (obj.operationName?.includes('cloudProject_cloudProjectBySlug')) { return cloudData } @@ -732,10 +744,6 @@ describe('App: Runs', { viewportWidth: 1200 }, () => { cy.startAppServer('component') }) - afterEach(() => { - cy.goOnline() - }) - it('shows alert warning if runs have been returned already', () => { cy.loginUser() cy.visitApp() @@ -773,11 +781,15 @@ describe('App: Runs', { viewportWidth: 1200 }, () => { cy.openProject('component-tests', ['--config-file', 'cypressWithoutProjectId.config.js']) cy.startAppServer('component') - cy.remoteGraphQLIntercept(async (obj) => { + cy.remoteGraphQLIntercept((obj) => { if (obj.result.data?.cloudViewer?.organizations?.nodes) { obj.result.data.cloudViewer.organizations.nodes = [] } + if (obj.result.data?.cloudViewer?.firstOrganization?.nodes) { + obj.result.data.cloudViewer.firstOrganization.nodes = [] + } + return obj.result }) @@ -845,7 +857,7 @@ describe('App: Runs', { viewportWidth: 1200 }, () => { cy.openProject('component-tests') cy.startAppServer('component') cy.loginUser() - cy.remoteGraphQLIntercept((obj, testState) => { + cy.remoteGraphQLIntercept((obj) => { if (obj.result.data?.cloudProjectBySlug?.runs?.nodes.length) { obj.result.data.cloudProjectBySlug.runs.nodes.map((run) => { run.status = 'RUNNING' diff --git a/packages/app/cypress/e2e/specs_list_latest_runs.cy.ts b/packages/app/cypress/e2e/specs_list_latest_runs.cy.ts index 6d99a9b6a384..7a661af52f78 100644 --- a/packages/app/cypress/e2e/specs_list_latest_runs.cy.ts +++ b/packages/app/cypress/e2e/specs_list_latest_runs.cy.ts @@ -160,7 +160,7 @@ describe('App/Cloud Integration - Latest runs and Average duration', { viewportW }) context('when no runs are recorded', () => { - beforeEach(() => { + it('shows placeholders for all visible specs', { defaultCommandTimeout: 6000 }, () => { cy.loginUser() cy.remoteGraphQLIntercept(async (obj) => { @@ -181,10 +181,6 @@ describe('App/Cloud Integration - Latest runs and Average duration', { viewportW }) cy.visitApp() - cy.findByTestId('sidebar-link-specs-page').click() - }) - - it('shows placeholders for all visible specs', () => { allVisibleSpecsShouldBePlaceholders() }) }) diff --git a/packages/app/cypress/e2e/subscriptions/createCloudOrgModal-subscription.cy.ts b/packages/app/cypress/e2e/subscriptions/createCloudOrgModal-subscription.cy.ts index 41f607621d12..1d4365597b50 100644 --- a/packages/app/cypress/e2e/subscriptions/createCloudOrgModal-subscription.cy.ts +++ b/packages/app/cypress/e2e/subscriptions/createCloudOrgModal-subscription.cy.ts @@ -1,7 +1,7 @@ import defaultMessages from '@packages/frontend-shared/src/locales/en-US.json' import type { SinonStub } from 'sinon' -describe('App: Runs', { viewportWidth: 1200 }, () => { +describe('CreateCloudOrgModalSubscription', { viewportWidth: 1200 }, () => { beforeEach(() => { cy.scaffoldProject('component-tests') cy.openProject('component-tests') @@ -22,6 +22,10 @@ describe('App: Runs', { viewportWidth: 1200 }, () => { obj.result.data.cloudViewer.organizations.nodes = [] } + if (obj.result.data?.cloudViewer?.firstOrganization?.nodes) { + obj.result.data.cloudViewer.firstOrganization.nodes = [] + } + return obj.result }) diff --git a/packages/app/cypress/e2e/top-nav.cy.ts b/packages/app/cypress/e2e/top-nav.cy.ts index 330e5f0796b1..e65a10d484f7 100644 --- a/packages/app/cypress/e2e/top-nav.cy.ts +++ b/packages/app/cypress/e2e/top-nav.cy.ts @@ -453,6 +453,17 @@ describe('App Top Nav Workflows', () => { cy.openProject('component-tests', ['--config-file', 'cypressWithoutProjectId.config.js']) cy.startAppServer() cy.visitApp() + cy.remoteGraphQLIntercept(async (obj) => { + if (obj.result.data?.cloudViewer) { + obj.result.data.cloudViewer.organizations = { + __typename: 'CloudOrganizationConnection', + id: 'test', + nodes: [{ __typename: 'CloudOrganization', id: '987' }], + } + } + + return obj.result + }) mockLogInActionsForUser(mockUser) logIn({ expectedNextStepText: 'Connect project', displayName: mockUser.name }) @@ -494,6 +505,24 @@ describe('App Top Nav Workflows', () => { cy.findByTestId('app-header-bar').findByTestId('user-avatar-title').should('be.visible') }) + it('if the project has no runs, shows "record your first run" prompt after clicking', () => { + cy.remoteGraphQLIntercept((obj) => { + if (obj.result?.data?.cloudProjectBySlug?.runs?.nodes?.length) { + obj.result.data.cloudProjectBySlug.runs.nodes = [] + } + + return obj.result + }) + + mockLogInActionsForUser(mockUserNoName) + + logIn({ expectedNextStepText: 'Continue', displayName: mockUserNoName.email }) + + cy.contains('[data-cy=standard-modal] h2', defaultMessages.specPage.banners.record.title).should('be.visible') + cy.contains('[data-cy=standard-modal]', defaultMessages.specPage.banners.record.content).should('be.visible') + cy.contains('button', 'Copy').should('be.visible') + }) + it('shows correct error when browser cannot launch', () => { cy.withCtx((ctx, o) => { o.sinon.stub(ctx._apis.authApi, 'logIn').callsFake(async (onMessage) => { diff --git a/packages/app/src/App.vue b/packages/app/src/App.vue index d76b3823321d..c0e8a339e7fe 100644 --- a/packages/app/src/App.vue +++ b/packages/app/src/App.vue @@ -4,13 +4,31 @@ :is="Component" /> + + + +