Merge remote-tracking branch 'origin/chore/update-vue' into chore/update-vue

This commit is contained in:
Jessica Sachs
2022-03-04 16:18:58 -05:00
426 changed files with 50916 additions and 1849 deletions
+21
View File
@@ -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
View File
@@ -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
View File
@@ -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',
+3 -1
View File
@@ -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 -1
View File
@@ -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`
+6
View File
@@ -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
View File
@@ -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)
+1
View File
@@ -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')
+31 -12
View File
@@ -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 {
+22 -19
View File
@@ -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', () => {
+7 -7
View File
@@ -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', () => {
+1 -1
View File
@@ -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,
+2 -13
View File
@@ -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>
+4 -4
View File
@@ -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
+1 -1
View File
@@ -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
View File
@@ -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>
+1 -1
View File
@@ -87,7 +87,7 @@ fragment RunCard on CloudRun {
`
const props = defineProps<{
gql: RunCardFragment
gql: RunCardFragment
}>()
const ICON_MAP = {
+1 -1
View File
@@ -38,7 +38,7 @@ fragment RunResults on CloudRun {
`
const props = defineProps<{
gql: RunResultsFragment
gql: RunResultsFragment
}>()
const results = [
+1 -65
View File
@@ -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')
})
})
+6 -40
View File
@@ -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,
+1 -1
View File
@@ -48,7 +48,7 @@ fragment RunsEmpty on CurrentProject {
`
const props = defineProps<{
gql: RunsEmptyFragment,
gql: RunsEmptyFragment
}>()
const projectName = computed(() => props.gql.title)
+6 -6
View File
@@ -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<{
+2 -2
View File
@@ -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')
})
+22 -11
View File
@@ -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">
+19 -11
View File
@@ -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'
+4 -4
View File
@@ -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>
+2 -2
View File
@@ -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'
+2 -2
View File
@@ -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()
})
})
+5 -5
View File
@@ -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',
})
+22
View File
@@ -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,
+2 -3
View File
@@ -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