feat: add RecordRunModal component (#23953)

* feat: add RecordRunModal component

* feat: add missing assertion

* feat: import using @packages syntax

* feat: remove unnecessary null check

* feat: move record key gql logic to separate component

* feat: add another test case

* feat: refactor RunsEmpty component

* feat: update test

* feat: remove unused query

* feat: use translation strings in tests

* feat: assert that command shows in RecordRunModal

Co-authored-by: Lachlan Miller <lachlan.miller.1990@outlook.com>
This commit is contained in:
Adam Stone
2022-09-28 04:09:07 +00:00
committed by GitHub
parent 296cebb09c
commit 10f2961c17
9 changed files with 204 additions and 100 deletions
-2
View File
@@ -20,7 +20,6 @@
/>
<RunsEmpty
v-else-if="!currentProject?.cloudProject?.runs?.nodes.length"
:gql="currentProject"
/>
<div
v-else
@@ -80,7 +79,6 @@ fragment RunsContainer on Query {
currentProject {
id
projectId
...RunsEmpty
...RunsConnectSuccessAlert
cloudProject {
__typename
+22 -10
View File
@@ -1,14 +1,26 @@
import RunsEmpty from './RunsEmpty.vue'
import { RunsEmptyFragmentDoc } from '../generated/graphql-test'
import { defaultMessages } from '@cy/i18n'
describe('<RunsEmpty />', () => {
it('playground', () => {
cy.mountFragment(RunsEmptyFragmentDoc, {
render (gqlVal) {
return (<div class="h-screen">
<RunsEmpty gql={gqlVal} />
</div>)
},
})
describe('RunsEmpty', () => {
it('renders expected content', () => {
cy.mount(<RunsEmpty />)
cy.gqlStub.Query.currentProject = {
id: 'test_id',
title: 'project_title',
currentTestingType: 'component',
cloudProject: {
__typename: 'CloudProject',
id: 'cloud_id',
recordKeys: [{
__typename: 'CloudRecordKey',
id: 'recordKey1',
key: 'abcd-efg-1234',
}],
} as any,
} as any
cy.contains(defaultMessages.specPage.banners.record.title).should('be.visible')
cy.contains('npx cypress run --component --record --key abcd-efg-1234').should('be.visible')
})
})
+2 -46
View File
@@ -10,59 +10,15 @@
<p class="h-48px mb-8px text-gray-600">
{{ t("runs.empty.description") }}
</p>
<TerminalPrompt
:command="recordCommand"
:project-folder-name="projectName"
class="max-w-700px"
/>
<RecordPromptAdapter />
</div>
</template>
<script lang="ts" setup>
import { computed } from 'vue'
import { gql } from '@urql/vue'
import TerminalPrompt from '@cy/components/TerminalPrompt.vue'
import type { RunsEmptyFragment } from '../generated/graphql'
import RecordPromptAdapter from '@packages/frontend-shared/src/gql-components/RecordPromptAdapter.vue'
import { useI18n } from '@cy/i18n'
const { t } = useI18n()
gql`
fragment RunsEmpty on CurrentProject {
id
title
projectId
configFile
currentTestingType
cloudProject {
__typename
... on CloudProject {
id
recordKeys {
id
...RecordKey
}
}
}
}
`
const props = defineProps<{
gql: RunsEmptyFragment
}>()
const projectName = computed(() => props.gql.title)
const firstRecordKey = computed(() => {
return props.gql.cloudProject?.__typename === 'CloudProject' && props.gql.cloudProject.recordKeys?.[0]?.key
? props.gql.cloudProject.recordKeys[0].key
: '<record-key>'
})
const recordCommand = computed(() => {
const componentFlagOrSpace = props.gql.currentTestingType === 'component' ? ' --component ' : ' '
return `npx cypress run${componentFlagOrSpace}--record --key ${firstRecordKey.value}`
})
</script>
<style scoped lang="scss">
@@ -19,44 +19,16 @@
<p class="mb-24px">
{{ t('specPage.banners.record.content') }}
</p>
<TerminalPrompt
:command="recordCommand"
:project-folder-name="query.data?.value?.currentProject?.title"
class="bg-white max-w-900px"
/>
<RecordPromptAdapter />
</TrackedBanner>
</template>
<script setup lang="ts">
import { gql, useQuery } from '@urql/vue'
import RecordIcon from '~icons/cy/action-record_x16.svg'
import { useI18n } from '@cy/i18n'
import TerminalPrompt from '@cy/components/TerminalPrompt.vue'
import TrackedBanner from './TrackedBanner.vue'
import { BannerIds } from '@packages/types'
import { RecordBannerDocument } from '../../generated/graphql'
import { computed } from 'vue'
gql`
query RecordBanner {
currentProject {
id
title
currentTestingType
cloudProject {
__typename
... on CloudProject {
id
recordKeys {
id
key
}
}
}
}
}
`
import RecordPromptAdapter from '@packages/frontend-shared/src/gql-components/RecordPromptAdapter.vue'
defineProps<{
modelValue: boolean
@@ -70,16 +42,4 @@ const emit = defineEmits<{
const { t } = useI18n()
const bannerId = BannerIds.ACI_082022_RECORD
const query = useQuery({ query: RecordBannerDocument })
const firstRecordKey = computed(() => {
return (query.data?.value?.currentProject?.cloudProject?.__typename === 'CloudProject' && query.data.value.currentProject.cloudProject.recordKeys?.[0]?.key) ?? '<record-key>'
})
const recordCommand = computed(() => {
const componentFlagOrSpace = query.data?.value?.currentProject?.currentTestingType === 'component' ? ' --component ' : ' '
return `npx cypress run${componentFlagOrSpace}--record --key ${firstRecordKey.value}`
})
</script>
@@ -0,0 +1,27 @@
import RecordPromptVue from './RecordPrompt.vue'
describe('RecordPrompt', () => {
it('renders with no props', () => {
cy.mount(<RecordPromptVue />)
cy.findByText('npx cypress run --record --key <record-key>').should('be.visible')
})
it('renders with component testing type', () => {
cy.mount(<RecordPromptVue currentTestingType="component" />)
cy.findByText('npx cypress run --component --record --key <record-key>').should('be.visible')
})
it('renders with record key', () => {
cy.mount(<RecordPromptVue recordKey="abc-123" />)
cy.findByText('npx cypress run --record --key abc-123').should('be.visible')
})
it('renders with record key for component testing', () => {
cy.mount(<RecordPromptVue currentTestingType="component" recordKey="abc-123" />)
cy.findByText('npx cypress run --component --record --key abc-123').should('be.visible')
})
})
@@ -0,0 +1,27 @@
<template>
<TerminalPrompt
:command="recordCommand"
class="bg-white max-w-900px"
/>
</template>
<script setup lang="ts">
import TerminalPrompt from '@cy/components/TerminalPrompt.vue'
import { computed } from 'vue'
const props = defineProps<{
recordKey?: string | null
currentTestingType?: string | null
}>()
const firstRecordKey = computed(() => {
return props.recordKey ?? '<record-key>'
})
const recordCommand = computed(() => {
const componentFlagOrSpace = props.currentTestingType === 'component' ? ' --component ' : ' '
return `npx cypress run${componentFlagOrSpace}--record --key ${firstRecordKey.value}`
})
</script>
@@ -0,0 +1,35 @@
<template>
<RecordPrompt
:record-key="query.data.value?.currentProject?.cloudProject?.__typename === 'CloudProject' ? query.data.value?.currentProject.cloudProject.recordKeys?.[0]?.key : ''"
:current-testing-type="query.data.value?.currentProject?.currentTestingType"
/>
</template>
<script setup lang="ts">
import { gql, useQuery } from '@urql/vue'
import { RecordPromptAdapterDocument } from '../generated/graphql'
import RecordPrompt from './RecordPrompt.vue'
gql`
query RecordPromptAdapter {
currentProject {
id
title
currentTestingType
cloudProject {
__typename
... on CloudProject {
id
recordKeys {
id
key
}
}
}
}
}
`
const query = useQuery({ query: RecordPromptAdapterDocument })
</script>
@@ -0,0 +1,43 @@
import RecordRunModalVue from './RecordRunModal.vue'
import { defaultMessages } from '@cy/i18n'
describe('RecordRunModal', () => {
it('is not open by default', () => {
cy.mount(<RecordRunModalVue isModalOpen={false} close={cy.stub()} utmMedium="Nav" />)
cy.findByTestId('record-run-modal').should('not.exist')
})
it('renders open', () => {
cy.mount(<RecordRunModalVue isModalOpen={true} close={cy.stub()} utmMedium="Nav" />)
cy.contains(defaultMessages.specPage.banners.record.title).should('be.visible')
cy.findByTestId('copy-button').should('be.visible')
cy.contains('npx cypress run --record --key 2aaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa').should('be.visible')
cy.percySnapshot()
})
it('calls close when X is clicked', () => {
const closeStub = cy.stub()
cy.mount(<RecordRunModalVue isModalOpen={true} close={closeStub} utmMedium="Nav" />)
cy.findByRole('button', { name: defaultMessages.actions.close }).click().then(() => {
expect(closeStub).to.have.been.called
})
})
it('sends UTM parameters with help link', () => {
cy.mount(<RecordRunModalVue isModalOpen={true} close={cy.stub()} utmMedium="Nav" utmContent="content" />)
cy.contains(defaultMessages.links.needHelp).should('have.attr', 'href', 'https://on.cypress.io/cypress-run-record-key?utm_medium=Nav&utm_source=Binary%3A+Launchpad&utm_content=content')
})
it('sends UTM parameters with help link without UTM content prop', () => {
cy.mount(<RecordRunModalVue isModalOpen={true} close={cy.stub()} utmMedium="Nav" />)
cy.contains(defaultMessages.links.needHelp).should('have.attr', 'href', 'https://on.cypress.io/cypress-run-record-key?utm_medium=Nav&utm_source=Binary%3A+Launchpad&utm_content=')
})
})
@@ -0,0 +1,46 @@
<template>
<StandardModal
class="transition transition-all duration-200"
variant="bare"
:title="t('specPage.banners.record.title')"
:model-value="isModalOpen"
:help-link="helpLink"
:no-help="!helpLink"
data-cy="record-run-modal"
@update:model-value="close"
>
<div class="max-w-175 py-7 px-6 text-gray-600">
<p class="mb-24px">
{{ t('specPage.banners.record.content') }}
</p>
<RecordPromptAdapter />
</div>
</StandardModal>
</template>
<script setup lang="ts" >
import { useI18n } from '@cy/i18n'
import StandardModal from '../components/StandardModal.vue'
import RecordPromptAdapter from './RecordPromptAdapter.vue'
import { getUtmSource } from '../utils/getUtmSource'
import { getUrlWithParams } from '../utils/getUrlWithParams'
const { t } = useI18n()
const props = defineProps<{
isModalOpen: boolean
close: () => void
utmMedium: string
utmContent?: string
}>()
const helpLink = getUrlWithParams({
url: 'https://on.cypress.io/cypress-run-record-key',
params: {
utm_medium: props.utmMedium,
utm_source: getUtmSource(),
utm_content: props.utmContent || '',
},
})
</script>