fix: Correctly catch and clear errors during resetErrorAndLoadConfig mutation (#22514)

This commit is contained in:
Adam Stone
2022-07-01 10:14:02 -04:00
committed by GitHub
parent 6e2dce039e
commit a80c863685
10 changed files with 45 additions and 19 deletions
+1 -1
View File
@@ -24,7 +24,7 @@
<BaseError
v-if="query.data.value?.baseError"
:gql="query.data.value.baseError"
:retry="resetErrorAndLoadConfig"
@retry="resetErrorAndLoadConfig"
/>
<div v-else>
<Spinner />
@@ -22,7 +22,7 @@ export class ErrorActions {
}
if (d.baseError?.id === id) {
d.baseError === null
d.baseError = null
}
})
}
@@ -797,7 +797,7 @@ export class ProjectLifecycleManager {
* centrally in the e2e tests, as well as notify the "pending initialization"
* for run mode
*/
private onLoadError = (err: any) => {
onLoadError = (err: CypressError) => {
if (this.ctx.isRunMode && this._configManager) {
this._configManager.onLoadError(err)
} else {
@@ -237,7 +237,6 @@ function startAppServer (mode: 'component' | 'e2e' = 'e2e', options: { skipMocki
const isInitialized = o.pDefer()
const initializeActive = ctx.actions.project.initializeActiveProject
const onErrorStub = o.sinon.stub(ctx, 'onError')
// @ts-expect-error - errors b/c it's a private method
const onLoadErrorStub = o.sinon.stub(ctx.lifecycleManager, 'onLoadError')
const initializeActiveProjectStub = o.sinon.stub(ctx.actions.project, 'initializeActiveProject')
@@ -114,7 +114,7 @@ const props = defineProps<{
suffixIcon?: FunctionalComponent<SVGAttributes>
size?: ButtonSizes
variant?: ButtonVariants
prefixIconClass?: string
prefixIconClass?: string | Record<string, string | boolean>
suffixIconClass?: string
href?: string // will cause the button to render as link element with button styles
to?: object | string // will render as a router-link with button styles
@@ -21,7 +21,7 @@ describe('<BaseError />', () => {
it('renders the default error the correct messages', () => {
cy.mountFragment(BaseErrorFragmentDoc, {
render: (gqlVal) => <BaseError gql={gqlVal} />,
render: (gqlVal) => <BaseError gql={gqlVal} showButtons={false} />,
})
.get(headerSelector)
.should('contain.text', cy.gqlStub.ErrorWrapper.title)
@@ -31,7 +31,7 @@ describe('<BaseError />', () => {
.should('not.exist')
})
context('retry prop', () => {
context('retry action', () => {
const { docsButton } = cy.i18n.launchpadErrors.generic
const mountFragmentWithError = (errorProps = {}) => {
@@ -41,7 +41,7 @@ describe('<BaseError />', () => {
render: (gqlVal) => (<BaseError gql={{
...gqlVal,
...errorProps,
}} retry={retrySpy} />),
}} onRetry={retrySpy} />),
})
}
@@ -69,18 +69,19 @@ describe('<BaseError />', () => {
.should('have.attr', 'href', docsButton.configGuide.link)
})
it('calls the retry function passed in', () => {
it(`emits a 'retry' event when clicked`, () => {
mountFragmentWithError()
cy.get(retryButtonSelector)
.should('not.be.disabled')
.click()
.click()
.get('@retry')
.should('have.been.calledTwice')
})
it('does not render retry or docs buttons when retry prop is NOT passed in', () => {
it('does not render retry or docs buttons when showButtons prop is false', () => {
cy.mountFragment(BaseErrorFragmentDoc, {
render: (gqlVal) => <BaseError gql={gqlVal} />,
render: (gqlVal) => <BaseError gql={gqlVal} showButtons={false} />,
})
cy.get(retryButtonSelector).should('not.exist')
@@ -16,15 +16,15 @@
</h1>
<div
v-if="retry"
class="font-medium w-full inline-flex pt-12px justify-center gap-4 "
v-if="showButtons"
class="font-medium w-full pt-12px gap-4 inline-flex justify-center "
>
<Button
variant="outline"
data-testid="error-retry-button"
:prefix-icon="RestartIcon"
prefix-icon-class="icon-dark-indigo-500"
@click="retry?.(baseError.id)"
@click="emit('retry', baseError.id)"
>
{{ t('launchpadErrors.generic.retryButton') }}
</Button>
@@ -139,9 +139,13 @@ fragment BaseError on ErrorWrapper {
const { t } = useI18n()
const props = defineProps<{
const props = withDefaults(defineProps<{
gql: BaseErrorFragment
retry?: (id: string) => void
showButtons?: boolean
}>(), { showButtons: true })
const emit = defineEmits<{
(e: 'retry', id: string): void
}>()
const markdownTarget = ref()
@@ -33,7 +33,7 @@ export const mutation = mutationType({
},
resolve: async (_, args, ctx) => {
ctx.actions.error.clearError(args.id)
await ctx.lifecycleManager.refreshLifecycle()
await ctx.lifecycleManager.refreshLifecycle().catch(ctx.lifecycleManager.onLoadError)
return {}
},
@@ -147,8 +147,30 @@ describe('Launchpad: Error System Tests', () => {
cy.visitLaunchpad()
cy.contains('h1', cy.i18n.launchpadErrors.generic.configErrorTitle)
cy.percySnapshot()
cy.withCtx(async (ctx) => {
await ctx.actions.file.writeFileInProject('cypress.config.ts', 'module.exports = { e2e: { supportFile: false } }')
await ctx.actions.file.writeFileInProject('cypress.config.ts', 'export default { e2e: { supportFile: false } }')
})
cy.findByRole('button', { name: 'Try again' }).click()
cy.get('h1').should('contain', 'Welcome to Cypress')
})
it(`clears the error correctly after first 'try again' attempt`, () => {
cy.scaffoldProject('config-with-ts-syntax-error')
cy.openProject('config-with-ts-syntax-error')
cy.visitLaunchpad()
cy.contains('h1', cy.i18n.launchpadErrors.generic.configErrorTitle)
// Try again while the config is still invalid
cy.findByRole('button', { name: 'Try again' }).click()
// Wait until config error is on screen again
cy.contains('h1', cy.i18n.launchpadErrors.generic.configErrorTitle)
cy.withCtx(async (ctx) => {
await ctx.actions.file.writeFileInProject('cypress.config.ts', 'export default { e2e: { supportFile: false } }')
})
cy.findByRole('button', { name: 'Try again' }).click()
+1 -1
View File
@@ -17,7 +17,7 @@
<BaseError
v-if="query.data.value.baseError"
:gql="query.data.value.baseError"
:retry="resetErrorAndLoadConfig"
@retry="resetErrorAndLoadConfig"
/>
<GlobalPage
v-else-if="query.data.value.isInGlobalMode || !query.data.value?.currentProject"