diff --git a/x-pack/plugins/code/public/components/hover/hover_buttons.tsx b/x-pack/plugins/code/public/components/hover/hover_buttons.tsx index d7f7d6572017f..b9200676a730c 100644 --- a/x-pack/plugins/code/public/components/hover/hover_buttons.tsx +++ b/x-pack/plugins/code/public/components/hover/hover_buttons.tsx @@ -27,6 +27,7 @@ export class HoverButtons extends React.PureComponent { size="s" isDisabled={this.props.state !== HoverState.READY} onClick={this.props.gotoDefinition} + data-test-subj="codeGoToDefinitionButton" > Goto Definition @@ -34,6 +35,7 @@ export class HoverButtons extends React.PureComponent { size="s" isDisabled={this.props.state !== HoverState.READY} onClick={this.props.findReferences} + data-test-subj="codeFindReferenceButton" > Find Reference diff --git a/x-pack/test/functional/apps/code/code_intelligence.ts b/x-pack/test/functional/apps/code/code_intelligence.ts new file mode 100644 index 0000000000000..769876d7df2b0 --- /dev/null +++ b/x-pack/test/functional/apps/code/code_intelligence.ts @@ -0,0 +1,228 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from 'expect.js'; +import { TestInvoker } from './lib/types'; + +// tslint:disable:no-default-export +export default function codeIntelligenceFunctonalTests({ + getService, + getPageObjects, +}: TestInvoker) { + // const esArchiver = getService('esArchiver'); + const testSubjects = getService('testSubjects'); + const retry = getService('retry'); + const log = getService('log'); + const browser = getService('browser'); + const find = getService('find'); + const config = getService('config'); + const FIND_TIME = config.get('timeouts.find'); + const PageObjects = getPageObjects(['common', 'header', 'security', 'code', 'home']); + + describe('Code', () => { + describe('Code intelligence in source view page', () => { + const repositoryListSelector = 'codeRepositoryList codeRepositoryItem'; + + before(async () => { + // Navigate to the code app. + await PageObjects.common.navigateToApp('code'); + await PageObjects.header.waitUntilLoadingHasFinished(); + + // Prepare a git repository for the test + await PageObjects.code.fillImportRepositoryUrlInputBox( + 'https://github.com/Microsoft/TypeScript-Node-Starter' + ); + // Click the import repository button. + await PageObjects.code.clickImportRepositoryButton(); + + await retry.tryForTime(300000, async () => { + const repositoryItems = await testSubjects.findAll(repositoryListSelector); + expect(repositoryItems).to.have.length(1); + expect(await repositoryItems[0].getVisibleText()).to.equal( + 'Microsoft/TypeScript-Node-Starter' + ); + + // Wait for the repository to finish index. + expect(await testSubjects.exists('repositoryIndexDone')).to.be(true); + }); + }); + + after(async () => { + // Navigate to the code app. + await PageObjects.common.navigateToApp('code'); + await PageObjects.header.waitUntilLoadingHasFinished(); + + // Clean up the imported repository + await PageObjects.code.clickDeleteRepositoryButton(); + await retry.try(async () => { + const repositoryItems = await testSubjects.findAll(repositoryListSelector); + expect(repositoryItems).to.have.length(0); + }); + + await PageObjects.security.logout(); + }); + + beforeEach(async () => { + // Navigate to the code app. + await PageObjects.common.navigateToApp('code'); + await PageObjects.header.waitUntilLoadingHasFinished(); + + // Enter the first repository from the admin page. + await testSubjects.click(repositoryListSelector); + }); + + it('Hover on a reference and jump to definition across file', async () => { + log.debug('Hover on a reference and jump to definition across file'); + + // Visit the /src/controllers/user.ts file + // Wait the file tree to be rendered and click the 'src' folder on the file tree. + await retry.try(async () => { + expect(await testSubjects.exists('codeFileTreeNode-Directory-src')).to.be(true); + }); + + await testSubjects.click('codeFileTreeNode-Directory-src'); + await retry.try(async () => { + expect(await testSubjects.exists('codeFileTreeNode-Directory-src/controllers')).to.be( + true + ); + }); + + await testSubjects.click('codeFileTreeNode-Directory-src/controllers'); + // Then the 'controllers' folder on the file tree. + await retry.try(async () => { + expect(await testSubjects.exists('codeFileTreeNode-File-src/controllers/user.ts')).to.be( + true + ); + }); + + await testSubjects.click('codeFileTreeNode-File-src/controllers/user.ts'); + // Then the 'user.ts' file on the file tree. + await retry.try(async () => { + expect(await testSubjects.exists('codeSourceViewer')).to.be(true); + }); + + // Hover on the 'UserModel' reference on line 5. + await retry.try(async () => { + const spans = await find.allByCssSelector('.mtk31', FIND_TIME); + expect(spans.length).to.greaterThan(1); + const userModelSpan = spans[1]; + expect(await userModelSpan.getVisibleText()).to.equal('UserModel'); + await browser.moveMouseTo(userModelSpan); + // Expect the go to defintion button show up eventually. + expect(await testSubjects.exists('codeGoToDefinitionButton')).to.be(true); + }); + + await testSubjects.click('codeGoToDefinitionButton'); + await retry.tryForTime(30000, async () => { + const currentUrl: string = await browser.getCurrentUrl(); + // Expect to jump to src/models/User.ts file on line 5. + expect(currentUrl.indexOf('src/models/User.ts!L5:13')).to.greaterThan(0); + }); + }); + + it('Find references and jump to reference', async () => { + log.debug('Find references and jump to reference'); + + // Visit the /src/models/User.ts file + // Wait the file tree to be rendered and click the 'src' folder on the file tree. + await retry.try(async () => { + expect(await testSubjects.exists('codeFileTreeNode-Directory-src')).to.be(true); + }); + + await testSubjects.click('codeFileTreeNode-Directory-src'); + await retry.try(async () => { + expect(await testSubjects.exists('codeFileTreeNode-Directory-src/models')).to.be(true); + }); + + await testSubjects.click('codeFileTreeNode-Directory-src/models'); + // Then the 'models' folder on the file tree. + await retry.try(async () => { + expect(await testSubjects.exists('codeFileTreeNode-File-src/models/User.ts')).to.be(true); + }); + + await testSubjects.click('codeFileTreeNode-File-src/models/User.ts'); + // Then the 'User.ts' file on the file tree. + await retry.try(async () => { + expect(await testSubjects.exists('codeSourceViewer')).to.be(true); + }); + + // Hover on the 'UserModel' reference on line 5. + await retry.try(async () => { + const spans = await find.allByCssSelector('.mtk31', FIND_TIME); + expect(spans.length).to.greaterThan(1); + const userModelSpan = spans[0]; + expect(await userModelSpan.getVisibleText()).to.equal('UserModel'); + await browser.moveMouseTo(userModelSpan); + // Expect the go to defintion button show up eventually. + expect(await testSubjects.exists('codeFindReferenceButton')).to.be(true); + }); + + await testSubjects.click('codeFindReferenceButton'); + await retry.tryForTime(30000, async () => { + // Expect the find references panel show up and having highlights. + const spans = await find.allByCssSelector('.code-search-highlight', FIND_TIME); + expect(spans.length).to.greaterThan(0); + const firstReference = spans[0]; + await firstReference.click(); + const currentUrl: string = await browser.getCurrentUrl(); + // Expect to jump to src/controllers/user.ts file on line 42. + expect(currentUrl.indexOf('src/controllers/user.ts!L42:0')).to.greaterThan(0); + }); + }); + + it('Hover on a reference and jump to a different repository', async () => { + log.debug('Hover on a reference and jump to a different repository'); + + // Visit the /src/controllers/user.ts file + // Wait the file tree to be rendered and click the 'src' folder on the file tree. + await retry.try(async () => { + expect(await testSubjects.exists('codeFileTreeNode-Directory-src')).to.be(true); + }); + + await testSubjects.click('codeFileTreeNode-Directory-src'); + await retry.try(async () => { + expect(await testSubjects.exists('codeFileTreeNode-Directory-src/controllers')).to.be( + true + ); + }); + + await testSubjects.click('codeFileTreeNode-Directory-src/controllers'); + // Then the 'controllers' folder on the file tree. + await retry.try(async () => { + expect(await testSubjects.exists('codeFileTreeNode-File-src/controllers/user.ts')).to.be( + true + ); + }); + + await testSubjects.click('codeFileTreeNode-File-src/controllers/user.ts'); + // Then the 'user.ts' file on the file tree. + await retry.try(async () => { + expect(await testSubjects.exists('codeSourceViewer')).to.be(true); + }); + + // Hover on the 'async' reference on line 1. + await retry.try(async () => { + const spans = await find.allByCssSelector('.mtk17', FIND_TIME); + expect(spans.length).to.greaterThan(1); + const asyncSpan = spans[1]; + expect(await asyncSpan.getVisibleText()).to.equal('async'); + await browser.moveMouseTo(asyncSpan); + // Expect the go to defintion button show up eventually. + expect(await testSubjects.exists('codeGoToDefinitionButton')).to.be(true); + }); + + await testSubjects.click('codeGoToDefinitionButton'); + await retry.tryForTime(30000, async () => { + const currentUrl: string = await browser.getCurrentUrl(); + // Expect to jump to repository github.com/DefinitelyTyped/DefinitelyTyped. + expect(currentUrl.indexOf('github.com/DefinitelyTyped/DefinitelyTyped')).to.greaterThan( + 0 + ); + }); + }); + }); + }); +} diff --git a/x-pack/test/functional/apps/code/explore_repository.ts b/x-pack/test/functional/apps/code/explore_repository.ts index 09326d089a922..d604bc00594b6 100644 --- a/x-pack/test/functional/apps/code/explore_repository.ts +++ b/x-pack/test/functional/apps/code/explore_repository.ts @@ -8,7 +8,7 @@ import expect from 'expect.js'; import { TestInvoker } from './lib/types'; // tslint:disable:no-default-export -export default function manageRepositoriesFunctonalTests({ +export default function exploreRepositoryFunctonalTests({ getService, getPageObjects, }: TestInvoker) { diff --git a/x-pack/test/functional/apps/code/index.ts b/x-pack/test/functional/apps/code/index.ts index 1644948ae04b1..83404ae382191 100644 --- a/x-pack/test/functional/apps/code/index.ts +++ b/x-pack/test/functional/apps/code/index.ts @@ -12,5 +12,6 @@ export default function codeApp({ loadTestFile }: TestInvoker) { loadTestFile(require.resolve('./manage_repositories')); loadTestFile(require.resolve('./search')); loadTestFile(require.resolve('./explore_repository')); + loadTestFile(require.resolve('./code_intelligence')); }); } diff --git a/x-pack/test/functional/apps/code/search.ts b/x-pack/test/functional/apps/code/search.ts index 727adccebca3a..60ec8b3ad86a9 100644 --- a/x-pack/test/functional/apps/code/search.ts +++ b/x-pack/test/functional/apps/code/search.ts @@ -8,10 +8,7 @@ import expect from 'expect.js'; import { TestInvoker } from './lib/types'; // tslint:disable:no-default-export -export default function manageRepositoriesFunctonalTests({ - getService, - getPageObjects, -}: TestInvoker) { +export default function searchFunctonalTests({ getService, getPageObjects }: TestInvoker) { const esArchiver = getService('esArchiver'); const testSubjects = getService('testSubjects'); const retry = getService('retry');