mirror of
https://github.com/cypress-io/cypress.git
synced 2026-05-24 09:29:35 -05:00
Merge remote-tracking branch 'origin/chore/update-vue' into chore/update-vue
This commit is contained in:
@@ -63,3 +63,24 @@ npm/cypress-schematic/src/**/*.js
|
||||
/npm/create-cypress-tests/**/*.template.*
|
||||
|
||||
packages/data-context/test/unit/codegen/files
|
||||
|
||||
# community templates we test against, no need to lint
|
||||
system-tests/projects/create-react-app-configured/**/*
|
||||
system-tests/projects/create-react-app-unconfigured/**/*
|
||||
|
||||
system-tests/projects/vueclivue2-unconfigured/**/*
|
||||
system-tests/projects/vueclivue2-configured/**/*
|
||||
|
||||
system-tests/projects/vueclivue3-unconfigured/**/*
|
||||
system-tests/projects/vueclivue3-configured/**/*
|
||||
|
||||
system-tests/projects/vue3-vite-ts-unconfigured/**/*
|
||||
system-tests/projects/vue3-vite-ts-configured/**/*
|
||||
|
||||
system-tests/projects/nextjs-unconfigured/**/*
|
||||
system-tests/projects/nextjs-configured/**/*
|
||||
|
||||
system-tests/projects/nuxtjs-vue2-unconfigured/**/*
|
||||
system-tests/projects/nuxtjs-vue2-configured/**/*
|
||||
|
||||
system-tests/projects/react-app-webpack-5-unconfigured/**/*
|
||||
|
||||
+18
-12
@@ -24,29 +24,35 @@ const validators = specifiedRules
|
||||
)
|
||||
|
||||
module.exports = {
|
||||
'plugins': [
|
||||
plugins: [
|
||||
'@cypress/dev',
|
||||
'graphql',
|
||||
],
|
||||
'extends': [
|
||||
extends: [
|
||||
'plugin:@cypress/dev/general',
|
||||
'plugin:@cypress/dev/tests',
|
||||
],
|
||||
'rules': {
|
||||
parser: '@typescript-eslint/parser',
|
||||
rules: {
|
||||
'no-duplicate-imports': 'off',
|
||||
'import/no-duplicates': 'off',
|
||||
'@typescript-eslint/no-duplicate-imports': [
|
||||
'error',
|
||||
],
|
||||
'prefer-spread': 'off',
|
||||
'prefer-rest-params': 'off',
|
||||
'no-useless-constructor': 'off',
|
||||
'no-restricted-properties': [
|
||||
'error',
|
||||
{
|
||||
'object': 'process',
|
||||
'property': 'geteuid',
|
||||
'message': 'process.geteuid() will throw on Windows. Do not use it unless you catch any potential errors.',
|
||||
object: 'process',
|
||||
property: 'geteuid',
|
||||
message: 'process.geteuid() will throw on Windows. Do not use it unless you catch any potential errors.',
|
||||
},
|
||||
{
|
||||
'object': 'os',
|
||||
'property': 'userInfo',
|
||||
'message': 'os.userInfo() will throw when there is not an `/etc/passwd` entry for the current user (like when running with --user 12345 in Docker). Do not use it unless you catch any potential errors.',
|
||||
object: 'os',
|
||||
property: 'userInfo',
|
||||
message: 'os.userInfo() will throw when there is not an `/etc/passwd` entry for the current user (like when running with --user 12345 in Docker). Do not use it unless you catch any potential errors.',
|
||||
},
|
||||
],
|
||||
'graphql/capitalized-type-name': ['warn', graphqlOpts],
|
||||
@@ -57,9 +63,9 @@ module.exports = {
|
||||
{ ...graphqlOpts, requiredFields: ['id'] },
|
||||
],
|
||||
},
|
||||
'settings': {
|
||||
'react': {
|
||||
'version': '16.8',
|
||||
settings: {
|
||||
react: {
|
||||
version: '16.8',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
+2
-1
@@ -2,7 +2,8 @@
|
||||
"prefix": "/* eslint-disable padding-line-between-statements */",
|
||||
"paths": [
|
||||
"packages/graphql/src/**/*",
|
||||
"packages/data-context/src/**/*"
|
||||
"packages/data-context/src/**/*",
|
||||
"packages/scaffold-config/src/**"
|
||||
],
|
||||
"ignore": [
|
||||
"packages/data-context/src/gen",
|
||||
|
||||
@@ -2,9 +2,10 @@ import * as React from 'react'
|
||||
import { mount } from '@cypress/react'
|
||||
|
||||
import { SearchInput } from './SearchInput'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { mountAndSnapshot } from 'util/testing'
|
||||
|
||||
const { useCallback, useState } = React
|
||||
|
||||
describe('SearchInput', () => {
|
||||
const StatefulWrapper: React.FC<{onInput?: (input: string) => void}> = ({ onInput }) => {
|
||||
const [value, setValue] = useState('')
|
||||
|
||||
@@ -3,7 +3,7 @@ import * as React from 'react'
|
||||
import { createStory, createStorybookConfig } from 'stories/util'
|
||||
|
||||
import { SearchInput as SearchInputComponent } from './SearchInput'
|
||||
import { useState } from 'react'
|
||||
const { useState } = React
|
||||
|
||||
export default createStorybookConfig({
|
||||
title: 'Components/SearchInput',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react'
|
||||
import { useRef, RefObject } from 'react'
|
||||
import type { RefObject } from 'react'
|
||||
import cs from 'classnames'
|
||||
|
||||
import { useButton } from '@react-aria/button'
|
||||
@@ -11,6 +11,8 @@ import styles from './Button.module.scss'
|
||||
import { FocusRing } from '@react-aria/focus'
|
||||
import { focusClass } from 'css/derived/util'
|
||||
|
||||
const { useRef } = React
|
||||
|
||||
interface SharedButtonProps extends TextSizableComponent {
|
||||
/**
|
||||
* Defaults to 'blue'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react'
|
||||
import { SVGAttributes } from 'react'
|
||||
import type { SVGAttributes } from 'react'
|
||||
import cs from 'classnames'
|
||||
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react'
|
||||
import { RefAttributes } from 'react'
|
||||
import type { RefAttributes } from 'react'
|
||||
import cs from 'classnames'
|
||||
import { useFocusRing } from '@react-aria/focus'
|
||||
import { PressEvent } from '@react-types/shared'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react'
|
||||
import { CSSProperties, InputHTMLAttributes, MutableRefObject, ReactNode, RefObject, TextareaHTMLAttributes, useMemo, useRef } from 'react'
|
||||
import type { CSSProperties, InputHTMLAttributes, MutableRefObject, ReactNode, RefObject, TextareaHTMLAttributes } from 'react'
|
||||
import { useTextField } from 'react-aria'
|
||||
import cs from 'classnames'
|
||||
|
||||
@@ -11,6 +11,8 @@ import { SizingProps } from 'core/shared'
|
||||
import styles from './InputBase.module.scss'
|
||||
import { useCombinedRefs } from 'hooks/useCombinedRefs'
|
||||
|
||||
const { useMemo, useRef } = React
|
||||
|
||||
export interface SharedInputBaseProps extends SizingProps, Omit<InputHTMLAttributes<HTMLInputElement>, 'size'> {
|
||||
inputRef?: MutableRefObject<HTMLTextAreaElement | HTMLInputElement | null> | null
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react'
|
||||
import { CSSProperties } from 'react'
|
||||
import type { CSSProperties } from 'react'
|
||||
import cs from 'classnames'
|
||||
|
||||
import { Spacing } from 'css'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react'
|
||||
import { CSSProperties } from 'react'
|
||||
import type { CSSProperties } from 'react'
|
||||
import cs from 'classnames'
|
||||
|
||||
import { LineHeight, TextSize } from 'css'
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { MutableRefObject, RefCallback, useEffect } from 'react'
|
||||
import type { MutableRefObject, RefCallback } from 'react'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
/**
|
||||
* Joins the `externalRef` to receive the same boxed value as `localRef`
|
||||
|
||||
@@ -261,6 +261,7 @@ module.exports = {
|
||||
files: [
|
||||
'*.ts',
|
||||
'*.tsx',
|
||||
'*.vue',
|
||||
],
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: [
|
||||
@@ -271,6 +272,11 @@ module.exports = {
|
||||
'no-unused-vars': 'off',
|
||||
'indent': 'off',
|
||||
'no-useless-constructor': 'off',
|
||||
'no-duplicate-imports': 'off',
|
||||
'import/no-duplicates': 'off',
|
||||
'@typescript-eslint/no-duplicate-imports': [
|
||||
'error',
|
||||
],
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'error',
|
||||
{
|
||||
|
||||
+2
-1
@@ -21,6 +21,7 @@
|
||||
"mocha/no-global-tests": "off",
|
||||
"no-unused-vars": "off",
|
||||
"no-console": "off",
|
||||
"@typescript-eslint/no-unused-vars": "off"
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"vue/multi-word-component-names": "off"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
import { ref } from 'vue'
|
||||
|
||||
defineProps<{
|
||||
msg: String,
|
||||
msg: String
|
||||
}>()
|
||||
|
||||
const count = ref(0)
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
"cypress:run:debug": "node ./scripts/debug.js cypress:run",
|
||||
"cypress:verify": "cypress verify --dev",
|
||||
"dev": "gulp dev",
|
||||
"dev:vite": "CYPRESS_INTERNAL_VITE_DEV=1 yarn dev",
|
||||
"dev:clean": "gulp dev:clean",
|
||||
"gulp:debug": "node --inspect-brk ./node_modules/.bin/gulp",
|
||||
"dev-debug": "node ./scripts/debug.js dev",
|
||||
|
||||
@@ -21,11 +21,11 @@ describe('Cypress In Cypress', { viewportWidth: 1500 }, () => {
|
||||
cy.findByTestId('aut-url').should('not.exist')
|
||||
cy.findByTestId('select-browser').click()
|
||||
|
||||
cy.contains('Firefox').should('be.visible')
|
||||
cy.contains('Canary').should('be.visible')
|
||||
cy.findByTestId('viewport').click()
|
||||
|
||||
snapshotAUTPanel('browsers open')
|
||||
cy.contains('Firefox').should('be.hidden')
|
||||
cy.contains('Canary').should('be.hidden')
|
||||
cy.contains('The viewport determines the width and height of your application. By default the viewport will be 500px by 500px for Component Testing unless specified by a cy.viewport command.')
|
||||
.should('be.visible')
|
||||
|
||||
|
||||
@@ -21,7 +21,9 @@ describe('Cypress In Cypress', { viewportWidth: 1500 }, () => {
|
||||
cy.findByTestId('playground-activator').should('be.visible')
|
||||
cy.findByTestId('select-browser').click()
|
||||
|
||||
cy.contains('Firefox').click()
|
||||
cy.contains('Canary').should('be.visible')
|
||||
cy.findByTestId('select-browser').click()
|
||||
cy.get('[data-cy="viewport"]').click()
|
||||
|
||||
cy.contains('Chrome 1')
|
||||
.focus()
|
||||
@@ -29,9 +31,9 @@ describe('Cypress In Cypress', { viewportWidth: 1500 }, () => {
|
||||
|
||||
snapshotAUTPanel('browsers open')
|
||||
|
||||
cy.contains('Firefox').should('be.hidden')
|
||||
cy.contains('Canary').should('be.hidden')
|
||||
|
||||
cy.findByTestId('viewport').click()
|
||||
cy.get('[data-cy="viewport"]').click()
|
||||
cy.contains('The viewport determines the width and height of your application. By default the viewport will be 1000px by 660px for End-to-end Testing unless specified by a cy.viewport command.')
|
||||
.should('be.visible')
|
||||
|
||||
|
||||
@@ -123,6 +123,8 @@ describe('App: Index', () => {
|
||||
})).to.have.lengthOf(options.expectedScaffoldPaths.length)
|
||||
}, { expectedScaffoldPaths })
|
||||
|
||||
cy.percySnapshot()
|
||||
|
||||
// Dismisses dialog with close button press
|
||||
cy.get('@CloseDialogButton').click()
|
||||
cy.findByRole('dialog').should('not.exist')
|
||||
@@ -166,6 +168,8 @@ describe('App: Index', () => {
|
||||
cy.get('[data-cy="card"]').contains(defaultMessages.createSpec.e2e.importEmptySpec.header).click()
|
||||
})
|
||||
|
||||
cy.percySnapshot('Default')
|
||||
|
||||
cy.findAllByLabelText(defaultMessages.createSpec.e2e.importEmptySpec.inputPlaceholder)
|
||||
.as('enterSpecInput')
|
||||
|
||||
@@ -179,6 +183,8 @@ describe('App: Index', () => {
|
||||
cy.contains(defaultMessages.createSpec.e2e.importEmptySpec.invalidSpecWarning)
|
||||
cy.contains('button', defaultMessages.createSpec.createSpec).should('be.disabled')
|
||||
|
||||
cy.percySnapshot('Invalid spec error')
|
||||
|
||||
//Shows extension warning
|
||||
cy.get('@enterSpecInput').clear().type(getPathForPlatform('cypress/e2e/MyTest.spec.j'))
|
||||
cy.intercept('mutation-EmptyGenerator_MatchSpecFile', (req) => {
|
||||
@@ -191,6 +197,7 @@ describe('App: Index', () => {
|
||||
|
||||
cy.get('@enterSpecInput').type('x')
|
||||
cy.contains(defaultMessages.createSpec.e2e.importEmptySpec.specExtensionWarning)
|
||||
cy.percySnapshot('Non-recommended spec pattern warning')
|
||||
cy.contains('span', '{filename}.cy.jx')
|
||||
|
||||
// Create spec
|
||||
@@ -200,6 +207,8 @@ describe('App: Index', () => {
|
||||
|
||||
cy.get('[data-cy="file-row"]').contains(getPathForPlatform('cypress/e2e/MyTest.cy.js')).click()
|
||||
|
||||
cy.percySnapshot('Generator success')
|
||||
|
||||
// TODO: code rendering is flaky in CI
|
||||
// cy.get('code').should('contain', 'describe(\'MyTest.cy.js\'')
|
||||
|
||||
@@ -395,13 +404,16 @@ describe('App: Index', () => {
|
||||
it('shows input for file extension filter', () => {
|
||||
cy.get('@CreateFromStoryDialog').within(() => {
|
||||
cy.findByTestId('file-match-indicator').should('contain', '1 Match')
|
||||
cy.percySnapshot('Create from story generator')
|
||||
cy.findByRole('button', { name: '*.stories.*' }).click()
|
||||
cy.percySnapshot('File list search dropdown')
|
||||
cy.findByPlaceholderText(defaultMessages.components.fileSearch.byExtensionInput)
|
||||
.as('ExtensionInput')
|
||||
.clear()
|
||||
.type('foobar')
|
||||
|
||||
cy.findByTestId('file-match-indicator').should('contain', 'No Matches')
|
||||
cy.percySnapshot('No Results')
|
||||
|
||||
cy.findByTestId('no-results-clear').click()
|
||||
|
||||
@@ -431,6 +443,7 @@ describe('App: Index', () => {
|
||||
.type('Button.stories.jsx')
|
||||
|
||||
cy.findByTestId('file-match-indicator').should('contain', '1 of 1 Matches')
|
||||
cy.percySnapshot()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -447,6 +460,8 @@ describe('App: Index', () => {
|
||||
cy.get('@NewSpecFile').click()
|
||||
})
|
||||
|
||||
cy.percySnapshot()
|
||||
|
||||
cy.findByRole('dialog', {
|
||||
name: defaultMessages.createSpec.successPage.header,
|
||||
}).as('SuccessDialog').within(() => {
|
||||
@@ -546,8 +561,8 @@ describe('App: Index', () => {
|
||||
it('shows input for file extension filter', () => {
|
||||
cy.get('@CreateFromComponentDialog').within(() => {
|
||||
cy.log('testing builds')
|
||||
cy.findByTestId('file-match-indicator').should('contain', '2 Matches')
|
||||
cy.findByRole('button', { name: '*.{jsx,tsx}' }).click()
|
||||
cy.findByTestId('file-match-indicator').should('contain', '4 Matches')
|
||||
cy.findByRole('button', { name: '*.{js,jsx,tsx}' }).click()
|
||||
cy.findByPlaceholderText(defaultMessages.components.fileSearch.byExtensionInput)
|
||||
.as('ExtensionInput')
|
||||
.clear()
|
||||
@@ -557,9 +572,9 @@ describe('App: Index', () => {
|
||||
|
||||
cy.findByTestId('no-results-clear').click()
|
||||
|
||||
cy.get('@ExtensionInput').should('have.value', '*.{jsx,tsx}')
|
||||
cy.get('@ExtensionInput').should('have.value', '*.{js,jsx,tsx}')
|
||||
|
||||
cy.findByTestId('file-match-indicator').should('contain', '2 Matches')
|
||||
cy.findByTestId('file-match-indicator').should('contain', '4 Matches')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -568,7 +583,7 @@ describe('App: Index', () => {
|
||||
cy.findByLabelText('file-name-input').as('FileNameInput')
|
||||
.should('have.value', '')
|
||||
|
||||
cy.findByTestId('file-match-indicator').should('contain', '2 Matches')
|
||||
cy.findByTestId('file-match-indicator').should('contain', '4 Matches')
|
||||
|
||||
cy.get('@FileNameInput')
|
||||
.type('foobar')
|
||||
@@ -577,18 +592,18 @@ describe('App: Index', () => {
|
||||
|
||||
cy.findByTestId('no-results-clear').click()
|
||||
|
||||
cy.findByTestId('file-match-indicator').should('contain', '2 Matches')
|
||||
cy.findByTestId('file-match-indicator').should('contain', '4 Matches')
|
||||
|
||||
cy.get('@FileNameInput')
|
||||
.type('App.jsx')
|
||||
|
||||
cy.findByTestId('file-match-indicator').should('contain', '1 of 2 Matches')
|
||||
cy.findByTestId('file-match-indicator').should('contain', '1 of 4 Matches')
|
||||
})
|
||||
})
|
||||
|
||||
it('shows success modal when spec is created from component', () => {
|
||||
cy.get('@CreateFromComponentDialog').within(() => {
|
||||
cy.findAllByTestId('file-list-row').eq(0).as('NewSpecFile')
|
||||
cy.findAllByTestId('file-list-row').contains('App.jsx').as('NewSpecFile')
|
||||
|
||||
// TODO: assert visibility of secondary text on hover/focus when
|
||||
// item is made keyboard accessible
|
||||
@@ -717,7 +732,7 @@ describe('App: Index', () => {
|
||||
.and('contain', defaultMessages.createSpec.component.importFromComponent.description).click()
|
||||
})
|
||||
|
||||
cy.get('[data-cy=file-list-row]').first().click()
|
||||
cy.get('[data-cy=file-list-row]').contains('App.jsx').click()
|
||||
|
||||
cy.get('input').invoke('val').should('eq', getPathForPlatform('src/App.cy.jsx'))
|
||||
cy.contains(defaultMessages.createSpec.component.importEmptySpec.header)
|
||||
@@ -735,7 +750,7 @@ describe('App: Index', () => {
|
||||
.and('contain', defaultMessages.createSpec.component.importFromComponent.description).click()
|
||||
})
|
||||
|
||||
cy.get('[data-cy=file-list-row]').first().click()
|
||||
cy.get('[data-cy=file-list-row]').contains('App.jsx').click()
|
||||
|
||||
cy.get('input').invoke('val').should('eq', getPathForPlatform('src/App.cy.jsx'))
|
||||
cy.contains(defaultMessages.createSpec.component.importEmptySpec.header)
|
||||
@@ -809,10 +824,11 @@ describe('App: Index', () => {
|
||||
})
|
||||
|
||||
cy.contains('Create from component').click()
|
||||
const componentGlob = '*.{jsx,tsx}'
|
||||
const componentGlob = '*.{js,jsx,tsx}'
|
||||
|
||||
cy.findByTestId('file-match-button').contains(componentGlob)
|
||||
checkCodeGenCandidates(['App.cy.jsx', 'App.jsx', 'index.jsx', 'Button.jsx', 'Button.stories.jsx'])
|
||||
cy.percySnapshot('Component Generator')
|
||||
checkCodeGenCandidates(['cypress.config.js', 'App.cy.jsx', 'App.jsx', 'index.jsx', 'support.js', 'Button.jsx', 'Button.stories.jsx'])
|
||||
|
||||
cy.intercept('query-ComponentGeneratorStepOne').as('code-gen-candidates')
|
||||
cy.findByTestId('file-match-button').click()
|
||||
@@ -826,6 +842,7 @@ describe('App: Index', () => {
|
||||
|
||||
cy.contains('Button.jsx').click()
|
||||
cy.findByTestId('file-row').contains(getPathForPlatform('src/stories/Button.cy.js')).click()
|
||||
cy.percySnapshot('Component Generator Success')
|
||||
|
||||
cy.withCtx(async (ctx, o) => {
|
||||
const spec = (
|
||||
@@ -843,12 +860,14 @@ describe('App: Index', () => {
|
||||
const storyGlob = '*.stories.*'
|
||||
|
||||
cy.findByTestId('file-match-button').contains(storyGlob)
|
||||
cy.percySnapshot('Story Generator')
|
||||
checkCodeGenCandidates(['Button.stories.jsx'])
|
||||
|
||||
cy.contains('Button.stories.jsx').click()
|
||||
cy.findByTestId('file-row').contains(getPathForPlatform('src/stories/Button.stories.cy.js')).click()
|
||||
cy.contains('composeStories')
|
||||
cy.contains('ExampleWithLongName')
|
||||
cy.percySnapshot('Story Generator Success')
|
||||
|
||||
cy.withCtx(async (ctx, o) => {
|
||||
const spec = (await ctx.project.findSpecs(ctx.currentProject ?? '', 'component', ['**/*.cy.jsx'], [], []))
|
||||
|
||||
@@ -87,7 +87,8 @@ describe('src/cypress/runner', () => {
|
||||
failCount: 0,
|
||||
})
|
||||
|
||||
cy.contains('li.command-name-assert.command-has-snapshot', 'assert')
|
||||
cy.contains('li.command-name-assert', 'assert')
|
||||
.find('.command-wrapper')
|
||||
.should('not.have.class', 'command-is-pinned')
|
||||
.click()
|
||||
.should('have.class', 'command-is-pinned')
|
||||
@@ -169,13 +170,13 @@ describe('src/cypress/runner', () => {
|
||||
})
|
||||
|
||||
cy.contains('.test', 'never gets here').should('have.class', 'runnable-failed')
|
||||
cy.contains('.command', 'beforeEach').should('have.class', 'command-state-failed')
|
||||
cy.contains('.command', 'beforeEach').find('.command-state-failed')
|
||||
cy.contains('.runnable-err', 'beforeEach').scrollIntoView().should('be.visible')
|
||||
|
||||
cy.contains('.test', 'is pending').should('have.class', 'runnable-pending')
|
||||
|
||||
cy.contains('.test', 'fails this').should('have.class', 'runnable-failed')
|
||||
cy.contains('.command', 'afterEach').should('have.class', 'command-state-failed')
|
||||
cy.contains('.command', 'afterEach').find('.command-state-failed')
|
||||
cy.contains('.runnable-err', 'afterEach').scrollIntoView().should('be.visible')
|
||||
|
||||
cy.contains('.test', 'does not run this').should('have.class', 'runnable-processing')
|
||||
@@ -183,7 +184,7 @@ describe('src/cypress/runner', () => {
|
||||
cy.contains('.test', 'runs this').should('have.class', 'runnable-passed')
|
||||
|
||||
cy.contains('.test', 'fails on this').should('have.class', 'runnable-failed')
|
||||
cy.contains('.command', 'after').should('have.class', 'command-state-failed')
|
||||
cy.contains('.command', 'after').find('.command-state-failed')
|
||||
cy.contains('.runnable-err', 'after').scrollIntoView().should('be.visible')
|
||||
})
|
||||
|
||||
|
||||
@@ -137,7 +137,7 @@ const verifyFailure = (options) => {
|
||||
cy.log('uncaught error has an associated log for the original error')
|
||||
cy.get('.command-name-uncaught-exception')
|
||||
.should('have.length', 1)
|
||||
.should('have.class', 'command-state-failed')
|
||||
.find('.command-state-failed')
|
||||
.find('.command-message-text')
|
||||
.should('include.text', uncaughtMessage || originalMessage)
|
||||
} else {
|
||||
|
||||
@@ -34,12 +34,12 @@ describe('App: Settings', () => {
|
||||
cy.wait('@ReconfigureProject')
|
||||
})
|
||||
|
||||
describe('Project Settings', () => {
|
||||
describe('Cloud Settings', () => {
|
||||
it('shows the projectId section when there is a projectId', () => {
|
||||
cy.startAppServer('e2e')
|
||||
cy.visitApp()
|
||||
cy.findByText('Settings').click()
|
||||
cy.findByText('Project Settings').click()
|
||||
cy.findByText('Dashboard Settings').click()
|
||||
cy.findByText('Project ID').should('be.visible')
|
||||
})
|
||||
|
||||
@@ -49,7 +49,7 @@ describe('App: Settings', () => {
|
||||
|
||||
cy.visitApp()
|
||||
cy.findByText('Settings').click()
|
||||
cy.findByText('Project Settings').click()
|
||||
cy.findByText('Dashboard Settings').click()
|
||||
cy.findByText('Record Key').should('be.visible')
|
||||
})
|
||||
|
||||
@@ -59,12 +59,26 @@ describe('App: Settings', () => {
|
||||
|
||||
cy.visitApp()
|
||||
cy.findByText('Settings').click()
|
||||
cy.findByText('Project Settings').click()
|
||||
cy.findByText('Dashboard Settings').click()
|
||||
cy.get('[data-cy="record-key"]').should('contain', '***')
|
||||
cy.get('[aria-label="Record Key Visibility Toggle"]').click()
|
||||
cy.get('[data-cy="record-key"]').should('contain', '2aaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa')
|
||||
})
|
||||
|
||||
it('opens cloud settings when clicking on "Manage Keys"', () => {
|
||||
cy.startAppServer('e2e')
|
||||
cy.loginUser()
|
||||
cy.intercept('mutation-ExternalLink_OpenExternal', { 'data': { 'openExternal': true } }).as('OpenExternal')
|
||||
cy.__incorrectlyVisitAppWithIntercept('settings')
|
||||
cy.findByText('Dashboard Settings').click()
|
||||
cy.findByText('Manage Keys').click()
|
||||
cy.wait('@OpenExternal')
|
||||
.its('request.body.variables.url')
|
||||
.should('equal', 'http:/test.cloud/cloud-project/settings')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Project Settings', () => {
|
||||
it('shows the Spec Patterns section (default specPattern value)', () => {
|
||||
cy.scaffoldProject('simple-ct')
|
||||
cy.openProject('simple-ct')
|
||||
@@ -144,18 +158,6 @@ describe('App: Settings', () => {
|
||||
// TODO: The Edit button isn't hooked up to do anything when it should trigger the openFileInIDE mutation (https://cypress-io.atlassian.net/browse/UNIFY-1164)
|
||||
it.skip('opens cypress.config.js file after clicking "Edit" button', () => {
|
||||
})
|
||||
|
||||
it('opens cloud settings when clicking on "Manage Keys"', () => {
|
||||
cy.startAppServer('e2e')
|
||||
cy.loginUser()
|
||||
cy.intercept('mutation-ExternalLink_OpenExternal', { 'data': { 'openExternal': true } }).as('OpenExternal')
|
||||
cy.__incorrectlyVisitAppWithIntercept('settings')
|
||||
cy.findByText('Project Settings').click()
|
||||
cy.findByText('Manage Keys').click()
|
||||
cy.wait('@OpenExternal')
|
||||
.its('request.body.variables.url')
|
||||
.should('equal', 'http:/test.cloud/cloud-project/settings')
|
||||
})
|
||||
})
|
||||
|
||||
describe('external editor', () => {
|
||||
@@ -232,15 +234,16 @@ describe('App: Settings', () => {
|
||||
})
|
||||
|
||||
describe('App: Settings without cloud', () => {
|
||||
it('hides the projectId section when there is no projectId', () => {
|
||||
it('the projectId section shows a prompt to connect when there is no projectId', () => {
|
||||
cy.scaffoldProject('simple-ct')
|
||||
cy.openProject('simple-ct')
|
||||
cy.startAppServer('component')
|
||||
|
||||
cy.visitApp()
|
||||
cy.findByText('Settings').click()
|
||||
cy.findByText('Project Settings').click()
|
||||
cy.findByText('Project ID').should('not.exist')
|
||||
cy.findByText('Dashboard Settings').click()
|
||||
cy.findByText('Project ID').should('exist')
|
||||
cy.contains('button', 'Log in to the Cypress Dashboard').should('be.visible')
|
||||
})
|
||||
|
||||
it('have returned browsers', () => {
|
||||
|
||||
@@ -70,22 +70,22 @@ describe('App Top Nav Workflows', () => {
|
||||
.should('exist')
|
||||
|
||||
cy.get('@browserItems').eq(1)
|
||||
.should('contain', 'Firefox')
|
||||
.and('contain', 'Version 5.6.7')
|
||||
.findByTestId('top-nav-browser-list-selected-item')
|
||||
.should('not.exist')
|
||||
|
||||
cy.get('@browserItems').eq(2)
|
||||
.should('contain', 'Edge')
|
||||
.and('contain', 'Version 8.9.10')
|
||||
.findByTestId('top-nav-browser-list-selected-item')
|
||||
.should('not.exist')
|
||||
|
||||
cy.get('@browserItems').eq(3)
|
||||
cy.get('@browserItems').eq(2)
|
||||
.should('contain', 'Electron')
|
||||
.and('contain', 'Version 12.13.14')
|
||||
.findByTestId('top-nav-browser-list-selected-item')
|
||||
.should('not.exist')
|
||||
|
||||
cy.get('@browserItems').eq(3)
|
||||
.should('contain', 'Firefox')
|
||||
.and('contain', 'Version 5.6.7')
|
||||
.findByTestId('top-nav-browser-list-selected-item')
|
||||
.should('not.exist')
|
||||
})
|
||||
|
||||
it('performs mutations to update and relaunch browser', () => {
|
||||
|
||||
@@ -96,7 +96,7 @@ type Matches = {
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
extensionPattern: string,
|
||||
extensionPattern: string
|
||||
pattern: string
|
||||
matches: Matches
|
||||
}>()
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
/>
|
||||
</div>
|
||||
<StandardModalFooter
|
||||
class="flex h-72px gap-16px items-center"
|
||||
class="flex gap-16px items-center"
|
||||
>
|
||||
<OpenConfigFileInIDE>
|
||||
<Button size="lg">
|
||||
|
||||
@@ -41,12 +41,12 @@ import type { FunctionalComponent, SVGAttributes } from 'vue'
|
||||
import SidebarTooltip from './SidebarTooltip.vue'
|
||||
|
||||
withDefaults(defineProps <{
|
||||
icon: FunctionalComponent<SVGAttributes, {}>,
|
||||
name: string,
|
||||
icon: FunctionalComponent<SVGAttributes, {}>
|
||||
name: string
|
||||
// Currently active row (generally the current route)
|
||||
active?: boolean
|
||||
isNavBarExpanded: boolean
|
||||
}>(), {
|
||||
}>(), {
|
||||
active: false,
|
||||
})
|
||||
|
||||
|
||||
@@ -42,9 +42,9 @@
|
||||
import { ref, nextTick } from 'vue'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
disabled?:boolean,
|
||||
popperTopOffset?:number,
|
||||
popperClass?:string,
|
||||
disabled?: boolean
|
||||
popperTopOffset?: number
|
||||
popperClass?: string
|
||||
}>(), {
|
||||
disabled: false,
|
||||
popperTopOffset: 0,
|
||||
|
||||
@@ -6,26 +6,15 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { gql, useQuery, useMutation } from '@urql/vue'
|
||||
import { gql, useQuery } from '@urql/vue'
|
||||
import SettingsContainer from '../settings/SettingsContainer.vue'
|
||||
import { Settings_ReconfigureProjectDocument, SettingsDocument } from '../generated/graphql'
|
||||
import { SettingsDocument } from '../generated/graphql'
|
||||
|
||||
gql`
|
||||
query Settings {
|
||||
...SettingsContainer
|
||||
}`
|
||||
|
||||
gql`
|
||||
mutation Settings_ReconfigureProject {
|
||||
reconfigureProject
|
||||
}
|
||||
`
|
||||
|
||||
const query = useQuery({ query: SettingsDocument })
|
||||
|
||||
const openElectron = useMutation(Settings_ReconfigureProjectDocument)
|
||||
|
||||
function reconfigure () {
|
||||
openElectron.executeMutation({})
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -74,7 +74,7 @@ const props = withDefaults(defineProps<{
|
||||
minPanel2Width?: number
|
||||
minPanel3Width?: number
|
||||
maxTotalWidth?: number // windowWidth in runner
|
||||
offsetLeft?: number,
|
||||
offsetLeft?: number
|
||||
}>(), {
|
||||
showPanel1: true,
|
||||
showPanel2: true,
|
||||
@@ -88,8 +88,8 @@ const props = withDefaults(defineProps<{
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'resizeEnd', value: DraggablePanel): void,
|
||||
(e: 'panelWidthUpdated', value: {panel: DraggablePanel, width: number}): void,
|
||||
(e: 'resizeEnd', value: DraggablePanel): void
|
||||
(e: 'panelWidthUpdated', value: {panel: DraggablePanel, width: number}): void
|
||||
}>()
|
||||
|
||||
const panel1HandleX = ref(props.initialPanel1Width)
|
||||
@@ -158,7 +158,7 @@ function handleResizeEnd (panel: DraggablePanel) {
|
||||
emit('resizeEnd', panel)
|
||||
}
|
||||
|
||||
function isNewWidthAllowed (mouseClientX:number, panel: DraggablePanel) {
|
||||
function isNewWidthAllowed (mouseClientX: number, panel: DraggablePanel) {
|
||||
if (panel === 'panel1') {
|
||||
const newWidth = mouseClientX - props.offsetLeft
|
||||
const result = panel1IsDragging.value && newWidth >= props.minPanel1Width && newWidth <= maxPanel1Width.value
|
||||
|
||||
@@ -27,7 +27,7 @@ interface ToggleMessage {
|
||||
id: string
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
defineProps<{
|
||||
messages: ToggleMessage[]
|
||||
}>()
|
||||
|
||||
|
||||
@@ -56,7 +56,6 @@
|
||||
import { computed } from 'vue'
|
||||
import { useAutStore } from '../store'
|
||||
import type { EventManager } from './event-manager'
|
||||
import { togglePlayground as _togglePlayground } from './utils'
|
||||
import SpecRunnerDropdown from './SpecRunnerDropdown.vue'
|
||||
import { allBrowsersIcons } from '@packages/frontend-shared/src/assets/browserLogos'
|
||||
|
||||
|
||||
@@ -85,13 +85,15 @@ import { useScreenshotStore } from '../store/screenshot-store'
|
||||
import ScriptError from './ScriptError.vue'
|
||||
import ResizablePanels from './ResizablePanels.vue'
|
||||
import HideDuringScreenshotOrRunMode from './screenshot/HideDuringScreenshotOrRunMode.vue'
|
||||
import AutomationDisconnected from './automation/AutomationDisconnected.vue'
|
||||
import AutomationMissing from './automation/AutomationMissing.vue'
|
||||
import AutomationElement from './automation/AutomationElement.vue'
|
||||
import { useResizablePanels, useRunnerStyle } from './useRunnerStyle'
|
||||
import { useEventManager } from './useEventManager'
|
||||
import SpecRunnerHeaderRunMode from './SpecRunnerHeaderRunMode.vue'
|
||||
|
||||
// See TODO comments within the template block of this file.
|
||||
// import AutomationDisconnected from './automation/AutomationDisconnected.vue'
|
||||
// import AutomationMissing from './automation/AutomationMissing.vue'
|
||||
|
||||
const eventManager = getEventManager()
|
||||
|
||||
const autStore = useAutStore()
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useSelectorPlaygroundStore } from '../store/selector-playground-store'
|
||||
import type JQuery from 'jquery'
|
||||
import { blankContents } from './blank-contents'
|
||||
import { visitFailure } from './visit-failure'
|
||||
import { logger } from './logger'
|
||||
|
||||
@@ -10,13 +10,12 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted } from 'vue'
|
||||
import { getEventManager } from '..'
|
||||
import { useScreenshotStore } from '../../store/screenshot-store'
|
||||
|
||||
const screenshotStore = useScreenshotStore()
|
||||
|
||||
const eventManager = getEventManager()
|
||||
getEventManager()
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import CloudConnectButton from './CloudConnectButton.vue'
|
||||
import { CloudConnectButtonFragmentDoc } from '../generated/graphql-test'
|
||||
import { CloudUserStubs } from '@packages/frontend-shared/cypress/support/mock-graphql/stubgql-CloudTypes'
|
||||
|
||||
describe('<CloudConnectButton />', () => {
|
||||
it('show user connect if not connected', () => {
|
||||
cy.mountFragment(CloudConnectButtonFragmentDoc, {
|
||||
onResult: (result) => {
|
||||
result.cloudViewer = null
|
||||
},
|
||||
render (gqlVal) {
|
||||
return <div class="h-screen"><CloudConnectButton gql={gqlVal} /></div>
|
||||
},
|
||||
})
|
||||
|
||||
cy.contains('button', 'Log in').should('be.visible')
|
||||
})
|
||||
|
||||
const cloudViewer = {
|
||||
...CloudUserStubs.me,
|
||||
organizationControl: null,
|
||||
organizations: {
|
||||
__typename: 'CloudOrganizationConnection' as const,
|
||||
nodes: [
|
||||
{
|
||||
__typename: 'CloudOrganization' as const,
|
||||
id: '1',
|
||||
name: 'Test Org',
|
||||
projects: {
|
||||
__typename: 'CloudProjectConnection' as const,
|
||||
nodes: [
|
||||
{
|
||||
__typename: 'CloudProject' as const,
|
||||
id: '1',
|
||||
name: 'Test Project',
|
||||
slug: 'test-project',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'CloudOrganization' as const,
|
||||
id: '2',
|
||||
name: 'Test Org 2',
|
||||
projects: {
|
||||
__typename: 'CloudProjectConnection' as const,
|
||||
nodes: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
it('show project connect if not connected', () => {
|
||||
cy.mountFragment(CloudConnectButtonFragmentDoc, {
|
||||
onResult: (result) => {
|
||||
result.cloudViewer = cloudViewer
|
||||
},
|
||||
render (gqlVal) {
|
||||
return <div class="h-screen"><CloudConnectButton gql={gqlVal} /></div>
|
||||
},
|
||||
})
|
||||
|
||||
cy.contains('button', 'Connect your project').should('be.visible')
|
||||
})
|
||||
|
||||
it('shows connect project dialog', () => {
|
||||
cy.mountFragment(CloudConnectButtonFragmentDoc, {
|
||||
onResult: (result) => {
|
||||
result.cloudViewer = cloudViewer
|
||||
},
|
||||
render (gqlVal) {
|
||||
return <div class="h-screen"><CloudConnectButton gql={gqlVal} /></div>
|
||||
},
|
||||
})
|
||||
|
||||
cy.contains('button', 'Connect your project').click()
|
||||
cy.get('[role="dialog"]').should('be.visible')
|
||||
cy.get('[role="dialog"] h2').should('contain', 'Connect Project')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<Button
|
||||
:class="props.class"
|
||||
:prefix-icon="isLoggedIn ? ChainIcon : UserIcon"
|
||||
prefix-icon-class="icon-dark-white icon-light-transparent"
|
||||
@click="openConnection"
|
||||
>
|
||||
{{ isLoggedIn ? t('runs.connect.buttonProject') : t('runs.connect.buttonUser') }}
|
||||
</Button>
|
||||
<LoginModal
|
||||
v-model="isLoginOpen"
|
||||
:gql="props.gql"
|
||||
/>
|
||||
<CloudConnectModals
|
||||
v-if="isProjectConnectOpen"
|
||||
:show="isProjectConnectOpen"
|
||||
:gql="props.gql"
|
||||
@cancel="isProjectConnectOpen = false"
|
||||
@success="isProjectConnectOpen = false; emit('success')"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue'
|
||||
import { gql } from '@urql/vue'
|
||||
import UserIcon from '~icons/cy/user-outline_x16.svg'
|
||||
import ChainIcon from '~icons/cy/chain-link_x16.svg'
|
||||
import Button from '@cy/components/Button.vue'
|
||||
import CloudConnectModals from './modals/CloudConnectModals.vue'
|
||||
import LoginModal from '@cy/gql-components/topnav/LoginModal.vue'
|
||||
import { useI18n } from '@cy/i18n'
|
||||
import type { CloudConnectButtonFragment } from '../generated/graphql'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
gql`
|
||||
fragment CloudConnectButton on Query {
|
||||
...CloudConnectModals
|
||||
...LoginModal
|
||||
}
|
||||
`
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'success'): void
|
||||
}>()
|
||||
|
||||
const props = defineProps<{
|
||||
gql: CloudConnectButtonFragment
|
||||
class?: string
|
||||
}>()
|
||||
|
||||
const isLoginOpen = ref(false)
|
||||
const isProjectConnectOpen = ref(false)
|
||||
const isLoggedIn = computed(() => Boolean(props.gql.cloudViewer?.id))
|
||||
|
||||
function openConnection () {
|
||||
if (!isLoggedIn.value) {
|
||||
// start logging in the user
|
||||
isLoginOpen.value = true
|
||||
} else {
|
||||
// if user is already logged in connect a cloud project
|
||||
isProjectConnectOpen.value = true
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -87,7 +87,7 @@ fragment RunCard on CloudRun {
|
||||
`
|
||||
|
||||
const props = defineProps<{
|
||||
gql: RunCardFragment
|
||||
gql: RunCardFragment
|
||||
}>()
|
||||
|
||||
const ICON_MAP = {
|
||||
|
||||
@@ -38,7 +38,7 @@ fragment RunResults on CloudRun {
|
||||
`
|
||||
|
||||
const props = defineProps<{
|
||||
gql: RunResultsFragment
|
||||
gql: RunResultsFragment
|
||||
}>()
|
||||
|
||||
const results = [
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import RunsConnect from './RunsConnect.vue'
|
||||
import { RunsConnectFragmentDoc } from '../generated/graphql-test'
|
||||
import { CloudUserStubs } from '@packages/frontend-shared/cypress/support/mock-graphql/stubgql-CloudTypes'
|
||||
|
||||
describe('<RunsConnect />', () => {
|
||||
it('show user connect if not connected', () => {
|
||||
it('show connect button', () => {
|
||||
cy.mountFragment(RunsConnectFragmentDoc, {
|
||||
onResult: (result) => {
|
||||
result.cloudViewer = null
|
||||
@@ -15,67 +14,4 @@ describe('<RunsConnect />', () => {
|
||||
|
||||
cy.contains('button', 'Log in').should('be.visible')
|
||||
})
|
||||
|
||||
const cloudViewer = {
|
||||
...CloudUserStubs.me,
|
||||
organizationControl: null,
|
||||
organizations: {
|
||||
__typename: 'CloudOrganizationConnection' as const,
|
||||
nodes: [
|
||||
{
|
||||
__typename: 'CloudOrganization' as const,
|
||||
id: '1',
|
||||
name: 'Test Org',
|
||||
projects: {
|
||||
__typename: 'CloudProjectConnection' as const,
|
||||
nodes: [
|
||||
{
|
||||
__typename: 'CloudProject' as const,
|
||||
id: '1',
|
||||
name: 'Test Project',
|
||||
slug: 'test-project',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'CloudOrganization' as const,
|
||||
id: '2',
|
||||
name: 'Test Org 2',
|
||||
projects: {
|
||||
__typename: 'CloudProjectConnection' as const,
|
||||
nodes: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
it('show project connect if not connected', () => {
|
||||
cy.mountFragment(RunsConnectFragmentDoc, {
|
||||
onResult: (result) => {
|
||||
result.cloudViewer = cloudViewer
|
||||
},
|
||||
render (gqlVal) {
|
||||
return <div class="h-screen"><RunsConnect gql={gqlVal} /></div>
|
||||
},
|
||||
})
|
||||
|
||||
cy.contains('button', 'Connect your project').should('be.visible')
|
||||
})
|
||||
|
||||
it('shows connect project dialog', () => {
|
||||
cy.mountFragment(RunsConnectFragmentDoc, {
|
||||
onResult: (result) => {
|
||||
result.cloudViewer = cloudViewer
|
||||
},
|
||||
render (gqlVal) {
|
||||
return <div class="h-screen"><RunsConnect gql={gqlVal} /></div>
|
||||
},
|
||||
})
|
||||
|
||||
cy.contains('button', 'Connect your project').click()
|
||||
cy.get('[role="dialog"]').should('be.visible')
|
||||
cy.get('[role="dialog"] h2').should('contain', 'Connect Project')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -17,48 +17,28 @@
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
<CloudConnectButton
|
||||
:gql="props.gql"
|
||||
class="mx-auto mt-40px"
|
||||
:prefix-icon="isLoggedIn ? ChainIcon : UserIcon"
|
||||
prefix-icon-class="icon-dark-white icon-light-transparent"
|
||||
@click="openConnection"
|
||||
>
|
||||
{{ isLoggedIn ? t('runs.connect.buttonProject') : t('runs.connect.buttonUser') }}
|
||||
</Button>
|
||||
<LoginModal
|
||||
v-model="isLoginOpen"
|
||||
:gql="props.gql"
|
||||
/>
|
||||
<CloudConnectModals
|
||||
v-if="isProjectConnectOpen"
|
||||
:show="isProjectConnectOpen"
|
||||
:gql="props.gql"
|
||||
@cancel="isProjectConnectOpen = false"
|
||||
@success="isProjectConnectOpen = false; emit('success')"
|
||||
@success="emit('success')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue'
|
||||
import { gql } from '@urql/vue'
|
||||
import SmartIcon from '~icons/cy/illustration-gear_x120.svg'
|
||||
import DebugIcon from '~icons/cy/illustration-debug_x120.svg'
|
||||
import ChartIcon from '~icons/cy/illustration-chart_x120.svg'
|
||||
import UserIcon from '~icons/cy/user-outline_x16.svg'
|
||||
import ChainIcon from '~icons/cy/chain-link_x16.svg'
|
||||
import Button from '@cy/components/Button.vue'
|
||||
import CloudConnectModals from './modals/CloudConnectModals.vue'
|
||||
import LoginModal from '@cy/gql-components/topnav/LoginModal.vue'
|
||||
import { useI18n } from '@cy/i18n'
|
||||
import CloudConnectButton from './CloudConnectButton.vue'
|
||||
import type { RunsConnectFragment } from '../generated/graphql'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
gql`
|
||||
fragment RunsConnect on Query {
|
||||
...CloudConnectModals
|
||||
...LoginModal
|
||||
...CloudConnectButton
|
||||
}
|
||||
`
|
||||
|
||||
@@ -67,23 +47,9 @@ const emit = defineEmits<{
|
||||
}>()
|
||||
|
||||
const props = defineProps<{
|
||||
gql: RunsConnectFragment,
|
||||
gql: RunsConnectFragment
|
||||
}>()
|
||||
|
||||
const isLoginOpen = ref(false)
|
||||
const isProjectConnectOpen = ref(false)
|
||||
const isLoggedIn = computed(() => Boolean(props.gql.cloudViewer?.id))
|
||||
|
||||
function openConnection () {
|
||||
if (!isLoggedIn.value) {
|
||||
// start logging in the user
|
||||
isLoginOpen.value = true
|
||||
} else {
|
||||
// if user is already logged in connect a cloud project
|
||||
isProjectConnectOpen.value = true
|
||||
}
|
||||
}
|
||||
|
||||
const notions = [
|
||||
{
|
||||
icon: SmartIcon,
|
||||
|
||||
@@ -48,7 +48,7 @@ fragment RunsEmpty on CurrentProject {
|
||||
`
|
||||
|
||||
const props = defineProps<{
|
||||
gql: RunsEmptyFragment,
|
||||
gql: RunsEmptyFragment
|
||||
}>()
|
||||
|
||||
const projectName = computed(() => props.gql.title)
|
||||
|
||||
@@ -30,14 +30,14 @@ import type { FunctionalComponent, SVGAttributes } from 'vue'
|
||||
import Button from '@cy/components/Button.vue'
|
||||
|
||||
defineProps<{
|
||||
icon: 'access' | 'error',
|
||||
message: string,
|
||||
buttonText: string,
|
||||
buttonIcon: FunctionalComponent<SVGAttributes, {}>,
|
||||
buttonDisabled?: boolean,
|
||||
icon: 'access' | 'error'
|
||||
message: string
|
||||
buttonText: string
|
||||
buttonIcon: FunctionalComponent<SVGAttributes, {}>
|
||||
buttonDisabled?: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'button-click'): void,
|
||||
(event: 'button-click'): void
|
||||
}>()
|
||||
</script>
|
||||
|
||||
@@ -97,7 +97,7 @@ query CheckCloudOrganizations {
|
||||
`
|
||||
|
||||
const props = defineProps<{
|
||||
gql: CreateCloudOrgModalFragment,
|
||||
gql: CreateCloudOrgModalFragment
|
||||
}>()
|
||||
|
||||
const query = useQuery({
|
||||
|
||||
@@ -69,7 +69,7 @@ import type { NeedManualUpdateModalFragment } from '../../generated/graphql'
|
||||
const { t } = useI18n()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event:'cancel'): void
|
||||
(event: 'cancel'): void
|
||||
}>()
|
||||
|
||||
gql`
|
||||
|
||||
@@ -223,7 +223,7 @@ mutation SelectCloudProjectModal_CreateCloudProject( $name: String!, $orgId: ID!
|
||||
`
|
||||
|
||||
const props = defineProps<{
|
||||
gql: SelectCloudProjectModalFragment,
|
||||
gql: SelectCloudProjectModalFragment
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
||||
@@ -44,8 +44,8 @@ import Collapsible from '@cy/components/Collapsible.vue'
|
||||
import ListRowHeader from '@cy/components/ListRowHeader.vue'
|
||||
|
||||
defineProps<{
|
||||
title: string,
|
||||
description: string,
|
||||
title: string
|
||||
description: string
|
||||
icon: FunctionalComponent<SVGAttributes, {}>
|
||||
maxHeight: string
|
||||
}>()
|
||||
|
||||
@@ -10,6 +10,20 @@ describe('<SettingsContainer />', { viewportHeight: 800, viewportWidth: 900 }, (
|
||||
cy.percySnapshot()
|
||||
})
|
||||
|
||||
it('expands and collapses project settings', () => {
|
||||
mountSettingsContainer()
|
||||
|
||||
cy.contains('Project Settings').click()
|
||||
|
||||
cy.findByText(defaultMessages.settingsPage.experiments.title).scrollIntoView().should('be.visible')
|
||||
cy.findByText(defaultMessages.settingsPage.specPattern.title).scrollIntoView().should('be.visible')
|
||||
cy.findByText(defaultMessages.settingsPage.config.title).scrollIntoView().should('be.visible')
|
||||
cy.percySnapshot()
|
||||
cy.findByText('Project Settings').click()
|
||||
|
||||
cy.findByText(defaultMessages.settingsPage.experiments.title).should('not.exist')
|
||||
})
|
||||
|
||||
it('expands and collapses device settings', () => {
|
||||
mountSettingsContainer()
|
||||
|
||||
@@ -25,17 +39,14 @@ describe('<SettingsContainer />', { viewportHeight: 800, viewportWidth: 900 }, (
|
||||
cy.findByText(defaultMessages.settingsPage.editor.title).should('not.exist')
|
||||
})
|
||||
|
||||
it('expands and collapses project settings', () => {
|
||||
it('expands and collapses cloud settings', () => {
|
||||
mountSettingsContainer()
|
||||
|
||||
cy.contains('Project Settings').click()
|
||||
cy.contains('Dashboard Settings').click()
|
||||
|
||||
cy.findByText(defaultMessages.settingsPage.projectId.title).scrollIntoView().should('be.visible')
|
||||
cy.findByText(defaultMessages.settingsPage.experiments.title).scrollIntoView().should('be.visible')
|
||||
cy.findByText(defaultMessages.settingsPage.specPattern.title).scrollIntoView().should('be.visible')
|
||||
cy.findByText(defaultMessages.settingsPage.config.title).scrollIntoView().should('be.visible')
|
||||
cy.percySnapshot()
|
||||
cy.findByText('Project Settings').click()
|
||||
cy.findByText('Dashboard Settings').click()
|
||||
|
||||
cy.findByText(defaultMessages.settingsPage.projectId.title).should('not.exist')
|
||||
})
|
||||
|
||||
@@ -4,16 +4,6 @@
|
||||
data-cy="settings"
|
||||
>
|
||||
<div class="space-y-24px">
|
||||
<SettingsCard
|
||||
:title="t('settingsPage.device.title')"
|
||||
:description="t('settingsPage.device.description')"
|
||||
:icon="IconLaptop"
|
||||
max-height="800px"
|
||||
>
|
||||
<ExternalEditorSettings :gql="props.gql" />
|
||||
<ProxySettings :gql="props.gql" />
|
||||
<TestingPreferences :gql="props.gql" />
|
||||
</SettingsCard>
|
||||
<SettingsCard
|
||||
:title="t('settingsPage.project.title')"
|
||||
:description="t('settingsPage.project.description')"
|
||||
@@ -25,9 +15,27 @@
|
||||
:gql="props.gql"
|
||||
/>
|
||||
</SettingsCard>
|
||||
<SettingsCard
|
||||
:title="t('settingsPage.device.title')"
|
||||
:description="t('settingsPage.device.description')"
|
||||
:icon="IconLaptop"
|
||||
max-height="800px"
|
||||
>
|
||||
<ExternalEditorSettings :gql="props.gql" />
|
||||
<ProxySettings :gql="props.gql" />
|
||||
<TestingPreferences :gql="props.gql" />
|
||||
</SettingsCard>
|
||||
<SettingsCard
|
||||
:title="t('settingsPage.cloud.title')"
|
||||
:description="t('settingsPage.cloud.description')"
|
||||
:icon="IconOdometer"
|
||||
max-height="10000px"
|
||||
>
|
||||
<CloudSettings :gql="props.gql" />
|
||||
</SettingsCard>
|
||||
</div>
|
||||
<hr class="border-gray-100">
|
||||
<p class="font-light mx-auto text-center max-w-500px text-16px text-gray-500 leading-24px">
|
||||
<p class="mx-auto font-light text-center text-gray-500 max-w-500px text-16px leading-24px">
|
||||
{{ t('settingsPage.footer.text') }}
|
||||
</p>
|
||||
<Button
|
||||
@@ -49,11 +57,13 @@ import Button from '@cy/components/Button.vue'
|
||||
import ExternalEditorSettings from './device/ExternalEditorSettings.vue'
|
||||
import ProxySettings from './device/ProxySettings.vue'
|
||||
import SettingsCard from './SettingsCard.vue'
|
||||
import CloudSettings from './project/CloudSettings.vue'
|
||||
import ProjectSettings from './project/ProjectSettings.vue'
|
||||
import TestingPreferences from './device/TestingPreferences.vue'
|
||||
import type { SettingsContainerFragment } from '../generated/graphql'
|
||||
import { SettingsContainer_ReconfigureProjectDocument } from '../generated/graphql'
|
||||
import IconLaptop from '~icons/cy/laptop_x24.svg'
|
||||
import IconOdometer from '~icons/cy/object-odometer_x24.svg'
|
||||
import IconFolder from '~icons/cy/folder-outline_x24.svg'
|
||||
import SettingsIcon from '~icons/cy/settings_x16.svg'
|
||||
|
||||
@@ -69,6 +79,7 @@ gql`
|
||||
fragment SettingsContainer on Query {
|
||||
...TestingPreferences
|
||||
...ProjectSettings
|
||||
...CloudSettings
|
||||
...ExternalEditorSettings
|
||||
...ProxySettings
|
||||
}`
|
||||
|
||||
@@ -5,7 +5,7 @@ describe('<SettingsSection />', () => {
|
||||
cy.viewport(800, 200)
|
||||
|
||||
const title = 'Project Id'
|
||||
const description = 'A Cypress config setting used to uniquely identify your project when recording runs to Cypress Cloud. Learn more.'
|
||||
const description = 'A Cypress config setting used to uniquely identify your project when recording runs to Cypress Dashboard. Learn more.'
|
||||
const slots = {
|
||||
description: () => <p>{description}</p>,
|
||||
title: () => <h1>{title}</h1>,
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import { defaultMessages } from '@cy/i18n'
|
||||
import { CloudSettingsFragmentDoc } from '../../generated/graphql-test'
|
||||
import CloudSettings from './CloudSettings.vue'
|
||||
|
||||
describe('<CloudSettings />', () => {
|
||||
it('displays the project Id and record key sections', () => {
|
||||
cy.mountFragment(CloudSettingsFragmentDoc, {
|
||||
|
||||
render: (gqlVal) => {
|
||||
return (
|
||||
<div class="py-4 px-8 children:py-24px">
|
||||
<CloudSettings gql={gqlVal}/>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
cy.findByText(defaultMessages.settingsPage.projectId.title).should('be.visible')
|
||||
cy.findByText(defaultMessages.settingsPage.recordKey.title).should('be.visible')
|
||||
|
||||
cy.percySnapshot()
|
||||
})
|
||||
|
||||
it('shows connect button when projectId is not present', () => {
|
||||
cy.mountFragment(CloudSettingsFragmentDoc, {
|
||||
onResult (ctx) {
|
||||
if (ctx.currentProject?.cloudProject?.__typename === 'CloudProject') {
|
||||
ctx.currentProject.projectId = null
|
||||
ctx.currentProject.cloudProject.recordKeys = []
|
||||
}
|
||||
},
|
||||
render: (gqlVal) => {
|
||||
return (
|
||||
<div class="py-4 px-8 children:py-24px">
|
||||
<CloudSettings gql={gqlVal}/>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
cy.findByText(defaultMessages.settingsPage.projectId.title).should('be.visible')
|
||||
cy.findByText(defaultMessages.runs.connect.buttonUser).should('be.visible')
|
||||
cy.findByText(defaultMessages.settingsPage.recordKey.title).should('not.exist')
|
||||
|
||||
cy.percySnapshot()
|
||||
})
|
||||
|
||||
it('hides record key when not present', () => {
|
||||
cy.mountFragment(CloudSettingsFragmentDoc, {
|
||||
onResult (ctx) {
|
||||
if (ctx.currentProject?.cloudProject?.__typename === 'CloudProject') {
|
||||
ctx.currentProject.projectId = null
|
||||
ctx.currentProject.cloudProject.recordKeys = []
|
||||
}
|
||||
},
|
||||
render: (gqlVal) => {
|
||||
return (
|
||||
<div class="py-4 px-8 children:py-24px">
|
||||
<CloudSettings gql={gqlVal}/>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
cy.findByText(defaultMessages.settingsPage.recordKey.title).should('not.exist')
|
||||
|
||||
cy.percySnapshot()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<ProjectId :gql="props.gql" />
|
||||
<template
|
||||
v-if="props.gql.currentProject?.cloudProject?.__typename === 'CloudProject'
|
||||
&& props.gql.currentProject.cloudProject.recordKeys?.length"
|
||||
>
|
||||
<RecordKey
|
||||
v-for="key of props.gql.currentProject.cloudProject.recordKeys"
|
||||
:key="key.id"
|
||||
:gql="key"
|
||||
:manage-keys-url="props.gql.currentProject.cloudProject.cloudProjectSettingsUrl"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { gql } from '@urql/vue'
|
||||
import RecordKey from './RecordKey.vue'
|
||||
import ProjectId from './ProjectId.vue'
|
||||
import type { CloudSettingsFragment } from '../../generated/graphql'
|
||||
|
||||
gql`
|
||||
fragment CloudSettings on Query {
|
||||
...ProjectId
|
||||
currentProject {
|
||||
id
|
||||
cloudProject {
|
||||
__typename
|
||||
... on CloudProject {
|
||||
id
|
||||
cloudProjectSettingsUrl
|
||||
recordKeys {
|
||||
id
|
||||
...RecordKey
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const props = defineProps<{
|
||||
gql: CloudSettingsFragment
|
||||
}>()
|
||||
</script>
|
||||
@@ -35,8 +35,8 @@
|
||||
import { FunctionalComponent, ref, SVGAttributes } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
code: string,
|
||||
prefixIcon: FunctionalComponent<SVGAttributes, {}>,
|
||||
code: string
|
||||
prefixIcon: FunctionalComponent<SVGAttributes, {}>
|
||||
confidential?: boolean
|
||||
}>()
|
||||
|
||||
|
||||
@@ -21,6 +21,6 @@ export default {
|
||||
</script>
|
||||
<script lang="ts" setup>
|
||||
defineProps<{
|
||||
label: string;
|
||||
label: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { set } from 'lodash'
|
||||
import { ProjectIdFragmentDoc } from '../../generated/graphql-test'
|
||||
import ProjectId from './ProjectId.vue'
|
||||
|
||||
@@ -11,7 +12,7 @@ describe('<ProjectId />', () => {
|
||||
|
||||
cy.mountFragment(ProjectIdFragmentDoc, {
|
||||
onResult: (result) => {
|
||||
result.projectId = givenProjectId
|
||||
set(result, 'currentProject.projectId', givenProjectId)
|
||||
},
|
||||
render: (gqlVal) => (
|
||||
<div class="py-4 px-8">
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<template>
|
||||
<SettingsSection
|
||||
v-if="props.gql?.projectId"
|
||||
code="projectId"
|
||||
data-cy="settings-projectId"
|
||||
>
|
||||
@@ -19,17 +18,23 @@
|
||||
</ExternalLink>
|
||||
</i18n-t>
|
||||
</template>
|
||||
<div class="flex gap-10px items-center">
|
||||
<div
|
||||
v-if="props.gql.currentProject?.projectId"
|
||||
class="flex gap-10px items-center"
|
||||
>
|
||||
<CodeBox
|
||||
:code="props.gql?.projectId || ''"
|
||||
:code="props.gql.currentProject?.projectId"
|
||||
:prefix-icon="IconOctothorpe"
|
||||
/>
|
||||
<CopyButton
|
||||
v-if="props.gql?.projectId"
|
||||
:text="props.gql?.projectId"
|
||||
:text="props.gql.currentProject?.projectId"
|
||||
variant="outline"
|
||||
/>
|
||||
</div>
|
||||
<CloudConnectButton
|
||||
v-else
|
||||
:gql="props.gql"
|
||||
/>
|
||||
</SettingsSection>
|
||||
</template>
|
||||
|
||||
@@ -42,18 +47,21 @@ import SettingsSection from '../SettingsSection.vue'
|
||||
import ExternalLink from '@cy/gql-components/ExternalLink.vue'
|
||||
import CodeBox from './CodeBox.vue'
|
||||
import type { ProjectIdFragment } from '../../generated/graphql'
|
||||
import CloudConnectButton from '../../runs/CloudConnectButton.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
gql`
|
||||
fragment ProjectId on CurrentProject {
|
||||
id
|
||||
projectId
|
||||
}
|
||||
`
|
||||
fragment ProjectId on Query {
|
||||
currentProject {
|
||||
id
|
||||
projectId
|
||||
}
|
||||
...CloudConnectButton
|
||||
}`
|
||||
|
||||
const props = defineProps<{
|
||||
gql?: ProjectIdFragment | null
|
||||
gql: ProjectIdFragment
|
||||
}>()
|
||||
|
||||
</script>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ProjectSettingsFragmentDoc } from '../../generated/graphql-test'
|
||||
import ProjectSettings from './ProjectSettings.vue'
|
||||
|
||||
describe('<ProjectSettings />', () => {
|
||||
it('displays the project Id, record key, and experiments sections', () => {
|
||||
it('displays the experiments section', () => {
|
||||
cy.mountFragment(ProjectSettingsFragmentDoc, {
|
||||
|
||||
render: (gqlVal) => {
|
||||
@@ -15,33 +15,8 @@ describe('<ProjectSettings />', () => {
|
||||
},
|
||||
})
|
||||
|
||||
cy.findByText(defaultMessages.settingsPage.projectId.title).should('be.visible')
|
||||
cy.findByText(defaultMessages.settingsPage.recordKey.title).should('be.visible')
|
||||
cy.findByText(defaultMessages.settingsPage.experiments.title).should('be.visible')
|
||||
|
||||
cy.percySnapshot()
|
||||
})
|
||||
|
||||
it('hides project Id, and record key when not present', () => {
|
||||
cy.mountFragment(ProjectSettingsFragmentDoc, {
|
||||
onResult (ctx) {
|
||||
if (ctx.currentProject?.cloudProject?.__typename === 'CloudProject') {
|
||||
ctx.currentProject.projectId = null
|
||||
ctx.currentProject.cloudProject.recordKeys = []
|
||||
}
|
||||
},
|
||||
render: (gqlVal) => {
|
||||
return (
|
||||
<div class="py-4 px-8 children:py-24px">
|
||||
<ProjectSettings gql={gqlVal}/>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
cy.findByText(defaultMessages.settingsPage.projectId.title).should('not.exist')
|
||||
cy.findByText(defaultMessages.settingsPage.recordKey.title).should('not.exist')
|
||||
|
||||
cy.percySnapshot()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,16 +1,4 @@
|
||||
<template>
|
||||
<ProjectId :gql="props.gql.currentProject" />
|
||||
<template
|
||||
v-if="props.gql.currentProject?.cloudProject?.__typename === 'CloudProject'
|
||||
&& props.gql.currentProject.cloudProject.recordKeys?.length"
|
||||
>
|
||||
<RecordKey
|
||||
v-for="key of props.gql.currentProject.cloudProject.recordKeys"
|
||||
:key="key.id"
|
||||
:gql="key"
|
||||
:manage-keys-url="props.gql.currentProject.cloudProject.cloudProjectSettingsUrl"
|
||||
/>
|
||||
</template>
|
||||
<SpecPatterns :gql="props.gql.currentProject" />
|
||||
<Experiments :gql="props.gql.currentProject" />
|
||||
<Config :gql="props.gql" />
|
||||
@@ -18,9 +6,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { gql } from '@urql/vue'
|
||||
import RecordKey from './RecordKey.vue'
|
||||
import Experiments from './Experiments.vue'
|
||||
import ProjectId from './ProjectId.vue'
|
||||
import Config from './Config.vue'
|
||||
import SpecPatterns from './SpecPatterns.vue'
|
||||
import type { ProjectSettingsFragment } from '../../generated/graphql'
|
||||
@@ -29,19 +15,7 @@ gql`
|
||||
fragment ProjectSettings on Query {
|
||||
currentProject {
|
||||
id
|
||||
...ProjectId
|
||||
...Experiments
|
||||
cloudProject {
|
||||
__typename
|
||||
... on CloudProject {
|
||||
id
|
||||
cloudProjectSettingsUrl
|
||||
recordKeys {
|
||||
id
|
||||
...RecordKey
|
||||
}
|
||||
}
|
||||
}
|
||||
...SpecPatterns_Settings
|
||||
}
|
||||
...Config
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue'
|
||||
import { computed } from 'vue'
|
||||
import { gql } from '@urql/core'
|
||||
import Button from '@cy/components/Button.vue'
|
||||
import CopyButton from '@cy/components/CopyButton.vue'
|
||||
|
||||
@@ -17,13 +17,13 @@ import type { FunctionalComponent, SVGAttributes } from 'vue'
|
||||
import Card from '@cy/components/Card.vue'
|
||||
|
||||
defineProps<{
|
||||
icon: FunctionalComponent<SVGAttributes>,
|
||||
header: string,
|
||||
description: string,
|
||||
icon: FunctionalComponent<SVGAttributes>
|
||||
header: string
|
||||
description: string
|
||||
disabled?: boolean
|
||||
}>()
|
||||
|
||||
const emits = defineEmits<{
|
||||
(eventName:'click'): void
|
||||
(eventName: 'click'): void
|
||||
}>()
|
||||
</script>
|
||||
|
||||
@@ -53,8 +53,8 @@ import { useI18n } from '@cy/i18n'
|
||||
import { getPathForPlatform } from '../paths'
|
||||
|
||||
const props = defineProps<{
|
||||
initialGenerator?: GeneratorId,
|
||||
show: boolean,
|
||||
initialGenerator?: GeneratorId
|
||||
show: boolean
|
||||
gql: CreateSpecModalFragment
|
||||
}>()
|
||||
|
||||
|
||||
@@ -79,8 +79,6 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Input from '@cy/components/Input.vue'
|
||||
import Button from '@cy/components/Button.vue'
|
||||
import { ref } from 'vue'
|
||||
import { useI18n } from '@cy/i18n'
|
||||
|
||||
|
||||
@@ -212,8 +212,8 @@ function getIdIfDirectory (row) {
|
||||
height: calc(100vh - 64px);
|
||||
}
|
||||
|
||||
/** List header is 72px */
|
||||
/** Search bar is 72px + List header is 40px = 112px offset */
|
||||
.spec-list-container {
|
||||
height: calc(100% - 72px)
|
||||
height: calc(100% - 112px)
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -59,6 +59,8 @@ describe('<SpecsListHeader />', { keystrokeDelay: 0 }, () => {
|
||||
.click()
|
||||
.get('@show-spec-pattern-modal')
|
||||
.should('have.been.called')
|
||||
|
||||
cy.percySnapshot()
|
||||
})
|
||||
|
||||
it('shows the count correctly when not searching', () => {
|
||||
@@ -74,10 +76,17 @@ describe('<SpecsListHeader />', { keystrokeDelay: 0 }, () => {
|
||||
.should('be.visible')
|
||||
.and('have.attr', 'aria-live', 'polite')
|
||||
|
||||
cy.percySnapshot('No Matches')
|
||||
|
||||
mountWithSpecCount(1)
|
||||
cy.contains('1 Match').should('be.visible')
|
||||
|
||||
cy.percySnapshot('Singular Match')
|
||||
|
||||
mountWithSpecCount(100)
|
||||
cy.contains('100 Matches').should('be.visible')
|
||||
|
||||
cy.percySnapshot('Plural Match')
|
||||
})
|
||||
|
||||
it('shows the count correctly while searching', () => {
|
||||
@@ -103,5 +112,7 @@ describe('<SpecsListHeader />', { keystrokeDelay: 0 }, () => {
|
||||
|
||||
mountWithCounts(5, 22)
|
||||
cy.contains('5 of 22 Matches').should('be.visible')
|
||||
|
||||
cy.percySnapshot()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -12,12 +12,12 @@
|
||||
>
|
||||
<template #suffix>
|
||||
<button
|
||||
class="rounded-r-md outline-none h-40px mr-[-0.75rem] transition-all group"
|
||||
class="rounded-r-md outline-none h-38px mr-[-0.75rem] group"
|
||||
aria-live="polite"
|
||||
@click="emit('showSpecPatternModal')"
|
||||
>
|
||||
<span
|
||||
class="border-transparent rounded-r flex h-full border-t-1 border-b-1 border-r-1 mr-1px px-16px items-center matches-button group-hocus:bg-indigo-50 group-hocus:border-indigo-300 group-hocus:text-indigo-500"
|
||||
class="bg-white border-transparent rounded-r flex h-full border-t-1 border-b-1 border-r-1 mr-1px px-16px transition-all items-center matches-button group-hocus:bg-indigo-50 group-hocus:text-indigo-500"
|
||||
>
|
||||
<span v-if="props.modelValue">
|
||||
{{ t('components.fileSearch.matchesIndicator', { count: specCount, denominator: specCount, numerator: resultCount}) }}
|
||||
@@ -65,7 +65,7 @@ const props = withDefaults(defineProps<{
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', value: string): void,
|
||||
(e: 'update:modelValue', value: string): void
|
||||
(e: 'showSpecPatternModal'): void
|
||||
(e: 'showCreateSpecModal'): void
|
||||
}>()
|
||||
@@ -80,8 +80,8 @@ const onInput = (e: Event) => {
|
||||
<style scoped>
|
||||
|
||||
.matches-button:before {
|
||||
@apply h-full bg-gray-100 transform transition ease-in w-1px
|
||||
scale-y-50 duration-150 group-hocus:bg-indigo-300 group-hocus:scale-y-100;
|
||||
@apply h-full bg-gray-100 transform transition w-1px
|
||||
scale-y-50 duration-150 group-hocus:bg-transparent;
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="flex flex-col justify-between flex-grow">
|
||||
<div class="flex flex-col flex-grow justify-between">
|
||||
<template v-if="!result">
|
||||
<div class="p-24px w-720px">
|
||||
<Input
|
||||
@@ -23,14 +23,14 @@
|
||||
>
|
||||
<div
|
||||
v-if="hasError"
|
||||
class="flex items-center font-medium rounded bg-error-100 mt-16px p-14px ring-2 ring-error-100 text-error-600 gap-8px"
|
||||
class="rounded flex font-medium bg-error-100 mt-16px p-14px ring-2 ring-error-100 text-error-600 gap-8px items-center"
|
||||
>
|
||||
<i-cy-errored-outline_x16 class="icon-dark-error-600" />
|
||||
<span>{{ invalidSpecWarning }}<em class="font-medium">specPattern</em>:</span>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="showExtensionWarning && props.type === 'e2e'"
|
||||
class="flex items-center font-medium rounded bg-warning-100 mt-16px p-16px text-warning-600 gap-8px"
|
||||
class="rounded flex font-medium bg-warning-100 mt-16px p-16px text-warning-600 gap-8px items-center"
|
||||
>
|
||||
<i-cy-errored-outline_x16 class="icon-dark-warning-600" />
|
||||
{{ t('createSpec.e2e.importEmptySpec.specExtensionWarning') }}<span class="rounded bg-warning-200 py-2px px-8px text-warning-700">{{ recommendedFileName }}</span>
|
||||
@@ -48,7 +48,7 @@
|
||||
class="flex gap-16px"
|
||||
>
|
||||
<Button
|
||||
class="w-110px"
|
||||
size="lg"
|
||||
:disabled="!isValidSpecFile"
|
||||
@click="createSpec"
|
||||
>
|
||||
@@ -56,6 +56,7 @@
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
size="lg"
|
||||
variant="outline"
|
||||
@click="emits('restart')"
|
||||
>
|
||||
@@ -69,7 +70,7 @@
|
||||
:file="result.file"
|
||||
/>
|
||||
<StandardModalFooter
|
||||
class="flex items-center h-72px gap-16px"
|
||||
class="flex gap-16px items-center"
|
||||
>
|
||||
<router-link
|
||||
class="outline-none"
|
||||
@@ -77,6 +78,7 @@
|
||||
"
|
||||
>
|
||||
<Button
|
||||
size="lg"
|
||||
:prefix-icon="TestResultsIcon"
|
||||
prefix-icon-class="w-16px h-16px icon-dark-white"
|
||||
@click="emits('close')"
|
||||
@@ -85,6 +87,7 @@
|
||||
</Button>
|
||||
</router-link>
|
||||
<Button
|
||||
size="lg"
|
||||
:prefix-icon="PlusButtonIcon"
|
||||
prefix-icon-class="w-16px h-16px icon-dark-gray-500"
|
||||
variant="outline"
|
||||
@@ -113,7 +116,7 @@ import TestResultsIcon from '~icons/cy/test-results_x24.svg'
|
||||
import PlusButtonIcon from '~icons/cy/add-large_x16.svg'
|
||||
|
||||
const props = defineProps<{
|
||||
title: string,
|
||||
title: string
|
||||
gql: EmptyGeneratorFragment
|
||||
type: 'e2e' | 'component' | 'story'
|
||||
specFileName: string
|
||||
@@ -144,7 +147,7 @@ mutation EmptyGenerator_generateSpec($codeGenCandidate: String!, $type: CodeGenT
|
||||
}`
|
||||
|
||||
const emits = defineEmits<{
|
||||
(event: 'update:title', value: string): void,
|
||||
(event: 'update:title', value: string): void
|
||||
(event: 'update:description', value: string): void
|
||||
(event: 'restart'): void
|
||||
(event: 'close'): void
|
||||
|
||||
@@ -64,7 +64,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Ref, ComputedRef } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import { debouncedWatch, useDebounce, useElementSize } from '@vueuse/core'
|
||||
import { useI18n } from '@cy/i18n'
|
||||
@@ -77,7 +76,7 @@ import { gql } from '@urql/core'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
files: any[]
|
||||
extensionPattern: string,
|
||||
extensionPattern: string
|
||||
loading?: boolean
|
||||
}>(), {
|
||||
|
||||
|
||||
@@ -33,7 +33,6 @@ import ShikiHighlight from '@cy/components/ShikiHighlight.vue'
|
||||
import Collapsible from '@cy/components/Collapsible.vue'
|
||||
import { gql } from '@urql/core'
|
||||
import type { GeneratorSuccessFileFragment } from '../../generated/graphql'
|
||||
import { ref } from 'vue'
|
||||
|
||||
gql`
|
||||
fragment GeneratorSuccessFile on ScaffoldedFile {
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
<div>
|
||||
<StandardModalFooter
|
||||
v-if="result"
|
||||
class="flex h-72px gap-16px items-center"
|
||||
class="flex gap-16px items-center"
|
||||
>
|
||||
<router-link
|
||||
class="outline-none"
|
||||
@@ -46,6 +46,7 @@
|
||||
"
|
||||
>
|
||||
<Button
|
||||
size="lg"
|
||||
:prefix-icon="TestResultsIcon"
|
||||
prefix-icon-class="w-16px h-16px icon-dark-white"
|
||||
@click="emits('close')"
|
||||
@@ -54,6 +55,7 @@
|
||||
</Button>
|
||||
</router-link>
|
||||
<Button
|
||||
size="lg"
|
||||
:prefix-icon="PlusButtonIcon"
|
||||
prefix-icon-class="w-16px h-16px icon-dark-gray-500"
|
||||
variant="outline"
|
||||
@@ -87,14 +89,14 @@ import TestResultsIcon from '~icons/cy/test-results_x24.svg'
|
||||
import EmptyGenerator from '../EmptyGenerator.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
title: string,
|
||||
title: string
|
||||
codeGenGlob: string
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const emits = defineEmits<{
|
||||
(event: 'update:title', value: string): void,
|
||||
(event: 'update:title', value: string): void
|
||||
(event: 'update:description', value: string): void
|
||||
(event: 'restart'): void
|
||||
(event: 'close'): void
|
||||
|
||||
@@ -84,7 +84,7 @@ mutation ScaffoldGeneratorStepOne_scaffoldIntegration {
|
||||
`
|
||||
|
||||
const emits = defineEmits<{
|
||||
(event: 'update:title', value: string): void,
|
||||
(event: 'update:title', value: string): void
|
||||
(event: 'update:description', value: string): void
|
||||
(event: 'close'): void
|
||||
}>()
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
"
|
||||
>
|
||||
<Button
|
||||
size="lg"
|
||||
:prefix-icon="TestResultsIcon"
|
||||
prefix-icon-class="w-16px h-16px icon-dark-white"
|
||||
@click="emits('close')"
|
||||
@@ -58,6 +59,7 @@
|
||||
</Button>
|
||||
</router-link>
|
||||
<Button
|
||||
size="lg"
|
||||
:prefix-icon="PlusButtonIcon"
|
||||
prefix-icon-class="w-16px h-16px icon-dark-gray-500"
|
||||
variant="outline"
|
||||
@@ -87,14 +89,14 @@ import TestResultsIcon from '~icons/cy/test-results_x24.svg'
|
||||
import EmptyGenerator from '../EmptyGenerator.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
title: string,
|
||||
title: string
|
||||
codeGenGlob: string
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const emits = defineEmits<{
|
||||
(event: 'update:title', value: string): void,
|
||||
(event: 'update:title', value: string): void
|
||||
(event: 'update:description', value: string): void
|
||||
(event: 'restart'): void
|
||||
(event: 'close'): void
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import path from 'path'
|
||||
import assert from 'assert'
|
||||
import type { DataContext } from '..'
|
||||
import {
|
||||
cleanUpIntegrationFolder,
|
||||
@@ -127,4 +128,24 @@ export class MigrationActions {
|
||||
throw Error(`Expected ${actual} to equal ${expected}`)
|
||||
}
|
||||
}
|
||||
|
||||
async assertSuccessfulConfigScaffold (configFile: `cypress.config.${'js'|'ts'}`) {
|
||||
assert(this.ctx.currentProject)
|
||||
|
||||
// we assert the generated configuration file against one from a project that has
|
||||
// been verified to run correctly.
|
||||
// each project has an `unconfigured` and `configured` variant in `system-tests/projects`
|
||||
// for example vueclivue2-configured and vueclivue2-unconfigured.
|
||||
// after setting the project up with the launchpad, the two projects should contain the same files.
|
||||
|
||||
const configuredProject = this.ctx.project.projectTitle(this.ctx.currentProject).replace('unconfigured', 'configured')
|
||||
const expectedProjectConfig = path.join(__dirname, '..', '..', '..', '..', 'system-tests', 'projects', configuredProject, configFile)
|
||||
|
||||
const actual = formatConfig(await this.ctx.actions.file.readFileInProject(configFile))
|
||||
const expected = formatConfig(await this.ctx.fs.readFile(expectedProjectConfig, 'utf8'))
|
||||
|
||||
if (actual !== expected) {
|
||||
throw Error(`Expected ${actual} to equal ${expected}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import type { CodeLanguageEnum, NexusGenEnums, NexusGenObjects } from '@packages/graphql/src/gen/nxs.gen'
|
||||
import { Bundler, CodeLanguage, CODE_LANGUAGES, FrontendFramework, FRONTEND_FRAMEWORKS } from '@packages/types'
|
||||
import { CodeLanguage, CODE_LANGUAGES } from '@packages/types'
|
||||
import { Bundler, FrontendFramework, FRONTEND_FRAMEWORKS, detect } from '@packages/scaffold-config'
|
||||
import assert from 'assert'
|
||||
import dedent from 'dedent'
|
||||
import path from 'path'
|
||||
import fs from 'fs-extra'
|
||||
import Debug from 'debug'
|
||||
|
||||
const debug = Debug('cypress:data-context:wizard-actions')
|
||||
@@ -12,7 +14,6 @@ import type { DataContext } from '..'
|
||||
interface WizardGetCodeComponent {
|
||||
chosenLanguage: CodeLanguage
|
||||
chosenFramework: FrontendFramework
|
||||
chosenBundler: Bundler
|
||||
}
|
||||
|
||||
export class WizardActions {
|
||||
@@ -28,28 +29,33 @@ export class WizardActions {
|
||||
return this.ctx.wizardData
|
||||
}
|
||||
|
||||
setFramework (framework: NexusGenEnums['FrontendFrameworkEnum'] | null) {
|
||||
const prevFramework = this.ctx.coreData.wizard.chosenFramework || ''
|
||||
setFramework (framework: typeof FRONTEND_FRAMEWORKS[number]['type'] | null): void {
|
||||
const next = FRONTEND_FRAMEWORKS.find((x) => x.type === framework)
|
||||
|
||||
this.ctx.coreData.wizard.chosenFramework = framework
|
||||
|
||||
if (framework !== 'react' && framework !== 'vue') {
|
||||
return this.setBundler('webpack')
|
||||
if (next?.supportedBundlers?.length === 1) {
|
||||
this.setBundler(next?.supportedBundlers?.[0].type)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const { chosenBundler } = this.ctx.coreData.wizard
|
||||
|
||||
// if the previous bundler was incompatible with the
|
||||
// new framework, we need to reset it
|
||||
if ((chosenBundler && !this.ctx.wizard.chosenFramework?.supportedBundlers.includes(chosenBundler))
|
||||
|| !['react', 'vue'].includes(prevFramework)) {
|
||||
return this.setBundler(null)
|
||||
}
|
||||
// new framework that was selected, we need to reset it
|
||||
const doesNotSupportChosenBundler = (chosenBundler && !new Set(
|
||||
this.ctx.wizard.chosenFramework?.supportedBundlers.map((x) => x.type) || [],
|
||||
).has(chosenBundler)) ?? false
|
||||
|
||||
return
|
||||
const prevFramework = this.ctx.coreData.wizard.chosenFramework || ''
|
||||
|
||||
if (doesNotSupportChosenBundler || !['react', 'vue'].includes(prevFramework)) {
|
||||
this.setBundler(null)
|
||||
}
|
||||
}
|
||||
|
||||
setBundler (bundler: NexusGenEnums['SupportedBundlers'] | null) {
|
||||
setBundler (bundler: Bundler | null) {
|
||||
this.ctx.coreData.wizard.chosenBundler = bundler
|
||||
|
||||
return this.data
|
||||
@@ -83,76 +89,40 @@ export class WizardActions {
|
||||
}
|
||||
|
||||
async initialize () {
|
||||
if (this.ctx.currentProject) {
|
||||
this.data.detectedFramework = null
|
||||
this.data.detectedBundler = null
|
||||
this.data.detectedLanguage = null
|
||||
|
||||
await this.detectLanguage()
|
||||
debug('detectedLanguage %s', this.data.detectedLanguage)
|
||||
this.data.chosenLanguage = this.data.detectedLanguage || 'js'
|
||||
|
||||
let hasPackageJson = true
|
||||
|
||||
try {
|
||||
await this.ctx.fs.access(path.join(this.ctx.currentProject, 'package.json'), this.ctx.fs.constants.R_OK)
|
||||
} catch (e) {
|
||||
debug('Could not read or find package.json: %O', e)
|
||||
hasPackageJson = false
|
||||
}
|
||||
const packageJson: {
|
||||
dependencies?: { [key: string]: string }
|
||||
devDependencies?: { [key: string]: string }
|
||||
} = hasPackageJson ? await this.ctx.fs.readJson(path.join(this.ctx.currentProject, 'package.json')) : {}
|
||||
|
||||
debug('packageJson %O', packageJson)
|
||||
const dependencies = [
|
||||
...Object.keys(packageJson.dependencies || {}),
|
||||
...Object.keys(packageJson.devDependencies || {}),
|
||||
]
|
||||
|
||||
this.detectFramework(dependencies)
|
||||
debug('detectedFramework %s', this.data.detectedFramework)
|
||||
this.detectBundler(dependencies)
|
||||
debug('detectedBundler %s', this.data.detectedBundler)
|
||||
|
||||
this.data.chosenFramework = this.data.detectedFramework || null
|
||||
this.data.chosenBundler = this.data.detectedBundler || null
|
||||
}
|
||||
}
|
||||
|
||||
private detectFramework (dependencies: string[]) {
|
||||
// Detect full featured frameworks
|
||||
if (dependencies.includes('next')) {
|
||||
this.ctx.wizardData.detectedFramework = 'nextjs'
|
||||
} else if (dependencies.includes('react-scripts')) {
|
||||
this.ctx.wizardData.detectedFramework = 'cra'
|
||||
} else if (dependencies.includes('nuxt')) {
|
||||
this.ctx.wizardData.detectedFramework = 'nuxtjs'
|
||||
} else if (dependencies.includes('@vue/cli-service')) {
|
||||
this.ctx.wizardData.detectedFramework = 'vuecli'
|
||||
} else if (dependencies.includes('react')) {
|
||||
this.ctx.wizardData.detectedFramework = 'react'
|
||||
} else if (dependencies.includes('vue')) {
|
||||
this.ctx.wizardData.detectedFramework = 'vue'
|
||||
}
|
||||
}
|
||||
|
||||
private detectBundler (dependencies: string[]) {
|
||||
const detectedFrameworkObject = FRONTEND_FRAMEWORKS.find((f) => f.type === this.ctx.wizardData.detectedFramework)
|
||||
|
||||
if (detectedFrameworkObject && detectedFrameworkObject.supportedBundlers.length === 1) {
|
||||
this.ctx.wizardData.detectedBundler = detectedFrameworkObject.supportedBundlers[0] ?? null
|
||||
|
||||
if (!this.ctx.currentProject) {
|
||||
return
|
||||
}
|
||||
|
||||
if (dependencies.includes('webpack')) {
|
||||
this.ctx.wizardData.detectedBundler = 'webpack'
|
||||
}
|
||||
this.ctx.update((coreData) => {
|
||||
coreData.wizard.detectedFramework = null
|
||||
coreData.wizard.detectedBundler = null
|
||||
coreData.wizard.detectedLanguage = null
|
||||
})
|
||||
|
||||
if (dependencies.includes('vite')) {
|
||||
this.ctx.wizardData.detectedBundler = 'vite'
|
||||
await this.detectLanguage()
|
||||
debug('detectedLanguage %s', this.data.detectedLanguage)
|
||||
this.data.chosenLanguage = this.data.detectedLanguage || 'js'
|
||||
|
||||
try {
|
||||
const detected = detect(await fs.readJson(path.join(this.ctx.currentProject, 'package.json')))
|
||||
|
||||
debug('detected %o', detected)
|
||||
|
||||
if (detected) {
|
||||
this.ctx.update((coreData) => {
|
||||
coreData.wizard.detectedFramework = detected.framework?.type ?? null
|
||||
coreData.wizard.chosenFramework = detected.framework?.type ?? null
|
||||
|
||||
if (!detected.framework?.supportedBundlers[0]) {
|
||||
return
|
||||
}
|
||||
|
||||
coreData.wizard.detectedBundler = detected.bundler || detected.framework.supportedBundlers[0].type
|
||||
coreData.wizard.chosenBundler = detected.bundler || detected.framework.supportedBundlers[0].type
|
||||
})
|
||||
}
|
||||
} catch {
|
||||
// Could not detect anything - no problem, no need to do anything.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,7 +188,6 @@ export class WizardActions {
|
||||
this.scaffoldFixtures(),
|
||||
this.scaffoldSupport('component', chosenLanguage.type),
|
||||
this.getComponentIndexHtml({
|
||||
chosenBundler,
|
||||
chosenFramework,
|
||||
chosenLanguage,
|
||||
}),
|
||||
@@ -252,11 +221,7 @@ export class WizardActions {
|
||||
|
||||
assert(chosenFramework && chosenLanguage && chosenBundler)
|
||||
|
||||
return this.wizardGetConfigCodeComponent({
|
||||
chosenLanguage,
|
||||
chosenFramework,
|
||||
chosenBundler,
|
||||
})
|
||||
return chosenFramework.config[chosenLanguage.type](chosenBundler.type)
|
||||
}
|
||||
|
||||
return this.wizardGetConfigCodeE2E(language)
|
||||
@@ -315,28 +280,6 @@ export class WizardActions {
|
||||
return codeBlocks.join('\n')
|
||||
}
|
||||
|
||||
private wizardGetConfigCodeComponent (opts: WizardGetCodeComponent): string {
|
||||
const codeBlocks: string[] = []
|
||||
const { chosenBundler, chosenFramework, chosenLanguage } = opts
|
||||
|
||||
const requirePath = chosenFramework.defaultPackagePath ?? chosenBundler.package
|
||||
|
||||
codeBlocks.push(chosenLanguage.type === 'ts' ? `import { defineConfig } from 'cypress'` : `const { defineConfig } = require('cypress')`)
|
||||
codeBlocks.push(chosenLanguage.type === 'ts' ? `import { devServer } from '${requirePath}'` : `const { devServer } = require('${requirePath}')`)
|
||||
codeBlocks.push('')
|
||||
codeBlocks.push(chosenLanguage.type === 'ts' ? `export default defineConfig({` : `module.exports = defineConfig({`)
|
||||
codeBlocks.push(` // Component testing, ${chosenLanguage.name}, ${chosenFramework.name}, ${chosenBundler.name}`)
|
||||
|
||||
codeBlocks.push(` ${COMPONENT_SCAFFOLD_BODY({
|
||||
lang: chosenLanguage.type,
|
||||
configOptionsString: '{}',
|
||||
}).replace(/\n/g, '\n ')}`)
|
||||
|
||||
codeBlocks.push(`})\n`)
|
||||
|
||||
return codeBlocks.join('\n')
|
||||
}
|
||||
|
||||
private async getComponentIndexHtml (opts: WizardGetCodeComponent): Promise<NexusGenObjects['ScaffoldedFile']> {
|
||||
const [storybookInfo] = await Promise.all([
|
||||
this.ctx.storybook.loadStorybookInfo(),
|
||||
@@ -442,21 +385,6 @@ const E2E_SCAFFOLD_BODY = dedent`
|
||||
},
|
||||
`
|
||||
|
||||
interface ComponentScaffoldOpts {
|
||||
lang: CodeLanguageEnum
|
||||
configOptionsString: string
|
||||
specPattern?: string
|
||||
}
|
||||
|
||||
const COMPONENT_SCAFFOLD_BODY = (opts: ComponentScaffoldOpts) => {
|
||||
return dedent`
|
||||
component: {
|
||||
devServer,
|
||||
devServerConfig: ${opts.configOptionsString}
|
||||
},
|
||||
`
|
||||
}
|
||||
|
||||
const FIXTURE_DATA = {
|
||||
'name': 'Using fixtures to represent data',
|
||||
'email': 'hello@cypress.io',
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { DataContext } from '../DataContext'
|
||||
import type { ParsedPath } from 'path'
|
||||
import { camelCase, capitalize } from 'lodash'
|
||||
import type { CodeGenType } from '@packages/graphql/src/gen/nxs.gen'
|
||||
import type { CodeGenFramework } from '@packages/types'
|
||||
import type { CodeGenFramework } from '@packages/scaffold-config'
|
||||
import { CsfFile, readCsfOrMdx } from '@storybook/csf-tools'
|
||||
|
||||
interface CodeGenOptions {
|
||||
|
||||
@@ -325,6 +325,8 @@ export class ProjectLifecycleManager {
|
||||
setCurrentTestingType (testingType: TestingType | null) {
|
||||
this.ctx.update((d) => {
|
||||
d.currentTestingType = testingType
|
||||
d.wizard.chosenBundler = null
|
||||
d.wizard.chosenFramework = null
|
||||
})
|
||||
|
||||
if (this._currentTestingType === testingType) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { BUNDLERS, FoundBrowser, Editor, Warning, AllowedState, AllModeOptions, TestingType, PACKAGE_MANAGERS, BrowserStatus, AuthStateName } from '@packages/types'
|
||||
import type { FoundBrowser, Editor, Warning, AllowedState, AllModeOptions, TestingType, BrowserStatus, PACKAGE_MANAGERS, AuthStateName } from '@packages/types'
|
||||
import type { Bundler, FRONTEND_FRAMEWORKS } from '@packages/scaffold-config'
|
||||
import type { NexusGenEnums, NexusGenObjects } from '@packages/graphql/src/gen/nxs.gen'
|
||||
import type { App, BrowserWindow } from 'electron'
|
||||
import type { ChildProcess } from 'child_process'
|
||||
@@ -61,14 +62,14 @@ export interface AppDataShape {
|
||||
}
|
||||
|
||||
export interface WizardDataShape {
|
||||
chosenBundler: NexusGenEnums['SupportedBundlers'] | null
|
||||
allBundlers: typeof BUNDLERS
|
||||
chosenFramework: NexusGenEnums['FrontendFrameworkEnum'] | null
|
||||
chosenBundler: Bundler | null
|
||||
chosenFramework: typeof FRONTEND_FRAMEWORKS[number]['type'] | null
|
||||
chosenLanguage: NexusGenEnums['CodeLanguageEnum']
|
||||
chosenManualInstall: boolean
|
||||
detectedLanguage: NexusGenEnums['CodeLanguageEnum'] | null
|
||||
detectedBundler: NexusGenEnums['SupportedBundlers'] | null
|
||||
detectedFramework: NexusGenEnums['FrontendFrameworkEnum'] | null
|
||||
detectedBundler: Bundler | null
|
||||
detectedFramework: typeof FRONTEND_FRAMEWORKS[number]['type'] | null
|
||||
__fakeInstalledPackagesForTesting: string[] | null
|
||||
}
|
||||
|
||||
export interface MigrationDataShape{
|
||||
@@ -168,9 +169,9 @@ export function makeCoreData (modeOptions: Partial<AllModeOptions> = {}): CoreDa
|
||||
chosenFramework: null,
|
||||
chosenLanguage: 'js',
|
||||
chosenManualInstall: false,
|
||||
allBundlers: BUNDLERS,
|
||||
detectedBundler: null,
|
||||
detectedFramework: null,
|
||||
__fakeInstalledPackagesForTesting: null,
|
||||
detectedLanguage: null,
|
||||
},
|
||||
migration: {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import os from 'os'
|
||||
import { FrontendFramework, FRONTEND_FRAMEWORKS, ResolvedFromConfig, RESOLVED_FROM, FoundSpec } from '@packages/types'
|
||||
import type { ResolvedFromConfig, RESOLVED_FROM, FoundSpec } from '@packages/types'
|
||||
import { FrontendFramework, FRONTEND_FRAMEWORKS } from '@packages/scaffold-config'
|
||||
import { scanFSForAvailableDependency } from 'create-cypress-tests'
|
||||
import minimatch from 'minimatch'
|
||||
import { debounce, isEqual } from 'lodash'
|
||||
@@ -261,7 +262,7 @@ export class ProjectDataSource {
|
||||
|
||||
private guessFramework (projectRoot: string) {
|
||||
const guess = FRONTEND_FRAMEWORKS.find((framework) => {
|
||||
const lookingForDeps = (framework.deps as readonly string[]).reduce(
|
||||
const lookingForDeps = framework.detectors.map((x) => x.dependency).reduce(
|
||||
(acc, dep) => ({ ...acc, [dep]: '*' }),
|
||||
{},
|
||||
)
|
||||
|
||||
@@ -1,57 +1,84 @@
|
||||
import Debug from 'debug'
|
||||
import { BUNDLERS, CODE_LANGUAGES, FRONTEND_FRAMEWORKS, PACKAGES_DESCRIPTIONS } from '@packages/types'
|
||||
import type { NexusGenObjects } from '@packages/graphql/src/gen/nxs.gen'
|
||||
import { CODE_LANGUAGES } from '@packages/types'
|
||||
import {
|
||||
BUNDLERS,
|
||||
DEPENDENCIES,
|
||||
FRONTEND_FRAMEWORKS,
|
||||
} from '@packages/scaffold-config'
|
||||
import type { DataContext } from '..'
|
||||
import path from 'path'
|
||||
import resolve from 'resolve-from'
|
||||
import assert from 'assert'
|
||||
|
||||
const debug = Debug('cypress:data-context:wizard-data-source')
|
||||
|
||||
interface PackageToInstall {
|
||||
name: typeof DEPENDENCIES[number]['name']
|
||||
installer: typeof DEPENDENCIES[number]['installer']
|
||||
description: typeof DEPENDENCIES[number]['description']
|
||||
package: typeof DEPENDENCIES[number]['package']
|
||||
}
|
||||
|
||||
export class WizardDataSource {
|
||||
constructor (private ctx: DataContext) {}
|
||||
|
||||
async packagesToInstall (): Promise<Array<NexusGenObjects['WizardNpmPackage']> | null> {
|
||||
async packagesToInstall (): Promise<PackageToInstall[] | null> {
|
||||
if (!this.chosenFramework || !this.chosenBundler) {
|
||||
return null
|
||||
}
|
||||
|
||||
const packages = [
|
||||
{
|
||||
name: this.chosenFramework.name as string,
|
||||
description: PACKAGES_DESCRIPTIONS[this.chosenFramework.package],
|
||||
package: this.chosenFramework.package,
|
||||
},
|
||||
{
|
||||
name: this.chosenBundler.name as string,
|
||||
description: PACKAGES_DESCRIPTIONS[this.chosenBundler.package],
|
||||
package: this.chosenBundler.package as string,
|
||||
},
|
||||
]
|
||||
const packages: PackageToInstall[] = [...this.chosenFramework.packages]
|
||||
|
||||
// find the matching dev server
|
||||
// vite -> @cypress/vite-dev-server
|
||||
// webpack -> @cypress/webpack-dev-server
|
||||
const bundler = BUNDLERS.find((x) => x.type === this.chosenBundler?.type)
|
||||
|
||||
assert(bundler)
|
||||
packages.push(...bundler.dependencies)
|
||||
|
||||
const storybookInfo = await this.ctx.storybook.loadStorybookInfo()
|
||||
const { storybookDep } = this.chosenFramework
|
||||
|
||||
if (storybookInfo && storybookDep) {
|
||||
packages.push({
|
||||
name: storybookDep,
|
||||
description: PACKAGES_DESCRIPTIONS[storybookDep],
|
||||
package: storybookDep,
|
||||
})
|
||||
packages.push(storybookDep)
|
||||
}
|
||||
|
||||
return packages
|
||||
}
|
||||
|
||||
async resolvePackagesToInstall (): Promise<string[]> {
|
||||
async installDependenciesCommand () {
|
||||
const commands = {
|
||||
'npm': 'npm install -D',
|
||||
'pnpm': 'pnpm install -D',
|
||||
'yarn': 'yarn add -D',
|
||||
}
|
||||
|
||||
let depsToInstall = (await this.ctx.wizard.packagesToInstall() ?? [])
|
||||
|
||||
if (!depsToInstall?.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
const deps = depsToInstall.map((pack) => `${pack.installer}`).join(' ')
|
||||
|
||||
return `${commands[this.ctx.coreData.packageManager ?? 'npm']} ${deps}`
|
||||
}
|
||||
|
||||
async installedPackages (): Promise<string[]> {
|
||||
if (this.ctx.coreData.wizard.__fakeInstalledPackagesForTesting) {
|
||||
return this.ctx.coreData.wizard.__fakeInstalledPackagesForTesting
|
||||
}
|
||||
|
||||
const packagesInitial = await this.packagesToInstall() || []
|
||||
|
||||
if (!this.ctx.currentProject) {
|
||||
throw Error('currentProject is not defined')
|
||||
}
|
||||
|
||||
debug('packages to install: %O', packagesInitial)
|
||||
debug('packages to install: %O in %s', packagesInitial, this.ctx.currentProject)
|
||||
|
||||
const installedPackages: (string|null)[] = packagesInitial.map((p) => {
|
||||
const installedPackages: Array<string | null> = packagesInitial.map((p) => {
|
||||
if (this.ctx.currentProject) {
|
||||
debug('package checked: %s', p.package)
|
||||
|
||||
@@ -62,6 +89,8 @@ export class WizardDataSource {
|
||||
// `package.json`
|
||||
const packageJsonPath = path.join(p.package, 'package.json')
|
||||
|
||||
debug('package.json path: %s', packageJsonPath)
|
||||
|
||||
try {
|
||||
resolve(this.ctx.currentProject, packageJsonPath)
|
||||
|
||||
|
||||
@@ -1,21 +1,17 @@
|
||||
import { parse } from '@babel/parser'
|
||||
import { graphqlSchema } from '@packages/graphql/src/schema'
|
||||
import { FRONTEND_FRAMEWORKS } from '@packages/types'
|
||||
import { FRONTEND_FRAMEWORKS } from '@packages/scaffold-config'
|
||||
import { expect } from 'chai'
|
||||
import dedent from 'dedent'
|
||||
import fs from 'fs-extra'
|
||||
import path from 'path'
|
||||
import sinon from 'sinon'
|
||||
import { DataContext } from '../../../src'
|
||||
import { AppApiShape, AuthApiShape, ElectronApiShape, LocalSettingsApiShape, ProjectApiShape } from '../../../src/actions'
|
||||
import {
|
||||
Action, codeGenerator, CodeGenResult, CodeGenResults,
|
||||
} from '../../../src/codegen/code-generator'
|
||||
import { SpecOptions } from '../../../src/codegen/spec-options'
|
||||
import templates from '../../../src/codegen/templates'
|
||||
import { InjectedConfigApi } from '../../../src/data'
|
||||
import { ErrorApiShape } from '../../../src/DataContext'
|
||||
import { BrowserApiShape } from '../../../src/sources'
|
||||
import { createTestDataContext } from '../helper'
|
||||
|
||||
const tmpPath = path.join(__dirname, 'tmp/test-code-gen')
|
||||
|
||||
@@ -29,21 +25,7 @@ describe('code-generator', () => {
|
||||
let ctx: DataContext
|
||||
|
||||
beforeEach(async () => {
|
||||
ctx = new DataContext({
|
||||
schema: graphqlSchema,
|
||||
mode: 'run',
|
||||
modeOptions: {},
|
||||
appApi: {} as AppApiShape,
|
||||
localSettingsApi: {} as LocalSettingsApiShape,
|
||||
authApi: {} as AuthApiShape,
|
||||
errorApi: {} as ErrorApiShape,
|
||||
configApi: {
|
||||
getServerPluginHandlers: () => [],
|
||||
} as InjectedConfigApi,
|
||||
projectApi: {} as ProjectApiShape,
|
||||
electronApi: {} as ElectronApiShape,
|
||||
browserApi: {} as BrowserApiShape,
|
||||
})
|
||||
ctx = createTestDataContext()
|
||||
|
||||
ctx.update((s) => {
|
||||
s.currentProject = tmpPath
|
||||
@@ -367,7 +349,7 @@ describe('code-generator', () => {
|
||||
sinon.stub(ctx.project.frameworkLoader, 'load').resolves(FRONTEND_FRAMEWORKS[1])
|
||||
|
||||
const newSpecCodeGenOptions = new SpecOptions(ctx, {
|
||||
codeGenPath: path.join(__dirname, 'files', 'vue', 'Button.stories.js'),
|
||||
codeGenPath: path.join(__dirname, 'files', 'vue', 'Button.stories.ts'),
|
||||
codeGenType: 'story',
|
||||
specFileExtension: '.cy',
|
||||
})
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
import 'mocha'
|
||||
import { e2eProjectDirs } from '@packages/frontend-shared/cypress/e2e/support/e2eProjectDirs'
|
||||
import Fixtures from '@tooling/system-tests/lib/fixtures'
|
||||
import { DataContext, DataContextConfig } from '../../src'
|
||||
import { graphqlSchema } from '@packages/graphql/src/schema'
|
||||
import type { BrowserApiShape } from '../../src/sources/BrowserDataSource'
|
||||
import type { AppApiShape, AuthApiShape, ElectronApiShape, LocalSettingsApiShape, ProjectApiShape } from '../../src/actions'
|
||||
import { InjectedConfigApi } from '../../src/data'
|
||||
|
||||
export async function scaffoldMigrationProject (project: typeof e2eProjectDirs[number]) {
|
||||
Fixtures.removeProject(project)
|
||||
@@ -10,3 +15,20 @@ export async function scaffoldMigrationProject (project: typeof e2eProjectDirs[n
|
||||
|
||||
return Fixtures.projectPath(project)
|
||||
}
|
||||
|
||||
export function createTestDataContext (mode: DataContextConfig['mode'] = 'run') {
|
||||
return new DataContext({
|
||||
schema: graphqlSchema,
|
||||
mode,
|
||||
modeOptions: {},
|
||||
appApi: {} as AppApiShape,
|
||||
localSettingsApi: {} as LocalSettingsApiShape,
|
||||
authApi: {} as AuthApiShape,
|
||||
configApi: {
|
||||
getServerPluginHandlers: () => [],
|
||||
} as InjectedConfigApi,
|
||||
projectApi: {} as ProjectApiShape,
|
||||
electronApi: {} as ElectronApiShape,
|
||||
browserApi: {} as BrowserApiShape,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import { expect } from 'chai'
|
||||
import { graphqlSchema } from '@packages/graphql/src/schema'
|
||||
import os from 'os'
|
||||
import sinon from 'sinon'
|
||||
import { DataContext } from '../../../src'
|
||||
import { AppApiShape, AuthApiShape, ElectronApiShape, LocalSettingsApiShape, ProjectApiShape } from '../../../src/actions'
|
||||
import { InjectedConfigApi } from '../../../src/data'
|
||||
import { ErrorApiShape } from '../../../src/DataContext'
|
||||
import { BrowserApiShape, VersionsDataSource } from '../../../src/sources'
|
||||
import { VersionsDataSource } from '../../../src/sources'
|
||||
import { createTestDataContext } from '../helper'
|
||||
|
||||
const pkg = require('@packages/root')
|
||||
const nmi = require('node-machine-id')
|
||||
@@ -21,21 +18,7 @@ describe('VersionsDataSource', () => {
|
||||
let currentCypressVersion: string = pkg.version
|
||||
|
||||
before(() => {
|
||||
ctx = new DataContext({
|
||||
schema: graphqlSchema,
|
||||
mode: 'open',
|
||||
modeOptions: {},
|
||||
appApi: {} as AppApiShape,
|
||||
localSettingsApi: {} as LocalSettingsApiShape,
|
||||
authApi: {} as AuthApiShape,
|
||||
errorApi: {} as ErrorApiShape,
|
||||
configApi: {
|
||||
getServerPluginHandlers: () => [],
|
||||
} as InjectedConfigApi,
|
||||
projectApi: {} as ProjectApiShape,
|
||||
electronApi: {} as ElectronApiShape,
|
||||
browserApi: {} as BrowserApiShape,
|
||||
})
|
||||
ctx = createTestDataContext('open')
|
||||
|
||||
ctx.coreData.currentTestingType = 'e2e'
|
||||
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
import { expect } from 'chai'
|
||||
import path from 'path'
|
||||
import { e2eProjectDirs } from '@packages/frontend-shared/cypress/e2e/support/e2eProjectDirs'
|
||||
import { createTestDataContext } from '../helper'
|
||||
|
||||
function getCurrentProject (project: typeof e2eProjectDirs[number]) {
|
||||
return path.join(__dirname, '..', '..', '..', '..', '..', 'system-tests', 'projects', project)
|
||||
}
|
||||
|
||||
describe('packagesToInstall', () => {
|
||||
it('create-react-app-unconfigured', async () => {
|
||||
const ctx = createTestDataContext()
|
||||
|
||||
ctx.coreData.currentProject = getCurrentProject('create-react-app-unconfigured')
|
||||
ctx.coreData.wizard.chosenFramework = 'crav5'
|
||||
ctx.coreData.wizard.chosenBundler = 'webpack5'
|
||||
|
||||
const actual = await ctx.wizard.installDependenciesCommand()
|
||||
|
||||
expect(actual).to.eq(`npm install -D @cypress/react@^5.0.0 webpack@^5.0.0 @cypress/webpack-dev-server@latest webpack-dev-server@^4.0.0 html-webpack-plugin@^5.0.0`)
|
||||
})
|
||||
|
||||
it('vueclivue2-unconfigured', async () => {
|
||||
const ctx = createTestDataContext()
|
||||
|
||||
ctx.coreData.currentProject = getCurrentProject('vueclivue2-unconfigured')
|
||||
ctx.coreData.wizard.chosenFramework = 'vueclivue2'
|
||||
ctx.coreData.wizard.chosenBundler = 'webpack4'
|
||||
|
||||
const actual = await ctx.wizard.installDependenciesCommand()
|
||||
|
||||
expect(actual).to.eq(`npm install -D @cypress/vue@^2.0.0 webpack@^4.0.0 @cypress/webpack-dev-server@latest webpack-dev-server@^4.0.0 html-webpack-plugin@^4.0.0`)
|
||||
})
|
||||
|
||||
it('vueclivue3-unconfigured', async () => {
|
||||
const ctx = createTestDataContext()
|
||||
|
||||
ctx.coreData.currentProject = getCurrentProject('vueclivue3-unconfigured')
|
||||
ctx.coreData.wizard.chosenFramework = 'vueclivue3'
|
||||
ctx.coreData.wizard.chosenBundler = 'webpack4'
|
||||
|
||||
const actual = await ctx.wizard.installDependenciesCommand()
|
||||
|
||||
expect(actual).to.eq(`npm install -D @cypress/vue@^3.0.0 webpack@^4.0.0 @cypress/webpack-dev-server@latest webpack-dev-server@^4.0.0 html-webpack-plugin@^4.0.0`)
|
||||
})
|
||||
|
||||
it('regular react project with vite', async () => {
|
||||
const ctx = createTestDataContext()
|
||||
|
||||
ctx.coreData.currentProject = getCurrentProject('react-vite-ts-unconfigured')
|
||||
ctx.coreData.wizard.chosenFramework = 'react'
|
||||
ctx.coreData.wizard.chosenBundler = 'vite'
|
||||
|
||||
const actual = await ctx.wizard.installDependenciesCommand()
|
||||
|
||||
expect(actual).to.eq(`npm install -D @cypress/react@^5.0.0 @cypress/vite-dev-server@latest`)
|
||||
})
|
||||
|
||||
it('regular vue project with vite', async () => {
|
||||
const ctx = createTestDataContext()
|
||||
|
||||
ctx.coreData.currentProject = getCurrentProject('vue3-vite-ts-unconfigured')
|
||||
ctx.coreData.wizard.chosenFramework = 'vue3'
|
||||
ctx.coreData.wizard.chosenBundler = 'vite'
|
||||
|
||||
const actual = await ctx.wizard.installDependenciesCommand()
|
||||
|
||||
expect(actual).to.eq(`npm install -D @cypress/vue@^3.0.0 @cypress/vite-dev-server@latest`)
|
||||
})
|
||||
|
||||
it('nextjs-unconfigured', async () => {
|
||||
const ctx = createTestDataContext()
|
||||
|
||||
ctx.coreData.currentProject = getCurrentProject('nextjs-unconfigured')
|
||||
ctx.coreData.wizard.chosenFramework = 'nextjs'
|
||||
ctx.coreData.wizard.chosenBundler = 'webpack4'
|
||||
|
||||
const actual = await ctx.wizard.installDependenciesCommand()
|
||||
|
||||
expect(actual).to.eq(`npm install -D @cypress/react@^5.0.0 webpack@^4.0.0 @cypress/webpack-dev-server@latest webpack-dev-server@^4.0.0 html-webpack-plugin@^4.0.0`)
|
||||
})
|
||||
|
||||
it('nuxtjs-vue2-unconfigured', async () => {
|
||||
const ctx = createTestDataContext()
|
||||
|
||||
ctx.coreData.currentProject = getCurrentProject('nuxtjs-vue2-unconfigured')
|
||||
ctx.coreData.wizard.chosenFramework = 'nuxtjs'
|
||||
ctx.coreData.wizard.chosenBundler = 'webpack4'
|
||||
|
||||
const actual = await ctx.wizard.installDependenciesCommand()
|
||||
|
||||
expect(actual).to.eq('npm install -D @cypress/vue@^2.0.0 webpack@^4.0.0 @cypress/webpack-dev-server@latest webpack-dev-server@^4.0.0 html-webpack-plugin@^4.0.0')
|
||||
})
|
||||
|
||||
it('pristine-with-e2e-testing-and-storybook', async () => {
|
||||
const ctx = createTestDataContext()
|
||||
|
||||
ctx.coreData.currentProject = getCurrentProject('pristine-with-e2e-testing-and-storybook')
|
||||
ctx.coreData.wizard.chosenFramework = 'react'
|
||||
ctx.coreData.wizard.chosenBundler = 'webpack4'
|
||||
|
||||
const actual = await ctx.wizard.installDependenciesCommand()
|
||||
|
||||
expect(actual).to.eq('npm install -D @cypress/react@^5.0.0 @cypress/webpack-dev-server@latest webpack-dev-server@^4.0.0 html-webpack-plugin@^4.0.0 @storybook/testing-react@latest')
|
||||
})
|
||||
|
||||
it('framework and bundler are undefined', async () => {
|
||||
const ctx = createTestDataContext()
|
||||
|
||||
// this should never happen!
|
||||
ctx.coreData.currentProject = getCurrentProject('pristine-with-e2e-testing-and-storybook')
|
||||
ctx.coreData.wizard.chosenFramework = undefined
|
||||
ctx.coreData.wizard.chosenBundler = undefined
|
||||
|
||||
const actual = await ctx.wizard.installDependenciesCommand()
|
||||
|
||||
expect(actual).to.be.null
|
||||
})
|
||||
})
|
||||
@@ -470,7 +470,6 @@ describe('src/cy/commands/agents', () => {
|
||||
|
||||
it('logs agent on creation', function () {
|
||||
expect(this.agentLogs[0].get('name')).to.eq('stub-1')
|
||||
expect(this.agentLogs[0].get('type')).to.eq('stub-1')
|
||||
expect(this.agentLogs[0].get('instrument')).to.eq('agent')
|
||||
})
|
||||
|
||||
@@ -687,7 +686,6 @@ describe('src/cy/commands/agents', () => {
|
||||
// same as cy.stub() except for name and type
|
||||
it('logs agent on creation', function () {
|
||||
expect(this.logs[0].get('name')).to.eq('spy-1')
|
||||
expect(this.logs[0].get('type')).to.eq('spy-1')
|
||||
expect(this.logs[0].get('instrument')).to.eq('agent')
|
||||
})
|
||||
|
||||
|
||||
@@ -167,7 +167,6 @@ export default function (Commands, Cypress, cy, state) {
|
||||
const log = Cypress.log({
|
||||
instrument: 'agent',
|
||||
name,
|
||||
type: name,
|
||||
functionName: method,
|
||||
count,
|
||||
callCount: 0,
|
||||
|
||||
@@ -35,8 +35,8 @@ const getSessionDetailsForTable = (sessState) => {
|
||||
const isSecureContext = (url) => url.startsWith('https:')
|
||||
|
||||
const getCurrentOriginStorage = () => {
|
||||
// localStorage.length propery is not always accurate, we must stringify to check for entries
|
||||
// for ex) try setting localStorge.key = 'val' and reading localStorage.length, may be 0.
|
||||
// localStorage.length property is not always accurate, we must stringify to check for entries
|
||||
// for ex) try setting localStorage.key = 'val' and reading localStorage.length, may be 0.
|
||||
const _localStorageStr = JSON.stringify(window.localStorage)
|
||||
const _localStorage = _localStorageStr.length > 2 && JSON.parse(_localStorageStr)
|
||||
const _sessionStorageStr = JSON.stringify(window.sessionStorage)
|
||||
@@ -764,7 +764,6 @@ export default function (Commands, Cypress, cy) {
|
||||
errorLog.error(err)
|
||||
errorLog.set({
|
||||
state: 'warn',
|
||||
|
||||
})
|
||||
|
||||
_log.set({
|
||||
|
||||
@@ -13,6 +13,8 @@ export const e2eProjectDirs = [
|
||||
'config-with-short-timeout',
|
||||
'config-with-ts',
|
||||
'cookies',
|
||||
'create-react-app-configured',
|
||||
'create-react-app-unconfigured',
|
||||
'cypress-in-cypress',
|
||||
'default-layout',
|
||||
'devServer-dynamic-import',
|
||||
@@ -49,6 +51,8 @@ export const e2eProjectDirs = [
|
||||
'multiple-config-files-with-json',
|
||||
'multiple-support-files',
|
||||
'multiple-task-registrations',
|
||||
'nextjs-configured',
|
||||
'nextjs-unconfigured',
|
||||
'no-scaffolding',
|
||||
'no-server',
|
||||
'no-specs',
|
||||
@@ -56,6 +60,8 @@ export const e2eProjectDirs = [
|
||||
'no-specs-no-storybook',
|
||||
'non-existent-spec',
|
||||
'non-proxied',
|
||||
'nuxtjs-vue2-configured',
|
||||
'nuxtjs-vue2-unconfigured',
|
||||
'odd-directory-name',
|
||||
'plugin-after-screenshot',
|
||||
'plugin-after-spec-deletes-video',
|
||||
@@ -88,7 +94,10 @@ export const e2eProjectDirs = [
|
||||
'pristine-with-e2e-testing',
|
||||
'pristine-with-e2e-testing-and-storybook',
|
||||
'pristine-yarn',
|
||||
'react-app-webpack-5-unconfigured',
|
||||
'react-code-gen',
|
||||
'react-vite-ts-configured',
|
||||
'react-vite-ts-unconfigured',
|
||||
'read-only-project-root',
|
||||
'record',
|
||||
'remote-debugging-disconnect',
|
||||
@@ -120,6 +129,12 @@ export const e2eProjectDirs = [
|
||||
'unify-plugin-errors',
|
||||
'various-file-types',
|
||||
'vite-ct',
|
||||
'vue3-vite-ts-configured',
|
||||
'vue3-vite-ts-unconfigured',
|
||||
'vueclivue2-configured',
|
||||
'vueclivue2-unconfigured',
|
||||
'vueclivue3-configured',
|
||||
'vueclivue3-unconfigured',
|
||||
'webpack-preprocessor',
|
||||
'webpack-preprocessor-awesome-typescript-loader',
|
||||
'webpack-preprocessor-ts-loader',
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { CodegenTypeMap, Wizard } from '../generated/test-graphql-types.gen'
|
||||
import { BUNDLERS, CODE_LANGUAGES, FRONTEND_FRAMEWORKS, PACKAGES_DESCRIPTIONS } from '@packages/types/src/constants'
|
||||
import { CODE_LANGUAGES } from '@packages/types/src/constants'
|
||||
import { BUNDLERS, CYPRESS_REACT_LATEST, CYPRESS_WEBPACK, FRONTEND_FRAMEWORKS } from '@packages/scaffold-config'
|
||||
import type { MaybeResolver } from './clientTestUtils'
|
||||
import { testNodeId } from './clientTestUtils'
|
||||
|
||||
@@ -14,18 +15,15 @@ export const allBundlers = BUNDLERS.map((bundler, idx) => {
|
||||
|
||||
export const stubWizard: MaybeResolver<Wizard> = {
|
||||
__typename: 'Wizard',
|
||||
installDependenciesCommand: 'npm install -D @cypress/react @cypress/webpack-dev-server',
|
||||
packagesToInstall: [
|
||||
{
|
||||
...testNodeId('WizardNpmPackage'),
|
||||
description: PACKAGES_DESCRIPTIONS['@cypress/react'],
|
||||
name: '@cypress/react',
|
||||
package: '@cypress/react',
|
||||
...CYPRESS_REACT_LATEST,
|
||||
},
|
||||
{
|
||||
...testNodeId('WizardNpmPackage'),
|
||||
description: PACKAGES_DESCRIPTIONS['@cypress/webpack-dev-server'],
|
||||
name: '@cypress/webpack-dev-server',
|
||||
package: '@cypress/webpack-dev-server',
|
||||
...CYPRESS_WEBPACK,
|
||||
},
|
||||
],
|
||||
allBundlers,
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10 3C9.44771 3 9 3.44772 9 4C9 4.55228 9.44771 5 10 5V3ZM10 11C9.44771 11 9 11.4477 9 12C9 12.5523 9.44771 13 10 13V11ZM6 5C6.55228 5 7 4.55228 7 4C7 3.44772 6.55228 3 6 3V5ZM6 13C6.55228 13 7 12.5523 7 12C7 11.4477 6.55228 11 6 11V13ZM5 7C4.44772 7 4 7.44772 4 8C4 8.55228 4.44772 9 5 9V7ZM11 9C11.5523 9 12 8.55228 12 8C12 7.44772 11.5523 7 11 7V9ZM10 5H11V3H10V5ZM11 11H10V13H11V11ZM5 5H6V3H5V5ZM6 11H5V13H6V11ZM5 9H11V7H5V9ZM2 8C2 6.34315 3.34315 5 5 5V3C2.23858 3 0 5.23858 0 8H2ZM0 8C0 10.7614 2.23858 13 5 13V11C3.34315 11 2 9.65685 2 8H0ZM14 8C14 9.65685 12.6569 11 11 11V13C13.7614 13 16 10.7614 16 8H14ZM16 8C16 5.23858 13.7614 3 11 3V5C12.6569 5 14 6.34315 14 8H16Z" fill="#1B1E2E" class="icon-dark"/>
|
||||
<path d="M10 3C9.44771 3 9 3.44772 9 4C9 4.55228 9.44771 5 10 5V3ZM10 11C9.44771 11 9 11.4477 9 12C9 12.5523 9.44771 13 10 13V11ZM6 5C6.55228 5 7 4.55228 7 4C7 3.44772 6.55228 3 6 3V5ZM6 13C6.55228 13 7 12.5523 7 12C7 11.4477 6.55228 11 6 11V13ZM5 7C4.44772 7 4 7.44772 4 8C4 8.55228 4.44772 9 5 9V7ZM11 9C11.5523 9 12 8.55228 12 8C12 7.44772 11.5523 7 11 7V9ZM10 5H11V3H10V5ZM11 11H10V13H11V11ZM5 5H6V3H5V5ZM6 11H5V13H6V11ZM5 9H11V7H5V9ZM2 8C2 6.34315 3.34315 5 5 5V3C2.23858 3 0 5.23858 0 8H2ZM0 8C0 10.7614 2.23858 13 5 13V11C3.34315 11 2 9.65685 2 8H0ZM14 8C14 9.65685 12.6569 11 11 11V13C13.7614 13 16 10.7614 16 8H14ZM16 8C16 5.23858 13.7614 3 11 3V5C12.6569 5 14 6.34315 14 8H16Z" fill="currentColor" class="icon-dark"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 826 B After Width: | Height: | Size: 831 B |
@@ -0,0 +1,5 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2 12.9996C2 7.47679 6.47715 2.99963 12 2.99963C17.5228 2.99963 22 7.47679 22 12.9996C22 16.2713 20.4289 19.176 18 21.0004H6C3.57111 19.176 2 16.2713 2 12.9996ZM12 13.9996C12.5523 13.9996 13 13.5519 13 12.9996C13 12.7235 12.8881 12.4735 12.7071 12.2925C12.5261 12.1116 12.2761 11.9996 12 11.9996C11.4477 11.9996 11 12.4473 11 12.9996C11 13.5519 11.4477 13.9996 12 13.9996Z" fill="#D0D2E0" class="icon-light"/>
|
||||
<path d="M6 21.0004L5.39942 21.7999C5.57262 21.93 5.78338 22.0004 6 22.0004V21.0004ZM18 21.0004V22.0004C18.2166 22.0004 18.4274 21.93 18.6006 21.7999L18 21.0004ZM11.2929 12.2925C10.9024 12.6831 10.9024 13.3162 11.2929 13.7067C11.6834 14.0973 12.3166 14.0973 12.7071 13.7067L11.2929 12.2925ZM17.7071 8.70674C18.0976 8.31622 18.0976 7.68305 17.7071 7.29253C17.3166 6.902 16.6834 6.902 16.2929 7.29253L17.7071 8.70674ZM3 12.9996C3 8.02907 7.02944 3.99963 12 3.99963V1.99963C5.92487 1.99963 1 6.9245 1 12.9996H3ZM12 3.99963C16.9706 3.99963 21 8.02907 21 12.9996H23C23 6.9245 18.0751 1.99963 12 1.99963V3.99963ZM6.60058 20.2008C4.41232 18.5571 3 15.9435 3 12.9996H1C1 16.5991 2.7299 19.7948 5.39942 21.7999L6.60058 20.2008ZM21 12.9996C21 15.9435 19.5877 18.5571 17.3994 20.2008L18.6006 21.7999C21.2701 19.7948 23 16.5991 23 12.9996H21ZM12 12.9996V14.9996C13.1046 14.9996 14 14.1042 14 12.9996H12ZM12 12.9996H10C10 14.1042 10.8954 14.9996 12 14.9996V12.9996ZM12 12.9996V10.9996C10.8954 10.9996 10 11.8951 10 12.9996H12ZM6 22.0004H18V20.0004H6V22.0004ZM14 12.9996C14 12.4476 13.775 11.9463 13.4142 11.5854L12 12.9996H14ZM13.4142 11.5854C13.0534 11.2246 12.552 10.9996 12 10.9996V12.9996L13.4142 11.5854ZM12.7071 13.7067L13.4142 12.9996L12 11.5854L11.2929 12.2925L12.7071 13.7067ZM13.4142 12.9996L17.7071 8.70674L16.2929 7.29253L12 11.5854L13.4142 12.9996Z" fill="currentColor" class="icon-dark"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.68012 8.09402C6.09073 7.5658 6.56611 7.09041 7.09433 6.6798L7.75731 7.34278C8.14783 7.7333 8.14783 8.36647 7.75731 8.75699C7.36678 9.14752 6.73362 9.14752 6.34309 8.75699L5.68012 8.09402ZM4.06189 13.9996C4.02104 13.672 4 13.3383 4 12.9996C4 12.661 4.02104 12.3272 4.06189 11.9996H5C5.55228 11.9996 6 12.4473 6 12.9996C6 13.5519 5.55228 13.9996 5 13.9996H4.06189ZM19.9381 11.9996C19.979 12.3272 20 12.661 20 12.9996C20 13.3383 19.979 13.672 19.9381 13.9996H19C18.4477 13.9996 18 13.5519 18 12.9996C18 12.4473 18.4477 11.9996 19 11.9996H19.9381ZM13 5.06153V5.99963C13 6.55192 12.5523 6.99963 12 6.99963C11.4477 6.99963 11 6.55192 11 5.99963V5.06153C11.3276 5.02068 11.6613 4.99963 12 4.99963C12.3387 4.99963 12.6724 5.02068 13 5.06153Z" fill="currentColor" class="icon-dark"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
@@ -65,7 +65,7 @@
|
||||
export type AlertStatus = 'error' | 'warning' | 'info' | 'default' | 'success'
|
||||
|
||||
export type AlertClasses = {
|
||||
headerClass: string,
|
||||
headerClass: string
|
||||
suffixIconClass: string
|
||||
suffixButtonClass: string
|
||||
bodyClass: string
|
||||
@@ -92,14 +92,14 @@ defineEmits<{
|
||||
const props = withDefaults(defineProps<{
|
||||
title?: string
|
||||
status?: AlertStatus
|
||||
icon?: FunctionalComponent<SVGAttributes, {}>,
|
||||
headerClass?: string,
|
||||
bodyClass?: string,
|
||||
dismissible?: boolean,
|
||||
collapsible?: boolean,
|
||||
modelValue?: boolean,
|
||||
iconClasses?: string,
|
||||
maxHeight?: string,
|
||||
icon?: FunctionalComponent<SVGAttributes, {}>
|
||||
headerClass?: string
|
||||
bodyClass?: string
|
||||
dismissible?: boolean
|
||||
collapsible?: boolean
|
||||
modelValue?: boolean
|
||||
iconClasses?: string
|
||||
maxHeight?: string
|
||||
}>(), {
|
||||
title: undefined,
|
||||
modelValue: true,
|
||||
|
||||
@@ -11,8 +11,8 @@ export const statusClassesObject: Record<BadgeRowStatus, string> = {
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{
|
||||
label: string | undefined,
|
||||
defineProps<{
|
||||
label: string | undefined
|
||||
status?: BadgeRowStatus
|
||||
}>()
|
||||
|
||||
|
||||
@@ -7,16 +7,10 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
// eslint-disable-next-line import/no-duplicates
|
||||
import { defineComponent } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
inheritAttrs: true,
|
||||
})
|
||||
export const inheritAttrs = true
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
// eslint-disable-next-line import/no-duplicates
|
||||
import { computed, useAttrs } from 'vue'
|
||||
import type { AnchorHTMLAttributes } from 'vue'
|
||||
|
||||
@@ -30,10 +24,11 @@ const classes = computed(() => {
|
||||
})
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
href: string,
|
||||
href?: string
|
||||
useDefaultHocus?: boolean
|
||||
}>(), {
|
||||
useDefaultHocus: true,
|
||||
href: '',
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
@@ -39,10 +39,7 @@
|
||||
<component
|
||||
:is="linkVersion"
|
||||
v-else
|
||||
:href="props.disabled ? null : props.href"
|
||||
:to="props.disabled ? null : props.to"
|
||||
:role="props.disabled ? 'link' : null"
|
||||
:aria-disabled="props.disabled ? 'disabled' : null "
|
||||
v-bind="linkProps"
|
||||
style="width: fit-content"
|
||||
class="border rounded flex outline-none gap-8px items-center select-none"
|
||||
:class="classes"
|
||||
@@ -78,16 +75,10 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
// eslint-disable-next-line import/no-duplicates
|
||||
import { defineComponent } from 'vue'
|
||||
import ButtonInternals from './ButtonInternals.vue'
|
||||
import ExternalLink from '../gql-components/ExternalLink.vue'
|
||||
import BaseLink from '../components/BaseLink.vue'
|
||||
|
||||
export default defineComponent({
|
||||
inheritAttrs: true,
|
||||
})
|
||||
|
||||
const VariantClassesTable = {
|
||||
primary: 'border-indigo-500 bg-indigo-500 text-white hocus-default',
|
||||
outline: 'border-gray-100 text-indigo-600 hocus-default',
|
||||
@@ -98,8 +89,6 @@ const VariantClassesTable = {
|
||||
secondary: 'bg-jade-500 text-white hocus-secondary',
|
||||
} as const
|
||||
|
||||
export type ButtonVariants = keyof(typeof VariantClassesTable)
|
||||
|
||||
const SizeClassesTable = {
|
||||
sm: 'px-6px py-2px text-14px h-24px',
|
||||
md: 'px-12px py-8px text-14px h-32px',
|
||||
@@ -107,17 +96,17 @@ const SizeClassesTable = {
|
||||
'lg-wide': 'px-32px py-8px',
|
||||
} as const
|
||||
|
||||
export type ButtonVariants = keyof(typeof VariantClassesTable)
|
||||
|
||||
export type ButtonSizes = keyof(typeof SizeClassesTable)
|
||||
|
||||
export const inheritAttrs = true
|
||||
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
||||
// eslint-disable-next-line import/no-duplicates
|
||||
import { computed, useAttrs } from 'vue'
|
||||
import { RouterLink } from 'vue-router'
|
||||
|
||||
// eslint-disable-next-line no-duplicate-imports
|
||||
import type { ButtonHTMLAttributes, FunctionalComponent, SVGAttributes } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -156,4 +145,20 @@ const linkVersion = computed(() => {
|
||||
|
||||
return RouterLink
|
||||
})
|
||||
|
||||
const linkProps = computed(() => {
|
||||
if (props.disabled) {
|
||||
return {
|
||||
role: 'link',
|
||||
ariaDisabled: 'disabled',
|
||||
href: null,
|
||||
}
|
||||
}
|
||||
|
||||
if (props.to) return { to: props.to }
|
||||
|
||||
if (props.href) return { href: props.href }
|
||||
|
||||
return {}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -39,7 +39,7 @@ export default defineComponent({
|
||||
// eslint-disable-next-line no-duplicate-imports
|
||||
import type { FunctionalComponent, SVGAttributes } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
defineProps<{
|
||||
prefixIcon?: FunctionalComponent<SVGAttributes>
|
||||
suffixIcon?: FunctionalComponent<SVGAttributes>
|
||||
prefixIconClass?: string
|
||||
|
||||
@@ -16,6 +16,6 @@ defineProps<{
|
||||
* NOTE: windi css does not allow specificity, so one
|
||||
* cannot "override" a css property
|
||||
*/
|
||||
bg?: boolean;
|
||||
bg?: boolean
|
||||
}>()
|
||||
</script>
|
||||
|
||||
@@ -42,8 +42,8 @@ import { useToggle } from '@vueuse/core'
|
||||
const props = withDefaults(defineProps<{
|
||||
maxHeight?: string
|
||||
initiallyOpen?: boolean
|
||||
lazy?: boolean,
|
||||
disable?: boolean,
|
||||
lazy?: boolean
|
||||
disable?: boolean
|
||||
}>(), {
|
||||
initiallyOpen: false,
|
||||
maxHeight: '500px',
|
||||
|
||||
@@ -33,10 +33,10 @@ import Button from '../components/Button.vue'
|
||||
import TransitionQuickFade from '../components/transitions/TransitionQuickFade.vue'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
text: string,
|
||||
noIcon?: boolean,
|
||||
variant?: ButtonVariants,
|
||||
size?: ButtonSizes,
|
||||
text: string
|
||||
noIcon?: boolean
|
||||
variant?: ButtonVariants
|
||||
size?: ButtonSizes
|
||||
}>(), {
|
||||
noIcon: false,
|
||||
variant: 'tertiary',
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
import type { FunctionalComponent, SVGAttributes } from 'vue'
|
||||
import { useSlots } from 'vue'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
withDefaults(defineProps<{
|
||||
description?: string
|
||||
icon?: FunctionalComponent<SVGAttributes>
|
||||
gray?: boolean
|
||||
@@ -50,6 +50,4 @@ const props = withDefaults(defineProps<{
|
||||
})
|
||||
|
||||
const slots = useSlots()
|
||||
|
||||
const gray = true
|
||||
</script>
|
||||
|
||||
@@ -36,7 +36,7 @@ import { useI18n } from '@cy/i18n'
|
||||
import NoResultsIllustration from '../assets/illustrations/no-results.svg'
|
||||
|
||||
defineProps<{
|
||||
search?: string,
|
||||
search?: string
|
||||
message?: string
|
||||
emptySearch?: boolean
|
||||
}>()
|
||||
|
||||
@@ -32,14 +32,14 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
const props = defineProps<{
|
||||
name: string,
|
||||
label: string,
|
||||
value?: string,
|
||||
name: string
|
||||
label: string
|
||||
value?: string
|
||||
options: Array<{
|
||||
label: string,
|
||||
value: string,
|
||||
description?: string,
|
||||
}>,
|
||||
label: string
|
||||
value: string
|
||||
description?: string
|
||||
}>
|
||||
}>()
|
||||
|
||||
const emits = defineEmits<{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user