diff --git a/.circleci/workflows.yml b/.circleci/workflows.yml index f78ea29459e6..ddccc7091b80 100644 --- a/.circleci/workflows.yml +++ b/.circleci/workflows.yml @@ -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 @@ -37,7 +37,7 @@ 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 >> @@ -45,7 +45,7 @@ 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 >> @@ -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 >> @@ -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 diff --git a/npm/webpack-dev-server/cypress/e2e/react.cy.ts b/npm/webpack-dev-server/cypress/e2e/react.cy.ts index 310d8bfe66fc..d3def96214ca 100644 --- a/npm/webpack-dev-server/cypress/e2e/react.cy.ts +++ b/npm/webpack-dev-server/cypress/e2e/react.cy.ts @@ -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() diff --git a/npm/webpack-preprocessor/__snapshots__/compilation.spec.js b/npm/webpack-preprocessor/__snapshots__/compilation.spec.js index 20c347c230c1..9d8868f8b77c 100644 --- a/npm/webpack-preprocessor/__snapshots__/compilation.spec.js +++ b/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'] = ` diff --git a/npm/webpack-preprocessor/test/unit/cross-origin-callback-loader.spec.ts b/npm/webpack-preprocessor/test/unit/cross-origin-callback-loader.spec.ts index 0e390956bc5f..06a334b48223 100644 --- a/npm/webpack-preprocessor/test/unit/cross-origin-callback-loader.spec.ts +++ b/npm/webpack-preprocessor/test/unit/cross-origin-callback-loader.spec.ts @@ -220,7 +220,6 @@ describe('./lib/cross-origin-callback-loader', () => { expectAddFileSource(store).to.equal(stripIndent` __cypressCrossOriginCallback = () => { const utils = require('../support/utils'); - utils.foo(); }`) }) @@ -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') }) })`, @@ -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'); }`) }) @@ -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') }) })`, @@ -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'); }`) }) @@ -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') }) })`, @@ -304,7 +294,6 @@ describe('./lib/cross-origin-callback-loader', () => { foo }) => { const result = require('./fn')(foo); - expect(result).to.equal('mutated someVar'); }`) }) diff --git a/packages/app/cypress/e2e/create-from-component.cy.ts b/packages/app/cypress/e2e/create-from-component.cy.ts index 5771917cb9b3..162508b36a9b 100644 --- a/packages/app/cypress/e2e/create-from-component.cy.ts +++ b/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', { @@ -82,31 +82,175 @@ function validateCreateFromComponentCard (beforeEachFn: () => void, expectedSpec .should('have.attr', 'href', `#/specs/runner?file=${expectedSpecPath}`).click() }) - cy.findByText('', { 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') + }) }) }) diff --git a/packages/app/cypress/e2e/specs.cy.ts b/packages/app/cypress/e2e/specs.cy.ts index 4d1838408ba9..6fbcce550edc 100644 --- a/packages/app/cypress/e2e/specs.cy.ts +++ b/packages/app/cypress/e2e/specs.cy.ts @@ -524,6 +524,12 @@ describe('App: Specs', () => { }) }) + function selectEmptySpecCard () { + cy.findAllByTestId('card').should('have.length', 2) + cy.findByRole('button', { name: 'Create from component' }).should('be.visible') + cy.findByRole('button', { name: 'Create new empty spec' }).should('be.visible').click() + } + describe('Testing Type: Component', { viewportHeight: 768, viewportWidth: 1024, @@ -535,7 +541,7 @@ describe('App: Specs', () => { cy.startAppServer('component') cy.visitApp() - cy.findAllByTestId('card').eq(0).as('EmptyCard') + cy.findAllByTestId('card').eq(1).as('EmptyCard') }) it('shows create new empty spec card', () => { @@ -589,7 +595,9 @@ describe('App: Specs', () => { // 'Create a new spec' dialog presents with options when user indicates they want to create // another spec. - cy.findByRole('dialog', { name: 'Enter the path for your new spec' }).should('be.visible') + cy.findAllByTestId('card').should('have.length', 2) + cy.findByRole('button', { name: 'Create new empty spec' }).should('be.visible') + cy.findByRole('button', { name: 'Create from component' }).should('be.visible') }) it('navigates to spec runner when selected', () => { @@ -628,7 +636,7 @@ describe('App: Specs', () => { }) cy.contains('Review the docs') - .should('have.attr', 'href', 'https://on.cypress.io/mount') + .should('have.attr', 'href', 'https://on.cypress.io/styling-components') cy.log('should not contain the link if you navigate away and back') cy.get('body').type('f') @@ -709,13 +717,17 @@ describe('App: Specs', () => { it('shows new spec button to start creation workflow', () => { cy.findByRole('button', { name: 'New spec', exact: false }).click() + selectEmptySpecCard() + cy.findByRole('dialog', { name: 'Enter the path for your new spec' }).should('be.visible') }) it('shows create first spec page with create empty option and goes back if it is cancel', () => { cy.findByRole('button', { name: 'New spec', exact: false }).click() - cy.contains('Cancel').click() + selectEmptySpecCard() + + cy.contains('Back').click() cy.findByRole('dialog', { name: 'Enter the path for your new spec' }).should('not.exist') }) @@ -738,6 +750,8 @@ describe('App: Specs', () => { cy.findByRole('button', { name: 'New spec' }).click() + selectEmptySpecCard() + cy.findByRole('dialog', { name: 'Enter the path for your new spec', }).within(() => { diff --git a/packages/app/src/paths.ts b/packages/app/src/paths.ts index d01d302b78b5..1c889ca8fbf1 100644 --- a/packages/app/src/paths.ts +++ b/packages/app/src/paths.ts @@ -18,3 +18,7 @@ export function getPathForPlatform (posixPath?: string) { return posixPath } + +export function posixify (path: string): string { + return path.replace(/\\/g, '/') +} diff --git a/packages/app/src/runner/SpecRunnerHeaderOpenMode.vue b/packages/app/src/runner/SpecRunnerHeaderOpenMode.vue index af42d0e06746..80c458927f3d 100644 --- a/packages/app/src/runner/SpecRunnerHeaderOpenMode.vue +++ b/packages/app/src/runner/SpecRunnerHeaderOpenMode.vue @@ -153,8 +153,8 @@ dismissible > - diff --git a/packages/app/src/specs/generators/ExpandableFileList.cy.tsx b/packages/app/src/specs/generators/ExpandableFileList.cy.tsx new file mode 100644 index 000000000000..01c3435b9109 --- /dev/null +++ b/packages/app/src/specs/generators/ExpandableFileList.cy.tsx @@ -0,0 +1,84 @@ +import ExpandableFileList from './ExpandableFileList.vue' +import data from '../../../cypress/fixtures/FileList.json' +import { ref, Ref } from 'vue' +import type { FileListItemFragment } from '../../generated/graphql-test' + +const difficultFile = { + baseName: '[...all].vue', + fileExtension: '.vue', +} + +const noResultsSlot = () =>
No Results
+const noResultsSelector = '[data-testid=no-results]' +const fileRowSelector = '[data-cy=file-list-row]' + +const allFiles = data as FileListItemFragment[] + +allFiles[1] = { ...allFiles[1], ...difficultFile } +describe('', { viewportHeight: 500, viewportWidth: 400 }, () => { + describe('with files', () => { + const files = allFiles + + beforeEach(() => { + const selectItemStub = cy.stub() + + cy.mount(() => (
+ +
)) + }) + + it('renders all of the files passed in', () => { + cy.get(fileRowSelector) + .should('have.length', 10) + }) + + it('expands rows when they are clicked', () => { + cy.mount(() => (
+ This is the expanded content
}} + files={files} /> + )) + + cy.contains('This is the expanded content').should('not.exist') + + cy.get(fileRowSelector) + .first() + .click() + + cy.contains('This is the expanded content').should('be.visible') + }) + + it('correctly formats a difficult file', () => { + cy.get('body').contains('[...all]') + cy.percySnapshot() + }) + }) + + describe('without files', () => { + it('shows the no results slot', () => { + const files: Ref = ref([]) + let idx = 0 + + cy.mount(() => (
+ + + + +
)) + .get(noResultsSelector).should('be.visible') + + cy.percySnapshot() + + cy.get('[data-testid=add-file]') + .click() + .get(noResultsSelector).should('not.exist') + }) + }) +}) diff --git a/packages/app/src/specs/generators/ExpandableFileList.vue b/packages/app/src/specs/generators/ExpandableFileList.vue new file mode 100644 index 000000000000..44ef10f09f91 --- /dev/null +++ b/packages/app/src/specs/generators/ExpandableFileList.vue @@ -0,0 +1,80 @@ + + + diff --git a/packages/app/src/specs/generators/FileChooser.cy.tsx b/packages/app/src/specs/generators/FileChooser.cy.tsx index 98f36a501b28..7711eb6629eb 100644 --- a/packages/app/src/specs/generators/FileChooser.cy.tsx +++ b/packages/app/src/specs/generators/FileChooser.cy.tsx @@ -3,10 +3,11 @@ import FileChooser from './FileChooser.vue' import { ref } from 'vue' import { defaultMessages } from '@cy/i18n' import data from '../../../cypress/fixtures/FileChooser.json' +import type { FileParts } from '@packages/data-context/src/gen/graphcache-config.gen' /*---------- Fixtures ----------*/ const numFiles = data.length -const allFiles = data +const allFiles = data as unknown as FileParts[] const extensionPattern = '*.jsx' const existentExtensionPattern = '*.tsx' const nonExistentFileName = 'non existent file' @@ -243,12 +244,16 @@ describe('', () => { }) it('fires a selectFile event when a file is clicked on', () => { - const onSelectFileSpy = cy.spy().as('onSelectFileSpy') + const onSelectFileStub = cy.stub() cy.mount(() => ( )) + + cy.findAllByTestId('file-list-row').first().click().then(() => { + expect(onSelectFileStub).to.be.calledOnce + }) }) }) diff --git a/packages/app/src/specs/generators/FileChooser.vue b/packages/app/src/specs/generators/FileChooser.vue index 7c250a5b1d39..ec3a55d812d6 100644 --- a/packages/app/src/specs/generators/FileChooser.vue +++ b/packages/app/src/specs/generators/FileChooser.vue @@ -73,9 +73,10 @@ import CreateSpecModalBody from './CreateSpecModalBody.vue' import FileList from './FileList.vue' import FileMatch from '../../components/FileMatch.vue' import { gql } from '@urql/core' +import type { FileParts } from '@packages/data-context/src/gen/graphcache-config.gen' const props = withDefaults(defineProps<{ - files: any[] + files: FileParts[] extensionPattern: string loading?: boolean }>(), { @@ -148,9 +149,13 @@ const noResults = computed(() => { return { search: filePathSearch.value || debouncedExtensionPattern.value, message: filePathSearch.value ? t('noResults.defaultMessage') : t('components.fileSearch.noMatchesForExtension'), - clear: filePathSearch.value ? - () => filePathSearch.value = '' : - () => localExtensionPattern.value = initialExtensionPattern, + clear: () => { + if (filePathSearch.value) { + filePathSearch.value = '' + } else { + localExtensionPattern.value = initialExtensionPattern + } + }, } }) diff --git a/packages/app/src/specs/generators/component/ComponentGenerator.tsx b/packages/app/src/specs/generators/component/ComponentGenerator.tsx deleted file mode 100644 index 1937264008b9..000000000000 --- a/packages/app/src/specs/generators/component/ComponentGenerator.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { filters } from '../GeneratorsCommon' -import ComponentGeneratorStepOne from './ComponentGeneratorStepOne.vue' -import type { SpecGenerator } from '../types' -import ComponentGeneratorCard from './ComponentGeneratorCard.vue' - -export const ComponentGenerator: SpecGenerator = { - card: ComponentGeneratorCard, - entry: ComponentGeneratorStepOne, - show: (currentProject) => { - return currentProject?.codeGenGlobs?.component === '*.vue' - }, - matches: filters.matchesCT, - id: 'component', -} diff --git a/packages/app/src/specs/generators/component/ReactComponentGenerator.tsx b/packages/app/src/specs/generators/component/ReactComponentGenerator.tsx new file mode 100644 index 000000000000..37345ff7add4 --- /dev/null +++ b/packages/app/src/specs/generators/component/ReactComponentGenerator.tsx @@ -0,0 +1,12 @@ +import { filters } from '../GeneratorsCommon' +import ReactComponentGeneratorStepOne from './ReactComponentGeneratorStepOne.vue' +import type { SpecGenerator } from '../types' +import ComponentGeneratorCard from './ComponentGeneratorCard.vue' + +export const ReactComponentGenerator: SpecGenerator = { + card: ComponentGeneratorCard, + entry: ReactComponentGeneratorStepOne, + show: (currentProject) => currentProject?.codeGenFramework === 'react', + matches: filters.matchesCT, + id: 'reactComponent', +} diff --git a/packages/app/src/specs/generators/component/ReactComponentGeneratorStepOne.vue b/packages/app/src/specs/generators/component/ReactComponentGeneratorStepOne.vue new file mode 100644 index 000000000000..b14a9ab862be --- /dev/null +++ b/packages/app/src/specs/generators/component/ReactComponentGeneratorStepOne.vue @@ -0,0 +1,188 @@ + + diff --git a/packages/app/src/specs/generators/component/ReactComponentList.cy.tsx b/packages/app/src/specs/generators/component/ReactComponentList.cy.tsx new file mode 100644 index 000000000000..5f3ad5b79ad3 --- /dev/null +++ b/packages/app/src/specs/generators/component/ReactComponentList.cy.tsx @@ -0,0 +1,59 @@ +import { ComponentList_GetReactComponentsFromFileDocument } from '../../../generated/graphql-test' +import ReactComponentList from './ReactComponentList.vue' + +describe('ReactComponentList', () => { + const mockFile = { + absolute: '/path/to/my/component', + id: 'fileId', + relative: '../path/to/my/component', + fileName: 'Component.js', + fileExtension: '.tsx', baseName: 'Component', + } + + it('renders empty state if no components are returned', () => { + cy.stubMutationResolver(ComponentList_GetReactComponentsFromFileDocument, (defineResult) => { + return defineResult({ getReactComponentsFromFile: { components: [], errored: false } }) + }) + + cy.mount() + + cy.contains('No components found').should('be.visible') + + cy.percySnapshot() + }) + + it('renders error state if errored is true', () => { + cy.stubMutationResolver(ComponentList_GetReactComponentsFromFileDocument, (defineResult) => { + return defineResult({ getReactComponentsFromFile: { components: [], errored: true } }) + }) + + cy.mount() + + cy.contains('Unable to parse file').should('be.visible') + + cy.percySnapshot() + }) + + it('fetches and displays a list of components', () => { + cy.mount() + + cy.contains('FooBar').should('be.visible') + cy.contains('BarFoo').should('be.visible') + cy.contains('FooBarBaz').should('be.visible') + + cy.percySnapshot() + }) + + it('calls selectItem on click', () => { + const onSelectItemStub = cy.stub() + + cy.mount() + + cy.contains('FooBar').should('be.visible').click().then(() => { + expect(onSelectItemStub).to.be.calledOnceWith({ file: mockFile, item: { exportName: 'FooBar', isDefault: false } }) + }) + + cy.contains('BarFoo').should('be.visible') + cy.contains('FooBarBaz').should('be.visible') + }) +}) diff --git a/packages/app/src/specs/generators/component/ReactComponentList.vue b/packages/app/src/specs/generators/component/ReactComponentList.vue new file mode 100644 index 000000000000..217b9054c353 --- /dev/null +++ b/packages/app/src/specs/generators/component/ReactComponentList.vue @@ -0,0 +1,87 @@ + + + diff --git a/packages/app/src/specs/generators/component/VueComponentGenerator.tsx b/packages/app/src/specs/generators/component/VueComponentGenerator.tsx new file mode 100644 index 000000000000..a56ee121fd84 --- /dev/null +++ b/packages/app/src/specs/generators/component/VueComponentGenerator.tsx @@ -0,0 +1,12 @@ +import { filters } from '../GeneratorsCommon' +import VueComponentGeneratorStepOne from './VueComponentGeneratorStepOne.vue' +import type { SpecGenerator } from '../types' +import ComponentGeneratorCard from './ComponentGeneratorCard.vue' + +export const VueComponentGenerator: SpecGenerator = { + card: ComponentGeneratorCard, + entry: VueComponentGeneratorStepOne, + show: (currentProject) => currentProject?.codeGenFramework === 'vue', + matches: filters.matchesCT, + id: 'vueComponent', +} diff --git a/packages/app/src/specs/generators/component/ComponentGeneratorStepOne.vue b/packages/app/src/specs/generators/component/VueComponentGeneratorStepOne.vue similarity index 79% rename from packages/app/src/specs/generators/component/ComponentGeneratorStepOne.vue rename to packages/app/src/specs/generators/component/VueComponentGeneratorStepOne.vue index 84ea5f54e2c3..0ad6a91ccc17 100644 --- a/packages/app/src/specs/generators/component/ComponentGeneratorStepOne.vue +++ b/packages/app/src/specs/generators/component/VueComponentGeneratorStepOne.vue @@ -40,20 +40,15 @@ v-if="result" class="flex gap-16px items-center" > - - - + {{ t('createSpec.successPage.runSpecButton') }} +