diff --git a/packages/app/src/debug/empty/DebugEmptyStates.cy.tsx b/packages/app/src/debug/empty/DebugEmptyStates.cy.tsx index f07e42d1b02d..a48f1b91d8f8 100644 --- a/packages/app/src/debug/empty/DebugEmptyStates.cy.tsx +++ b/packages/app/src/debug/empty/DebugEmptyStates.cy.tsx @@ -3,6 +3,7 @@ import DebugNoProject from './DebugNoProject.vue' import DebugNoRuns from './DebugNoRuns.vue' import DebugLoading from './DebugLoading.vue' import DebugError from './DebugError.vue' +import DebugEmptyView from './DebugEmptyView.vue' import { useLoginConnectStore } from '@packages/frontend-shared/src/store/login-connect-store' import { DebugEmptyView_RecordEventDocument, DebugEmptyView_SetPreferencesDocument, UseCohorts_DetermineCohortDocument, _DebugEmptyViewFragment, _DebugEmptyViewFragmentDoc } from '../../generated/graphql-test' import { DEBUG_SLIDESHOW } from '../utils/constants' @@ -58,6 +59,22 @@ function mountWithGql (component: JSX.Element, gqlOptions?: { debugSlideshowComp } describe('Debug page empty states', () => { + context('empty view', () => { + it('renders with slot', () => { + const slotVariableStub = cy.stub().as('slot') + + mountWithGql( + + {{ + cta: slotVariableStub, + }} + , + ) + + cy.get('@slot').should('be.calledWith', { utmContent: Cypress.sinon.match.string }) + }) + }) + context('not logged in', () => { it('renders', () => { const loginConnectStore = useLoginConnectStore() diff --git a/packages/app/src/debug/empty/DebugEmptyView.vue b/packages/app/src/debug/empty/DebugEmptyView.vue index b169f3fd826b..bdea0970aebf 100644 --- a/packages/app/src/debug/empty/DebugEmptyView.vue +++ b/packages/app/src/debug/empty/DebugEmptyView.vue @@ -15,7 +15,10 @@ - + - diff --git a/packages/app/src/debug/empty/DebugNotLoggedIn.vue b/packages/app/src/debug/empty/DebugNotLoggedIn.vue index 3c93a88e6ad2..cd3810c0b043 100644 --- a/packages/app/src/debug/empty/DebugNotLoggedIn.vue +++ b/packages/app/src/debug/empty/DebugNotLoggedIn.vue @@ -7,8 +7,11 @@ :slideshow-campaign="DEBUG_SLIDESHOW.campaigns.login" help-link-href="https://on.cypress.io/debug-page" > - diff --git a/packages/app/src/debug/utils/constants.ts b/packages/app/src/debug/utils/constants.ts index 5cbf8a446c2c..7ab6f734f36a 100644 --- a/packages/app/src/debug/utils/constants.ts +++ b/packages/app/src/debug/utils/constants.ts @@ -7,7 +7,7 @@ export const DEBUG_SLIDESHOW = { connectProject: 'Debug Connect Project Empty State', recordRun: 'Debug Record Run Empty State', }, - medium: 'Debug tab', + medium: 'Debug Tab', } as const export type DebugSlideshowCampaigns = ValueOf diff --git a/packages/app/src/runs/CloudConnectButton.cy.tsx b/packages/app/src/runs/CloudConnectButton.cy.tsx index 05cb7a6cd106..35e9c5825ecb 100644 --- a/packages/app/src/runs/CloudConnectButton.cy.tsx +++ b/packages/app/src/runs/CloudConnectButton.cy.tsx @@ -31,7 +31,16 @@ describe('', { viewportHeight: 60, viewportWidth: 400 }, ( cy.contains('button', 'Connect a Cypress Cloud project').click() - cy.get('@openLoginConnectModal').should('have.been.calledWith', { utmMedium: 'testing' }) + cy.get('@openLoginConnectModal').should('have.been.calledWith', { utmMedium: 'testing', utmContent: undefined }) + }) + + it('uses the store to open the Login Connect modal with utmContent', () => { + loginConnectStore.openLoginConnectModal = cy.spy().as('openLoginConnectModal') + cy.mount(() =>
) + + cy.contains('button', 'Connect a Cypress Cloud project').click() + + cy.get('@openLoginConnectModal').should('have.been.calledWith', { utmMedium: 'testing', utmContent: 'content' }) }) }) }) diff --git a/packages/app/src/runs/CloudConnectButton.vue b/packages/app/src/runs/CloudConnectButton.vue index fba70554b6d0..255d8d389997 100644 --- a/packages/app/src/runs/CloudConnectButton.vue +++ b/packages/app/src/runs/CloudConnectButton.vue @@ -3,7 +3,7 @@ :class="props.class" :prefix-icon="user.isLoggedIn ? ChainIcon : CypressIcon" prefix-icon-class="icon-dark-white icon-light-transparent" - @click="openLoginConnectModal({ utmMedium: props.utmMedium })" + @click="openLoginConnectModal({ utmMedium: utmMedium, utmContent: utmContent })" > {{ user.isLoggedIn ? t('runs.connect.buttonProject') : t('runs.connect.buttonUser') }} @@ -24,6 +24,7 @@ const { t } = useI18n() const props = defineProps<{ class?: string utmMedium: string + utmContent?: string }>() diff --git a/packages/frontend-shared/src/gql-components/LoginConnectModalsContent.cy.tsx b/packages/frontend-shared/src/gql-components/LoginConnectModalsContent.cy.tsx index ba64ce522afd..d828523e259b 100644 --- a/packages/frontend-shared/src/gql-components/LoginConnectModalsContent.cy.tsx +++ b/packages/frontend-shared/src/gql-components/LoginConnectModalsContent.cy.tsx @@ -1,4 +1,4 @@ -import { LoginConnectModalsContentFragmentDoc } from '../generated/graphql-test' +import { Auth_LoginDocument, LoginConnectModalsContentFragmentDoc } from '../generated/graphql-test' import LoginConnectModalsContent from './LoginConnectModalsContent.vue' import { CloudUserStubs } from '@packages/graphql/test/stubCloudTypes' import { SelectCloudProjectModal_CreateCloudProjectDocument } from '../generated/graphql' @@ -7,96 +7,118 @@ import { useLoginConnectStore } from '../store/login-connect-store' describe('', () => { context('when user is logged out', () => { - it('shows login modal', () => { - const { openLoginConnectModal } = useLoginConnectStore() - - cy.mountFragment(LoginConnectModalsContentFragmentDoc, { - onResult: (result) => { - result.cloudViewer = null - }, - render: (gqlVal) => { - return - }, - }) + [undefined, 'testContent'].forEach((content) => { + it(`shows login modal with utmContent: ${content}`, () => { + const { openLoginConnectModal } = useLoginConnectStore() + + cy.mountFragment(LoginConnectModalsContentFragmentDoc, { + onResult: (result) => { + result.cloudViewer = null + }, + render: (gqlVal) => { + return + }, + }) + + cy.contains('Log in to Cypress') + .should('not.exist') + .then(() => { + openLoginConnectModal({ utmMedium: 'testing', utmContent: content }) + + cy.contains('Log in to Cypress').should('be.visible') + }) - cy.contains('Log in to Cypress') - .should('not.exist') - .then(() => { - openLoginConnectModal({ utmMedium: 'testing' }) + const loginStub = cy.stub().as('createProjectStub') - cy.contains('Log in to Cypress').should('be.visible') + cy.stubMutationResolver(Auth_LoginDocument, (defineResult, variables) => { + loginStub(variables) + + return defineResult({} as any) + }) + + cy.contains('button', 'Log in') + .click() + .then(() => { + expect(loginStub.lastCall.args[0]).to.deep.eq({ + utmSource: 'Binary: Launchpad', + utmMedium: 'testing', + utmContent: content || null, + }) + }) }) }) }) context('when user is logged in', () => { - it('shows "Create Project" state if project is not set up', () => { - const { openLoginConnectModal, setUserFlag, setProjectFlag } = useLoginConnectStore() - - setUserFlag('isLoggedIn', true) - setUserFlag('isMemberOfOrganization', true) - setUserFlag('isOrganizationLoaded', true) - setProjectFlag('isConfigLoaded', true) - setProjectFlag('isProjectConnected', false) - - cy.mountFragment(LoginConnectModalsContentFragmentDoc, { - onResult: (result) => { - result.cloudViewer = { ...CloudUserStubs.me, - firstOrganization: { - __typename: 'CloudOrganizationConnection', - nodes: [{ __typename: 'CloudOrganization', id: '122' }], - }, - organizations: { - __typename: 'CloudOrganizationConnection', - nodes: [{ - __typename: 'CloudOrganization', - name: `Cypress Test Account`, - id: '122', - projects: { - nodes: [], - }, - }], - }, - } - - result.currentProject = null - }, - render: (gqlVal) => { - return - }, - }) + [undefined, 'testContent'].forEach((content) => { + it('shows "Create Project" state if project is not set up', () => { + const { openLoginConnectModal, setUserFlag, setProjectFlag } = useLoginConnectStore() + + setUserFlag('isLoggedIn', true) + setUserFlag('isMemberOfOrganization', true) + setUserFlag('isOrganizationLoaded', true) + setProjectFlag('isConfigLoaded', true) + setProjectFlag('isProjectConnected', false) + + cy.mountFragment(LoginConnectModalsContentFragmentDoc, { + onResult: (result) => { + result.cloudViewer = { ...CloudUserStubs.me, + firstOrganization: { + __typename: 'CloudOrganizationConnection', + nodes: [{ __typename: 'CloudOrganization', id: '122' }], + }, + organizations: { + __typename: 'CloudOrganizationConnection', + nodes: [{ + __typename: 'CloudOrganization', + name: `Cypress Test Account`, + id: '122', + projects: { + nodes: [], + }, + }], + }, + } + + result.currentProject = null + }, + render: (gqlVal) => { + return + }, + }) - const createProjectStub = cy.stub().as('createProjectStub') + const createProjectStub = cy.stub().as('createProjectStub') - cy.stubMutationResolver(SelectCloudProjectModal_CreateCloudProjectDocument, (defineResult, variables) => { - createProjectStub(variables) + cy.stubMutationResolver(SelectCloudProjectModal_CreateCloudProjectDocument, (defineResult, variables) => { + createProjectStub(variables) - return defineResult({} as any) - }) + return defineResult({} as any) + }) - cy.contains('Create project') - .should('not.exist') - .then(() => { - openLoginConnectModal({ utmMedium: 'testing' }) - }) + cy.contains('Create project') + .should('not.exist') + .then(() => { + openLoginConnectModal({ utmMedium: 'testing', utmContent: content }) + }) - cy.findAllByLabelText('Project name*(You can change this later)').type('test-project') - - cy.contains('button', 'Create project') - .click() - .then(() => { - expect(createProjectStub.lastCall.args[0]).to.deep.eq({ - name: 'test-project', - orgId: '122', - medium: 'testing', - source: 'Binary: Launchpad', - public: false, - campaign: 'Create project', - cohort: '', + cy.findAllByLabelText('Project name*(You can change this later)').type('test-project') + + cy.contains('button', 'Create project') + .click() + .then(() => { + expect(createProjectStub.lastCall.args[0]).to.deep.eq({ + name: 'test-project', + orgId: '122', + medium: 'testing', + source: 'Binary: Launchpad', + public: false, + campaign: 'Create project', + cohort: content || '', + }) }) - }) - cy.get('@createProjectStub').should('have.been.calledOnce') + cy.get('@createProjectStub').should('have.been.calledOnce') + }) }) }) }) diff --git a/packages/frontend-shared/src/gql-components/LoginConnectModalsContent.vue b/packages/frontend-shared/src/gql-components/LoginConnectModalsContent.vue index f8bca76719ec..3626b8f4df96 100644 --- a/packages/frontend-shared/src/gql-components/LoginConnectModalsContent.vue +++ b/packages/frontend-shared/src/gql-components/LoginConnectModalsContent.vue @@ -4,12 +4,14 @@ v-if="userStatusMatches('isLoggedOut') || keepLoginOpen" :gql="gqlRef" :utm-medium="loginConnectStore.utmMedium" + :utm-content="loginConnectStore.utmContent" @cancel="closeLoginConnectModal" @close="handleCloseLogin" /> diff --git a/packages/frontend-shared/src/store/login-connect-store.ts b/packages/frontend-shared/src/store/login-connect-store.ts index 1ed3c0f3b5b9..91a1a7f904b7 100644 --- a/packages/frontend-shared/src/store/login-connect-store.ts +++ b/packages/frontend-shared/src/store/login-connect-store.ts @@ -10,6 +10,7 @@ export interface LoginConnectState { hasInitiallyLoaded: boolean isLoginConnectOpen: boolean utmMedium: string + utmContent?: string cypressFirstOpened?: number user: { isLoggedIn: boolean @@ -54,6 +55,7 @@ export const useLoginConnectStore = defineStore({ return { hasInitiallyLoaded: false, utmMedium: '', + utmContent: undefined, isLoginConnectOpen: false, cypressFirstOpened: undefined, userData: undefined, @@ -80,13 +82,15 @@ export const useLoginConnectStore = defineStore({ setHasInitiallyLoaded () { this.hasInitiallyLoaded = true }, - openLoginConnectModal ({ utmMedium }: { utmMedium: string }) { + openLoginConnectModal ({ utmMedium, utmContent }: { utmMedium: string, utmContent?: string }) { this.isLoginConnectOpen = true this.utmMedium = utmMedium + this.utmContent = utmContent }, closeLoginConnectModal () { this.isLoginConnectOpen = false this.utmMedium = '' + this.utmContent = undefined }, setUserFlag (name: keyof LoginConnectState['user'], newVal: boolean) { this.user[name] = newVal