Skip to content

Commit

Permalink
feat: create from React component (#25168)
Browse files Browse the repository at this point in the history
* feat: server logic for create from React component (#24881)

Co-authored-by: Ryan Manuel <ryanm@cypress.io>
Co-authored-by: Lachlan Miller <lachlan.miller.1990@outlook.com>

* fix: add default export detection (#24954)

Co-authored-by: astone123 <adams@cypress.io>

* update cache

* update yarn.lock to fix builds

* fix: compilation with webpack preprocessor

* feat: create from React component UI (#24982)

* feat: WIP server logic for create from React component

* feat: add more tests; error handling

* feat: WIP create from React UI

* feat: PR feedback [run CI]

* feat: try committing snapshot cache changes [run ci]

* feat: try re-generating snapshot [run ci]

* fix build

* regenerate cache on darwin

* update caches

* Revert "feat: try re-generating snapshot [run ci]"

This reverts commit d763e1f.

* fix typing error

* types

* fix test

* chore: try using react-docgen@6.0.0-alpha.3

* update test

* regen linux snapshot

* update snapshots for darwin

* re-gen linux snapshot

* yarn install

* update snapshots

* update snapshot metadata

* update snapshots due to babel deps changing slightly

* make react docgen a dep

* update tests

* revert

* snapshots again??

* revert

* update

* update

* try change snapshot

* change snap

* update snap

* feat: remove unnecessary ts-ignore

* feat: add more test cases

* feat: create CodegenActions; other minor refactors

* feat: continue UI work

* feat: ignore config and Cypress-related files

* feat: PR feedback

* update Vue component link

* merge in default export work

* consolidate graphql queries

* other misc feedback

* use network-only policy to fetch files; include cypress/ dir for code gen candidates; fix type error

* add basic e2e test

* fix app integration tests

* refactor and fix app component and webpack dev server tests

* add error state; fix unit tests [skip ci]

* simplify generator show logic [skip ci]

* more testing

* fix types

* style updates [skip ci]

* fix error state [skip ci]

* fix list padding [skip ci]

* use slots (#25079)

* add more tests; fix unit tests

* fix types

* fix test describe

* add percy snapshots for new list

* update trouble rendering banner link [skip ci]

* use collapsible component

* use button for component list items

* fix tests

* build binaries

* revert changes to circle config

* remove eslintignore and extra loading div [skip ci] because we know it will fail

* revert changes to framework glob patterns [skip ci]

Co-authored-by: Ryan Manuel <ryanm@cypress.io>
Co-authored-by: Lachlan Miller <lachlan.miller.1990@outlook.com>

* feat: pass parser options to allow parsing of tsx files (#25145)

* fix create from component e2e test

* build binaries [run ci]

* fix component tests [run ci]

* regen windows snapshot

Co-authored-by: Ryan Manuel <ryanm@cypress.io>
Co-authored-by: Lachlan Miller <lachlan.miller.1990@outlook.com>
Co-authored-by: Zachary Williams <ZachJW34@gmail.com>
  • Loading branch information
4 people committed Dec 19, 2022
1 parent fcc49b5 commit 166b694
Show file tree
Hide file tree
Showing 84 changed files with 7,568 additions and 4,529 deletions.
10 changes: 5 additions & 5 deletions .circleci/workflows.yml
Expand Up @@ -28,7 +28,7 @@ mainBuildFilters: &mainBuildFilters
only:
- develop
- /^release\/\d+\.\d+\.\d+$/
- 'matth/fix/electron-video-performance'
- 'feature/create-from-react-component'

# usually we don't build Mac app - it takes a long time
# but sometimes we want to really confirm we are doing the right thing
Expand All @@ -37,15 +37,15 @@ macWorkflowFilters: &darwin-workflow-filters
when:
or:
- equal: [ develop, << pipeline.git.branch >> ]
- equal: [ 'matth/fix/electron-video-performance', << pipeline.git.branch >> ]
- equal: [ 'feature/create-from-react-component', << pipeline.git.branch >> ]
- matches:
pattern: /^release\/\d+\.\d+\.\d+$/
value: << pipeline.git.branch >>
linuxArm64WorkflowFilters: &linux-arm64-workflow-filters
when:
or:
- equal: [ develop, << pipeline.git.branch >> ]
- equal: [ 'matth/fix/electron-video-performance', << pipeline.git.branch >> ]
- equal: [ 'feature/create-from-react-component', << pipeline.git.branch >> ]
- matches:
pattern: /^release\/\d+\.\d+\.\d+$/
value: << pipeline.git.branch >>
Expand All @@ -63,7 +63,7 @@ windowsWorkflowFilters: &windows-workflow-filters
when:
or:
- equal: [ develop, << pipeline.git.branch >> ]
- equal: [ 'matth/fix/electron-video-performance', << pipeline.git.branch >> ]
- equal: [ 'feature/create-from-react-component', << pipeline.git.branch >> ]
- matches:
pattern: /^release\/\d+\.\d+\.\d+$/
value: << pipeline.git.branch >>
Expand Down Expand Up @@ -129,7 +129,7 @@ commands:
- run:
name: Check current branch to persist artifacts
command: |
if [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "release/"* && "$CIRCLE_BRANCH" != "matth/fix/electron-video-performance" ]]; then
if [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "release/"* && "$CIRCLE_BRANCH" != "feature/create-from-react-component" ]]; then
echo "Not uploading artifacts or posting install comment for this branch."
circleci-agent step halt
fi
Expand Down
2 changes: 2 additions & 0 deletions npm/webpack-dev-server/cypress/e2e/react.cy.ts
Expand Up @@ -129,6 +129,8 @@ for (const project of WEBPACK_REACT) {

// 4. recreate spec, with same name as removed spec
cy.findByTestId('new-spec-button').click()
cy.findByRole('button', { name: 'Create new empty spec' }).should('be.visible').click()

cy.findByRole('dialog').within(() => {
cy.get('input').clear().type('src/App.cy.jsx')
cy.contains('button', 'Create spec').click()
Expand Down
2 changes: 1 addition & 1 deletion npm/webpack-preprocessor/__snapshots__/compilation.spec.js
@@ -1,6 +1,6 @@
exports['webpack preprocessor - e2e correctly preprocesses the file 1'] = `
it("is a test",(function(){expect(1).to.equal(1),expect(2).to.equal(2),expect(Math.min.apply(Math,[3,4])).to.equal(3)}));
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXhhbXBsZV9zcGVjX291dHB1dC5qcyIsIm1hcHBpbmdzIjoiQUFBQUEsR0FBRyxhQUFhLFdBR2RDLE9BRmdCLEdBRU5DLEdBQUdDLE1BQU0sR0FDbkJGLE9BSG1CLEdBR1RDLEdBQUdDLE1BQU0sR0FDbkJGLE9BQU9HLEtBQUtDLElBQUwsTUFBQUQsS0FBWSxDQUFDLEVBQUcsS0FBS0YsR0FBR0MsTUFBTSIsInNvdXJjZXMiOlsid2VicGFjazovL0BjeXByZXNzL3dlYnBhY2stcHJlcHJvY2Vzc29yLy4vdGVzdC9fdGVzdC1vdXRwdXQvZXhhbXBsZV9zcGVjLmpzIl0sInNvdXJjZXNDb250ZW50IjpbIml0KCdpcyBhIHRlc3QnLCAoKSA9PiB7XG4gIGNvbnN0IFthLCBiXSA9IFsxLCAyXVxuXG4gIGV4cGVjdChhKS50by5lcXVhbCgxKVxuICBleHBlY3QoYikudG8uZXF1YWwoMilcbiAgZXhwZWN0KE1hdGgubWluKC4uLlszLCA0XSkpLnRvLmVxdWFsKDMpXG59KVxuIl0sIm5hbWVzIjpbIml0IiwiZXhwZWN0IiwidG8iLCJlcXVhbCIsIk1hdGgiLCJtaW4iXSwic291cmNlUm9vdCI6IiJ9
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXhhbXBsZV9zcGVjX291dHB1dC5qcyIsIm1hcHBpbmdzIjoiQUFBQUEsR0FBRyxhQUFhLFdBR2RDLE9BRmdCLEdBRU5DLEdBQUdDLE1BQU0sR0FDbkJGLE9BSG1CLEdBR1RDLEdBQUdDLE1BQU0sR0FDbkJGLE9BQU9HLEtBQUtDLElBQUcsTUFBUkQsS0FBWSxDQUFDLEVBQUcsS0FBS0YsR0FBR0MsTUFBTSIsInNvdXJjZXMiOlsid2VicGFjazovL0BjeXByZXNzL3dlYnBhY2stcHJlcHJvY2Vzc29yLy4vdGVzdC9fdGVzdC1vdXRwdXQvZXhhbXBsZV9zcGVjLmpzIl0sInNvdXJjZXNDb250ZW50IjpbIml0KCdpcyBhIHRlc3QnLCAoKSA9PiB7XG4gIGNvbnN0IFthLCBiXSA9IFsxLCAyXVxuXG4gIGV4cGVjdChhKS50by5lcXVhbCgxKVxuICBleHBlY3QoYikudG8uZXF1YWwoMilcbiAgZXhwZWN0KE1hdGgubWluKC4uLlszLCA0XSkpLnRvLmVxdWFsKDMpXG59KVxuIl0sIm5hbWVzIjpbIml0IiwiZXhwZWN0IiwidG8iLCJlcXVhbCIsIk1hdGgiLCJtaW4iXSwic291cmNlUm9vdCI6IiJ9
`

exports['webpack preprocessor - e2e has less verbose syntax error 1'] = `
Expand Down
Expand Up @@ -220,7 +220,6 @@ describe('./lib/cross-origin-callback-loader', () => {
expectAddFileSource(store).to.equal(stripIndent`
__cypressCrossOriginCallback = () => {
const utils = require('../support/utils');
utils.foo();
}`)
})
Expand All @@ -230,9 +229,7 @@ describe('./lib/cross-origin-callback-loader', () => {
`it('test', () => {
cy.origin('http://www.foobar.com:3500', () => {
require('../support/commands')
const utils = require('../support/utils')
const _ = require('lodash')
})
})`,
Expand All @@ -241,9 +238,7 @@ describe('./lib/cross-origin-callback-loader', () => {
expectAddFileSource(store).to.equal(stripIndent`
__cypressCrossOriginCallback = () => {
require('../support/commands');
const utils = require('../support/utils');
const _ = require('lodash');
}`)
})
Expand All @@ -270,9 +265,7 @@ describe('./lib/cross-origin-callback-loader', () => {
`it('test', () => {
cy.origin('http://www.foobar.com:3500', () => {
const someVar = 'someValue'
const result = require('./fn')(someVar)
expect(result).to.equal('mutated someVar')
})
})`,
Expand All @@ -281,9 +274,7 @@ describe('./lib/cross-origin-callback-loader', () => {
expectAddFileSource(store).to.equal(stripIndent`
__cypressCrossOriginCallback = () => {
const someVar = 'someValue';
const result = require('./fn')(someVar);
expect(result).to.equal('mutated someVar');
}`)
})
Expand All @@ -293,7 +284,6 @@ describe('./lib/cross-origin-callback-loader', () => {
`it('test', () => {
cy.origin('http://www.foobar.com:3500', { args: { foo: 'foo'}}, ({ foo }) => {
const result = require('./fn')(foo)
expect(result).to.equal('mutated someVar')
})
})`,
Expand All @@ -304,7 +294,6 @@ describe('./lib/cross-origin-callback-loader', () => {
foo
}) => {
const result = require('./fn')(foo);
expect(result).to.equal('mutated someVar');
}`)
})
Expand Down
192 changes: 168 additions & 24 deletions packages/app/cypress/e2e/create-from-component.cy.ts
@@ -1,10 +1,10 @@
import defaultMessages from '@packages/frontend-shared/src/locales/en-US.json'
import { getPathForPlatform } from '../../src/paths'

function validateCreateFromComponentCard (beforeEachFn: () => void, expectedSpecPath: string) {
function validateCreateFromVueComponentCard (beforeEachFn: () => void, expectedSpecPath: string) {
beforeEach(beforeEachFn)

it('Shows create from component card for Vue projects with default spec patterns', () => {
it('Shows create from component card for Vue projects', () => {
cy.get('@ComponentCard')
.within(() => {
cy.findByRole('button', {
Expand Down Expand Up @@ -82,31 +82,175 @@ function validateCreateFromComponentCard (beforeEachFn: () => void, expectedSpec
.should('have.attr', 'href', `#/specs/runner?file=${expectedSpecPath}`).click()
})

cy.findByText('<HelloWorld ... />', { timeout: 10000 }).should('be.visible')
cy.waitForSpecToFinish({ passCount: 1 })
})
}

function validateCreateFromReactComponentCard (beforeEachFn: () => void, expectedSpecPath: string) {
beforeEach(beforeEachFn)

it('Shows create from component card for React projects', () => {
cy.get('@ComponentCard')
.within(() => {
cy.findByRole('button', {
name: 'Create from component',
}).should('be.visible')
.and('not.be.disabled')
})
})

it('Can be closed with the x button', () => {
cy.get('@ComponentCard').click()

cy.findByRole('button', { name: 'Close' }).as('DialogCloseButton')

cy.get('@DialogCloseButton').click()
cy.findByRole('dialog', {
name: 'Choose a component',
}).should('not.exist')
})

it('Lists files in the project', () => {
cy.get('@ComponentCard').click()

cy.findByText('5 matches').should('be.visible')

cy.findByText('App').should('be.visible')
cy.findByText('index').should('be.visible')
})

it('Allows for the user to search through their components', () => {
cy.get('@ComponentCard').click()

cy.findByText('*.{js,jsx,tsx}').should('be.visible')
cy.findByText('5 matches').should('be.visible')
cy.findByLabelText('file-name-input').type('App')

cy.findByText('App').should('be.visible')
cy.findByText('1 of 5 matches').should('be.visible')
cy.findByText('index').should('not.exist')
cy.findByText('component').should('not.exist')
})

it('shows \'No components found\' if there are no exported components', () => {
cy.get('@ComponentCard').click()

cy.findByText('index').should('be.visible').click()

cy.findByTestId('react-component-row').should('not.exist')
cy.contains('No components found').should('be.visible')
})

it('shows \'Unable to parse file\' if there was an error parsing the file', () => {
cy.get('@ComponentCard').click()

// This component has a syntax error so we will fail to parse it
cy.findByText('Invalid').should('be.visible').click()

cy.findByTestId('react-component-row').should('not.exist')
cy.contains('Unable to parse file').should('be.visible')
})

it('shows success modal when component spec is created', () => {
cy.get('@ComponentCard').click()

// Expand the row
cy.findByText('App').should('be.visible').click()

// Click on 'app' component
cy.findByTestId('react-component-row').should('contain', 'App').click()

cy.findByRole('dialog', {
name: defaultMessages.createSpec.successPage.header,
}).as('SuccessDialog').within(() => {
cy.contains(getPathForPlatform(expectedSpecPath)).should('be.visible')
cy.findByRole('button', { name: 'Close' }).should('be.visible')

cy.findByRole('link', { name: 'Okay, run the spec' })
.should('have.attr', 'href', `#/specs/runner?file=${expectedSpecPath}`)

cy.findByRole('button', { name: 'Create another spec' }).click()
})

// 'Create from component' card appears again when the user selects "create another spec"
cy.findByText('Create from component').should('be.visible')
})

it('runs generated spec', () => {
cy.get('@ComponentCard').click()

// Expand the row
cy.findByText('App').should('be.visible').click()

// Click on 'app' component
cy.findByTestId('react-component-row').should('contain', 'App').click()

cy.findByRole('dialog', {
name: defaultMessages.createSpec.successPage.header,
}).as('SuccessDialog').within(() => {
cy.contains(getPathForPlatform(expectedSpecPath)).should('be.visible')
cy.findByRole('button', { name: 'Close' }).should('be.visible')

// There appears to be a race condition here where sometimes we try to run the spec
// before the file has been written to. Waiting here for 1 second resolves the issue.
cy.wait(2000)

cy.findByRole('link', { name: 'Okay, run the spec' })
.should('have.attr', 'href', `#/specs/runner?file=${expectedSpecPath}`).click()
})

cy.waitForSpecToFinish({ passCount: 1 })
})
}

describe('Create from component card', () => {
context('project with default spec pattern', () => {
validateCreateFromComponentCard(() => {
cy.scaffoldProject('no-specs-vue-2')
cy.openProject('no-specs-vue-2')
cy.startAppServer('component')
cy.visitApp()

cy.findAllByTestId('card').eq(0).as('ComponentCard')
}, 'src/components/HelloWorld.cy.js')
})

context('project with custom spec pattern', () => {
validateCreateFromComponentCard(() => {
cy.scaffoldProject('no-specs-vue-2')
cy.openProject('no-specs-vue-2', ['--config-file', 'cypress-custom-spec-pattern.config.js'])
cy.startAppServer('component')
cy.visitApp()

cy.findByText('New spec').click()
cy.findAllByTestId('card').eq(0).as('ComponentCard')
}, 'src/specs-folder/HelloWorld.cy.js')
context('Vue', () => {
context('project with default spec pattern', () => {
validateCreateFromVueComponentCard(() => {
cy.scaffoldProject('no-specs-vue-2')
cy.openProject('no-specs-vue-2')
cy.startAppServer('component')
cy.visitApp()

cy.findAllByTestId('card').eq(0).as('ComponentCard')
}, 'src/components/HelloWorld.cy.js')
})

context('project with custom spec pattern', () => {
validateCreateFromVueComponentCard(() => {
cy.scaffoldProject('no-specs-vue-2')
cy.openProject('no-specs-vue-2', ['--config-file', 'cypress-custom-spec-pattern.config.js'])
cy.startAppServer('component')
cy.visitApp()

cy.findByText('New spec').click()
cy.findAllByTestId('card').eq(0).as('ComponentCard')
}, 'src/specs-folder/HelloWorld.cy.js')
})
})

context('React', () => {
context('project with default spec pattern', () => {
validateCreateFromReactComponentCard(() => {
cy.scaffoldProject('no-specs')
cy.openProject('no-specs')
cy.startAppServer('component')
cy.visitApp()

cy.findAllByTestId('card').eq(0).as('ComponentCard')
}, 'src/App.cy.jsx')
})

context('project with custom spec pattern', () => {
validateCreateFromReactComponentCard(() => {
cy.scaffoldProject('no-specs')
cy.openProject('no-specs', ['--config-file', 'cypress-custom-spec-pattern.config.ts'])
cy.startAppServer('component')
cy.visitApp()

cy.findByText('New spec').click()
cy.findAllByTestId('card').eq(0).as('ComponentCard')
}, 'src/specs-folder/App.cy.jsx')
})
})
})

5 comments on commit 166b694

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 166b694 Dec 19, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the linux arm64 version of the Test Runner.

Learn more about this pre-release build at https://on.cypress.io/advanced-installation#Install-pre-release-version

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/12.2.0/linux-arm64/develop-166b69414c5e347ef825c121330b0b561a4caa3b/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 166b694 Dec 19, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the linux x64 version of the Test Runner.

Learn more about this pre-release build at https://on.cypress.io/advanced-installation#Install-pre-release-version

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/12.2.0/linux-x64/develop-166b69414c5e347ef825c121330b0b561a4caa3b/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 166b694 Dec 19, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the darwin arm64 version of the Test Runner.

Learn more about this pre-release build at https://on.cypress.io/advanced-installation#Install-pre-release-version

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/12.2.0/darwin-arm64/develop-166b69414c5e347ef825c121330b0b561a4caa3b/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 166b694 Dec 19, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the darwin x64 version of the Test Runner.

Learn more about this pre-release build at https://on.cypress.io/advanced-installation#Install-pre-release-version

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/12.2.0/darwin-x64/develop-166b69414c5e347ef825c121330b0b561a4caa3b/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 166b694 Dec 19, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the win32 x64 version of the Test Runner.

Learn more about this pre-release build at https://on.cypress.io/advanced-installation#Install-pre-release-version

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/12.2.0/win32-x64/develop-166b69414c5e347ef825c121330b0b561a4caa3b/cypress.tgz

Please sign in to comment.