test: update tests, add github actions reporter (#7238)
* make jest tests silent * fix path warning in tests * don't hide build/ or node_modules/ * fix createUploadHandler test * fix conversation handler test * use github actions reporter for test results * include test location in output * move jest dep into test-utils and use correct reporter name * ignore tests in dist * fix up botProject open handle * bump browserslist and update caniuse-lite * use node env for node tests supposedly this should help speed up tests that do not require jsdom * perpare server for @swc-node/jest * prepare client for @swc-node/jest * switch to swc for transpiling ts in tests * fix compiler errors * removed unused var * special case the fallback recognizer (#7634) * chore: bump wait-on to v5.3.0 (#7648) * fix: update left nav and top bar strings (#7609) * start fixing header * adjust header chrome and left nav order * Update en-US.json * fix typecheck errors * fix unit tests * Update en-US.json * change Design to Create in e2e tests * Update LuisDeploy.spec.ts * update flaky check in visitPage * add 'checked' flag to visitPage to minimize surface area * fix punctuation * fix e2e test! * fix security issue * Revert "fix security issue" This reverts commit 3aa6e3e4e36a951bb37244e01dd411fa4eca10dd. Co-authored-by: Chris Whitten <christopher.whitten@microsoft.com> * fix: rephrase Enable Orchestrator dialog (#7639) * "Add a skill" page changes * Update EnableOrchestrator.tsx * Update skill.test.tsx * Update package.json * Update yarn.lock * Update en-US.json * fix: Update provision & publishing copy (#7583) (#7642) * Issue-7583: update copy for provision and publishing * en-us json files * Issue-7583: fix formatDialogTitle * Issue-7583: refactor formatDialogTitle * Issue-7583: reformat azure provision dialog block * PR fixes * Fixed spacing Co-authored-by: GeoffCoxMSFT <gcox@microsoft.com> * fix: Added cleanup logic to app on quit (#7645) * Added cleanup logic to app on quit * Added missing electron ipc event listener Co-authored-by: TJ Durnford <tjdford@gmail.com> Co-authored-by: Soroush <hatpick@gmail.com> * feat: show tunneling information notification when starting bot with remote skills (#7611) * add util to get device OS * add port to local publish result * add port to botEndpointsState * show ngrok notification when starting a bot with remote skills * only show the remote skills notification onces per session per bot * update l10n file * address feedback * update locale file after merge * fix failing test Co-authored-by: Soroush <hatpick@gmail.com> * feat: detect "old" bots and migrate them to new runtime (#6526) * introduce a migration system for updating legacy projcts to the 2.0 runtime * updated * ensure root dialog gets updated appropriately * adjust method for updating root dialog * remove comments * fire migration warning when loading bot * use original project name for new project * close modal on submit * address feedback * add confirm modal * Rename azureFunctionsPublish publish targets to azurePublish (new shared id) * Delete a.en-us.lu * Delete a.en-us.qna * Delete b.en-us.lu * Delete b.en-us.qna * Delete bot1.en-us.lu * Delete bot1.en-us.qna * do not require pva to migrate * Add missing parameters * clarify types * Fix tests reduce redundant code * Fixes #6844: migrate appinsights key to new location * fix migrate lint issue * plumb through the yeomanOptions parameter, required to set the location of the settings folder when migrating * feat: new validation pipeline - schema existence validation (#7001) * add placeholder for schema validator * add schema validator pipeline with mocked fn * add schema visitor * display diagnostics data in debug panel * revert sdk.ts * decrease schema diagnostic severity to 'Warning' * optmize path join logic * impl a unified walker covers SwitchCondition * fix lint error: use BaseSchema * feat: disable actions without schema * wrap in useEffect * optimization: avoid frequent recoil submission * optimization: aggregate paths rather than updatedDialog to reduce time complexity * chore: comments & var name * lint * add comments * defense undefined skip-level 'actions' * defense potential exceptions * get sdk.schema content correctly * fix lint * fix folder name case problem * Do not specify the luis endpoint key as a parameter to the runtime if no vlaue is present (#7240) (leaving this paramter blank causes issues on windows) * disable telemetry calls in the provision dialog while we investigate why telemetryclient is null (#7256) Co-authored-by: Geoff Cox (Microsoft) <gcox@microsoft.com> * prefer the botName field instead of the name field when managing connections (#7262) * fix: Empty Webchat inspector text and Disabling items in PVA context (#7241) * show floating notifications over eveything (#7269) Co-authored-by: Soroush <sorgh@microsoft.com> * Region for Microsoft Bot Channels Registration is now global (#7270) Co-authored-by: Ben Brown <benbro@microsoft.com> Co-authored-by: Soroush <hatpick@gmail.com> * fix: adjust package manager feeds (#7243) * Fix #7092: set default page size to 100 items * Fixes #6854: merge community feeds into main feed, sort by downloads * Fixes #7043: include any component tagged msbot-component * improve error handling * restore different checks for declarative only vs code driven components * refactor to use includes instead of indexOf Co-authored-by: Ben Yackley <61990921+beyackle@users.noreply.github.com> * fix: remodel About page (#7191) * remodel About page * add SHA to version * fixes from suggestion * get info from Electron and use it for Release field Co-authored-by: Chris Whitten <christopher.whitten@microsoft.com> Co-authored-by: Chris Whitten <christopher.whitten@microsoft.com> Co-authored-by: Ben Brown <benbro@microsoft.com> Co-authored-by: Geoff Cox (Microsoft) <gcox@microsoft.com> Co-authored-by: Srinaath Ravichandran <srinaath27@gmail.com> Co-authored-by: Soroush <hatpick@gmail.com> Co-authored-by: Soroush <sorgh@microsoft.com> Co-authored-by: Vamsi Modem <12182973+VamsiModem@users.noreply.github.com> Co-authored-by: Ben Yackley <61990921+beyackle@users.noreply.github.com> * fix mapping of schema files during migration * fix lint * fix default name of migrated project * run schema merge as part of the migration * do we need to migrate teh schema? or not? * do not migrate schema * - Adding field for runtime language - Fixing field population for runtime type * Passing runtime lang and type down to yeoman calls * Fixing adaptive runtime name * Hardcoding runtime version and fixing settings page generation on migrate * Fixing check for 'inBotMigration' to be based on CreationStatusState > path substring * Fix default naming * revert preload.js * Resolving PR comments and fixing errors from merge * Change schema diagnostics severity to 'Error' * Fetch @latest version from npm before migration Co-authored-by: Dong Lei <donglei@microsoft.com> Co-authored-by: leilzh <leilzh@microsoft.com> Co-authored-by: zhixzhan <zhixzhan@microsoft.com> Co-authored-by: zeye <zeye@microsoft.com> Co-authored-by: Chris Whitten <christopher.whitten@microsoft.com> Co-authored-by: Geoff Cox (Microsoft) <gcox@microsoft.com> Co-authored-by: Srinaath Ravichandran <srinaath27@gmail.com> Co-authored-by: Soroush <hatpick@gmail.com> Co-authored-by: Soroush <sorgh@microsoft.com> Co-authored-by: Vamsi Modem <12182973+VamsiModem@users.noreply.github.com> Co-authored-by: Ben Yackley <61990921+beyackle@users.noreply.github.com> Co-authored-by: Patrick Volum <pavolum@outlook.com> Co-authored-by: Chris Whitten <christopherwhitten@gmail.com> * fix: data race writing on setting file (#7475) * fix data race writing on setting file * remove isSkill in skillConfiguration * remove default allowedCallers value Co-authored-by: TJ Durnford <tjdford@gmail.com> Co-authored-by: Chris Whitten <christopher.whitten@microsoft.com> Co-authored-by: Ben Yackley <61990921+beyackle@users.noreply.github.com> Co-authored-by: Soroush <hatpick@gmail.com> * remove appsettings file from default gitignore (#7679) * fix: restore line beneath page header (#7676) * Update Page.tsx * Update BotProjectSettings.tsx * chore: Rebase main with 1.4.1 release (#7678) * exclude items from the package manager list that do not have names or versions -- such as locally implemented custom actions (#7683) * Add components field to migrated settings. Guard against missing field during component merge (#7674) Co-authored-by: Dong Lei <donglei@microsoft.com> Co-authored-by: Chris Whitten <christopher.whitten@microsoft.com> * fix: (Un) Installing a package now stops the currently running bot beforehand (#7689) * Feed string, not int, to Switch per schema (#7707) Co-authored-by: Chris Whitten <christopher.whitten@microsoft.com> * fix: generate manifest file when creating new publish target (#7666) * generate manifest file when creating new profile * test Co-authored-by: Lu Han <32191031+luhan2017@users.noreply.github.com> Co-authored-by: TJ Durnford <tjdford@gmail.com> * fix: converting bot with custom actions (ui update) (#7672) * Hides the PVA publish profile from the profle creation dropdown (#7725) * fix: set func-related settings during build step (#7723) * set func-related settings during build step * slight refactor to reduce duplicated code * add type * add typings in runtime plugin definition * set type of port to number * fix types, remove _ on variables that are used * feat: Add preparatory work before connecting remote skill (#7519) * draft dialog * ux css * css * jump to create profile & set microsoftAppId to publish target * comments & lint * refactor dialog wrapper * fix publish types missing in provision dialog (#7697) * test case * refactor * fix type define * fix title and json parse * fix app Id not sync when create new profile * Adds AppID and Password sections * test case fixed * Adds AppID/Password * Update copy Co-authored-by: Soroush <hatpick@gmail.com> Co-authored-by: Chris Whitten <christopher.whitten@microsoft.com> Co-authored-by: Ben Yackley <61990921+beyackle@users.noreply.github.com> Co-authored-by: VanyLaw <wenyluo@microsoft.com> Co-authored-by: Lu Han <32191031+luhan2017@users.noreply.github.com> Co-authored-by: Chris Whitten <christopherwhitten@gmail.com> * fix: Revert changes to adaptive card templates to support PVA (#7808) * fix: Revert changes to adaptive card templates to support PVA * requested changes * fetch publish types for each project * fix: Throttle restart conversation (#7824) * Throttle restart Signed-off-by: Srinaath Ravichandran <srravich@microsoft.com> * Remove await Signed-off-by: Srinaath Ravichandran <srravich@microsoft.com> * Updated throttle time Signed-off-by: Srinaath Ravichandran <srravich@microsoft.com> * Unit test update Signed-off-by: Srinaath Ravichandran <srravich@microsoft.com> * Enable only on connected Signed-off-by: Srinaath Ravichandran <srravich@microsoft.com> * removed if Signed-off-by: Srinaath Ravichandran <srravich@microsoft.com> Co-authored-by: Srinaath Ravichandran <srravich@microsoft.com> * chore: automated localization updates (#7759) * Localized resource files from OneLocBuild * fix typos and stray apostrophes * fix apostrophes * typo fix * Update en-US.json * fix more apostrophes * revert reversions Co-authored-by: Composer Localization <botframework-composer-eng@service.microsoft.com> Co-authored-by: Ben Yackley <61990921+beyackle@users.noreply.github.com> Co-authored-by: Chris Whitten <christopher.whitten@microsoft.com> * Deploy exception returns real error (#7837) * fix: Fix missing data collection settings on server (#7814) * fix: Fix missing data collection settings on server * fix tests * fix test again * minor change Co-authored-by: Chris Whitten <christopher.whitten@microsoft.com> * fix: add inner scrollbar to selection area (#7782) * scroll * css * test Co-authored-by: Lu Han <32191031+luhan2017@users.noreply.github.com> Co-authored-by: Chris Whitten <christopher.whitten@microsoft.com> * Migration: adjust target blob transcript field names (#7848) * fix: Always use intermediate lg template for text and speak modalities (#7842) * allow click outside blocking modals (#7727) Co-authored-by: Soroush <sorgh@microsoft.com> Co-authored-by: TJ Durnford <tjdford@gmail.com> * fix: Remove depth and transparentBorders from FieldProps, remove ObjectArrayField, and refactor OpenObjectField (#6633) * Remove depth and transparentBorder * Update OneOfField dropdown styles * Remove ObjectArrayField * fix tests * Refactor OpenObjectField * updated test * minor * fix test * minor Co-authored-by: Ben Yackley <61990921+beyackle@users.noreply.github.com> * fix: Teaching bubble missing content (#7764) * fix: Teaching bubble missing content * Fix item ref Co-authored-by: Ben Yackley <61990921+beyackle@users.noreply.github.com> Co-authored-by: Soroush <hatpick@gmail.com> * fix: Reset allowed callers cache when switching between project settings (#7874) * fix: Reset allowed callers cache when switching between project settings * comment Co-authored-by: Soroush <hatpick@gmail.com> * fix: breadcrumb & show code button collision (#7720) * fix breadcrumb & show code button collision * use flex * update styles * observe button width, auto layout ... breadcrumb * ignore typescript ResizeObserver declarations missing * fix type in test * useResizeObserver * fix mock Co-authored-by: Srinaath Ravichandran <srinaath27@gmail.com> Co-authored-by: Soroush <sorgh@microsoft.com> Co-authored-by: Soroush <hatpick@gmail.com> * Update home feed for Build 2021 (#7912) * fix (#7440) Co-authored-by: Soroush <sorgh@microsoft.com> Co-authored-by: TJ Durnford <tjdford@gmail.com> * fix: Custom function declaration (#7775) * Custom function declaration Signed-off-by: Srinaath Ravichandran <srravich@microsoft.com> * Typo in custom functions Signed-off-by: Srinaath Ravichandran <srravich@microsoft.com> Co-authored-by: Srinaath Ravichandran <srravich@microsoft.com> * add new caller when caller still have empty list, just focus on the empty one (#7767) Co-authored-by: Lu Han <32191031+luhan2017@users.noreply.github.com> * Resolve dns-packet to 1.3.4 to fix CVE-2021-23386 (#7942) Co-authored-by: Soroush <hatpick@fmail.com> * fix: invalid profile case (#7784) * invalid profile case * comments * remove unused component * refactor publish profile wrapper dialog * toekn page jump logic err Co-authored-by: Lu Han <32191031+luhan2017@users.noreply.github.com> Co-authored-by: Soroush <hatpick@gmail.com> * fix: Need to wait for zip deployment to be done processing (#7858) * Handled 202 * Added error case for processing * Improvements Co-authored-by: Soroush <hatpick@gmail.com> * Update readme screenshot (#7918) Co-authored-by: Soroush <sorgh@microsoft.com> * upgrade json-ptr (#7988) * update test-utils dependencies * remove console overrides * fix tests for jest 27 * use fake timers for jest * fix more tests for jest 27 * fix merge conflict * clean up * replace HookResult with RenderResult * fix orchestrator test * fix test warning * fix jest-haste-map warning * fix testid error * attempt to resolve NetworkError in tests that use jsdom * rename test file * globally mock http client * fix unhandled rejection error * clear mocks after each test * fix type errors * rename test file * move integration tests into own workspace this is required due to a dependency resolution conflict within @testing-library/dom. See https://github.com/testing-library/dom-testing-library/issues/963 * remove unused dependency * fix integration tests linting errors * use correct directory for running e2e script * re-enable parallel cypress tests * pass --record option to cypress runner * actually set up cypress to run in parallel * add @testing-library/user-event to test-utils exports * fix fixture path * use fs-extra to ensure test bot directory * update cypress comands * remove pr trigger from pipeline 2 runs were happening. Trying to see if this fixes that. * re-enable cleanup after all tests * update lockfile * fix mocks and disabled button checks * add axios mock * remove cypress examples * add hasOrchestrator to useEffect dependencies * add description to integration-tests package.json * fix e2e script for windows * remove console.log Co-authored-by: taicchoumsft <61705609+taicchoumsft@users.noreply.github.com> Co-authored-by: Ben Yackley <61990921+beyackle@users.noreply.github.com> Co-authored-by: Chris Whitten <christopher.whitten@microsoft.com> Co-authored-by: natalgar <82851479+natalgar@users.noreply.github.com> Co-authored-by: GeoffCoxMSFT <gcox@microsoft.com> Co-authored-by: Tony Anziano <tonyanziano5@gmail.com> Co-authored-by: TJ Durnford <tjdford@gmail.com> Co-authored-by: Soroush <hatpick@gmail.com> Co-authored-by: Ben Brown <benbro@microsoft.com> Co-authored-by: Dong Lei <donglei@microsoft.com> Co-authored-by: leilzh <leilzh@microsoft.com> Co-authored-by: zhixzhan <zhixzhan@microsoft.com> Co-authored-by: zeye <zeye@microsoft.com> Co-authored-by: Srinaath Ravichandran <srinaath27@gmail.com> Co-authored-by: Soroush <sorgh@microsoft.com> Co-authored-by: Vamsi Modem <12182973+VamsiModem@users.noreply.github.com> Co-authored-by: Patrick Volum <pavolum@outlook.com> Co-authored-by: Chris Whitten <christopherwhitten@gmail.com> Co-authored-by: Long Alan <alanlong9278@126.com> Co-authored-by: Lu Han <32191031+luhan2017@users.noreply.github.com> Co-authored-by: Long Alan <julong@microsoft.com> Co-authored-by: VanyLaw <wenyluo@microsoft.com> Co-authored-by: Srinaath Ravichandran <srravich@microsoft.com> Co-authored-by: Composer Localization <botframework-composer-eng@service.microsoft.com> Co-authored-by: Carlos Castro <carlosscastro@users.noreply.github.com> Co-authored-by: Gary Pretty <gary@garypretty.co.uk> Co-authored-by: Soroush <hatpick@fmail.com>
This commit is contained in:
Родитель
971e45e37e
Коммит
5cfac6ac6c
|
@ -41,8 +41,8 @@ jobs:
|
|||
run: yarn build:dev
|
||||
- name: yarn lint
|
||||
run: yarn lint:ci && yarn lint:extensions
|
||||
- name: yarn test:coverage
|
||||
run: yarn test:coverage
|
||||
- name: yarn test:ci
|
||||
run: yarn test:ci
|
||||
- name: Coveralls
|
||||
uses: coverallsapp/github-action@v1.1.1
|
||||
continue-on-error: true
|
||||
|
|
|
@ -7,9 +7,7 @@
|
|||
},
|
||||
"files.exclude": {
|
||||
"**/.git": true,
|
||||
"**/.DS_Store": true,
|
||||
"**/node_modules": true,
|
||||
"**/build": true
|
||||
"**/.DS_Store": true
|
||||
},
|
||||
"eslint.packageManager": "yarn",
|
||||
"eslint.validate": [
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
module.exports = {
|
||||
extends: ['../.eslintrc.js', 'plugin:cypress/recommended'],
|
||||
};
|
|
@ -1 +0,0 @@
|
|||
{"error":"Access denied due to invalid subscription key. Make sure you are subscribed to an API you are trying to call and provide the right key."}
|
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"status": {
|
||||
"MyProject_main_en-us_lu": {
|
||||
"version": "0000000001",
|
||||
"checksum": "",
|
||||
"status": 1
|
||||
}
|
||||
},
|
||||
"luFiles": [
|
||||
{
|
||||
"content": "#Dummy\r\n--I am Dummy",
|
||||
"diagnostics": [],
|
||||
"id": "Main",
|
||||
"intents": [],
|
||||
"relativePath": "Main/Main.lu",
|
||||
"lastPublishTime": 2,
|
||||
"lastUpdateTime": 1
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,272 +0,0 @@
|
|||
/// <reference types="Cypress" />
|
||||
|
||||
context('Actions', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('https://example.cypress.io/commands/actions')
|
||||
})
|
||||
|
||||
// https://on.cypress.io/interacting-with-elements
|
||||
|
||||
it('.type() - type into a DOM element', () => {
|
||||
// https://on.cypress.io/type
|
||||
cy.get('.action-email')
|
||||
.type('fake@email.com').should('have.value', 'fake@email.com')
|
||||
|
||||
// .type() with special character sequences
|
||||
.type('{leftarrow}{rightarrow}{uparrow}{downarrow}')
|
||||
.type('{del}{selectall}{backspace}')
|
||||
|
||||
// .type() with key modifiers
|
||||
.type('{alt}{option}') //these are equivalent
|
||||
.type('{ctrl}{control}') //these are equivalent
|
||||
.type('{meta}{command}{cmd}') //these are equivalent
|
||||
.type('{shift}')
|
||||
|
||||
// Delay each keypress by 0.1 sec
|
||||
.type('slow.typing@email.com', { delay: 100 })
|
||||
.should('have.value', 'slow.typing@email.com')
|
||||
|
||||
cy.get('.action-disabled')
|
||||
// Ignore error checking prior to type
|
||||
// like whether the input is visible or disabled
|
||||
.type('disabled error checking', { force: true })
|
||||
.should('have.value', 'disabled error checking')
|
||||
})
|
||||
|
||||
it('.focus() - focus on a DOM element', () => {
|
||||
// https://on.cypress.io/focus
|
||||
cy.get('.action-focus').focus()
|
||||
.should('have.class', 'focus')
|
||||
.prev().should('have.attr', 'style', 'color: orange;')
|
||||
})
|
||||
|
||||
it('.blur() - blur off a DOM element', () => {
|
||||
// https://on.cypress.io/blur
|
||||
cy.get('.action-blur').type('About to blur').blur()
|
||||
.should('have.class', 'error')
|
||||
.prev().should('have.attr', 'style', 'color: red;')
|
||||
})
|
||||
|
||||
it('.clear() - clears an input or textarea element', () => {
|
||||
// https://on.cypress.io/clear
|
||||
cy.get('.action-clear').type('Clear this text')
|
||||
.should('have.value', 'Clear this text')
|
||||
.clear()
|
||||
.should('have.value', '')
|
||||
})
|
||||
|
||||
it('.submit() - submit a form', () => {
|
||||
// https://on.cypress.io/submit
|
||||
cy.get('.action-form')
|
||||
.find('[type="text"]').type('HALFOFF')
|
||||
cy.get('.action-form').submit()
|
||||
.next().should('contain', 'Your form has been submitted!')
|
||||
})
|
||||
|
||||
it('.click() - click on a DOM element', () => {
|
||||
// https://on.cypress.io/click
|
||||
cy.get('.action-btn').click()
|
||||
|
||||
// You can click on 9 specific positions of an element:
|
||||
// -----------------------------------
|
||||
// | topLeft top topRight |
|
||||
// | |
|
||||
// | |
|
||||
// | |
|
||||
// | left center right |
|
||||
// | |
|
||||
// | |
|
||||
// | |
|
||||
// | bottomLeft bottom bottomRight |
|
||||
// -----------------------------------
|
||||
|
||||
// clicking in the center of the element is the default
|
||||
cy.get('#action-canvas').click()
|
||||
|
||||
cy.get('#action-canvas').click('topLeft')
|
||||
cy.get('#action-canvas').click('top')
|
||||
cy.get('#action-canvas').click('topRight')
|
||||
cy.get('#action-canvas').click('left')
|
||||
cy.get('#action-canvas').click('right')
|
||||
cy.get('#action-canvas').click('bottomLeft')
|
||||
cy.get('#action-canvas').click('bottom')
|
||||
cy.get('#action-canvas').click('bottomRight')
|
||||
|
||||
// .click() accepts an x and y coordinate
|
||||
// that controls where the click occurs :)
|
||||
|
||||
cy.get('#action-canvas')
|
||||
.click(80, 75) // click 80px on x coord and 75px on y coord
|
||||
.click(170, 75)
|
||||
.click(80, 165)
|
||||
.click(100, 185)
|
||||
.click(125, 190)
|
||||
.click(150, 185)
|
||||
.click(170, 165)
|
||||
|
||||
// click multiple elements by passing multiple: true
|
||||
cy.get('.action-labels>.label').click({ multiple: true })
|
||||
|
||||
// Ignore error checking prior to clicking
|
||||
cy.get('.action-opacity>.btn').click({ force: true })
|
||||
})
|
||||
|
||||
it('.dblclick() - double click on a DOM element', () => {
|
||||
// https://on.cypress.io/dblclick
|
||||
|
||||
// Our app has a listener on 'dblclick' event in our 'scripts.js'
|
||||
// that hides the div and shows an input on double click
|
||||
cy.get('.action-div').dblclick().should('not.be.visible')
|
||||
cy.get('.action-input-hidden').should('be.visible')
|
||||
})
|
||||
|
||||
it('.check() - check a checkbox or radio element', () => {
|
||||
// https://on.cypress.io/check
|
||||
|
||||
// By default, .check() will check all
|
||||
// matching checkbox or radio elements in succession, one after another
|
||||
cy.get('.action-checkboxes [type="checkbox"]').not('[disabled]')
|
||||
.check().should('be.checked')
|
||||
|
||||
cy.get('.action-radios [type="radio"]').not('[disabled]')
|
||||
.check().should('be.checked')
|
||||
|
||||
// .check() accepts a value argument
|
||||
cy.get('.action-radios [type="radio"]')
|
||||
.check('radio1').should('be.checked')
|
||||
|
||||
// .check() accepts an array of values
|
||||
cy.get('.action-multiple-checkboxes [type="checkbox"]')
|
||||
.check(['checkbox1', 'checkbox2']).should('be.checked')
|
||||
|
||||
// Ignore error checking prior to checking
|
||||
cy.get('.action-checkboxes [disabled]')
|
||||
.check({ force: true }).should('be.checked')
|
||||
|
||||
cy.get('.action-radios [type="radio"]')
|
||||
.check('radio3', { force: true }).should('be.checked')
|
||||
})
|
||||
|
||||
it('.uncheck() - uncheck a checkbox element', () => {
|
||||
// https://on.cypress.io/uncheck
|
||||
|
||||
// By default, .uncheck() will uncheck all matching
|
||||
// checkbox elements in succession, one after another
|
||||
cy.get('.action-check [type="checkbox"]')
|
||||
.not('[disabled]')
|
||||
.uncheck().should('not.be.checked')
|
||||
|
||||
// .uncheck() accepts a value argument
|
||||
cy.get('.action-check [type="checkbox"]')
|
||||
.check('checkbox1')
|
||||
.uncheck('checkbox1').should('not.be.checked')
|
||||
|
||||
// .uncheck() accepts an array of values
|
||||
cy.get('.action-check [type="checkbox"]')
|
||||
.check(['checkbox1', 'checkbox3'])
|
||||
.uncheck(['checkbox1', 'checkbox3']).should('not.be.checked')
|
||||
|
||||
// Ignore error checking prior to unchecking
|
||||
cy.get('.action-check [disabled]')
|
||||
.uncheck({ force: true }).should('not.be.checked')
|
||||
})
|
||||
|
||||
it('.select() - select an option in a <select> element', () => {
|
||||
// https://on.cypress.io/select
|
||||
|
||||
// Select option(s) with matching text content
|
||||
cy.get('.action-select').select('apples')
|
||||
|
||||
cy.get('.action-select-multiple')
|
||||
.select(['apples', 'oranges', 'bananas'])
|
||||
|
||||
// Select option(s) with matching value
|
||||
cy.get('.action-select').select('fr-bananas')
|
||||
|
||||
cy.get('.action-select-multiple')
|
||||
.select(['fr-apples', 'fr-oranges', 'fr-bananas'])
|
||||
})
|
||||
|
||||
it('.scrollIntoView() - scroll an element into view', () => {
|
||||
// https://on.cypress.io/scrollintoview
|
||||
|
||||
// normally all of these buttons are hidden,
|
||||
// because they're not within
|
||||
// the viewable area of their parent
|
||||
// (we need to scroll to see them)
|
||||
cy.get('#scroll-horizontal button')
|
||||
.should('not.be.visible')
|
||||
|
||||
// scroll the button into view, as if the user had scrolled
|
||||
cy.get('#scroll-horizontal button').scrollIntoView()
|
||||
.should('be.visible')
|
||||
|
||||
cy.get('#scroll-vertical button')
|
||||
.should('not.be.visible')
|
||||
|
||||
// Cypress handles the scroll direction needed
|
||||
cy.get('#scroll-vertical button').scrollIntoView()
|
||||
.should('be.visible')
|
||||
|
||||
cy.get('#scroll-both button')
|
||||
.should('not.be.visible')
|
||||
|
||||
// Cypress knows to scroll to the right and down
|
||||
cy.get('#scroll-both button').scrollIntoView()
|
||||
.should('be.visible')
|
||||
})
|
||||
|
||||
it('.trigger() - trigger an event on a DOM element', () => {
|
||||
// https://on.cypress.io/trigger
|
||||
|
||||
// To interact with a range input (slider)
|
||||
// we need to set its value & trigger the
|
||||
// event to signal it changed
|
||||
|
||||
// Here, we invoke jQuery's val() method to set
|
||||
// the value and trigger the 'change' event
|
||||
cy.get('.trigger-input-range')
|
||||
.invoke('val', 25)
|
||||
.trigger('change')
|
||||
.get('input[type=range]').siblings('p')
|
||||
.should('have.text', '25')
|
||||
})
|
||||
|
||||
it('cy.scrollTo() - scroll the window or element to a position', () => {
|
||||
|
||||
// https://on.cypress.io/scrollTo
|
||||
|
||||
// You can scroll to 9 specific positions of an element:
|
||||
// -----------------------------------
|
||||
// | topLeft top topRight |
|
||||
// | |
|
||||
// | |
|
||||
// | |
|
||||
// | left center right |
|
||||
// | |
|
||||
// | |
|
||||
// | |
|
||||
// | bottomLeft bottom bottomRight |
|
||||
// -----------------------------------
|
||||
|
||||
// if you chain .scrollTo() off of cy, we will
|
||||
// scroll the entire window
|
||||
cy.scrollTo('bottom')
|
||||
|
||||
cy.get('#scrollable-horizontal').scrollTo('right')
|
||||
|
||||
// or you can scroll to a specific coordinate:
|
||||
// (x axis, y axis) in pixels
|
||||
cy.get('#scrollable-vertical').scrollTo(250, 250)
|
||||
|
||||
// or you can scroll to a specific percentage
|
||||
// of the (width, height) of the element
|
||||
cy.get('#scrollable-both').scrollTo('75%', '25%')
|
||||
|
||||
// control the easing of the scroll (default is 'swing')
|
||||
cy.get('#scrollable-vertical').scrollTo('center', { easing: 'linear' })
|
||||
|
||||
// control the duration of the scroll (in ms)
|
||||
cy.get('#scrollable-both').scrollTo('center', { duration: 2000 })
|
||||
})
|
||||
})
|
|
@ -1,42 +0,0 @@
|
|||
/// <reference types="Cypress" />
|
||||
|
||||
context('Aliasing', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('https://example.cypress.io/commands/aliasing')
|
||||
})
|
||||
|
||||
it('.as() - alias a DOM element for later use', () => {
|
||||
// https://on.cypress.io/as
|
||||
|
||||
// Alias a DOM element for use later
|
||||
// We don't have to traverse to the element
|
||||
// later in our code, we reference it with @
|
||||
|
||||
cy.get('.as-table').find('tbody>tr')
|
||||
.first().find('td').first()
|
||||
.find('button').as('firstBtn')
|
||||
|
||||
// when we reference the alias, we place an
|
||||
// @ in front of its name
|
||||
cy.get('@firstBtn').click()
|
||||
|
||||
cy.get('@firstBtn')
|
||||
.should('have.class', 'btn-success')
|
||||
.and('contain', 'Changed')
|
||||
})
|
||||
|
||||
it('.as() - alias a route for later use', () => {
|
||||
|
||||
// Alias the route to wait for its response
|
||||
cy.server()
|
||||
cy.route('GET', 'comments/*').as('getComment')
|
||||
|
||||
// we have code that gets a comment when
|
||||
// the button is clicked in scripts.js
|
||||
cy.get('.network-btn').click()
|
||||
|
||||
// https://on.cypress.io/wait
|
||||
cy.wait('@getComment').its('status').should('eq', 200)
|
||||
|
||||
})
|
||||
})
|
|
@ -1,168 +0,0 @@
|
|||
/// <reference types="Cypress" />
|
||||
|
||||
context('Assertions', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('https://example.cypress.io/commands/assertions')
|
||||
})
|
||||
|
||||
describe('Implicit Assertions', () => {
|
||||
it('.should() - make an assertion about the current subject', () => {
|
||||
// https://on.cypress.io/should
|
||||
cy.get('.assertion-table')
|
||||
.find('tbody tr:last')
|
||||
.should('have.class', 'success')
|
||||
.find('td')
|
||||
.first()
|
||||
// checking the text of the <td> element in various ways
|
||||
.should('have.text', 'Column content')
|
||||
.should('contain', 'Column content')
|
||||
.should('have.html', 'Column content')
|
||||
// chai-jquery uses "is()" to check if element matches selector
|
||||
.should('match', 'td')
|
||||
// to match text content against a regular expression
|
||||
// first need to invoke jQuery method text()
|
||||
// and then match using regular expression
|
||||
.invoke('text')
|
||||
.should('match', /column content/i)
|
||||
|
||||
// a better way to check element's text content against a regular expression
|
||||
// is to use "cy.contains"
|
||||
// https://on.cypress.io/contains
|
||||
cy.get('.assertion-table')
|
||||
.find('tbody tr:last')
|
||||
// finds first <td> element with text content matching regular expression
|
||||
.contains('td', /column content/i)
|
||||
.should('be.visible')
|
||||
|
||||
// for more information about asserting element's text
|
||||
// see https://on.cypress.io/using-cypress-faq#How-do-I-get-an-element’s-text-contents
|
||||
})
|
||||
|
||||
it('.and() - chain multiple assertions together', () => {
|
||||
// https://on.cypress.io/and
|
||||
cy.get('.assertions-link')
|
||||
.should('have.class', 'active')
|
||||
.and('have.attr', 'href')
|
||||
.and('include', 'cypress.io')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Explicit Assertions', () => {
|
||||
// https://on.cypress.io/assertions
|
||||
it('expect - make an assertion about a specified subject', () => {
|
||||
// We can use Chai's BDD style assertions
|
||||
expect(true).to.be.true
|
||||
const o = { foo: 'bar' }
|
||||
|
||||
expect(o).to.equal(o)
|
||||
expect(o).to.deep.equal({ foo: 'bar' })
|
||||
// matching text using regular expression
|
||||
expect('FooBar').to.match(/bar$/i)
|
||||
})
|
||||
|
||||
it('pass your own callback function to should()', () => {
|
||||
// Pass a function to should that can have any number
|
||||
// of explicit assertions within it.
|
||||
// The ".should(cb)" function will be retried
|
||||
// automatically until it passes all your explicit assertions or times out.
|
||||
cy.get('.assertions-p')
|
||||
.find('p')
|
||||
.should(($p) => {
|
||||
// https://on.cypress.io/$
|
||||
// return an array of texts from all of the p's
|
||||
// @ts-ignore TS6133 unused variable
|
||||
const texts = $p.map((i, el) => Cypress.$(el).text())
|
||||
|
||||
// jquery map returns jquery object
|
||||
// and .get() convert this to simple array
|
||||
const paragraphs = texts.get()
|
||||
|
||||
// array should have length of 3
|
||||
expect(paragraphs, 'has 3 paragraphs').to.have.length(3)
|
||||
|
||||
// use second argument to expect(...) to provide clear
|
||||
// message with each assertion
|
||||
expect(paragraphs, 'has expected text in each paragraph').to.deep.eq([
|
||||
'Some text from first p',
|
||||
'More text from second p',
|
||||
'And even more text from third p',
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
it('finds element by class name regex', () => {
|
||||
cy.get('.docs-header')
|
||||
.find('div')
|
||||
// .should(cb) callback function will be retried
|
||||
.should(($div) => {
|
||||
expect($div).to.have.length(1)
|
||||
|
||||
const className = $div[0].className
|
||||
|
||||
expect(className).to.match(/heading-/)
|
||||
})
|
||||
// .then(cb) callback is not retried,
|
||||
// it either passes or fails
|
||||
.then(($div) => {
|
||||
expect($div, 'text content').to.have.text('Introduction')
|
||||
})
|
||||
})
|
||||
|
||||
it('can throw any error', () => {
|
||||
cy.get('.docs-header')
|
||||
.find('div')
|
||||
.should(($div) => {
|
||||
if ($div.length !== 1) {
|
||||
// you can throw your own errors
|
||||
throw new Error('Did not find 1 element')
|
||||
}
|
||||
|
||||
const className = $div[0].className
|
||||
|
||||
if (!className.match(/heading-/)) {
|
||||
throw new Error(`Could not find class "heading-" in ${className}`)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('matches unknown text between two elements', () => {
|
||||
/**
|
||||
* Text from the first element.
|
||||
* @type {string}
|
||||
*/
|
||||
let text
|
||||
|
||||
/**
|
||||
* Normalizes passed text,
|
||||
* useful before comparing text with spaces and different capitalization.
|
||||
* @param {string} s Text to normalize
|
||||
*/
|
||||
const normalizeText = (s) => s.replace(/\s/g, '').toLowerCase()
|
||||
|
||||
cy.get('.two-elements')
|
||||
.find('.first')
|
||||
.then(($first) => {
|
||||
// save text from the first element
|
||||
text = normalizeText($first.text())
|
||||
})
|
||||
|
||||
cy.get('.two-elements')
|
||||
.find('.second')
|
||||
.should(($div) => {
|
||||
// we can massage text before comparing
|
||||
const secondText = normalizeText($div.text())
|
||||
|
||||
expect(secondText, 'second text').to.equal(text)
|
||||
})
|
||||
})
|
||||
|
||||
it('assert - assert shape of an object', () => {
|
||||
const person = {
|
||||
name: 'Joe',
|
||||
age: 20,
|
||||
}
|
||||
|
||||
assert.isObject(person, 'value is object')
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,56 +0,0 @@
|
|||
/// <reference types="Cypress" />
|
||||
|
||||
context('Connectors', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('https://example.cypress.io/commands/connectors')
|
||||
})
|
||||
|
||||
it('.each() - iterate over an array of elements', () => {
|
||||
// https://on.cypress.io/each
|
||||
cy.get('.connectors-each-ul>li')
|
||||
.each(($el, index, $list) => {
|
||||
console.log($el, index, $list)
|
||||
})
|
||||
})
|
||||
|
||||
it('.its() - get properties on the current subject', () => {
|
||||
// https://on.cypress.io/its
|
||||
cy.get('.connectors-its-ul>li')
|
||||
// calls the 'length' property yielding that value
|
||||
.its('length')
|
||||
.should('be.gt', 2)
|
||||
})
|
||||
|
||||
it('.invoke() - invoke a function on the current subject', () => {
|
||||
// our div is hidden in our script.js
|
||||
// $('.connectors-div').hide()
|
||||
|
||||
// https://on.cypress.io/invoke
|
||||
cy.get('.connectors-div').should('be.hidden')
|
||||
// call the jquery method 'show' on the 'div.container'
|
||||
.invoke('show')
|
||||
.should('be.visible')
|
||||
})
|
||||
|
||||
it('.spread() - spread an array as individual args to callback function', () => {
|
||||
// https://on.cypress.io/spread
|
||||
const arr = ['foo', 'bar', 'baz']
|
||||
|
||||
cy.wrap(arr).spread((foo, bar, baz) => {
|
||||
expect(foo).to.eq('foo')
|
||||
expect(bar).to.eq('bar')
|
||||
expect(baz).to.eq('baz')
|
||||
})
|
||||
})
|
||||
|
||||
it('.then() - invoke a callback function with the current subject', () => {
|
||||
// https://on.cypress.io/then
|
||||
cy.get('.connectors-list > li')
|
||||
.then(($lis) => {
|
||||
expect($lis, '3 items').to.have.length(3)
|
||||
expect($lis.eq(0), 'first item').to.contain('Walk the dog')
|
||||
expect($lis.eq(1), 'second item').to.contain('Feed the cat')
|
||||
expect($lis.eq(2), 'third item').to.contain('Write JavaScript')
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,78 +0,0 @@
|
|||
/// <reference types="Cypress" />
|
||||
|
||||
context('Cookies', () => {
|
||||
beforeEach(() => {
|
||||
Cypress.Cookies.debug(true)
|
||||
|
||||
cy.visit('https://example.cypress.io/commands/cookies')
|
||||
|
||||
// clear cookies again after visiting to remove
|
||||
// any 3rd party cookies picked up such as cloudflare
|
||||
cy.clearCookies()
|
||||
})
|
||||
|
||||
it('cy.getCookie() - get a browser cookie', () => {
|
||||
// https://on.cypress.io/getcookie
|
||||
cy.get('#getCookie .set-a-cookie').click()
|
||||
|
||||
// cy.getCookie() yields a cookie object
|
||||
cy.getCookie('token').should('have.property', 'value', '123ABC')
|
||||
})
|
||||
|
||||
it('cy.getCookies() - get browser cookies', () => {
|
||||
// https://on.cypress.io/getcookies
|
||||
cy.getCookies().should('be.empty')
|
||||
|
||||
cy.get('#getCookies .set-a-cookie').click()
|
||||
|
||||
// cy.getCookies() yields an array of cookies
|
||||
cy.getCookies().should('have.length', 1).should((cookies) => {
|
||||
|
||||
// each cookie has these properties
|
||||
expect(cookies[0]).to.have.property('name', 'token')
|
||||
expect(cookies[0]).to.have.property('value', '123ABC')
|
||||
expect(cookies[0]).to.have.property('httpOnly', false)
|
||||
expect(cookies[0]).to.have.property('secure', false)
|
||||
expect(cookies[0]).to.have.property('domain')
|
||||
expect(cookies[0]).to.have.property('path')
|
||||
})
|
||||
})
|
||||
|
||||
it('cy.setCookie() - set a browser cookie', () => {
|
||||
// https://on.cypress.io/setcookie
|
||||
cy.getCookies().should('be.empty')
|
||||
|
||||
cy.setCookie('foo', 'bar')
|
||||
|
||||
// cy.getCookie() yields a cookie object
|
||||
cy.getCookie('foo').should('have.property', 'value', 'bar')
|
||||
})
|
||||
|
||||
it('cy.clearCookie() - clear a browser cookie', () => {
|
||||
// https://on.cypress.io/clearcookie
|
||||
cy.getCookie('token').should('be.null')
|
||||
|
||||
cy.get('#clearCookie .set-a-cookie').click()
|
||||
|
||||
cy.getCookie('token').should('have.property', 'value', '123ABC')
|
||||
|
||||
// cy.clearCookies() yields null
|
||||
cy.clearCookie('token').should('be.null')
|
||||
|
||||
cy.getCookie('token').should('be.null')
|
||||
})
|
||||
|
||||
it('cy.clearCookies() - clear browser cookies', () => {
|
||||
// https://on.cypress.io/clearcookies
|
||||
cy.getCookies().should('be.empty')
|
||||
|
||||
cy.get('#clearCookies .set-a-cookie').click()
|
||||
|
||||
cy.getCookies().should('have.length', 1)
|
||||
|
||||
// cy.clearCookies() yields null
|
||||
cy.clearCookies()
|
||||
|
||||
cy.getCookies().should('be.empty')
|
||||
})
|
||||
})
|
|
@ -1,222 +0,0 @@
|
|||
/// <reference types="Cypress" />
|
||||
|
||||
context('Cypress.Commands', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('https://example.cypress.io/cypress-api')
|
||||
})
|
||||
|
||||
// https://on.cypress.io/custom-commands
|
||||
|
||||
it('.add() - create a custom command', () => {
|
||||
Cypress.Commands.add('console', {
|
||||
prevSubject: true,
|
||||
}, (subject, method) => {
|
||||
// the previous subject is automatically received
|
||||
// and the commands arguments are shifted
|
||||
|
||||
// allow us to change the console method used
|
||||
method = method || 'log'
|
||||
|
||||
// log the subject to the console
|
||||
// @ts-ignore TS7017
|
||||
console[method]('The subject is', subject)
|
||||
|
||||
// whatever we return becomes the new subject
|
||||
// we don't want to change the subject so
|
||||
// we return whatever was passed in
|
||||
return subject
|
||||
})
|
||||
|
||||
// @ts-ignore TS2339
|
||||
cy.get('button').console('info').then(($button) => {
|
||||
// subject is still $button
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
context('Cypress.Cookies', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('https://example.cypress.io/cypress-api')
|
||||
})
|
||||
|
||||
// https://on.cypress.io/cookies
|
||||
it('.debug() - enable or disable debugging', () => {
|
||||
Cypress.Cookies.debug(true)
|
||||
|
||||
// Cypress will now log in the console when
|
||||
// cookies are set or cleared
|
||||
cy.setCookie('fakeCookie', '123ABC')
|
||||
cy.clearCookie('fakeCookie')
|
||||
cy.setCookie('fakeCookie', '123ABC')
|
||||
cy.clearCookie('fakeCookie')
|
||||
cy.setCookie('fakeCookie', '123ABC')
|
||||
})
|
||||
|
||||
it('.preserveOnce() - preserve cookies by key', () => {
|
||||
// normally cookies are reset after each test
|
||||
cy.getCookie('fakeCookie').should('not.be.ok')
|
||||
|
||||
// preserving a cookie will not clear it when
|
||||
// the next test starts
|
||||
cy.setCookie('lastCookie', '789XYZ')
|
||||
Cypress.Cookies.preserveOnce('lastCookie')
|
||||
})
|
||||
|
||||
it('.defaults() - set defaults for all cookies', () => {
|
||||
// now any cookie with the name 'session_id' will
|
||||
// not be cleared before each new test runs
|
||||
Cypress.Cookies.defaults({
|
||||
whitelist: 'session_id',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
context('Cypress.Server', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('https://example.cypress.io/cypress-api')
|
||||
})
|
||||
|
||||
// Permanently override server options for
|
||||
// all instances of cy.server()
|
||||
|
||||
// https://on.cypress.io/cypress-server
|
||||
it('.defaults() - change default config of server', () => {
|
||||
Cypress.Server.defaults({
|
||||
delay: 0,
|
||||
force404: false,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context('Cypress.arch', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('https://example.cypress.io/cypress-api')
|
||||
})
|
||||
|
||||
it('Get CPU architecture name of underlying OS', () => {
|
||||
// https://on.cypress.io/arch
|
||||
expect(Cypress.arch).to.exist
|
||||
})
|
||||
})
|
||||
|
||||
context('Cypress.config()', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('https://example.cypress.io/cypress-api')
|
||||
})
|
||||
|
||||
it('Get and set configuration options', () => {
|
||||
// https://on.cypress.io/config
|
||||
let myConfig = Cypress.config()
|
||||
|
||||
expect(myConfig).to.have.property('animationDistanceThreshold', 5)
|
||||
expect(myConfig).to.have.property('baseUrl', null)
|
||||
expect(myConfig).to.have.property('defaultCommandTimeout', 4000)
|
||||
expect(myConfig).to.have.property('requestTimeout', 5000)
|
||||
expect(myConfig).to.have.property('responseTimeout', 30000)
|
||||
expect(myConfig).to.have.property('viewportHeight', 660)
|
||||
expect(myConfig).to.have.property('viewportWidth', 1000)
|
||||
expect(myConfig).to.have.property('pageLoadTimeout', 60000)
|
||||
expect(myConfig).to.have.property('waitForAnimations', true)
|
||||
|
||||
expect(Cypress.config('pageLoadTimeout')).to.eq(60000)
|
||||
|
||||
// this will change the config for the rest of your tests!
|
||||
Cypress.config('pageLoadTimeout', 20000)
|
||||
|
||||
expect(Cypress.config('pageLoadTimeout')).to.eq(20000)
|
||||
|
||||
Cypress.config('pageLoadTimeout', 60000)
|
||||
})
|
||||
})
|
||||
|
||||
context('Cypress.dom', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('https://example.cypress.io/cypress-api')
|
||||
})
|
||||
|
||||
// https://on.cypress.io/dom
|
||||
it('.isHidden() - determine if a DOM element is hidden', () => {
|
||||
let hiddenP = Cypress.$('.dom-p p.hidden').get(0)
|
||||
let visibleP = Cypress.$('.dom-p p.visible').get(0)
|
||||
|
||||
// our first paragraph has css class 'hidden'
|
||||
expect(Cypress.dom.isHidden(hiddenP)).to.be.true
|
||||
expect(Cypress.dom.isHidden(visibleP)).to.be.false
|
||||
})
|
||||
})
|
||||
|
||||
context('Cypress.env()', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('https://example.cypress.io/cypress-api')
|
||||
})
|
||||
|
||||
// We can set environment variables for highly dynamic values
|
||||
|
||||
// https://on.cypress.io/environment-variables
|
||||
it('Get environment variables', () => {
|
||||
// https://on.cypress.io/env
|
||||
// set multiple environment variables
|
||||
Cypress.env({
|
||||
host: 'veronica.dev.local',
|
||||
api_server: 'http://localhost:8888/v1/',
|
||||
})
|
||||
|
||||
// get environment variable
|
||||
expect(Cypress.env('host')).to.eq('veronica.dev.local')
|
||||
|
||||
// set environment variable
|
||||
Cypress.env('api_server', 'http://localhost:8888/v2/')
|
||||
expect(Cypress.env('api_server')).to.eq('http://localhost:8888/v2/')
|
||||
|
||||
// get all environment variable
|
||||
expect(Cypress.env()).to.have.property('host', 'veronica.dev.local')
|
||||
expect(Cypress.env()).to.have.property('api_server', 'http://localhost:8888/v2/')
|
||||
})
|
||||
})
|
||||
|
||||
context('Cypress.log', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('https://example.cypress.io/cypress-api')
|
||||
})
|
||||
|
||||
it('Control what is printed to the Command Log', () => {
|
||||
// https://on.cypress.io/cypress-log
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
context('Cypress.platform', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('https://example.cypress.io/cypress-api')
|
||||
})
|
||||
|
||||
it('Get underlying OS name', () => {
|
||||
// https://on.cypress.io/platform
|
||||
expect(Cypress.platform).to.be.exist
|
||||
})
|
||||
})
|
||||
|
||||
context('Cypress.version', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('https://example.cypress.io/cypress-api')
|
||||
})
|
||||
|
||||
it('Get current version of Cypress being run', () => {
|
||||
// https://on.cypress.io/version
|
||||
expect(Cypress.version).to.be.exist
|
||||
})
|
||||
})
|
||||
|
||||
context('Cypress.spec', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('https://example.cypress.io/cypress-api')
|
||||
})
|
||||
|
||||
it('Get current spec information', () => {
|
||||
// https://on.cypress.io/spec
|
||||
// wrap the object so we can inspect it easily by clicking in the command log
|
||||
cy.wrap(Cypress.spec).should('have.keys', ['name', 'relative', 'absolute'])
|
||||
})
|
||||
})
|
|
@ -1,86 +0,0 @@
|
|||
/// <reference types="Cypress" />
|
||||
|
||||
context('Files', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('https://example.cypress.io/commands/files')
|
||||
})
|
||||
it('cy.fixture() - load a fixture', () => {
|
||||
// https://on.cypress.io/fixture
|
||||
|
||||
// Instead of writing a response inline you can
|
||||
// use a fixture file's content.
|
||||
|
||||
cy.server()
|
||||
cy.fixture('example.json').as('comment')
|
||||
cy.route('GET', 'comments/*', '@comment').as('getComment')
|
||||
|
||||
// we have code that gets a comment when
|
||||
// the button is clicked in scripts.js
|
||||
cy.get('.fixture-btn').click()
|
||||
|
||||
cy.wait('@getComment').its('responseBody')
|
||||
.should('have.property', 'name')
|
||||
.and('include', 'Using fixtures to represent data')
|
||||
|
||||
// you can also just write the fixture in the route
|
||||
cy.route('GET', 'comments/*', 'fixture:example.json').as('getComment')
|
||||
|
||||
// we have code that gets a comment when
|
||||
// the button is clicked in scripts.js
|
||||
cy.get('.fixture-btn').click()
|
||||
|
||||
cy.wait('@getComment').its('responseBody')
|
||||
.should('have.property', 'name')
|
||||
.and('include', 'Using fixtures to represent data')
|
||||
|
||||
// or write fx to represent fixture
|
||||
// by default it assumes it's .json
|
||||
cy.route('GET', 'comments/*', 'fx:example').as('getComment')
|
||||
|
||||
// we have code that gets a comment when
|
||||
// the button is clicked in scripts.js
|
||||
cy.get('.fixture-btn').click()
|
||||
|
||||
cy.wait('@getComment').its('responseBody')
|
||||
.should('have.property', 'name')
|
||||
.and('include', 'Using fixtures to represent data')
|
||||
})
|
||||
|
||||
it('cy.readFile() - read a files contents', () => {
|
||||
// https://on.cypress.io/readfile
|
||||
|
||||
// You can read a file and yield its contents
|
||||
// The filePath is relative to your project's root.
|
||||
cy.readFile('cypress.json').then((json) => {
|
||||
expect(json).to.be.an('object')
|
||||
})
|
||||
})
|
||||
|
||||
it('cy.writeFile() - write to a file', () => {
|
||||
// https://on.cypress.io/writefile
|
||||
|
||||
// You can write to a file
|
||||
|
||||
// Use a response from a request to automatically
|
||||
// generate a fixture file for use later
|
||||
cy.request('https://jsonplaceholder.cypress.io/users')
|
||||
.then((response) => {
|
||||
cy.writeFile('cypress/fixtures/users.json', response.body)
|
||||
})
|
||||
cy.fixture('users').should((users) => {
|
||||
expect(users[0].name).to.exist
|
||||
})
|
||||
|
||||
// JavaScript arrays and objects are stringified
|
||||
// and formatted into text.
|
||||
cy.writeFile('cypress/fixtures/profile.json', {
|
||||
id: 8739,
|
||||
name: 'Jane',
|
||||
email: 'jane@example.com',
|
||||
})
|
||||
|
||||
cy.fixture('profile').should((profile) => {
|
||||
expect(profile.name).to.eq('Jane')
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,52 +0,0 @@
|
|||
/// <reference types="Cypress" />
|
||||
|
||||
context('Local Storage', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('https://example.cypress.io/commands/local-storage')
|
||||
})
|
||||
// Although local storage is automatically cleared
|
||||
// in between tests to maintain a clean state
|
||||
// sometimes we need to clear the local storage manually
|
||||
|
||||
it('cy.clearLocalStorage() - clear all data in local storage', () => {
|
||||
// https://on.cypress.io/clearlocalstorage
|
||||
cy.get('.ls-btn').click().should(() => {
|
||||
expect(localStorage.getItem('prop1')).to.eq('red')
|
||||
expect(localStorage.getItem('prop2')).to.eq('blue')
|
||||
expect(localStorage.getItem('prop3')).to.eq('magenta')
|
||||
})
|
||||
|
||||
// clearLocalStorage() yields the localStorage object
|
||||
cy.clearLocalStorage().should((ls) => {
|
||||
expect(ls.getItem('prop1')).to.be.null
|
||||
expect(ls.getItem('prop2')).to.be.null
|
||||
expect(ls.getItem('prop3')).to.be.null
|
||||
})
|
||||
|
||||
// Clear key matching string in Local Storage
|
||||
cy.get('.ls-btn').click().should(() => {
|
||||
expect(localStorage.getItem('prop1')).to.eq('red')
|
||||
expect(localStorage.getItem('prop2')).to.eq('blue')
|
||||
expect(localStorage.getItem('prop3')).to.eq('magenta')
|
||||
})
|
||||
|
||||
cy.clearLocalStorage('prop1').should((ls) => {
|
||||
expect(ls.getItem('prop1')).to.be.null
|
||||
expect(ls.getItem('prop2')).to.eq('blue')
|
||||
expect(ls.getItem('prop3')).to.eq('magenta')
|
||||
})
|
||||
|
||||
// Clear keys matching regex in Local Storage
|
||||
cy.get('.ls-btn').click().should(() => {
|
||||
expect(localStorage.getItem('prop1')).to.eq('red')
|
||||
expect(localStorage.getItem('prop2')).to.eq('blue')
|
||||
expect(localStorage.getItem('prop3')).to.eq('magenta')
|
||||
})
|
||||
|
||||
cy.clearLocalStorage(/prop1|2/).should((ls) => {
|
||||
expect(ls.getItem('prop1')).to.be.null
|
||||
expect(ls.getItem('prop2')).to.be.null
|
||||
expect(ls.getItem('prop3')).to.eq('magenta')
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,32 +0,0 @@
|
|||
/// <reference types="Cypress" />
|
||||
|
||||
context('Location', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('https://example.cypress.io/commands/location')
|
||||
})
|
||||
|
||||
it('cy.hash() - get the current URL hash', () => {
|
||||
// https://on.cypress.io/hash
|
||||
cy.hash().should('be.empty')
|
||||
})
|
||||
|
||||
it('cy.location() - get window.location', () => {
|
||||
// https://on.cypress.io/location
|
||||
cy.location().should((location) => {
|
||||
expect(location.hash).to.be.empty
|
||||
expect(location.href).to.eq('https://example.cypress.io/commands/location')
|
||||
expect(location.host).to.eq('example.cypress.io')
|
||||
expect(location.hostname).to.eq('example.cypress.io')
|
||||
expect(location.origin).to.eq('https://example.cypress.io')
|
||||
expect(location.pathname).to.eq('/commands/location')
|
||||
expect(location.port).to.eq('')
|
||||
expect(location.protocol).to.eq('https:')
|
||||
expect(location.search).to.be.empty
|
||||
})
|
||||
})
|
||||
|
||||
it('cy.url() - get the current URL', () => {
|
||||
// https://on.cypress.io/url
|
||||
cy.url().should('eq', 'https://example.cypress.io/commands/location')
|
||||
})
|
||||
})
|
|
@ -1,83 +0,0 @@
|
|||
/// <reference types="Cypress" />
|
||||
|
||||
context('Misc', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('https://example.cypress.io/commands/misc')
|
||||
})
|
||||
|
||||
it('.end() - end the command chain', () => {
|
||||
// https://on.cypress.io/end
|
||||
|
||||
// cy.end is useful when you want to end a chain of commands
|
||||
// and force Cypress to re-query from the root element
|
||||
cy.get('.misc-table').within(() => {
|
||||
// ends the current chain and yields null
|
||||
cy.contains('Cheryl').click().end()
|
||||
|
||||
// queries the entire table again
|
||||
cy.contains('Charles').click()
|
||||
})
|
||||
})
|
||||
|
||||
it('cy.exec() - execute a system command', () => {
|
||||
// https://on.cypress.io/exec
|
||||
|
||||
// execute a system command.
|
||||
// so you can take actions necessary for
|
||||
// your test outside the scope of Cypress.
|
||||
cy.exec('echo Jane Lane')
|
||||
.its('stdout').should('contain', 'Jane Lane')
|
||||
|
||||
// we can use Cypress.platform string to
|
||||
// select appropriate command
|
||||
// https://on.cypress/io/platform
|
||||
cy.log(`Platform ${Cypress.platform} architecture ${Cypress.arch}`)
|
||||
|
||||
if (Cypress.platform === 'win32') {
|
||||
cy.exec('print cypress.json')
|
||||
.its('stderr').should('be.empty')
|
||||
} else {
|
||||
cy.exec('cat cypress.json')
|
||||
.its('stderr').should('be.empty')
|
||||
|
||||
cy.exec('pwd')
|
||||
.its('code').should('eq', 0)
|
||||
}
|
||||
})
|
||||
|
||||
it('cy.focused() - get the DOM element that has focus', () => {
|
||||
// https://on.cypress.io/focused
|
||||
cy.get('.misc-form').find('#name').click()
|
||||
cy.focused().should('have.id', 'name')
|
||||
|
||||
cy.get('.misc-form').find('#description').click()
|
||||
cy.focused().should('have.id', 'description')
|
||||
})
|
||||
|
||||
context('Cypress.Screenshot', function () {
|
||||
it('cy.screenshot() - take a screenshot', () => {
|
||||
// https://on.cypress.io/screenshot
|
||||
cy.screenshot('my-image')
|
||||
})
|
||||
|
||||
it('Cypress.Screenshot.defaults() - change default config of screenshots', function () {
|
||||
Cypress.Screenshot.defaults({
|
||||
blackout: ['.foo'],
|
||||
capture: 'viewport',
|
||||
clip: { x: 0, y: 0, width: 200, height: 200 },
|
||||
scale: false,
|
||||
disableTimersAndAnimations: true,
|
||||
screenshotOnRunFailure: true,
|
||||
beforeScreenshot () { },
|
||||
afterScreenshot () { },
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('cy.wrap() - wrap an object', () => {
|
||||
// https://on.cypress.io/wrap
|
||||
cy.wrap({ foo: 'bar' })
|
||||
.should('have.property', 'foo')
|
||||
.and('include', 'bar')
|
||||
})
|
||||
})
|
|
@ -1,56 +0,0 @@
|
|||
/// <reference types="Cypress" />
|
||||
|
||||
context('Navigation', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('https://example.cypress.io')
|
||||
cy.get('.navbar-nav').contains('Commands').click()
|
||||
cy.get('.dropdown-menu').contains('Navigation').click()
|
||||
})
|
||||
|
||||
it('cy.go() - go back or forward in the browser\'s history', () => {
|
||||
// https://on.cypress.io/go
|
||||
|
||||
cy.location('pathname').should('include', 'navigation')
|
||||
|
||||
cy.go('back')
|
||||
cy.location('pathname').should('not.include', 'navigation')
|
||||
|
||||
cy.go('forward')
|
||||
cy.location('pathname').should('include', 'navigation')
|
||||
|
||||
// clicking back
|
||||
cy.go(-1)
|
||||
cy.location('pathname').should('not.include', 'navigation')
|
||||
|
||||
// clicking forward
|
||||
cy.go(1)
|
||||
cy.location('pathname').should('include', 'navigation')
|
||||
})
|
||||
|
||||
it('cy.reload() - reload the page', () => {
|
||||
// https://on.cypress.io/reload
|
||||
cy.reload()
|
||||
|
||||
// reload the page without using the cache
|
||||
cy.reload(true)
|
||||
})
|
||||
|
||||
it('cy.visit() - visit a remote url', () => {
|
||||
// https://on.cypress.io/visit
|
||||
|
||||
// Visit any sub-domain of your current domain
|
||||
|
||||
// Pass options to the visit
|
||||
cy.visit('https://example.cypress.io/commands/navigation', {
|
||||
timeout: 50000, // increase total time for the visit to resolve
|
||||
onBeforeLoad (contentWindow) {
|
||||
// contentWindow is the remote page's window object
|
||||
expect(typeof contentWindow === 'object').to.be.true
|
||||
},
|
||||
onLoad (contentWindow) {
|
||||
// contentWindow is the remote page's window object
|
||||
expect(typeof contentWindow === 'object').to.be.true
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,140 +0,0 @@
|
|||
/// <reference types="Cypress" />
|
||||
|
||||
context('Network Requests', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('https://example.cypress.io/commands/network-requests')
|
||||
})
|
||||
|
||||
// Manage AJAX / XHR requests in your app
|
||||
|
||||
it('cy.server() - control behavior of network requests and responses', () => {
|
||||
// https://on.cypress.io/server
|
||||
|
||||
cy.server().should((server) => {
|
||||
// the default options on server
|
||||
// you can override any of these options
|
||||
expect(server.delay).to.eq(0)
|
||||
expect(server.method).to.eq('GET')
|
||||
expect(server.status).to.eq(200)
|
||||
expect(server.headers).to.be.null
|
||||
expect(server.response).to.be.null
|
||||
expect(server.onRequest).to.be.undefined
|
||||
expect(server.onResponse).to.be.undefined
|
||||
expect(server.onAbort).to.be.undefined
|
||||
|
||||
// These options control the server behavior
|
||||
// affecting all requests
|
||||
|
||||
// pass false to disable existing route stubs
|
||||
expect(server.enable).to.be.true
|
||||
// forces requests that don't match your routes to 404
|
||||
expect(server.force404).to.be.false
|
||||
// whitelists requests from ever being logged or stubbed
|
||||
expect(server.whitelist).to.be.a('function')
|
||||
})
|
||||
|
||||
cy.server({
|
||||
method: 'POST',
|
||||
delay: 1000,
|
||||
status: 422,
|
||||
response: {},
|
||||
})
|
||||
|
||||
// any route commands will now inherit the above options
|
||||
// from the server. anything we pass specifically
|
||||
// to route will override the defaults though.
|
||||
})
|
||||
|
||||
it('cy.request() - make an XHR request', () => {
|
||||
// https://on.cypress.io/request
|
||||
cy.request('https://jsonplaceholder.cypress.io/comments')
|
||||
.should((response) => {
|
||||
expect(response.status).to.eq(200)
|
||||
expect(response.body).to.have.length(500)
|
||||
expect(response).to.have.property('headers')
|
||||
expect(response).to.have.property('duration')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
it('cy.request() - verify response using BDD syntax', () => {
|
||||
cy.request('https://jsonplaceholder.cypress.io/comments')
|
||||
.then((response) => {
|
||||
// https://on.cypress.io/assertions
|
||||
expect(response).property('status').to.equal(200)
|
||||
expect(response).property('body').to.have.length(500)
|
||||
expect(response).to.include.keys('headers', 'duration')
|
||||
})
|
||||
})
|
||||
|
||||
it('cy.request() with query parameters', () => {
|
||||
// will execute request
|
||||
// https://jsonplaceholder.cypress.io/comments?postId=1&id=3
|
||||
cy.request({
|
||||
url: 'https://jsonplaceholder.cypress.io/comments',
|
||||
qs: {
|
||||
postId: 1,
|
||||
id: 3,
|
||||
},
|
||||
})
|
||||
.its('body')
|
||||
.should('be.an', 'array')
|
||||
.and('have.length', 1)
|
||||
.its('0') // yields first element of the array
|
||||
.should('contain', {
|
||||
postId: 1,
|
||||
id: 3,
|
||||
})
|
||||
})
|
||||
|
||||
it('cy.route() - route responses to matching requests', () => {
|
||||
// https://on.cypress.io/route
|
||||
|
||||
let message = 'whoa, this comment does not exist'
|
||||
|
||||
cy.server()
|
||||
|
||||
// Listen to GET to comments/1
|
||||
cy.route('GET', 'comments/*').as('getComment')
|
||||
|
||||
// we have code that gets a comment when
|
||||
// the button is clicked in scripts.js
|
||||
cy.get('.network-btn').click()
|
||||
|
||||
// https://on.cypress.io/wait
|
||||
cy.wait('@getComment').its('status').should('eq', 200)
|
||||
|
||||
// Listen to POST to comments
|
||||
cy.route('POST', '/comments').as('postComment')
|
||||
|
||||
// we have code that posts a comment when
|
||||
// the button is clicked in scripts.js
|
||||
cy.get('.network-post').click()
|
||||
cy.wait('@postComment')
|
||||
|
||||
// get the route
|
||||
cy.get('@postComment').should((xhr) => {
|
||||
expect(xhr.requestBody).to.include('email')
|
||||
expect(xhr.requestHeaders).to.have.property('Content-Type')
|
||||
expect(xhr.responseBody).to.have.property('name', 'Using POST in cy.route()')
|
||||
})
|
||||
|
||||
// Stub a response to PUT comments/ ****
|
||||
cy.route({
|
||||
method: 'PUT',
|
||||
url: 'comments/*',
|
||||
status: 404,
|
||||
response: { error: message },
|
||||
delay: 500,
|
||||
}).as('putComment')
|
||||
|
||||
// we have code that puts a comment when
|
||||
// the button is clicked in scripts.js
|
||||
cy.get('.network-put').click()
|
||||
|
||||
cy.wait('@putComment')
|
||||
|
||||
// our 404 statusCode logic in scripts.js executed
|
||||
cy.get('.network-put-comment').should('contain', message)
|
||||
})
|
||||
})
|
|
@ -1,87 +0,0 @@
|
|||
/// <reference types="Cypress" />
|
||||
|
||||
context('Querying', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('https://example.cypress.io/commands/querying')
|
||||
})
|
||||
|
||||
// The most commonly used query is 'cy.get()', you can
|
||||
// think of this like the '$' in jQuery
|
||||
|
||||
it('cy.get() - query DOM elements', () => {
|
||||
// https://on.cypress.io/get
|
||||
|
||||
cy.get('#query-btn').should('contain', 'Button')
|
||||
|
||||
cy.get('.query-btn').should('contain', 'Button')
|
||||
|
||||
cy.get('#querying .well>button:first').should('contain', 'Button')
|
||||
// ↲
|
||||
// Use CSS selectors just like jQuery
|
||||
|
||||
cy.get('[data-test-id="test-example"]').should('have.class', 'example')
|
||||
|
||||
// 'cy.get()' yields jQuery object, you can get its attribute
|
||||
// by invoking `.attr()` method
|
||||
cy.get('[data-test-id="test-example"]')
|
||||
.invoke('attr', 'data-test-id')
|
||||
.should('equal', 'test-example')
|
||||
|
||||
// or you can get element's CSS property
|
||||
cy.get('[data-test-id="test-example"]')
|
||||
.invoke('css', 'position')
|
||||
.should('equal', 'static')
|
||||
|
||||
// or use assertions directly during 'cy.get()'
|
||||
// https://on.cypress.io/assertions
|
||||
cy.get('[data-test-id="test-example"]')
|
||||
.should('have.attr', 'data-test-id', 'test-example')
|
||||
.and('have.css', 'position', 'static')
|
||||
})
|
||||
|
||||
it('cy.contains() - query DOM elements with matching content', () => {
|
||||
// https://on.cypress.io/contains
|
||||
cy.get('.query-list')
|
||||
.contains('bananas')
|
||||
.should('have.class', 'third')
|
||||
|
||||
// we can pass a regexp to `.contains()`
|
||||
cy.get('.query-list')
|
||||
.contains(/^b\w+/)
|
||||
.should('have.class', 'third')
|
||||
|
||||
cy.get('.query-list')
|
||||
.contains('apples')
|
||||
.should('have.class', 'first')
|
||||
|
||||
// passing a selector to contains will
|
||||
// yield the selector containing the text
|
||||
cy.get('#querying')
|
||||
.contains('ul', 'oranges')
|
||||
.should('have.class', 'query-list')
|
||||
|
||||
cy.get('.query-button')
|
||||
.contains('Save Form')
|
||||
.should('have.class', 'btn')
|
||||
})
|
||||
|
||||
it('.within() - query DOM elements within a specific element', () => {
|
||||
// https://on.cypress.io/within
|
||||
cy.get('.query-form').within(() => {
|
||||
cy.get('input:first').should('have.attr', 'placeholder', 'Email')
|
||||
cy.get('input:last').should('have.attr', 'placeholder', 'Password')
|
||||
})
|
||||
})
|
||||
|
||||
it('cy.root() - query the root DOM element', () => {
|
||||
// https://on.cypress.io/root
|
||||
|
||||
// By default, root is the document
|
||||
cy.root().should('match', 'html')
|
||||
|
||||
cy.get('.query-ul').within(() => {
|
||||
// In this within, the root is now the ul DOM element
|
||||
cy.root().should('have.class', 'query-ul')
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,69 +0,0 @@
|
|||
/// <reference types="Cypress" />
|
||||
|
||||
context('Spies, Stubs, and Clock', () => {
|
||||
it('cy.spy() - wrap a method in a spy', () => {
|
||||
// https://on.cypress.io/spy
|
||||
cy.visit('https://example.cypress.io/commands/spies-stubs-clocks')
|
||||
|
||||
let obj = {
|
||||
foo () {},
|
||||
}
|
||||
|
||||
let spy = cy.spy(obj, 'foo').as('anyArgs')
|
||||
|
||||
obj.foo()
|
||||
|
||||
expect(spy).to.be.called
|
||||
})
|
||||
|
||||
it('cy.stub() - create a stub and/or replace a function with stub', () => {
|
||||
// https://on.cypress.io/stub
|
||||
cy.visit('https://example.cypress.io/commands/spies-stubs-clocks')
|
||||
|
||||
let obj = {
|
||||
/**
|
||||
* prints both arguments to the console
|
||||
* @param a {string}
|
||||
* @param b {string}
|
||||
*/
|
||||
foo (a, b) {
|
||||
console.log('a', a, 'b', b)
|
||||
},
|
||||
}
|
||||
|
||||
let stub = cy.stub(obj, 'foo').as('foo')
|
||||
|
||||
obj.foo('foo', 'bar')
|
||||
|
||||
expect(stub).to.be.called
|
||||
})
|
||||
|
||||
it('cy.clock() - control time in the browser', () => {
|
||||
// https://on.cypress.io/clock
|
||||
|
||||
// create the date in UTC so its always the same
|
||||
// no matter what local timezone the browser is running in
|
||||
let now = new Date(Date.UTC(2017, 2, 14)).getTime()
|
||||
|
||||
cy.clock(now)
|
||||
cy.visit('https://example.cypress.io/commands/spies-stubs-clocks')
|
||||
cy.get('#clock-div').click()
|
||||
.should('have.text', '1489449600')
|
||||
})
|
||||
|
||||
it('cy.tick() - move time in the browser', () => {
|
||||
// https://on.cypress.io/tick
|
||||
|
||||
// create the date in UTC so its always the same
|
||||
// no matter what local timezone the browser is running in
|
||||
let now = new Date(Date.UTC(2017, 2, 14)).getTime()
|
||||
|
||||
cy.clock(now)
|
||||
cy.visit('https://example.cypress.io/commands/spies-stubs-clocks')
|
||||
cy.get('#tick-div').click()
|
||||
.should('have.text', '1489449600')
|
||||
cy.tick(10000) // 10 seconds passed
|
||||
cy.get('#tick-div').click()
|
||||
.should('have.text', '1489449610')
|
||||
})
|
||||
})
|
|
@ -1,121 +0,0 @@
|
|||
/// <reference types="Cypress" />
|
||||
|
||||
context('Traversal', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('https://example.cypress.io/commands/traversal')
|
||||
})
|
||||
|
||||
it('.children() - get child DOM elements', () => {
|
||||
// https://on.cypress.io/children
|
||||
cy.get('.traversal-breadcrumb')
|
||||
.children('.active')
|
||||
.should('contain', 'Data')
|
||||
})
|
||||
|
||||
it('.closest() - get closest ancestor DOM element', () => {
|
||||
// https://on.cypress.io/closest
|
||||
cy.get('.traversal-badge')
|
||||
.closest('ul')
|
||||
.should('have.class', 'list-group')
|
||||
})
|
||||
|
||||
it('.eq() - get a DOM element at a specific index', () => {
|
||||
// https://on.cypress.io/eq
|
||||
cy.get('.traversal-list>li')
|
||||
.eq(1).should('contain', 'siamese')
|
||||
})
|
||||
|
||||
it('.filter() - get DOM elements that match the selector', () => {
|
||||
// https://on.cypress.io/filter
|
||||
cy.get('.traversal-nav>li')
|
||||
.filter('.active').should('contain', 'About')
|
||||
})
|
||||
|
||||
it('.find() - get descendant DOM elements of the selector', () => {
|
||||
// https://on.cypress.io/find
|
||||
cy.get('.traversal-pagination')
|
||||
.find('li').find('a')
|
||||
.should('have.length', 7)
|
||||
})
|
||||
|
||||
it('.first() - get first DOM element', () => {
|
||||
// https://on.cypress.io/first
|
||||
cy.get('.traversal-table td')
|
||||
.first().should('contain', '1')
|
||||
})
|
||||
|
||||
it('.last() - get last DOM element', () => {
|
||||
// https://on.cypress.io/last
|
||||
cy.get('.traversal-buttons .btn')
|
||||
.last().should('contain', 'Submit')
|
||||
})
|
||||
|
||||
it('.next() - get next sibling DOM element', () => {
|
||||
// https://on.cypress.io/next
|
||||
cy.get('.traversal-ul')
|
||||
.contains('apples').next().should('contain', 'oranges')
|
||||
})
|
||||
|
||||
it('.nextAll() - get all next sibling DOM elements', () => {
|
||||
// https://on.cypress.io/nextall
|
||||
cy.get('.traversal-next-all')
|
||||
.contains('oranges')
|
||||
.nextAll().should('have.length', 3)
|
||||
})
|
||||
|
||||
it('.nextUntil() - get next sibling DOM elements until next el', () => {
|
||||
// https://on.cypress.io/nextuntil
|
||||
cy.get('#veggies')
|
||||
.nextUntil('#nuts').should('have.length', 3)
|
||||
})
|
||||
|
||||
it('.not() - remove DOM elements from set of DOM elements', () => {
|
||||
// https://on.cypress.io/not
|
||||
cy.get('.traversal-disabled .btn')
|
||||
.not('[disabled]').should('not.contain', 'Disabled')
|
||||
})
|
||||
|
||||
it('.parent() - get parent DOM element from DOM elements', () => {
|
||||
// https://on.cypress.io/parent
|
||||
cy.get('.traversal-mark')
|
||||
.parent().should('contain', 'Morbi leo risus')
|
||||
})
|
||||
|
||||
it('.parents() - get parent DOM elements from DOM elements', () => {
|
||||
// https://on.cypress.io/parents
|
||||
cy.get('.traversal-cite')
|
||||
.parents().should('match', 'blockquote')
|
||||
})
|
||||
|
||||
it('.parentsUntil() - get parent DOM elements from DOM elements until el', () => {
|
||||
// https://on.cypress.io/parentsuntil
|
||||
cy.get('.clothes-nav')
|
||||
.find('.active')
|
||||
.parentsUntil('.clothes-nav')
|
||||
.should('have.length', 2)
|
||||
})
|
||||
|
||||
it('.prev() - get previous sibling DOM element', () => {
|
||||
// https://on.cypress.io/prev
|
||||
cy.get('.birds').find('.active')
|
||||
.prev().should('contain', 'Lorikeets')
|
||||
})
|
||||
|
||||
it('.prevAll() - get all previous sibling DOM elements', () => {
|
||||
// https://on.cypress.io/prevAll
|
||||
cy.get('.fruits-list').find('.third')
|
||||
.prevAll().should('have.length', 2)
|
||||
})
|
||||
|
||||
it('.prevUntil() - get all previous sibling DOM elements until el', () => {
|
||||
// https://on.cypress.io/prevUntil
|
||||
cy.get('.foods-list').find('#nuts')
|
||||
.prevUntil('#veggies').should('have.length', 3)
|
||||
})
|
||||
|
||||
it('.siblings() - get all sibling DOM elements', () => {
|
||||
// https://on.cypress.io/siblings
|
||||
cy.get('.traversal-pills .active')
|
||||
.siblings().should('have.length', 2)
|
||||
})
|
||||
})
|
|
@ -1,117 +0,0 @@
|
|||
/// <reference types="Cypress" />
|
||||
|
||||
context('Utilities', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('https://example.cypress.io/utilities')
|
||||
})
|
||||
|
||||
it('Cypress._ - call a lodash method', () => {
|
||||
// https://on.cypress.io/_
|
||||
cy.request('https://jsonplaceholder.cypress.io/users')
|
||||
.then((response) => {
|
||||
let ids = Cypress._.chain(response.body).map('id').take(3).value()
|
||||
|
||||
expect(ids).to.deep.eq([1, 2, 3])
|
||||
})
|
||||
})
|
||||
|
||||
it('Cypress.$ - call a jQuery method', () => {
|
||||
// https://on.cypress.io/$
|
||||
let $li = Cypress.$('.utility-jquery li:first')
|
||||
|
||||
cy.wrap($li)
|
||||
.should('not.have.class', 'active')
|
||||
.click()
|
||||
.should('have.class', 'active')
|
||||
})
|
||||
|
||||
it('Cypress.Blob - blob utilities and base64 string conversion', () => {
|
||||
// https://on.cypress.io/blob
|
||||
cy.get('.utility-blob').then(($div) =>
|
||||
// https://github.com/nolanlawson/blob-util#imgSrcToDataURL
|
||||
// get the dataUrl string for the javascript-logo
|
||||
Cypress.Blob.imgSrcToDataURL('https://example.cypress.io/assets/img/javascript-logo.png', undefined, 'anonymous')
|
||||
.then((dataUrl) => {
|
||||
// create an <img> element and set its src to the dataUrl
|
||||
let img = Cypress.$('<img />', { src: dataUrl })
|
||||
|
||||
// need to explicitly return cy here since we are initially returning
|
||||
// the Cypress.Blob.imgSrcToDataURL promise to our test
|
||||
// append the image
|
||||
$div.append(img)
|
||||
|
||||
cy.get('.utility-blob img').click()
|
||||
.should('have.attr', 'src', dataUrl)
|
||||
}))
|
||||
})
|
||||
|
||||
it('Cypress.minimatch - test out glob patterns against strings', () => {
|
||||
// https://on.cypress.io/minimatch
|
||||
let matching = Cypress.minimatch('/users/1/comments', '/users/*/comments', {
|
||||
matchBase: true,
|
||||
})
|
||||
|
||||
expect(matching, 'matching wildcard').to.be.true
|
||||
|
||||
matching = Cypress.minimatch('/users/1/comments/2', '/users/*/comments', {
|
||||
matchBase: true,
|
||||
})
|
||||
expect(matching, 'comments').to.be.false
|
||||
|
||||
// ** matches against all downstream path segments
|
||||
matching = Cypress.minimatch('/foo/bar/baz/123/quux?a=b&c=2', '/foo/**', {
|
||||
matchBase: true,
|
||||
})
|
||||
expect(matching, 'comments').to.be.true
|
||||
|
||||
// whereas * matches only the next path segment
|
||||
|
||||
matching = Cypress.minimatch('/foo/bar/baz/123/quux?a=b&c=2', '/foo/*', {
|
||||
matchBase: false,
|
||||
})
|
||||
expect(matching, 'comments').to.be.false
|
||||
})
|
||||
|
||||
|
||||
it('Cypress.moment() - format or parse dates using a moment method', () => {
|
||||
// https://on.cypress.io/moment
|
||||
const time = Cypress.moment().utc('2014-04-25T19:38:53.196Z').format('h:mm A')
|
||||
|
||||
expect(time).to.be.a('string')
|
||||
|
||||
cy.get('.utility-moment').contains('3:38 PM')
|
||||
.should('have.class', 'badge')
|
||||
})
|
||||
|
||||
|
||||
it('Cypress.Promise - instantiate a bluebird promise', () => {
|
||||
// https://on.cypress.io/promise
|
||||
let waited = false
|
||||
|
||||
/**
|
||||
* @return Bluebird<string>
|
||||
*/
|
||||
function waitOneSecond () {
|
||||
// return a promise that resolves after 1 second
|
||||
// @ts-ignore TS2351 (new Cypress.Promise)
|
||||
return new Cypress.Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
// set waited to true
|
||||
waited = true
|
||||
|
||||
// resolve with 'foo' string
|
||||
resolve('foo')
|
||||
}, 1000)
|
||||
})
|
||||
}
|
||||
|
||||
cy.then(() =>
|
||||
// return a promise to cy.then() that
|
||||
// is awaited until it resolves
|
||||
// @ts-ignore TS7006
|
||||
waitOneSecond().then((str) => {
|
||||
expect(str).to.eq('foo')
|
||||
expect(waited).to.be.true
|
||||
}))
|
||||
})
|
||||
})
|
|
@ -1,59 +0,0 @@
|
|||
/// <reference types="Cypress" />
|
||||
|
||||
context('Viewport', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('https://example.cypress.io/commands/viewport')
|
||||
})
|
||||
|
||||
it('cy.viewport() - set the viewport size and dimension', () => {
|
||||
// https://on.cypress.io/viewport
|
||||
|
||||
cy.get('#navbar').should('be.visible')
|
||||
cy.viewport(320, 480)
|
||||
|
||||
// the navbar should have collapse since our screen is smaller
|
||||
cy.get('#navbar').should('not.be.visible')
|
||||
cy.get('.navbar-toggle').should('be.visible').click()
|
||||
cy.get('.nav').find('a').should('be.visible')
|
||||
|
||||
// lets see what our app looks like on a super large screen
|
||||
cy.viewport(2999, 2999)
|
||||
|
||||
// cy.viewport() accepts a set of preset sizes
|
||||
// to easily set the screen to a device's width and height
|
||||
|
||||
// We added a cy.wait() between each viewport change so you can see
|
||||
// the change otherwise it is a little too fast to see :)
|
||||
|
||||
cy.viewport('macbook-15')
|
||||
cy.wait(200)
|
||||
cy.viewport('macbook-13')
|
||||
cy.wait(200)
|
||||
cy.viewport('macbook-11')
|
||||
cy.wait(200)
|
||||
cy.viewport('ipad-2')
|
||||
cy.wait(200)
|
||||
cy.viewport('ipad-mini')
|
||||
cy.wait(200)
|
||||
cy.viewport('iphone-6+')
|
||||
cy.wait(200)
|
||||
cy.viewport('iphone-6')
|
||||
cy.wait(200)
|
||||
cy.viewport('iphone-5')
|
||||
cy.wait(200)
|
||||
cy.viewport('iphone-4')
|
||||
cy.wait(200)
|
||||
cy.viewport('iphone-3')
|
||||
cy.wait(200)
|
||||
|
||||
// cy.viewport() accepts an orientation for all presets
|
||||
// the default orientation is 'portrait'
|
||||
cy.viewport('ipad-2', 'portrait')
|
||||
cy.wait(200)
|
||||
cy.viewport('iphone-4', 'landscape')
|
||||
cy.wait(200)
|
||||
|
||||
// The viewport will be reset back to the default dimensions
|
||||
// in between tests (the default can be set in cypress.json)
|
||||
})
|
||||
})
|
|
@ -1,34 +0,0 @@
|
|||
/// <reference types="Cypress" />
|
||||
|
||||
context('Waiting', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('https://example.cypress.io/commands/waiting')
|
||||
})
|
||||
// BE CAREFUL of adding unnecessary wait times.
|
||||
// https://on.cypress.io/best-practices#Unnecessary-Waiting
|
||||
|
||||
// https://on.cypress.io/wait
|
||||
it('cy.wait() - wait for a specific amount of time', () => {
|
||||
cy.get('.wait-input1').type('Wait 1000ms after typing')
|
||||
cy.wait(1000)
|
||||
cy.get('.wait-input2').type('Wait 1000ms after typing')
|
||||
cy.wait(1000)
|
||||
cy.get('.wait-input3').type('Wait 1000ms after typing')
|
||||
cy.wait(1000)
|
||||
})
|
||||
|
||||
it('cy.wait() - wait for a specific route', () => {
|
||||
cy.server()
|
||||
|
||||
// Listen to GET to comments/1
|
||||
cy.route('GET', 'comments/*').as('getComment')
|
||||
|
||||
// we have code that gets a comment when
|
||||
// the button is clicked in scripts.js
|
||||
cy.get('.network-btn').click()
|
||||
|
||||
// wait for GET comments/1
|
||||
cy.wait('@getComment').its('status').should('eq', 200)
|
||||
})
|
||||
|
||||
})
|
|
@ -1,22 +0,0 @@
|
|||
/// <reference types="Cypress" />
|
||||
|
||||
context('Window', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('https://example.cypress.io/commands/window')
|
||||
})
|
||||
|
||||
it('cy.window() - get the global window object', () => {
|
||||
// https://on.cypress.io/window
|
||||
cy.window().should('have.property', 'top')
|
||||
})
|
||||
|
||||
it('cy.document() - get the document object', () => {
|
||||
// https://on.cypress.io/document
|
||||
cy.document().should('have.property', 'charset').and('eq', 'UTF-8')
|
||||
})
|
||||
|
||||
it('cy.title() - get the title', () => {
|
||||
// https://on.cypress.io/title
|
||||
cy.title().should('include', 'Kitchen Sink')
|
||||
})
|
||||
})
|
|
@ -6,6 +6,7 @@
|
|||
"@babel/parser": "^7.11.3",
|
||||
"@types/react": "16.9.23",
|
||||
"bl": "^4.0.3",
|
||||
"browserslist": "^4.16.5",
|
||||
"elliptic": "^6.5.3",
|
||||
"kind-of": "^6.0.3",
|
||||
"lodash": "^4.17.21",
|
||||
|
@ -34,13 +35,14 @@
|
|||
"packages/adaptive-form",
|
||||
"packages/client",
|
||||
"packages/electron-server",
|
||||
"packages/extension",
|
||||
"packages/extension-client",
|
||||
"packages/extension",
|
||||
"packages/form-dialogs",
|
||||
"packages/integration-tests",
|
||||
"packages/intellisense",
|
||||
"packages/lib/*",
|
||||
"packages/server",
|
||||
"packages/server-workers",
|
||||
"packages/server",
|
||||
"packages/test-utils",
|
||||
"packages/tools/built-in-functions",
|
||||
"packages/tools/language-servers/*",
|
||||
|
@ -48,7 +50,8 @@
|
|||
"packages/ui-plugins/*"
|
||||
],
|
||||
"nohoist": [
|
||||
"**/server-workers/**"
|
||||
"**/server-workers/**",
|
||||
"**/integration-tests/**"
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
|
@ -71,14 +74,12 @@
|
|||
"start:server": "yarn workspace @bfc/server start",
|
||||
"start:server:dev": "yarn workspace @bfc/server start:dev",
|
||||
"runtime": "cd ../runtime/dotnet/azurewebapp && dotnet build && dotnet run",
|
||||
"test": "cross-env NODE_OPTIONS=--max-old-space-size=4096 yarn typecheck && jest",
|
||||
"test": "cross-env NODE_OPTIONS=--max-old-space-size=4096 yarn typecheck && jest --silent",
|
||||
"test:watch": "yarn typecheck && jest --watch",
|
||||
"test:coverage": "yarn test --coverage --no-cache --forceExit --reporters=default --silent",
|
||||
"test:integration": "cypress run --browser edge",
|
||||
"test:integration:start-server": "node scripts/e2e.js",
|
||||
"test:integration:open": "cypress open",
|
||||
"test:integration:clean": "node scripts/clean-e2e.js",
|
||||
"test:integration:clean-all": "node scripts/clean-e2e.js --all",
|
||||
"test:ci": "cross-env CI=true yarn test --coverage --no-cache --forceExit --reporters=default --reporters=jest-github-actions-reporter --testLocationInResults",
|
||||
"test:integration": "yarn workspace @bfc/integration-tests start",
|
||||
"test:integration:start-server": "yarn workspace @bfc/integration-tests start:server",
|
||||
"test:integration:open": "yarn workspace @bfc/integration-tests open",
|
||||
"lint": "wsrun --exclude-missing --collect-logs --report lint",
|
||||
"lint:ci": "wsrun --exclude-missing --collect-logs --report --no-prefix lint --format github-actions",
|
||||
"lint:fix": "wsrun --exclude-missing --collect-logs --report lint:fix",
|
||||
|
@ -119,20 +120,14 @@
|
|||
"@babel/preset-typescript": "^7.9.0",
|
||||
"@bfc/eslint-plugin-bfcomposer": "*",
|
||||
"@emotion/babel-preset-css-prop": "^10.0.27",
|
||||
"@testing-library/cypress": "7.0.0-beta.1",
|
||||
"@testing-library/user-event": "12.6.3",
|
||||
"@typescript-eslint/eslint-plugin": "2.34.0",
|
||||
"@typescript-eslint/parser": "2.34.0",
|
||||
"chalk": "^4.0.0",
|
||||
"concurrently": "^4.1.0",
|
||||
"coveralls": "^3.1.0",
|
||||
"cross-env": "^6.0.3",
|
||||
"cypress": "^5.0.0",
|
||||
"cypress-plugin-tab": "^1.0.5",
|
||||
"eslint": "7.0.0",
|
||||
"eslint-config-prettier": "6.11.0",
|
||||
"eslint-formatter-github-actions": "^1.0.0",
|
||||
"eslint-plugin-cypress": "2.11.1",
|
||||
"eslint-plugin-emotion": "10.0.27",
|
||||
"eslint-plugin-format-message": "6.2.3",
|
||||
"eslint-plugin-import": "2.20.2",
|
||||
|
|
|
@ -24,8 +24,8 @@ const recognizers = [
|
|||
{
|
||||
id: 'TestRecognizer',
|
||||
displayName: 'TestRecognizer',
|
||||
intentEditor: ({ id, onChange }) => (
|
||||
<div id={id}>
|
||||
intentEditor: ({ id, onChange, label }) => (
|
||||
<div aria-label={label} id={id}>
|
||||
Test Recognizer <button onClick={onChange}>Update</button>
|
||||
</div>
|
||||
),
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
let mock = jest.createMockFromModule('@azure/arm-resources');
|
||||
|
||||
mock.ResourceManagementClient = () => {
|
||||
function ResourceManagementClient() {
|
||||
return {
|
||||
resourceGroups: {
|
||||
list: async () => {
|
||||
|
@ -14,6 +14,8 @@ mock.ResourceManagementClient = () => {
|
|||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
mock.ResourceManagementClient = ResourceManagementClient;
|
||||
|
||||
module.exports = mock;
|
|
@ -2,7 +2,7 @@
|
|||
// Licensed under the MIT License.
|
||||
let mock = jest.createMockFromModule('@azure/arm-subscriptions');
|
||||
|
||||
mock.SubscriptionClient = () => {
|
||||
function SubscriptionClient() {
|
||||
return {
|
||||
subscriptions: {
|
||||
list: async () => {
|
||||
|
@ -22,6 +22,7 @@ mock.SubscriptionClient = () => {
|
|||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
||||
mock.SubscriptionClient = SubscriptionClient;
|
||||
|
||||
module.exports = mock;
|
|
@ -0,0 +1,4 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
module.exports = jest.createMockFromModule('axios');
|
|
@ -8,12 +8,11 @@ import { OpenEmulatorButton } from '../../../src/components/BotRuntimeController
|
|||
import { botEndpointsState, botStatusState, settingsState } from '../../../src/recoilModel';
|
||||
import { BotStatus } from '../../../src/constants';
|
||||
import { renderWithRecoil } from '../../testUtils';
|
||||
|
||||
const mockCallEmulator = jest.fn();
|
||||
import { openInEmulator } from '../../../src/utils/navigation';
|
||||
|
||||
jest.mock('../../../src/utils/navigation', () => {
|
||||
return {
|
||||
openInEmulator: mockCallEmulator,
|
||||
openInEmulator: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -44,7 +43,7 @@ const initialState = ({ currentStatus = BotStatus.connected } = {}) => ({ set })
|
|||
|
||||
describe('<OpenEmulatorButton />', () => {
|
||||
it('should show the button to open emulator', async () => {
|
||||
mockCallEmulator.mockImplementationOnce((url) => {
|
||||
(openInEmulator as jest.Mock).mockImplementationOnce((url) => {
|
||||
expect(url).toBeDefined();
|
||||
});
|
||||
const { findByTestId } = renderWithRecoil(<OpenEmulatorButton isRootBot projectId={projectId} />, initialState());
|
||||
|
|
|
@ -90,10 +90,12 @@ describe('<FileSelector/>', () => {
|
|||
it('should create a new folder', async () => {
|
||||
const component = renderComponent();
|
||||
const createFolderBtn = await component.findByText('Create new folder');
|
||||
fireEvent.click(createFolderBtn);
|
||||
const textField = await component.findByTestId('newFolderTextField');
|
||||
fireEvent.change(textField, { target: { value: 'newFolder' } });
|
||||
fireEvent.keyDown(textField, { key: 'Enter' });
|
||||
await act(async () => {
|
||||
fireEvent.click(createFolderBtn);
|
||||
const textField = await component.findByTestId('newFolderTextField');
|
||||
fireEvent.change(textField, { target: { value: 'newFolder' } });
|
||||
fireEvent.keyDown(textField, { key: 'Enter' });
|
||||
});
|
||||
//locally this should be 'C:\\test-folder\\Desktop', but online it should be 'C:/test-folder/Desktop'
|
||||
expect(
|
||||
createFolder.mock.calls[0][0] === 'C:/test-folder/Desktop' ||
|
||||
|
|
|
@ -11,7 +11,7 @@ const serviceName = 'Language Understanding';
|
|||
const DOWN_ARROW = { keyCode: 40 };
|
||||
|
||||
jest.mock('@azure/arm-cognitiveservices', () => ({
|
||||
CognitiveServicesManagementClient: () => {
|
||||
CognitiveServicesManagementClient: function CognitiveServicesManagementClient() {
|
||||
return {
|
||||
accounts: {
|
||||
create: async () => {},
|
||||
|
@ -103,7 +103,7 @@ describe('<ManageLuis />', () => {
|
|||
const onNext = jest.fn();
|
||||
const onToggleVisibility = jest.fn();
|
||||
|
||||
const { baseElement, findByText, findByTestId } = renderWithRecoil(
|
||||
const { baseElement, findByText, findByTestId, findByRole } = renderWithRecoil(
|
||||
<ManageLuis
|
||||
hidden={false}
|
||||
onDismiss={onDismiss}
|
||||
|
@ -115,7 +115,7 @@ describe('<ManageLuis />', () => {
|
|||
|
||||
// test the default option (choose existing)
|
||||
// click the next button, ensure the title changes
|
||||
const nextButton = await findByText('Next');
|
||||
const nextButton = await findByRole('button', { name: 'Next' });
|
||||
expect(nextButton).toBeDefined();
|
||||
await act(async () => {
|
||||
await fireEvent.click(nextButton);
|
||||
|
@ -136,7 +136,7 @@ describe('<ManageLuis />', () => {
|
|||
|
||||
// ensure that since a subscription hasn't been selected
|
||||
// this button is disabled
|
||||
const nextButton2 = await findByText('Next');
|
||||
const nextButton2 = await findByRole('button', { name: 'Next' });
|
||||
expect(nextButton2).toBeDefined();
|
||||
expect(nextButton2).toBeDisabled();
|
||||
|
||||
|
@ -188,7 +188,7 @@ describe('<ManageLuis />', () => {
|
|||
const onNext = jest.fn();
|
||||
const onToggleVisibility = jest.fn();
|
||||
|
||||
const { baseElement, findByText, findByTestId } = renderWithRecoil(
|
||||
const { baseElement, findByText, findByTestId, findByRole } = renderWithRecoil(
|
||||
<ManageLuis
|
||||
hidden={false}
|
||||
onDismiss={onDismiss}
|
||||
|
@ -204,7 +204,7 @@ describe('<ManageLuis />', () => {
|
|||
fireEvent.click(createOption);
|
||||
|
||||
// click the next button, ensure the title changes
|
||||
const nextButton = await findByText('Next');
|
||||
const nextButton = await findByRole('button', { name: 'Next' });
|
||||
expect(nextButton).toBeDefined();
|
||||
await act(async () => {
|
||||
await fireEvent.click(nextButton);
|
||||
|
@ -213,7 +213,7 @@ describe('<ManageLuis />', () => {
|
|||
|
||||
// ensure that since a subscription hasn't been selected
|
||||
// this button is disabled
|
||||
const nextButton2 = await findByText('Next');
|
||||
const nextButton2 = await findByRole('button', { name: 'Next' });
|
||||
expect(nextButton2).toBeDefined();
|
||||
expect(nextButton2).toBeDisabled();
|
||||
|
||||
|
@ -244,7 +244,7 @@ describe('<ManageLuis />', () => {
|
|||
await fireEvent.click(nextButton2);
|
||||
});
|
||||
|
||||
const nextButton3 = await findByText('Next');
|
||||
const nextButton3 = await findByRole('button', { name: 'Next' });
|
||||
expect(nextButton3).toBeDefined();
|
||||
expect(nextButton3).toBeDisabled();
|
||||
|
||||
|
@ -303,7 +303,7 @@ describe('<ManageLuis />', () => {
|
|||
const onNext = jest.fn();
|
||||
const onToggleVisibility = jest.fn();
|
||||
|
||||
const { baseElement, findByText } = renderWithRecoil(
|
||||
const { baseElement, findByText, findByRole } = renderWithRecoil(
|
||||
<ManageLuis
|
||||
hidden={false}
|
||||
onDismiss={onDismiss}
|
||||
|
@ -319,7 +319,7 @@ describe('<ManageLuis />', () => {
|
|||
fireEvent.click(generateOption);
|
||||
|
||||
// click the next button, ensure the title changes
|
||||
const nextButton = await findByText('Next');
|
||||
const nextButton = await findByRole('button', { name: 'Next' });
|
||||
expect(nextButton).toBeDefined();
|
||||
await act(async () => {
|
||||
await fireEvent.click(nextButton);
|
||||
|
|
|
@ -11,7 +11,7 @@ const serviceName = 'QnA Maker';
|
|||
const DOWN_ARROW = { keyCode: 40 };
|
||||
|
||||
jest.mock('@azure/arm-appservice', () => ({
|
||||
WebSiteManagementClient: () => {
|
||||
WebSiteManagementClient: function WebSiteManagementClient() {
|
||||
return {
|
||||
appServicePlans: {
|
||||
createOrUpdate: jest.fn(),
|
||||
|
@ -30,7 +30,7 @@ jest.mock('@azure/arm-appservice', () => ({
|
|||
},
|
||||
}));
|
||||
jest.mock('@azure/arm-search', () => ({
|
||||
SearchManagementClient: () => {
|
||||
SearchManagementClient: function SearchManagementClient() {
|
||||
return {
|
||||
services: {
|
||||
createOrUpdate: jest.fn(),
|
||||
|
@ -44,7 +44,7 @@ jest.mock('@azure/arm-search', () => ({
|
|||
},
|
||||
}));
|
||||
jest.mock('@azure/arm-cognitiveservices', () => ({
|
||||
CognitiveServicesManagementClient: () => {
|
||||
CognitiveServicesManagementClient: function CognitiveServicesManagementClient() {
|
||||
return {
|
||||
accounts: {
|
||||
create: async () => {},
|
||||
|
@ -136,7 +136,7 @@ describe('<ManageQNA />', () => {
|
|||
const onNext = jest.fn();
|
||||
const onToggleVisibility = jest.fn();
|
||||
|
||||
const { baseElement, findByText, findByTestId } = renderWithRecoil(
|
||||
const { baseElement, findByText, findByTestId, findByRole } = renderWithRecoil(
|
||||
<ManageQNA
|
||||
hidden={false}
|
||||
onDismiss={onDismiss}
|
||||
|
@ -148,7 +148,7 @@ describe('<ManageQNA />', () => {
|
|||
|
||||
// test the default option (choose existing)
|
||||
// click the next button, ensure the title changes
|
||||
const nextButton = await findByText('Next');
|
||||
const nextButton = await findByRole('button', { name: 'Next' });
|
||||
expect(nextButton).toBeDefined();
|
||||
await act(async () => {
|
||||
await fireEvent.click(nextButton);
|
||||
|
@ -169,7 +169,7 @@ describe('<ManageQNA />', () => {
|
|||
|
||||
// ensure that since a subscription hasn't been selected
|
||||
// this button is disabled
|
||||
const nextButton2 = await findByText('Next');
|
||||
const nextButton2 = await findByRole('button', { name: 'Next' });
|
||||
expect(nextButton2).toBeDefined();
|
||||
expect(nextButton2).toBeDisabled();
|
||||
|
||||
|
@ -221,7 +221,7 @@ describe('<ManageQNA />', () => {
|
|||
const onNext = jest.fn();
|
||||
const onToggleVisibility = jest.fn();
|
||||
|
||||
const { baseElement, findByText, findByTestId } = renderWithRecoil(
|
||||
const { baseElement, findByText, findByTestId, findByRole } = renderWithRecoil(
|
||||
<ManageQNA
|
||||
hidden={false}
|
||||
onDismiss={onDismiss}
|
||||
|
@ -237,7 +237,7 @@ describe('<ManageQNA />', () => {
|
|||
fireEvent.click(createOption);
|
||||
|
||||
// click the next button, ensure the title changes
|
||||
const nextButton = await findByText('Next');
|
||||
const nextButton = await findByRole('button', { name: 'Next' });
|
||||
expect(nextButton).toBeDefined();
|
||||
await act(async () => {
|
||||
await fireEvent.click(nextButton);
|
||||
|
@ -246,7 +246,7 @@ describe('<ManageQNA />', () => {
|
|||
|
||||
// ensure that since a subscription hasn't been selected
|
||||
// this button is disabled
|
||||
const nextButton2 = await findByText('Next');
|
||||
const nextButton2 = await findByRole('button', { name: 'Next' });
|
||||
expect(nextButton2).toBeDefined();
|
||||
expect(nextButton2).toBeDisabled();
|
||||
|
||||
|
@ -277,7 +277,7 @@ describe('<ManageQNA />', () => {
|
|||
await fireEvent.click(nextButton2);
|
||||
});
|
||||
|
||||
const nextButton3 = await findByText('Next');
|
||||
const nextButton3 = await findByRole('button', { name: 'Next' });
|
||||
expect(nextButton3).toBeDefined();
|
||||
expect(nextButton3).toBeDisabled();
|
||||
|
||||
|
@ -358,7 +358,7 @@ describe('<ManageQNA />', () => {
|
|||
const onNext = jest.fn();
|
||||
const onToggleVisibility = jest.fn();
|
||||
|
||||
const { baseElement, findByText } = renderWithRecoil(
|
||||
const { baseElement, findByText, findByRole } = renderWithRecoil(
|
||||
<ManageQNA
|
||||
hidden={false}
|
||||
onDismiss={onDismiss}
|
||||
|
@ -374,7 +374,7 @@ describe('<ManageQNA />', () => {
|
|||
fireEvent.click(generateOption);
|
||||
|
||||
// click the next button, ensure the title changes
|
||||
const nextButton = await findByText('Next');
|
||||
const nextButton = await findByRole('button', { name: 'Next' });
|
||||
expect(nextButton).toBeDefined();
|
||||
await act(async () => {
|
||||
await fireEvent.click(nextButton);
|
||||
|
|
|
@ -17,7 +17,7 @@ const handoffInstructions = 'handoffInstructions';
|
|||
const DOWN_ARROW = { keyCode: 40 };
|
||||
|
||||
jest.mock('@azure/arm-cognitiveservices', () => ({
|
||||
CognitiveServicesManagementClient: () => {
|
||||
CognitiveServicesManagementClient: function CognitiveServicesManagementClient() {
|
||||
return {
|
||||
accounts: {
|
||||
list: async () => {
|
||||
|
@ -129,7 +129,7 @@ describe('<ManageService />', () => {
|
|||
const onNext = jest.fn();
|
||||
const onToggleVisibility = jest.fn();
|
||||
|
||||
const { baseElement, findByText, findByTestId } = renderWithRecoil(
|
||||
const { baseElement, findByText, findByTestId, findByRole } = renderWithRecoil(
|
||||
<ManageService
|
||||
createService={createService}
|
||||
handoffInstructions={handoffInstructions}
|
||||
|
@ -148,10 +148,10 @@ describe('<ManageService />', () => {
|
|||
|
||||
// test the default option (choose existing)
|
||||
// click the next button, ensure the title changes
|
||||
const nextButton = await findByText('Next');
|
||||
const nextButton = await findByRole('button', { name: 'Next' });
|
||||
expect(nextButton).toBeDefined();
|
||||
await act(async () => {
|
||||
await fireEvent.click(nextButton);
|
||||
act(() => {
|
||||
fireEvent.click(nextButton);
|
||||
});
|
||||
|
||||
const tenantOption = await findByTestId('service-useexisting-tenant-selection');
|
||||
|
@ -169,45 +169,48 @@ describe('<ManageService />', () => {
|
|||
|
||||
// ensure that since a subscription hasn't been selected
|
||||
// this button is disabled
|
||||
const nextButton2 = await findByText('Next');
|
||||
const nextButton2 = await findByRole('button', { name: 'Next' });
|
||||
expect(nextButton2).toBeDefined();
|
||||
expect(nextButton2).toBeDisabled();
|
||||
|
||||
// select a subscription
|
||||
await act(async () => {
|
||||
await fireEvent.keyDown(subscriptionOption, DOWN_ARROW);
|
||||
act(() => {
|
||||
fireEvent.keyDown(subscriptionOption, DOWN_ARROW);
|
||||
});
|
||||
|
||||
const mySub = await findByText('mockSubscription');
|
||||
expect(mySub).toBeDefined();
|
||||
|
||||
await act(async () => {
|
||||
await fireEvent.click(mySub);
|
||||
act(() => {
|
||||
fireEvent.click(mySub);
|
||||
});
|
||||
|
||||
// select a resource group
|
||||
const resourceOption = await findByTestId('service-useexisting-key-selection');
|
||||
expect(resourceOption).toBeDefined();
|
||||
expect(resourceOption).toBeEnabled();
|
||||
await act(async () => {
|
||||
await fireEvent.keyDown(resourceOption, DOWN_ARROW);
|
||||
act(() => {
|
||||
fireEvent.keyDown(resourceOption, DOWN_ARROW);
|
||||
});
|
||||
|
||||
// select the key
|
||||
const myKey = await findByText('mockedAccount');
|
||||
expect(myKey).toBeDefined();
|
||||
await act(async () => {
|
||||
await fireEvent.click(myKey);
|
||||
act(() => {
|
||||
fireEvent.click(myKey);
|
||||
});
|
||||
|
||||
// make sure the next button is appropriately enabled
|
||||
expect(nextButton2).toBeEnabled();
|
||||
|
||||
// click next
|
||||
await act(async () => {
|
||||
await fireEvent.click(nextButton2);
|
||||
act(() => {
|
||||
fireEvent.click(nextButton2);
|
||||
});
|
||||
|
||||
// let promises flush
|
||||
await Promise.resolve();
|
||||
|
||||
// ensure that the final callback was called
|
||||
expect(onGetKey).toBeCalledWith({
|
||||
region: 'westus',
|
||||
|
@ -222,7 +225,7 @@ describe('<ManageService />', () => {
|
|||
const onNext = jest.fn();
|
||||
const onToggleVisibility = jest.fn();
|
||||
|
||||
const { baseElement, findByText, findByTestId } = renderWithRecoil(
|
||||
const { baseElement, findByText, findByTestId, findByRole } = renderWithRecoil(
|
||||
<ManageService
|
||||
createService={createService}
|
||||
handoffInstructions={handoffInstructions}
|
||||
|
@ -245,16 +248,16 @@ describe('<ManageService />', () => {
|
|||
fireEvent.click(createOption);
|
||||
|
||||
// click the next button, ensure the title changes
|
||||
const nextButton = await findByText('Next');
|
||||
const nextButton = await findByRole('button', { name: 'Next' });
|
||||
expect(nextButton).toBeDefined();
|
||||
await act(async () => {
|
||||
await fireEvent.click(nextButton);
|
||||
act(() => {
|
||||
fireEvent.click(nextButton);
|
||||
});
|
||||
expect(baseElement).toHaveTextContent(`Create ${serviceName} resources`);
|
||||
|
||||
// ensure that since a subscription hasn't been selected
|
||||
// this button is disabled
|
||||
const nextButton2 = await findByText('Next');
|
||||
const nextButton2 = await findByRole('button', { name: 'Next' });
|
||||
expect(nextButton2).toBeDefined();
|
||||
expect(nextButton2).toBeDisabled();
|
||||
|
||||
|
@ -267,25 +270,25 @@ describe('<ManageService />', () => {
|
|||
expect(subscriptionOption).toBeEnabled();
|
||||
|
||||
// choose subscription
|
||||
await act(async () => {
|
||||
await fireEvent.keyDown(subscriptionOption, DOWN_ARROW);
|
||||
act(() => {
|
||||
fireEvent.keyDown(subscriptionOption, DOWN_ARROW);
|
||||
});
|
||||
|
||||
const mySub = await findByText('mockSubscription');
|
||||
expect(mySub).toBeDefined();
|
||||
|
||||
await act(async () => {
|
||||
await fireEvent.click(mySub);
|
||||
act(() => {
|
||||
fireEvent.click(mySub);
|
||||
});
|
||||
|
||||
// next button should now be enabled
|
||||
expect(nextButton2).toBeEnabled();
|
||||
|
||||
await act(async () => {
|
||||
await fireEvent.click(nextButton2);
|
||||
act(() => {
|
||||
fireEvent.click(nextButton2);
|
||||
});
|
||||
|
||||
const nextButton3 = await findByText('Next');
|
||||
const nextButton3 = await findByRole('button', { name: 'Next' });
|
||||
expect(nextButton3).toBeDefined();
|
||||
expect(nextButton3).toBeDisabled();
|
||||
|
||||
|
@ -298,16 +301,16 @@ describe('<ManageService />', () => {
|
|||
expect(resourceName).toBeEnabled();
|
||||
|
||||
// choose subscription
|
||||
await act(async () => {
|
||||
await fireEvent.click(resourceOption);
|
||||
act(() => {
|
||||
fireEvent.click(resourceOption);
|
||||
});
|
||||
|
||||
const myGroup = await findByText('mockedGroup');
|
||||
expect(myGroup).toBeDefined();
|
||||
|
||||
await act(async () => {
|
||||
await fireEvent.click(myGroup);
|
||||
await fireEvent.change(resourceName, { target: { value: 'mockedResource' } });
|
||||
act(() => {
|
||||
fireEvent.click(myGroup);
|
||||
fireEvent.change(resourceName, { target: { value: 'mockedResource' } });
|
||||
});
|
||||
|
||||
// select region
|
||||
|
@ -315,22 +318,25 @@ describe('<ManageService />', () => {
|
|||
expect(regionOption).toBeDefined();
|
||||
expect(regionOption).toBeEnabled();
|
||||
// choose subscription
|
||||
await act(async () => {
|
||||
await fireEvent.keyDown(regionOption, DOWN_ARROW);
|
||||
act(() => {
|
||||
fireEvent.keyDown(regionOption, DOWN_ARROW);
|
||||
});
|
||||
|
||||
const myRegion = await findByText('West US');
|
||||
expect(myRegion).toBeDefined();
|
||||
|
||||
await act(async () => {
|
||||
await fireEvent.click(myRegion);
|
||||
act(() => {
|
||||
fireEvent.click(myRegion);
|
||||
});
|
||||
|
||||
expect(nextButton3).toBeEnabled();
|
||||
await act(async () => {
|
||||
await fireEvent.click(nextButton3);
|
||||
act(() => {
|
||||
fireEvent.click(nextButton3);
|
||||
});
|
||||
|
||||
// let promises flush
|
||||
await Promise.resolve();
|
||||
|
||||
expect(createService).toBeCalledWith(
|
||||
expect.anything(),
|
||||
'mockSubscription',
|
||||
|
@ -354,7 +360,7 @@ describe('<ManageService />', () => {
|
|||
const onNext = jest.fn();
|
||||
const onToggleVisibility = jest.fn();
|
||||
|
||||
const { baseElement, findByText, findByTestId } = renderWithRecoil(
|
||||
const { baseElement, findByText, findByTestId, findByRole } = renderWithRecoil(
|
||||
<ManageService
|
||||
createService={createService}
|
||||
handoffInstructions={handoffInstructions}
|
||||
|
@ -378,16 +384,16 @@ describe('<ManageService />', () => {
|
|||
fireEvent.click(createOption);
|
||||
|
||||
// click the next button, ensure the title changes
|
||||
const nextButton = await findByText('Next');
|
||||
const nextButton = await findByRole('button', { name: 'Next' });
|
||||
expect(nextButton).toBeDefined();
|
||||
await act(async () => {
|
||||
await fireEvent.click(nextButton);
|
||||
act(() => {
|
||||
fireEvent.click(nextButton);
|
||||
});
|
||||
expect(baseElement).toHaveTextContent(`Create ${serviceName} resources`);
|
||||
|
||||
// ensure that since a subscription hasn't been selected
|
||||
// this button is disabled
|
||||
const nextButton2 = await findByText('Next');
|
||||
const nextButton2 = await findByRole('button', { name: 'Next' });
|
||||
expect(nextButton2).toBeDefined();
|
||||
expect(nextButton2).toBeDisabled();
|
||||
|
||||
|
@ -400,25 +406,25 @@ describe('<ManageService />', () => {
|
|||
expect(subscriptionOption).toBeEnabled();
|
||||
|
||||
// choose subscription
|
||||
await act(async () => {
|
||||
await fireEvent.keyDown(subscriptionOption, DOWN_ARROW);
|
||||
act(() => {
|
||||
fireEvent.keyDown(subscriptionOption, DOWN_ARROW);
|
||||
});
|
||||
|
||||
const mySub = await findByText('mockSubscription');
|
||||
expect(mySub).toBeDefined();
|
||||
|
||||
await act(async () => {
|
||||
await fireEvent.click(mySub);
|
||||
act(() => {
|
||||
fireEvent.click(mySub);
|
||||
});
|
||||
|
||||
// next button should now be enabled
|
||||
expect(nextButton2).toBeEnabled();
|
||||
|
||||
await act(async () => {
|
||||
await fireEvent.click(nextButton2);
|
||||
act(() => {
|
||||
fireEvent.click(nextButton2);
|
||||
});
|
||||
|
||||
const nextButton3 = await findByText('Next');
|
||||
const nextButton3 = await findByRole('button', { name: 'Next' });
|
||||
expect(nextButton3).toBeDefined();
|
||||
expect(nextButton3).toBeDisabled();
|
||||
|
||||
|
@ -431,16 +437,16 @@ describe('<ManageService />', () => {
|
|||
expect(resourceName).toBeEnabled();
|
||||
|
||||
// choose subscription
|
||||
await act(async () => {
|
||||
await fireEvent.click(resourceOption);
|
||||
act(() => {
|
||||
fireEvent.click(resourceOption);
|
||||
});
|
||||
|
||||
const myGroup = await findByText('mockedGroup');
|
||||
expect(myGroup).toBeDefined();
|
||||
|
||||
await act(async () => {
|
||||
await fireEvent.click(myGroup);
|
||||
await fireEvent.change(resourceName, { target: { value: 'mockedResource' } });
|
||||
act(() => {
|
||||
fireEvent.click(myGroup);
|
||||
fireEvent.change(resourceName, { target: { value: 'mockedResource' } });
|
||||
});
|
||||
|
||||
// select region
|
||||
|
@ -448,15 +454,15 @@ describe('<ManageService />', () => {
|
|||
expect(regionOption).toBeDefined();
|
||||
expect(regionOption).toBeEnabled();
|
||||
// choose subscription
|
||||
await act(async () => {
|
||||
await fireEvent.keyDown(regionOption, DOWN_ARROW);
|
||||
act(() => {
|
||||
fireEvent.keyDown(regionOption, DOWN_ARROW);
|
||||
});
|
||||
|
||||
const myRegion = await findByText('West US');
|
||||
expect(myRegion).toBeDefined();
|
||||
|
||||
await act(async () => {
|
||||
await fireEvent.click(myRegion);
|
||||
act(() => {
|
||||
fireEvent.click(myRegion);
|
||||
});
|
||||
|
||||
// NEXT BUTTON SHOULD STILL BE DISABLED! need to do tier selection!
|
||||
|
@ -466,24 +472,27 @@ describe('<ManageService />', () => {
|
|||
expect(tierOption).toBeDefined();
|
||||
expect(tierOption).toBeEnabled();
|
||||
// choose subscription
|
||||
await act(async () => {
|
||||
await fireEvent.keyDown(tierOption, DOWN_ARROW);
|
||||
act(() => {
|
||||
fireEvent.keyDown(tierOption, DOWN_ARROW);
|
||||
});
|
||||
|
||||
const myTier = await findByText('mockedTier');
|
||||
expect(myTier).toBeDefined();
|
||||
|
||||
await act(async () => {
|
||||
await fireEvent.click(myTier);
|
||||
act(() => {
|
||||
fireEvent.click(myTier);
|
||||
});
|
||||
|
||||
// finally the button should now be enabled
|
||||
expect(nextButton3).toBeEnabled();
|
||||
|
||||
await act(async () => {
|
||||
await fireEvent.click(nextButton3);
|
||||
act(() => {
|
||||
fireEvent.click(nextButton3);
|
||||
});
|
||||
|
||||
// let promises flush
|
||||
await Promise.resolve();
|
||||
|
||||
expect(createService).toBeCalledWith(
|
||||
expect.anything(),
|
||||
'mockSubscription',
|
||||
|
@ -507,7 +516,7 @@ describe('<ManageService />', () => {
|
|||
const onNext = jest.fn();
|
||||
const onToggleVisibility = jest.fn();
|
||||
|
||||
const { baseElement, findByText, findByTestId } = renderWithRecoil(
|
||||
const { baseElement, findByText, findByTestId, findByRole } = renderWithRecoil(
|
||||
<ManageService
|
||||
createService={createService}
|
||||
handoffInstructions={handoffInstructions}
|
||||
|
@ -530,16 +539,16 @@ describe('<ManageService />', () => {
|
|||
fireEvent.click(createOption);
|
||||
|
||||
// click the next button, ensure the title changes
|
||||
const nextButton = await findByText('Next');
|
||||
const nextButton = await findByRole('button', { name: 'Next' });
|
||||
expect(nextButton).toBeDefined();
|
||||
await act(async () => {
|
||||
await fireEvent.click(nextButton);
|
||||
act(() => {
|
||||
fireEvent.click(nextButton);
|
||||
});
|
||||
expect(baseElement).toHaveTextContent(`Create ${serviceName} resources`);
|
||||
|
||||
// ensure that since a subscription hasn't been selected
|
||||
// this button is disabled
|
||||
const nextButton2 = await findByText('Next');
|
||||
const nextButton2 = await findByRole('button', { name: 'Next' });
|
||||
expect(nextButton2).toBeDefined();
|
||||
expect(nextButton2).toBeDisabled();
|
||||
|
||||
|
@ -552,25 +561,25 @@ describe('<ManageService />', () => {
|
|||
expect(subscriptionOption).toBeEnabled();
|
||||
|
||||
// choose subscription
|
||||
await act(async () => {
|
||||
await fireEvent.keyDown(subscriptionOption, DOWN_ARROW);
|
||||
act(() => {
|
||||
fireEvent.keyDown(subscriptionOption, DOWN_ARROW);
|
||||
});
|
||||
|
||||
const mySub = await findByText('mockSubscription');
|
||||
expect(mySub).toBeDefined();
|
||||
|
||||
await act(async () => {
|
||||
await fireEvent.click(mySub);
|
||||
act(() => {
|
||||
fireEvent.click(mySub);
|
||||
});
|
||||
|
||||
// next button should now be enabled
|
||||
expect(nextButton2).toBeEnabled();
|
||||
|
||||
await act(async () => {
|
||||
await fireEvent.click(nextButton2);
|
||||
act(() => {
|
||||
fireEvent.click(nextButton2);
|
||||
});
|
||||
|
||||
const nextButton3 = await findByText('Next');
|
||||
const nextButton3 = await findByRole('button', { name: 'Next' });
|
||||
expect(nextButton3).toBeDefined();
|
||||
expect(nextButton3).toBeDisabled();
|
||||
|
||||
|
@ -583,16 +592,16 @@ describe('<ManageService />', () => {
|
|||
expect(resourceName).toBeEnabled();
|
||||
|
||||
// choose subscription
|
||||
await act(async () => {
|
||||
await fireEvent.click(resourceOption);
|
||||
act(() => {
|
||||
fireEvent.click(resourceOption);
|
||||
});
|
||||
|
||||
const myGroup = await findByText('mockedGroup');
|
||||
expect(myGroup).toBeDefined();
|
||||
|
||||
await act(async () => {
|
||||
await fireEvent.click(myGroup);
|
||||
await fireEvent.change(resourceName, { target: { value: 'mockedResource' } });
|
||||
act(() => {
|
||||
fireEvent.click(myGroup);
|
||||
fireEvent.change(resourceName, { target: { value: 'mockedResource' } });
|
||||
});
|
||||
|
||||
// select region
|
||||
|
@ -600,15 +609,15 @@ describe('<ManageService />', () => {
|
|||
expect(regionOption).toBeDefined();
|
||||
expect(regionOption).toBeEnabled();
|
||||
// choose subscription
|
||||
await act(async () => {
|
||||
await fireEvent.keyDown(regionOption, DOWN_ARROW);
|
||||
act(() => {
|
||||
fireEvent.keyDown(regionOption, DOWN_ARROW);
|
||||
});
|
||||
|
||||
const myRegion = await findByText('West US');
|
||||
expect(myRegion).toBeDefined();
|
||||
|
||||
await act(async () => {
|
||||
await fireEvent.click(myRegion);
|
||||
act(() => {
|
||||
fireEvent.click(myRegion);
|
||||
});
|
||||
|
||||
// NEXT BUTTON SHOULD STILL BE DISABLED! need to do tier selection!
|
||||
|
@ -618,24 +627,27 @@ describe('<ManageService />', () => {
|
|||
expect(tierOption).toBeDefined();
|
||||
expect(tierOption).toBeEnabled();
|
||||
// choose subscription
|
||||
await act(async () => {
|
||||
await fireEvent.keyDown(tierOption, DOWN_ARROW);
|
||||
act(() => {
|
||||
fireEvent.keyDown(tierOption, DOWN_ARROW);
|
||||
});
|
||||
|
||||
const myTier = await findByText('mockedTier');
|
||||
expect(myTier).toBeDefined();
|
||||
|
||||
await act(async () => {
|
||||
await fireEvent.click(myTier);
|
||||
act(() => {
|
||||
fireEvent.click(myTier);
|
||||
});
|
||||
|
||||
// finally the button should now be enabled
|
||||
expect(nextButton3).toBeEnabled();
|
||||
|
||||
await act(async () => {
|
||||
await fireEvent.click(nextButton3);
|
||||
act(() => {
|
||||
fireEvent.click(nextButton3);
|
||||
});
|
||||
|
||||
// let promises flush
|
||||
await Promise.resolve();
|
||||
|
||||
expect(createService).toBeCalledWith(
|
||||
expect.anything(),
|
||||
'mockSubscription',
|
||||
|
@ -659,7 +671,7 @@ describe('<ManageService />', () => {
|
|||
const onNext = jest.fn();
|
||||
const onToggleVisibility = jest.fn();
|
||||
|
||||
const { baseElement, findByText } = renderWithRecoil(
|
||||
const { baseElement, findByText, findByRole } = renderWithRecoil(
|
||||
<ManageService
|
||||
createService={createService}
|
||||
handoffInstructions={handoffInstructions}
|
||||
|
@ -682,10 +694,10 @@ describe('<ManageService />', () => {
|
|||
fireEvent.click(generateOption);
|
||||
|
||||
// click the next button, ensure the title changes
|
||||
const nextButton = await findByText('Next');
|
||||
const nextButton = await findByRole('button', { name: 'Next' });
|
||||
expect(nextButton).toBeDefined();
|
||||
await act(async () => {
|
||||
await fireEvent.click(nextButton);
|
||||
act(() => {
|
||||
fireEvent.click(nextButton);
|
||||
});
|
||||
|
||||
expect(baseElement).toHaveTextContent(
|
||||
|
|
|
@ -11,7 +11,7 @@ const serviceName = 'Speech';
|
|||
const DOWN_ARROW = { keyCode: 40 };
|
||||
|
||||
jest.mock('@azure/arm-cognitiveservices', () => ({
|
||||
CognitiveServicesManagementClient: () => {
|
||||
CognitiveServicesManagementClient: function CognitiveServicesManagementClient() {
|
||||
return {
|
||||
accounts: {
|
||||
create: async () => {},
|
||||
|
@ -103,7 +103,7 @@ describe('<ManageSpeech />', () => {
|
|||
const onNext = jest.fn();
|
||||
const onToggleVisibility = jest.fn();
|
||||
|
||||
const { baseElement, findByText, findByTestId } = renderWithRecoil(
|
||||
const { baseElement, findByText, findByTestId, findByRole } = renderWithRecoil(
|
||||
<ManageSpeech
|
||||
hidden={false}
|
||||
onDismiss={onDismiss}
|
||||
|
@ -115,10 +115,10 @@ describe('<ManageSpeech />', () => {
|
|||
|
||||
// test the default option (choose existing)
|
||||
// click the next button, ensure the title changes
|
||||
const nextButton = await findByText('Next');
|
||||
const nextButton = await findByRole('button', { name: 'Next' });
|
||||
expect(nextButton).toBeDefined();
|
||||
await act(async () => {
|
||||
await fireEvent.click(nextButton);
|
||||
act(() => {
|
||||
fireEvent.click(nextButton);
|
||||
});
|
||||
|
||||
const tenantOption = await findByTestId('service-useexisting-tenant-selection');
|
||||
|
@ -136,7 +136,7 @@ describe('<ManageSpeech />', () => {
|
|||
|
||||
// ensure that since a subscription hasn't been selected
|
||||
// this button is disabled
|
||||
const nextButton2 = await findByText('Next');
|
||||
const nextButton2 = await findByRole('button', { name: 'Next' });
|
||||
expect(nextButton2).toBeDefined();
|
||||
expect(nextButton2).toBeDisabled();
|
||||
|
||||
|
@ -188,7 +188,7 @@ describe('<ManageSpeech />', () => {
|
|||
const onNext = jest.fn();
|
||||
const onToggleVisibility = jest.fn();
|
||||
|
||||
const { baseElement, findByText, findByTestId } = renderWithRecoil(
|
||||
const { baseElement, findByText, findByTestId, findByRole } = renderWithRecoil(
|
||||
<ManageSpeech
|
||||
hidden={false}
|
||||
onDismiss={onDismiss}
|
||||
|
@ -204,7 +204,7 @@ describe('<ManageSpeech />', () => {
|
|||
fireEvent.click(createOption);
|
||||
|
||||
// click the next button, ensure the title changes
|
||||
const nextButton = await findByText('Next');
|
||||
const nextButton = await findByRole('button', { name: 'Next' });
|
||||
expect(nextButton).toBeDefined();
|
||||
await act(async () => {
|
||||
await fireEvent.click(nextButton);
|
||||
|
@ -213,7 +213,7 @@ describe('<ManageSpeech />', () => {
|
|||
|
||||
// ensure that since a subscription hasn't been selected
|
||||
// this button is disabled
|
||||
const nextButton2 = await findByText('Next');
|
||||
const nextButton2 = await findByRole('button', { name: 'Next' });
|
||||
expect(nextButton2).toBeDefined();
|
||||
expect(nextButton2).toBeDisabled();
|
||||
|
||||
|
@ -244,7 +244,7 @@ describe('<ManageSpeech />', () => {
|
|||
await fireEvent.click(nextButton2);
|
||||
});
|
||||
|
||||
const nextButton3 = await findByText('Next');
|
||||
const nextButton3 = await findByRole('button', { name: 'Next' });
|
||||
expect(nextButton3).toBeDefined();
|
||||
expect(nextButton3).toBeDisabled();
|
||||
|
||||
|
@ -304,7 +304,7 @@ describe('<ManageSpeech />', () => {
|
|||
const onNext = jest.fn();
|
||||
const onToggleVisibility = jest.fn();
|
||||
|
||||
const { baseElement, findByText } = renderWithRecoil(
|
||||
const { baseElement, findByText, findByRole } = renderWithRecoil(
|
||||
<ManageSpeech
|
||||
hidden={false}
|
||||
onDismiss={onDismiss}
|
||||
|
@ -320,7 +320,7 @@ describe('<ManageSpeech />', () => {
|
|||
fireEvent.click(generateOption);
|
||||
|
||||
// click the next button, ensure the title changes
|
||||
const nextButton = await findByText('Next');
|
||||
const nextButton = await findByRole('button', { name: 'Next' });
|
||||
expect(nextButton).toBeDefined();
|
||||
await act(async () => {
|
||||
await fireEvent.click(nextButton);
|
||||
|
|
|
@ -12,8 +12,6 @@ import CreateSkillModal, {
|
|||
} from '../../src/components/AddRemoteSkillModal/CreateSkillModal';
|
||||
import { botProjectFileState, currentProjectIdState, settingsState } from '../../src/recoilModel';
|
||||
|
||||
jest.mock('../../src//utils/httpUtil');
|
||||
|
||||
jest.mock('../../src/components/Modal/dialogStyle', () => ({}));
|
||||
|
||||
const projectId = '123a.234';
|
||||
|
@ -105,6 +103,8 @@ describe('<SkillForm />', () => {
|
|||
value: 'https://onenote-dev.azurewebsites.net/manifests/OneNoteSync-2-1-preview-1-manifest.json',
|
||||
},
|
||||
});
|
||||
// allow validatation debounce to execute
|
||||
jest.runAllTimers();
|
||||
});
|
||||
|
||||
expect(urlInput.getAttribute('value')).toBe(
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
import * as React from 'react';
|
||||
import React from 'react';
|
||||
import { render } from '@botframework-composer/test-utils';
|
||||
|
||||
import { BASEPATH } from '../src/constants';
|
|
@ -2,8 +2,7 @@
|
|||
// Licensed under the MIT License.
|
||||
|
||||
import React from 'react';
|
||||
import { act, fireEvent, within } from '@botframework-composer/test-utils';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { act, fireEvent, within, userEvent } from '@botframework-composer/test-utils';
|
||||
|
||||
import ExternalAdapterSettings from '../../../src/pages/botProject/adapters/ExternalAdapterSettings';
|
||||
import { renderWithRecoilAndCustomDispatchers } from '../../testUtils';
|
||||
|
@ -140,7 +139,7 @@ describe('ExternalAdapterSettings', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('does not proceed if required settings are missing', async () => {
|
||||
it('does not proceed if required settings are missing', () => {
|
||||
const { getByTestId } = renderWithRecoilAndCustomDispatchers(
|
||||
<ExternalAdapterSettings projectId={PROJECT_ID} />,
|
||||
initRecoilState
|
||||
|
@ -152,10 +151,10 @@ describe('ExternalAdapterSettings', () => {
|
|||
});
|
||||
|
||||
const modal = getByTestId('adapterModal');
|
||||
expect(within(modal).getByText('Configure')).toBeDisabled();
|
||||
expect(within(modal).getByRole('button', { name: 'Configure' })).toBeDisabled();
|
||||
});
|
||||
|
||||
it('disables an adapter', async () => {
|
||||
it('disables an adapter', () => {
|
||||
const initStateWithAdapter = {
|
||||
runtimeSettings: {
|
||||
adapters: [{ name: 'Adapter.Mock', enabled: true, route: 'mock', type: 'Adapter.Full.Type.Mock' }],
|
||||
|
@ -176,7 +175,7 @@ describe('ExternalAdapterSettings', () => {
|
|||
const toggle = queryByTestId('toggle_Adapter.Mock');
|
||||
expect(toggle).not.toBeNull();
|
||||
|
||||
await act(async () => {
|
||||
act(() => {
|
||||
fireEvent.click(toggle!);
|
||||
});
|
||||
|
||||
|
@ -190,7 +189,7 @@ describe('ExternalAdapterSettings', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('enables an adapter', async () => {
|
||||
it('enables an adapter', () => {
|
||||
const initStateWithAdapter = {
|
||||
runtimeSettings: {
|
||||
adapters: [{ name: 'Adapter.Mock', enabled: false, route: 'mock', type: 'Adapter.Full.Type.Mock' }],
|
||||
|
@ -210,7 +209,7 @@ describe('ExternalAdapterSettings', () => {
|
|||
const toggle = queryByTestId('toggle_Adapter.Mock');
|
||||
expect(toggle).not.toBeNull();
|
||||
|
||||
await act(async () => {
|
||||
act(() => {
|
||||
fireEvent.click(toggle!);
|
||||
});
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
import { renderHook, HookResult } from '@botframework-composer/test-utils/lib/hooks';
|
||||
import { renderHook, RenderResult } from '@botframework-composer/test-utils/lib/hooks';
|
||||
import * as React from 'react';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
|
||||
|
@ -31,7 +31,7 @@ const state = {
|
|||
|
||||
describe('use lgApi hooks', () => {
|
||||
let removeLgTemplatesMock, initRecoilState, copyLgTemplateMock, updateLgTemplateMock;
|
||||
let result: HookResult<any>;
|
||||
let result: RenderResult<any>;
|
||||
|
||||
beforeEach(() => {
|
||||
updateLgTemplateMock = jest.fn();
|
||||
|
|
|
@ -4,9 +4,7 @@
|
|||
import { getExtension, getBaseName, upperCaseName, loadLocale, getUniqueName } from '../../src/utils/fileUtil';
|
||||
import httpClient from '../../src/utils/httpUtil';
|
||||
|
||||
jest.mock('../../src/utils/httpUtil');
|
||||
|
||||
const files = ['a.text', 'a.b.text', 1];
|
||||
const files = ['a.text', 'a.b.text', 1] as string[];
|
||||
|
||||
describe('getExtension', () => {
|
||||
it('returns extension', () => {
|
||||
|
@ -48,19 +46,19 @@ describe('loadLocale', () => {
|
|||
|
||||
const LOCALE = 'en-test';
|
||||
it("does not set locale if it can't find one", async () => {
|
||||
jest.spyOn(httpClient, 'get').mockImplementation(() => ({ data: null }));
|
||||
(httpClient.get as jest.Mock).mockImplementation(() => ({ data: null }));
|
||||
|
||||
expect(await loadLocale(LOCALE)).toBeNull();
|
||||
});
|
||||
it('does not set locale if the server returns an error page', async () => {
|
||||
jest.spyOn(httpClient, 'get').mockImplementation(() => ({ data: 'error page' }));
|
||||
(httpClient.get as jest.Mock).mockImplementation(() => ({ data: 'error page' }));
|
||||
|
||||
expect(await loadLocale(LOCALE)).toBeNull();
|
||||
});
|
||||
it('sets locale if it does find one', async () => {
|
||||
const RESPONSE = { data: { abc: 'def' } };
|
||||
|
||||
jest.spyOn(httpClient, 'get').mockImplementation(() => RESPONSE);
|
||||
(httpClient.get as jest.Mock).mockImplementation(() => RESPONSE);
|
||||
|
||||
expect(await loadLocale(LOCALE)).toMatchObject({
|
||||
locale: LOCALE,
|
|
@ -1,5 +1,9 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
/// <reference types="jest" />
|
||||
|
||||
// for tests using Electron IPC to talk to main process
|
||||
(window as any).ipcRenderer = { on: jest.fn() };
|
||||
|
||||
jest.mock('./src/utils/httpUtil');
|
||||
|
|
|
@ -54,7 +54,7 @@ export interface CreateSkillModalProps {
|
|||
onDismiss: () => void;
|
||||
}
|
||||
|
||||
export const validateManifestUrl = async ({ formData, formDataErrors, setFormDataErrors }, skills: string[] = []) => {
|
||||
export const validateManifestUrl = ({ formData, formDataErrors, setFormDataErrors }, skills: string[] = []) => {
|
||||
const { manifestUrl } = formData;
|
||||
const { manifestUrl: _, ...errors } = formDataErrors;
|
||||
|
||||
|
@ -71,6 +71,7 @@ export const validateManifestUrl = async ({ formData, formDataErrors, setFormDat
|
|||
setFormDataErrors({});
|
||||
}
|
||||
};
|
||||
|
||||
export const getSkillManifest = async (projectId: string, manifestUrl: string, setSkillManifest, setFormDataErrors) => {
|
||||
try {
|
||||
const { data } = await httpClient.get(`/projects/${projectId}/skill/retrieveSkillManifest`, {
|
||||
|
|
|
@ -3,8 +3,7 @@
|
|||
/** @jsx jsx */
|
||||
import { jsx } from '@emotion/core';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { Suspense, Fragment } from 'react';
|
||||
import React from 'react';
|
||||
import React, { Suspense, Fragment } from 'react';
|
||||
|
||||
import { onboardingDisabled } from '../../constants';
|
||||
import { useLocation } from '../../utils/hooks';
|
||||
|
|
|
@ -13,12 +13,6 @@ const container = css`
|
|||
position: relative;
|
||||
`;
|
||||
|
||||
const top = css`
|
||||
width: 100%;
|
||||
height: 10px;
|
||||
background-color: #efeaf5;
|
||||
`;
|
||||
|
||||
// -------------------- Conversation -------------------- //
|
||||
|
||||
const Conversation = (props) => {
|
|
@ -4,7 +4,7 @@
|
|||
import { DialogTypes, DialogWrapper } from '@bfc/ui-shared/lib/components/DialogWrapper';
|
||||
import { SDKKinds } from '@botframework-composer/types';
|
||||
import { Button } from 'office-ui-fabric-react/lib/components/Button/Button';
|
||||
import React, { useMemo } from 'react';
|
||||
import React, { useMemo, useEffect } from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
|
||||
import { enableOrchestratorDialog } from '../../constants';
|
||||
|
@ -32,6 +32,12 @@ export const OrchestratorForSkillsDialog = () => {
|
|||
return curRecognizers.some((f) => f.id === fileName && f.content.$kind === SDKKinds.OrchestratorRecognizer);
|
||||
}, [curRecognizers, dialogId, locale]);
|
||||
|
||||
useEffect(() => {
|
||||
if (showOrchestratorDialog && hasOrchestrator) {
|
||||
setShowOrchestratorDialog(false);
|
||||
}
|
||||
}, [hasOrchestrator, showOrchestratorDialog]);
|
||||
|
||||
const handleOrchestratorSubmit = async (event: React.MouseEvent<HTMLElement | Button>, enable?: boolean) => {
|
||||
event.preventDefault();
|
||||
if (enable) {
|
||||
|
@ -41,25 +47,14 @@ export const OrchestratorForSkillsDialog = () => {
|
|||
setShowOrchestratorDialog(false);
|
||||
};
|
||||
|
||||
const setVisibility = () => {
|
||||
if (showOrchestratorDialog) {
|
||||
if (hasOrchestrator) {
|
||||
setShowOrchestratorDialog(false);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const onDismissHandler = (event: React.MouseEvent<HTMLButtonElement> | undefined) => {
|
||||
const onDismissHandler = () => {
|
||||
setShowOrchestratorDialog(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<DialogWrapper
|
||||
dialogType={DialogTypes.CreateFlow}
|
||||
isOpen={setVisibility()}
|
||||
isOpen={showOrchestratorDialog}
|
||||
title={enableOrchestratorDialog.title}
|
||||
onDismiss={onDismissHandler}
|
||||
>
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
import { act, getQueriesForElement, within } from '@botframework-composer/test-utils';
|
||||
import { act, screen, userEvent } from '@botframework-composer/test-utils';
|
||||
import { SDKKinds } from '@botframework-composer/types';
|
||||
import * as React from 'react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
import { renderWithRecoil } from '../../../../__tests__/testUtils/renderWithRecoil';
|
||||
import {
|
||||
|
@ -42,55 +41,55 @@ describe('<OrchestratorForSkillsDialog />', () => {
|
|||
});
|
||||
|
||||
it('should not open OrchestratorForSkillsDialog if orchestratorForSkillsDialogState is false', () => {
|
||||
const { baseElement } = renderWithRecoil(<OrchestratorForSkillsDialog />, ({ set }) => {
|
||||
renderWithRecoil(<OrchestratorForSkillsDialog />, ({ set }) => {
|
||||
makeInitialState(set);
|
||||
set(orchestratorForSkillsDialogState, false);
|
||||
});
|
||||
const dialog = getQueriesForElement(baseElement).queryByTestId(orchestratorTestId);
|
||||
const dialog = screen.queryByTestId(orchestratorTestId);
|
||||
expect(dialog).toBeNull();
|
||||
});
|
||||
|
||||
it('should not open OrchestratorForSkillsDialog if orchestrator already being used in root', () => {
|
||||
const { baseElement } = renderWithRecoil(<OrchestratorForSkillsDialog />, ({ set }) => {
|
||||
renderWithRecoil(<OrchestratorForSkillsDialog />, ({ set }) => {
|
||||
makeInitialState(set);
|
||||
set(recognizersSelectorFamily('rootBotId'), [
|
||||
{ id: 'rootBotRootDialogId.en-us.lu.dialog', content: { $kind: SDKKinds.OrchestratorRecognizer } },
|
||||
]);
|
||||
});
|
||||
const dialog = getQueriesForElement(baseElement).queryByTestId(orchestratorTestId);
|
||||
const dialog = screen.queryByTestId(orchestratorTestId);
|
||||
expect(dialog).toBeNull();
|
||||
});
|
||||
|
||||
it('open OrchestratorForSkillsDialog if orchestratorForSkillsDialogState and Orchestrator not used in Root Bot Root Dialog', () => {
|
||||
const { baseElement } = renderWithRecoil(<OrchestratorForSkillsDialog />, ({ set }) => {
|
||||
renderWithRecoil(<OrchestratorForSkillsDialog />, ({ set }) => {
|
||||
makeInitialState(set);
|
||||
});
|
||||
const dialog = getQueriesForElement(baseElement).queryByTestId(orchestratorTestId);
|
||||
const dialog = screen.queryByTestId(orchestratorTestId);
|
||||
expect(dialog).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should install Orchestrator package when user clicks Continue', async () => {
|
||||
const { baseElement } = renderWithRecoil(<OrchestratorForSkillsDialog />, ({ set }) => {
|
||||
renderWithRecoil(<OrchestratorForSkillsDialog />, ({ set }) => {
|
||||
makeInitialState(set);
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
userEvent.click(within(baseElement).getByTestId('import-orchestrator'));
|
||||
userEvent.click(screen.getByTestId('import-orchestrator'));
|
||||
});
|
||||
|
||||
expect(importOrchestrator).toBeCalledWith('rootBotId', expect.anything(), expect.anything());
|
||||
});
|
||||
|
||||
it('should not install Orchestrator package when user clicks skip', async () => {
|
||||
const { baseElement } = renderWithRecoil(<OrchestratorForSkillsDialog />, ({ set }) => {
|
||||
renderWithRecoil(<OrchestratorForSkillsDialog />, ({ set }) => {
|
||||
makeInitialState(set);
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
userEvent.click(await within(baseElement).findByText('Skip'));
|
||||
userEvent.click(await screen.findByText('Skip'));
|
||||
});
|
||||
|
||||
const dialog = getQueriesForElement(baseElement).queryByTestId(orchestratorTestId);
|
||||
const dialog = screen.queryByTestId(orchestratorTestId);
|
||||
expect(dialog).toBeNull();
|
||||
|
||||
expect(importOrchestrator).toBeCalledTimes(0);
|
||||
|
|
|
@ -78,7 +78,7 @@ export const ImportQnAFromUrlModal: React.FC<ImportQnAFromUrlModalProps> = (prop
|
|||
<div css={dialogWindow}>
|
||||
<Stack>
|
||||
<TextField
|
||||
data-testId={'ImportNewUrlToOverwriteQnAFile'}
|
||||
data-testid="ImportNewUrlToOverwriteQnAFile"
|
||||
errorMessage={formErrors.url}
|
||||
label={formatMessage('FAQ website (source)')}
|
||||
placeholder={formatMessage('Enter a URL to Import QnA resource')}
|
||||
|
|
|
@ -61,8 +61,12 @@ describe('<WebchatPanel />', () => {
|
|||
|
||||
mockstartNewConversation.mockResolvedValue({
|
||||
directline: {
|
||||
activity$: jest.fn(),
|
||||
activity$: {
|
||||
subscribe: jest.fn(),
|
||||
},
|
||||
subscribe: jest.fn(),
|
||||
connectionStatus$: { subscribe: jest.fn() },
|
||||
postActivity: jest.fn(),
|
||||
},
|
||||
chatMode: 'conversation',
|
||||
projectId: '123-12',
|
||||
|
|
|
@ -9,8 +9,6 @@ import { botBuildTimeErrorState, botStatusState } from '../../../recoilModel';
|
|||
import { BotStatus } from '../../../constants';
|
||||
import { BotRuntimeStatus } from '../../BotRuntimeController/BotRuntimeStatus';
|
||||
|
||||
jest.mock('../../../utils/httpUtil');
|
||||
|
||||
const mockStart = jest.fn();
|
||||
const mockStop = jest.fn();
|
||||
const pollingInterval = 1500;
|
||||
|
|
|
@ -8,8 +8,6 @@ import { botBuildTimeErrorState, botStatusState } from '../../../recoilModel';
|
|||
import { BotStatus, BotStatusesCopy } from '../../../constants';
|
||||
import { BotStatusIndicator } from '../../BotRuntimeController/BotStatusIndicator';
|
||||
|
||||
jest.mock('../../../utils/httpUtil');
|
||||
|
||||
const mockStart = jest.fn();
|
||||
const mockStop = jest.fn();
|
||||
|
||||
|
|
|
@ -16,7 +16,6 @@ import {
|
|||
import { OutputsTabContent } from '../DebugPanel/TabExtensions/RuntimeOutputLog/OutputTabContent';
|
||||
const projectId = '123-avw';
|
||||
|
||||
jest.mock('../../../utils/httpUtil');
|
||||
const standardOut = `/Users/tester/Desktop/conversational_core_3/conversational_core_3/conversational_core_3.csproj : warning NU1701: Package 'Microsoft.Azure.KeyVault.Core 1.0.0' was restored using '.NETFramework,Version=v4.6.1, .NETFramework,Version=v4.6.2, .NETFramework,Version=v4.7, .NETFramework,Version=v4.7.1, .NETFramework,Version=v4.7.2, .NETFramework,Version=v4.8' instead of the project target framework '.NETCoreApp,Version=v3.1'. This package may not be fully compatible with your project.
|
||||
/Users/tester/Desktop/conversational_core_3/conversational_core_3/conversational_core_3.csproj : warning NU1701: Package 'Microsoft.Azure.KeyVault.Core 1.0.0' was restored using '.NETFramework,Version=v4.6.1, .NETFramework,Version=v4.6.2, .NETFramework,Version=v4.7, .NETFramework,Version=v4.7.1, .NETFramework,Version=v4.7.2, .NETFramework,Version=v4.8' instead of the project target framework '.NETCoreApp,Version=v3.1'. This package may not be fully compatible with your project.
|
||||
info: Microsoft.Hosting.Lifetime[0]
|
||||
|
|
|
@ -352,7 +352,7 @@ const TableView: React.FC<TableViewProps> = (props) => {
|
|||
return (
|
||||
<IconButton
|
||||
hidden
|
||||
data-testId={'knowledgeBaseMore'}
|
||||
data-testid="knowledgeBaseMore"
|
||||
menuIconProps={{ iconName: 'More' }}
|
||||
menuProps={{ items: overflowItems || [] }}
|
||||
role="menuitem"
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
import { useRef, useState, Fragment, useLayoutEffect, MutableRefObject } from 'react';
|
||||
import React, { useRef, useState, Fragment, useLayoutEffect, MutableRefObject } from 'react';
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
import { useRecoilTransactionObserver_UNSTABLE, Snapshot, useRecoilState } from 'recoil';
|
||||
import once from 'lodash/once';
|
||||
import React from 'react';
|
||||
import { BotAssets } from '@bfc/shared';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// Licensed under the MIT License.
|
||||
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { act, RenderHookResult, HookResult } from '@botframework-composer/test-utils/lib/hooks';
|
||||
import { act, RenderHookResult, RenderResult } from '@botframework-composer/test-utils/lib/hooks';
|
||||
// eslint-disable-next-line lodash/import-scope
|
||||
import debounce from 'lodash/debounce';
|
||||
|
||||
|
@ -23,7 +23,7 @@ import { StateError } from '../../types';
|
|||
jest.mock('lodash/debounce');
|
||||
(debounce as any).mockImplementation((fn) => fn);
|
||||
|
||||
describe('<Editor />', () => {
|
||||
describe('application dispatchers', () => {
|
||||
const useRecoilTestHook = () => {
|
||||
const [appUpdater, setAppUpdater] = useRecoilState(appUpdateState);
|
||||
const dispatcher = useRecoilValue(dispatcherState);
|
||||
|
@ -42,9 +42,7 @@ describe('<Editor />', () => {
|
|||
onboarding,
|
||||
};
|
||||
};
|
||||
let renderedComponent: HookResult<ReturnType<typeof useRecoilTestHook>>, dispatcher: Dispatcher;
|
||||
|
||||
beforeAll(() => {});
|
||||
let renderedComponent: RenderResult<ReturnType<typeof useRecoilTestHook>>, dispatcher: Dispatcher;
|
||||
|
||||
beforeEach(() => {
|
||||
const rendered: RenderHookResult<unknown, ReturnType<typeof useRecoilTestHook>> = renderRecoilHook(
|
|
@ -2,7 +2,7 @@
|
|||
// Licensed under the MIT License.
|
||||
|
||||
import { selector, useRecoilValue, selectorFamily, useRecoilState } from 'recoil';
|
||||
import { act, RenderHookResult, HookResult } from '@botframework-composer/test-utils/lib/hooks';
|
||||
import { act, RenderHookResult, RenderResult } from '@botframework-composer/test-utils/lib/hooks';
|
||||
import noop from 'lodash/noop';
|
||||
import { BotProjectFile, Skill } from '@bfc/shared';
|
||||
|
||||
|
@ -23,7 +23,6 @@ import {
|
|||
} from '../../atoms';
|
||||
import { Dispatcher } from '..';
|
||||
|
||||
jest.mock('../../../utils/httpUtil');
|
||||
const rootBotProjectId = '2345.32324';
|
||||
const testSkillId = '123.1sd23';
|
||||
|
||||
|
@ -83,7 +82,7 @@ describe('Bot Project File dispatcher', () => {
|
|||
};
|
||||
};
|
||||
|
||||
let renderedComponent: HookResult<ReturnType<typeof useRecoilTestHook>>, dispatcher: Dispatcher;
|
||||
let renderedComponent: RenderResult<ReturnType<typeof useRecoilTestHook>>, dispatcher: Dispatcher;
|
||||
beforeEach(() => {
|
||||
const rendered: RenderHookResult<unknown, ReturnType<typeof useRecoilTestHook>> = renderRecoilHook(
|
||||
useRecoilTestHook,
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import test from '@bfc/indexers';
|
||||
import { act, HookResult } from '@botframework-composer/test-utils/lib/hooks';
|
||||
import { act, RenderResult } from '@botframework-composer/test-utils/lib/hooks';
|
||||
|
||||
import { dialogsDispatcher } from '../dialogs';
|
||||
import { renderRecoilHook } from '../../../../__tests__/testUtils';
|
||||
|
@ -117,7 +117,7 @@ describe('dialog dispatcher', () => {
|
|||
};
|
||||
};
|
||||
|
||||
let renderedComponent: HookResult<ReturnType<typeof useRecoilTestHook>>, dispatcher: Dispatcher;
|
||||
let renderedComponent: RenderResult<ReturnType<typeof useRecoilTestHook>>, dispatcher: Dispatcher;
|
||||
|
||||
beforeEach(() => {
|
||||
const { result } = renderRecoilHook(useRecoilTestHook, {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// Licensed under the MIT License.
|
||||
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { act, HookResult } from '@botframework-composer/test-utils/lib/hooks';
|
||||
import { act, RenderResult } from '@botframework-composer/test-utils/lib/hooks';
|
||||
|
||||
import httpClient from '../../../utils/httpUtil';
|
||||
import { exportDispatcher } from '../export';
|
||||
|
@ -10,7 +10,6 @@ import { renderRecoilHook } from '../../../../__tests__/testUtils';
|
|||
import { botDisplayNameState, currentProjectIdState, dispatcherState } from '../../atoms';
|
||||
import { Dispatcher } from '../../../recoilModel/dispatchers';
|
||||
|
||||
jest.mock('../../../utils/httpUtil');
|
||||
const projectId = '2345.32324';
|
||||
|
||||
describe('Export dispatcher', () => {
|
||||
|
@ -23,7 +22,7 @@ describe('Export dispatcher', () => {
|
|||
};
|
||||
};
|
||||
|
||||
let renderedComponent: HookResult<ReturnType<typeof useRecoilTestHook>>,
|
||||
let renderedComponent: RenderResult<ReturnType<typeof useRecoilTestHook>>,
|
||||
dispatcher: Dispatcher,
|
||||
prevDocumentCreateElement,
|
||||
prevCreateObjectURL,
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
import { useRecoilState } from 'recoil';
|
||||
import { LgFile, LgTemplate } from '@bfc/shared';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { act, HookResult } from '@botframework-composer/test-utils/lib/hooks';
|
||||
import { act, RenderResult } from '@botframework-composer/test-utils/lib/hooks';
|
||||
|
||||
import { lgDispatcher } from '../lg';
|
||||
import { renderRecoilHook } from '../../../../__tests__/testUtils';
|
||||
|
@ -70,7 +70,7 @@ describe('Lg dispatcher', () => {
|
|||
};
|
||||
};
|
||||
|
||||
let renderedComponent: HookResult<ReturnType<typeof useRecoilTestHook>>, dispatcher: Dispatcher;
|
||||
let renderedComponent: RenderResult<ReturnType<typeof useRecoilTestHook>>, dispatcher: Dispatcher;
|
||||
|
||||
beforeEach(() => {
|
||||
const { result } = renderRecoilHook(useRecoilTestHook, {
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
import { useRecoilState } from 'recoil';
|
||||
import { LuIntentSection, LuFile } from '@bfc/shared';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { act, HookResult } from '@botframework-composer/test-utils/lib/hooks';
|
||||
import { act, RenderResult } from '@botframework-composer/test-utils/lib/hooks';
|
||||
import { luUtil } from '@bfc/indexers';
|
||||
|
||||
import { renderRecoilHook } from '../../../../__tests__/testUtils';
|
||||
|
@ -51,7 +51,7 @@ describe('Lu dispatcher', () => {
|
|||
};
|
||||
};
|
||||
|
||||
let renderedComponent: HookResult<ReturnType<typeof useRecoilTestHook>>, dispatcher: Dispatcher;
|
||||
let renderedComponent: RenderResult<ReturnType<typeof useRecoilTestHook>>, dispatcher: Dispatcher;
|
||||
|
||||
beforeEach(() => {
|
||||
const { result } = renderRecoilHook(useRecoilTestHook, {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// Licensed under the MIT License.
|
||||
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { act, HookResult } from '@botframework-composer/test-utils/lib/hooks';
|
||||
import { act, RenderResult } from '@botframework-composer/test-utils/lib/hooks';
|
||||
|
||||
import { renderRecoilHook } from '../../../../__tests__/testUtils';
|
||||
import {
|
||||
|
@ -62,7 +62,7 @@ describe('Multilang dispatcher', () => {
|
|||
};
|
||||
};
|
||||
|
||||
let renderedComponent: HookResult<ReturnType<typeof useRecoilTestHook>>, dispatcher: Dispatcher;
|
||||
let renderedComponent: RenderResult<ReturnType<typeof useRecoilTestHook>>, dispatcher: Dispatcher;
|
||||
|
||||
beforeEach(() => {
|
||||
const { result } = renderRecoilHook(useRecoilTestHook, {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// Licensed under the MIT License.
|
||||
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { act, HookResult } from '@botframework-composer/test-utils/lib/hooks';
|
||||
import { act, RenderResult } from '@botframework-composer/test-utils/lib/hooks';
|
||||
import { SDKKinds } from '@bfc/shared';
|
||||
|
||||
import { navigationDispatcher } from '../navigation';
|
||||
|
@ -53,7 +53,7 @@ describe('navigation dispatcher', () => {
|
|||
};
|
||||
};
|
||||
|
||||
let renderedComponent: HookResult<ReturnType<typeof useRecoilTestHook>>, dispatcher: Dispatcher;
|
||||
let renderedComponent: RenderResult<ReturnType<typeof useRecoilTestHook>>, dispatcher: Dispatcher;
|
||||
|
||||
beforeEach(() => {
|
||||
mockCheckUrl.mockClear();
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
import { selector, useRecoilValue } from 'recoil';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { act, RenderHookResult, HookResult } from '@botframework-composer/test-utils/lib/hooks';
|
||||
import { act, RenderHookResult, RenderResult } from '@botframework-composer/test-utils/lib/hooks';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import endsWith from 'lodash/endsWith';
|
||||
|
@ -46,26 +46,20 @@ import {
|
|||
import { dialogsSelectorFamily, lgFilesSelectorFamily, luFilesSelectorFamily } from '../../selectors';
|
||||
import { Dispatcher } from '../../dispatchers';
|
||||
import { BotStatus } from '../../../constants';
|
||||
import { navigateTo } from '../../../utils/navigation';
|
||||
|
||||
import mockProjectData from './mocks/mockProjectResponse.json';
|
||||
import mockManifestData from './mocks/mockManifest.json';
|
||||
import mockBotProjectFileData from './mocks/mockBotProjectFile.json';
|
||||
|
||||
// let httpMocks;
|
||||
let navigateTo;
|
||||
|
||||
const projectId = '30876.502871204648';
|
||||
|
||||
jest.mock('../../../utils/navigation', () => {
|
||||
const navigateMock = jest.fn();
|
||||
navigateTo = navigateMock;
|
||||
return {
|
||||
navigateTo: navigateMock,
|
||||
navigateTo: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('../../../utils/httpUtil');
|
||||
|
||||
jest.mock('../../parsers/lgWorker', () => {
|
||||
return {
|
||||
flush: () => new Promise((resolve) => resolve(null)),
|
||||
|
@ -193,10 +187,10 @@ describe('Project dispatcher', () => {
|
|||
};
|
||||
};
|
||||
|
||||
let renderedComponent: HookResult<ReturnType<typeof useRecoilTestHook>>, dispatcher: Dispatcher;
|
||||
let renderedComponent: RenderResult<ReturnType<typeof useRecoilTestHook>>, dispatcher: Dispatcher;
|
||||
|
||||
beforeEach(async () => {
|
||||
navigateTo.mockReset();
|
||||
(navigateTo as jest.Mock).mockReset();
|
||||
mockProjectResponse = cloneDeep(mockProjectData);
|
||||
mockManifestResponse = cloneDeep(mockManifestData);
|
||||
mockBotProjectResponse = cloneDeep(mockBotProjectFileData);
|
||||
|
@ -480,7 +474,7 @@ describe('Project dispatcher', () => {
|
|||
expect(renderedComponent.current.botStates.oneNoteSync).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should be able to open a project and its skills in Bot project file', async (done) => {
|
||||
it('should be able to open a project and its skills in Bot project file', async () => {
|
||||
let callIndex = 0;
|
||||
(httpClient.put as jest.Mock).mockImplementation(() => {
|
||||
let mockSkillData: any;
|
||||
|
@ -513,12 +507,9 @@ describe('Project dispatcher', () => {
|
|||
await dispatcher.openProject('../test/empty-bot', 'default');
|
||||
});
|
||||
|
||||
setImmediate(() => {
|
||||
expect(renderedComponent.current.botStates.todoSkill.botDisplayName).toBe('todo-skill');
|
||||
expect(renderedComponent.current.botStates.googleKeepSync.botDisplayName).toBe('google-keep-sync');
|
||||
expect(renderedComponent.current.botProjectSpaceLoaded).toBeTruthy();
|
||||
done();
|
||||
});
|
||||
expect(renderedComponent.current.botStates.todoSkill.botDisplayName).toBe('todo-skill');
|
||||
expect(renderedComponent.current.botStates.googleKeepSync.botDisplayName).toBe('google-keep-sync');
|
||||
expect(renderedComponent.current.botProjectSpaceLoaded).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should migrate skills from existing bots and add them to botproject file', async () => {
|
||||
|
|
|
@ -5,7 +5,7 @@ import { useRecoilState } from 'recoil';
|
|||
import { QnAFile } from '@bfc/shared';
|
||||
import { qnaUtil } from '@bfc/indexers';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { act, HookResult } from '@botframework-composer/test-utils/lib/hooks';
|
||||
import { act, RenderResult } from '@botframework-composer/test-utils/lib/hooks';
|
||||
|
||||
import { qnaDispatcher } from '../qna';
|
||||
import { renderRecoilHook } from '../../../../__tests__/testUtils';
|
||||
|
@ -67,7 +67,7 @@ describe('QnA dispatcher', () => {
|
|||
};
|
||||
};
|
||||
|
||||
let renderedComponent: HookResult<ReturnType<typeof useRecoilTestHook>>, dispatcher: Dispatcher;
|
||||
let renderedComponent: RenderResult<ReturnType<typeof useRecoilTestHook>>, dispatcher: Dispatcher;
|
||||
|
||||
beforeEach(() => {
|
||||
const { result } = renderRecoilHook(useRecoilTestHook, {
|
||||
|
|
|
@ -2,15 +2,13 @@
|
|||
// Licensed under the MIT License.
|
||||
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { act, HookResult } from '@botframework-composer/test-utils/lib/hooks';
|
||||
import { act, RenderResult } from '@botframework-composer/test-utils/lib/hooks';
|
||||
|
||||
import { renderRecoilHook } from '../../../../__tests__/testUtils';
|
||||
import { settingsState, currentProjectIdState, dispatcherState } from '../../atoms';
|
||||
import { Dispatcher } from '..';
|
||||
import { settingsDispatcher } from '../setting';
|
||||
|
||||
jest.mock('../../../utils/httpUtil');
|
||||
|
||||
const projectId = '1235a.2341';
|
||||
|
||||
const settings = {
|
||||
|
@ -87,7 +85,7 @@ describe('setting dispatcher', () => {
|
|||
};
|
||||
};
|
||||
|
||||
let renderedComponent: HookResult<ReturnType<typeof useRecoilTestHook>>, dispatcher: Dispatcher;
|
||||
let renderedComponent: RenderResult<ReturnType<typeof useRecoilTestHook>>, dispatcher: Dispatcher;
|
||||
|
||||
beforeEach(() => {
|
||||
const { result } = renderRecoilHook(useRecoilTestHook, {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// Licensed under the MIT License.
|
||||
|
||||
import { selectorFamily, useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { act, HookResult } from '@botframework-composer/test-utils/lib/hooks';
|
||||
import { act, RenderResult } from '@botframework-composer/test-utils/lib/hooks';
|
||||
|
||||
import { skillDispatcher } from '../skill';
|
||||
import { botProjectFileDispatcher } from '../botProjectFile';
|
||||
|
@ -22,25 +22,16 @@ import {
|
|||
import { botEndpointsState, botProjectIdsState, currentProjectIdState, displaySkillManifestState } from '../../atoms';
|
||||
import { Dispatcher } from '..';
|
||||
import { skillsStateSelector } from '../../selectors';
|
||||
import httpClient from '../../../utils/httpUtil';
|
||||
|
||||
import mockBotProjectFileData from './mocks/mockBotProjectFile.json';
|
||||
|
||||
jest.mock('../../../utils/httpUtil', () => {
|
||||
return {
|
||||
__esModule: true,
|
||||
default: {
|
||||
post: (url, skillObject) => ({
|
||||
url,
|
||||
data: skillObject.skills,
|
||||
}),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const mockDialogComplete = jest.fn();
|
||||
const projectId = '42345.23432';
|
||||
const skillIds = ['1234.123', '234.234'];
|
||||
|
||||
(httpClient.post as jest.Mock).mockImplementation((url, skillObject) => ({ url, data: skillObject.skills }));
|
||||
|
||||
describe('skill dispatcher', () => {
|
||||
const skillsDataSelector = selectorFamily({
|
||||
key: 'skillSelector-skill',
|
||||
|
@ -93,7 +84,7 @@ describe('skill dispatcher', () => {
|
|||
};
|
||||
};
|
||||
|
||||
let renderedComponent: HookResult<ReturnType<typeof useRecoilTestHook>>, dispatcher: Dispatcher;
|
||||
let renderedComponent: RenderResult<ReturnType<typeof useRecoilTestHook>>, dispatcher: Dispatcher;
|
||||
|
||||
beforeEach(() => {
|
||||
mockDialogComplete.mockClear();
|
||||
|
|
|
@ -2,29 +2,23 @@
|
|||
// Licensed under the MIT License.
|
||||
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { act, RenderHookResult, HookResult } from '@botframework-composer/test-utils/lib/hooks';
|
||||
import { act, RenderHookResult, RenderResult } from '@botframework-composer/test-utils/lib/hooks';
|
||||
|
||||
import httpClient from '../../../utils/httpUtil';
|
||||
import { storageDispatcher } from '../storage';
|
||||
import { renderRecoilHook } from '../../../../__tests__/testUtils';
|
||||
import { runtimeTemplatesState, currentProjectIdState, dispatcherState } from '../../atoms';
|
||||
import { Dispatcher } from '../../dispatchers';
|
||||
|
||||
// let httpMocks;
|
||||
let navigateTo;
|
||||
import { navigateTo } from '../../../utils/navigation';
|
||||
|
||||
const projectId = '30876.502871204648';
|
||||
|
||||
jest.mock('../../../utils/navigation', () => {
|
||||
const navigateMock = jest.fn();
|
||||
navigateTo = navigateMock;
|
||||
return {
|
||||
navigateTo: navigateMock,
|
||||
navigateTo: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('../../../utils/httpUtil');
|
||||
|
||||
jest.mock('../../parsers/lgWorker', () => {
|
||||
return {
|
||||
flush: () => new Promise((resolve) => resolve()),
|
||||
|
@ -55,10 +49,10 @@ describe('Storage dispatcher', () => {
|
|||
};
|
||||
};
|
||||
|
||||
let renderedComponent: HookResult<ReturnType<typeof useRecoilTestHook>>, dispatcher: Dispatcher;
|
||||
let renderedComponent: RenderResult<ReturnType<typeof useRecoilTestHook>>, dispatcher: Dispatcher;
|
||||
|
||||
beforeEach(() => {
|
||||
navigateTo.mockReset();
|
||||
(navigateTo as jest.Mock).mockReset();
|
||||
const rendered: RenderHookResult<unknown, ReturnType<typeof useRecoilTestHook>> = renderRecoilHook(
|
||||
useRecoilTestHook,
|
||||
{
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// Licensed under the MIT License.
|
||||
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { act, HookResult } from '@botframework-composer/test-utils/lib/hooks';
|
||||
import { act, RenderResult } from '@botframework-composer/test-utils/lib/hooks';
|
||||
|
||||
import { dialogsDispatcher } from '../dialogs';
|
||||
import { triggerDispatcher } from '../trigger';
|
||||
|
@ -118,7 +118,7 @@ describe('trigger dispatcher', () => {
|
|||
qnaFiles,
|
||||
};
|
||||
};
|
||||
let renderedComponent: HookResult<ReturnType<typeof useRecoilTestHook>>, dispatcher: Dispatcher;
|
||||
let renderedComponent: RenderResult<ReturnType<typeof useRecoilTestHook>>, dispatcher: Dispatcher;
|
||||
|
||||
beforeEach(() => {
|
||||
const { result } = renderRecoilHook(useRecoilTestHook, {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// Licensed under the MIT License.
|
||||
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { act, HookResult } from '@botframework-composer/test-utils/lib/hooks';
|
||||
import { act, RenderResult } from '@botframework-composer/test-utils/lib/hooks';
|
||||
import jwtDecode from 'jwt-decode';
|
||||
|
||||
import { userDispatcher } from '../user';
|
||||
|
@ -53,7 +53,7 @@ describe('user dispatcher', () => {
|
|||
};
|
||||
};
|
||||
|
||||
let renderedComponent: HookResult<ReturnType<typeof useRecoilTestHook>>, dispatcher: Dispatcher;
|
||||
let renderedComponent: RenderResult<ReturnType<typeof useRecoilTestHook>>, dispatcher: Dispatcher;
|
||||
|
||||
beforeEach(() => {
|
||||
process.env.COMPOSER_REQUIRE_AUTH = 'true'; // needs to be a string
|
||||
|
@ -118,6 +118,7 @@ describe('user dispatcher', () => {
|
|||
});
|
||||
|
||||
it('sets values given a decodable token', async () => {
|
||||
const setTimeoutSpy = jest.spyOn(global, 'setTimeout');
|
||||
mockJwtDecode.mockImplementationOnce(() => {
|
||||
return {
|
||||
exp: 12345,
|
||||
|
@ -138,7 +139,7 @@ describe('user dispatcher', () => {
|
|||
// 12345 is the expiration time in seconds, *1000 = 12345000
|
||||
// 10000000 is the mock time we set
|
||||
// 2045000 = 12345000 - 10000000 - (1000 * 60 * 5)
|
||||
expect(setTimeout).toHaveBeenCalledWith(expect.anything(), 2045000);
|
||||
expect(setTimeoutSpy).toHaveBeenCalledWith(expect.anything(), 2045000);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// Licensed under the MIT License.
|
||||
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { act, HookResult } from '@botframework-composer/test-utils/lib/hooks';
|
||||
import { act, RenderResult } from '@botframework-composer/test-utils/lib/hooks';
|
||||
|
||||
import { Dispatcher } from '..';
|
||||
import { renderRecoilHook } from '../../../../__tests__/testUtils';
|
||||
|
@ -29,7 +29,7 @@ describe('web chat dispatcher', () => {
|
|||
};
|
||||
};
|
||||
|
||||
let renderedComponent: HookResult<ReturnType<typeof useRecoilTestHook>>, dispatcher: Dispatcher;
|
||||
let renderedComponent: RenderResult<ReturnType<typeof useRecoilTestHook>>, dispatcher: Dispatcher;
|
||||
|
||||
beforeEach(() => {
|
||||
const { result } = renderRecoilHook(useRecoilTestHook, {
|
||||
|
|
|
@ -5,18 +5,6 @@ import { DialogInfo, DialogSchemaFile, LgFile, LuFile, BotAssets } from '@bfc/sh
|
|||
import FilePersistence from '../FilePersistence';
|
||||
const projectId = '2123.2234as';
|
||||
|
||||
jest.mock('axios', () => {
|
||||
return {
|
||||
create: jest.fn(() => {
|
||||
return {
|
||||
put: new Promise((resolve) => setTimeout(() => resolve({ data: {} }), 10)),
|
||||
post: new Promise((resolve) => setTimeout(() => resolve({ data: {} }), 10)),
|
||||
delete: new Promise((resolve) => setTimeout(() => resolve({ data: {} }), 10)),
|
||||
};
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('../../parsers/fileDiffCalculator', () => {
|
||||
return {
|
||||
difference: require('../../parsers/workers/calculator.worker').getDifferenceItems,
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// Licensed under the MIT License.
|
||||
|
||||
import { selectorFamily, useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
import { act, RenderHookResult, HookResult } from '@botframework-composer/test-utils/lib/hooks';
|
||||
import { act, RenderHookResult, RenderResult } from '@botframework-composer/test-utils/lib/hooks';
|
||||
import noop from 'lodash/noop';
|
||||
|
||||
import { renderRecoilHook } from '../../../../__tests__/testUtils';
|
||||
|
@ -62,7 +62,7 @@ const useRecoilTestHook = () => {
|
|||
};
|
||||
};
|
||||
|
||||
let renderedComponent: HookResult<ReturnType<typeof useRecoilTestHook>>;
|
||||
let renderedComponent: RenderResult<ReturnType<typeof useRecoilTestHook>>;
|
||||
|
||||
beforeEach(() => {
|
||||
const rendered: RenderHookResult<unknown, ReturnType<typeof useRecoilTestHook>> = renderRecoilHook(
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
import { jsx } from '@emotion/core';
|
||||
import { act } from 'react-test-renderer';
|
||||
import { useRecoilValue, useSetRecoilState, useRecoilState } from 'recoil';
|
||||
import { HookResult } from '@botframework-composer/test-utils/lib/hooks';
|
||||
import { RenderResult } from '@botframework-composer/test-utils/lib/hooks';
|
||||
|
||||
import { UndoRoot, undoFunctionState, undoHistoryState } from '../history';
|
||||
import {
|
||||
|
@ -55,7 +55,7 @@ describe('<UndoRoot/>', () => {
|
|||
};
|
||||
};
|
||||
|
||||
let renderedComponent: HookResult<ReturnType<typeof useRecoilTestHook>>;
|
||||
let renderedComponent: RenderResult<ReturnType<typeof useRecoilTestHook>>;
|
||||
|
||||
beforeEach(() => {
|
||||
const { result } = renderRecoilHook(useRecoilTestHook, {
|
||||
|
|
|
@ -6,8 +6,6 @@ import { TelemetryEventTypes } from '@bfc/shared';
|
|||
import httpClient from '../../utils/httpUtil';
|
||||
import AppInsightsClient from '../AppInsightsClient';
|
||||
|
||||
jest.mock('../../utils/httpUtil');
|
||||
|
||||
describe('Application Insights Logger', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
|
|
@ -2,5 +2,6 @@ const { createConfig } = require('@botframework-composer/test-utils');
|
|||
|
||||
module.exports = createConfig('electron-server', 'node', {
|
||||
setupFilesAfterEnv: ['./__tests__/setupTests.js'],
|
||||
testPathIgnorePatterns: ['/node_modules/', '/__tests__/setupTests.js'],
|
||||
testPathIgnorePatterns: ['/node_modules/', '/__tests__/setupTests.js', 'dist'],
|
||||
modulePathIgnorePatterns: ['dist'],
|
||||
});
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
module.exports = {
|
||||
extends: ['../../.eslintrc.js', 'plugin:cypress/recommended'],
|
||||
rules: {
|
||||
'cypress/no-unnecessary-waiting': 'warn',
|
||||
},
|
||||
};
|
|
@ -1,9 +1,6 @@
|
|||
{
|
||||
"projectId": "27ikaq",
|
||||
"ignoreTestFiles": [
|
||||
"**/examples/*",
|
||||
"*.hot-update.js"
|
||||
],
|
||||
"ignoreTestFiles": ["**/examples/*", "*.hot-update.js"],
|
||||
"supportFile": "cypress/support/index.ts",
|
||||
"video": false,
|
||||
"videoUploadOnPasses": false,
|
|
@ -2,4 +2,4 @@
|
|||
"name": "Using fixtures to represent data",
|
||||
"email": "hello@cypress.io",
|
||||
"body": "Fixtures are a great way to mock data for responses to routes"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"error": "Access denied due to invalid subscription key. Make sure you are subscribed to an API you are trying to call and provide the right key."
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"status": {
|
||||
"MyProject_main_en-us_lu": {
|
||||
"version": "0000000001",
|
||||
"checksum": "",
|
||||
"status": 1
|
||||
}
|
||||
},
|
||||
"luFiles": [
|
||||
{
|
||||
"content": "#Dummy\r\n--I am Dummy",
|
||||
"diagnostics": [],
|
||||
"id": "Main",
|
||||
"intents": [],
|
||||
"relativePath": "Main/Main.lu",
|
||||
"lastPublishTime": 2,
|
||||
"lastUpdateTime": 1
|
||||
}
|
||||
]
|
||||
}
|
|
@ -2,4 +2,4 @@
|
|||
"id": 8739,
|
||||
"name": "Jane",
|
||||
"email": "jane@example.com"
|
||||
}
|
||||
}
|
|
@ -229,4 +229,4 @@
|
|||
"bs": "target end-to-end models"
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
|
@ -24,7 +24,7 @@ context('Luis Deploy', () => {
|
|||
method: 'POST',
|
||||
url: 'api/projects/*/build',
|
||||
status: 400,
|
||||
response: 'fixture:luPublish/failure',
|
||||
response: 'fixture:luPublish/error',
|
||||
});
|
||||
cy.findByTestId('startBotButton').click();
|
||||
cy.findByTestId('runtime-logs-sidebar');
|
||||
|
@ -37,5 +37,7 @@ context('Luis Deploy', () => {
|
|||
});
|
||||
cy.findByTestId('startBotButton').click();
|
||||
cy.findByTitle(/^Starting bot../);
|
||||
cy.route('GET', '/api/publish/*/status/default', { endpointURL: 'anything', status: 200 });
|
||||
cy.route('PUT', '/api/projects/*/files/appsettings.json', { status: 200 });
|
||||
});
|
||||
});
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
module.exports = on => {};
|
||||
module.exports = (on) => {};
|
|
@ -9,19 +9,19 @@ declare namespace Cypress {
|
|||
* Creates a bot based on empty bot.
|
||||
* @example cy.createBot('EmptySample', ()=> {})
|
||||
*/
|
||||
createBot(botName: string, createdCallback: (bot: any) => void): void;
|
||||
createBot(botName: string, createdCallback?: (bot: any) => void): void;
|
||||
|
||||
/**
|
||||
* Creates a bot based on empty bot.
|
||||
* @example cy.createTemplateBot('EmptySample', ()=> {})
|
||||
*/
|
||||
createTemplateBot(botName: string, createdCallback: (bot: any) => void): void;
|
||||
createTemplateBot(botName: string, createdCallback?: (bot: any) => void): void;
|
||||
|
||||
/**
|
||||
* Creates a bot from above created template bot.
|
||||
* @example cy.createTestBot('EmptySample', ()=> {})
|
||||
*/
|
||||
createTestBot(botName: string, createdCallback: (bot: any) => void): void;
|
||||
createTestBot(botName: string, createdCallback?: (bot: any) => void): void;
|
||||
|
||||
/**
|
||||
* Visits a page from the left nav bar using the page's testid
|
|
@ -5,7 +5,7 @@ import '@testing-library/cypress/add-commands';
|
|||
|
||||
let TemplateBotProjectId = '';
|
||||
|
||||
Cypress.Commands.add('createBot', (botName: string, callback: (bot: any) => void) => {
|
||||
Cypress.Commands.add('createBot', (botName: string, callback?: (bot: any) => void) => {
|
||||
const params = {
|
||||
description: '',
|
||||
location: '',
|
||||
|
@ -15,10 +15,10 @@ Cypress.Commands.add('createBot', (botName: string, callback: (bot: any) => void
|
|||
schemaUrl: '',
|
||||
storageId: 'default',
|
||||
templateId: '@microsoft/generator-bot-empty',
|
||||
templateVersion: '1.0.0-rc3',
|
||||
templateVersion: '1.0.0',
|
||||
};
|
||||
|
||||
const pollingRequestBotStatus = (jobId: string, callback: (result: any) => void) => {
|
||||
const pollingRequestBotStatus = (jobId: string, callback?: (result: any) => void) => {
|
||||
cy.wait(2000);
|
||||
try {
|
||||
cy.request('get', `/api/status/${jobId}`).then((res) => {
|
||||
|
@ -26,10 +26,11 @@ Cypress.Commands.add('createBot', (botName: string, callback: (bot: any) => void
|
|||
if (httpStatusCode !== 200) {
|
||||
pollingRequestBotStatus(id, callback);
|
||||
} else {
|
||||
callback(result);
|
||||
callback?.(result);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
pollingRequestBotStatus(jobId, callback);
|
||||
}
|
||||
};
|
||||
|
@ -37,18 +38,19 @@ Cypress.Commands.add('createBot', (botName: string, callback: (bot: any) => void
|
|||
cy.request('post', '/api/projects', params).then((res) => {
|
||||
const { jobId } = res.body;
|
||||
// install package can take a long time.
|
||||
cy.wait(20000);
|
||||
pollingRequestBotStatus(jobId, (result) => callback(result));
|
||||
cy.wait(5000);
|
||||
pollingRequestBotStatus(jobId, (result) => callback?.(result));
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('createTemplateBot', (botName: string, callback: (bot: any) => void) => {
|
||||
Cypress.Commands.add('createTemplateBot', (botName: string, callback?: (bot: any) => void) => {
|
||||
cy.createBot(`TemplateBot_${botName}`, (bot) => {
|
||||
TemplateBotProjectId = bot.id;
|
||||
callback?.(bot);
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('createTestBot', (botName: string, callback: (bot: any) => void) => {
|
||||
Cypress.Commands.add('createTestBot', (botName: string, callback?: (bot: any) => void) => {
|
||||
const name = `TestBot_${botName}`;
|
||||
|
||||
const params = {
|
||||
|
@ -59,7 +61,7 @@ Cypress.Commands.add('createTestBot', (botName: string, callback: (bot: any) =>
|
|||
};
|
||||
|
||||
cy.request('post', `/api/projects/${TemplateBotProjectId}/project/saveAs`, params).then((res) => {
|
||||
callback(res.body);
|
||||
callback?.(res.body);
|
||||
});
|
||||
});
|
||||
|
|
@ -4,14 +4,12 @@
|
|||
import './commands';
|
||||
|
||||
before(() => {
|
||||
cy.exec('yarn test:integration:clean-all');
|
||||
cy.createTemplateBot('EmptySample', ({ id }) => {
|
||||
cy.visit(`/bot/${id}`);
|
||||
});
|
||||
cy.exec('yarn clean-all');
|
||||
cy.createTemplateBot('EmptySample');
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.exec('yarn test:integration:clean');
|
||||
cy.exec('yarn clean');
|
||||
window.localStorage.setItem('composer:userSettings', JSON.stringify({ telemetry: { allowDataCollection: false } }));
|
||||
window.localStorage.setItem('composer:OnboardingState', JSON.stringify({ complete: true }));
|
||||
window.sessionStorage.setItem('composer:ProjectIdCache', '');
|
||||
|
@ -20,5 +18,5 @@ beforeEach(() => {
|
|||
after(() => {
|
||||
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||
cy.wait(500);
|
||||
cy.exec('yarn test:integration:clean-all');
|
||||
cy.exec('yarn clean-all');
|
||||
});
|
|
@ -3,7 +3,7 @@
|
|||
"strict": true,
|
||||
"baseUrl": "../node_modules",
|
||||
"target": "es5",
|
||||
"lib": ["es5", "dom"],
|
||||
"lib": ["es2015", "dom"],
|
||||
"types": ["cypress", "@testing-library/cypress", "./support/commands"]
|
||||
},
|
||||
"include": ["**/*.ts"]
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"name": "@bfc/integration-tests",
|
||||
"version": "1.0.0",
|
||||
"description": "Composer end-to-end tests using the cypress testing framework.",
|
||||
"scripts": {
|
||||
"start": "cypress run --browser edge",
|
||||
"start:server": "node scripts/e2e.js",
|
||||
"open": "cypress open",
|
||||
"clean": "node scripts/clean-e2e.js",
|
||||
"clean-all": "node scripts/clean-e2e.js --all",
|
||||
"lint": "eslint --quiet cypress"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@testing-library/cypress": "7.0.6",
|
||||
"chalk": "^4.0.0",
|
||||
"cypress": "^7.5.0",
|
||||
"eslint-plugin-cypress": "2.11.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/fs-extra": "^9.0.11",
|
||||
"fs-extra": "^10.0.0"
|
||||
}
|
||||
}
|
|
@ -4,10 +4,13 @@ function cleanup {
|
|||
kill $SERVER_PID
|
||||
}
|
||||
|
||||
# move up to composer root dir
|
||||
cd ../..
|
||||
yarn start >> e2e.log 2>&1 &
|
||||
SERVER_PID=$!
|
||||
|
||||
npx cypress run
|
||||
cd packages/integration-tests
|
||||
npx cypress run "$@"
|
||||
EXIT_CODE=$?
|
||||
|
||||
# kill server process
|
|
@ -7,15 +7,14 @@
|
|||
/* eslint-disable security/detect-child-process */
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const { promisify } = require('util');
|
||||
const { spawn, execSync } = require('child_process');
|
||||
|
||||
const fs = require('fs-extra');
|
||||
const chalk = require('chalk');
|
||||
|
||||
const mkdir = promisify(fs.mkdir);
|
||||
|
||||
const rootDir = path.resolve(__dirname, '..');
|
||||
const composerRootDir = path.resolve(rootDir, '..', '..');
|
||||
|
||||
process.env.COMPOSER_BOTS_FOLDER = path.resolve(rootDir, 'cypress/__test_bots__');
|
||||
process.env.COMPOSER_APP_DATA = path.resolve(rootDir, 'cypress/__e2e_data.json');
|
||||
|
@ -61,7 +60,7 @@ Wait for the server to come up and then start cypress.
|
|||
|
||||
async function setup() {
|
||||
try {
|
||||
await mkdir(process.env.COMPOSER_BOTS_FOLDER);
|
||||
await fs.ensureDir(process.env.COMPOSER_BOTS_FOLDER);
|
||||
} catch (err) {
|
||||
process.stderr.write('There was a problem setting up.\n');
|
||||
process.stderr.write(`Error:\n${err.message}\n`);
|
||||
|
@ -71,7 +70,12 @@ async function setup() {
|
|||
async function run() {
|
||||
return new Promise((resolve) => {
|
||||
const startCommand = isDev ? 'start:dev' : 'start';
|
||||
const server = spawn('yarn', [startCommand], { cwd: path.resolve(rootDir), stdio: 'inherit' });
|
||||
|
||||
const spawnOptions = { cwd: composerRootDir, stdio: 'inherit' };
|
||||
if (process.platform === 'win32') {
|
||||
spawnOptions.shell = true;
|
||||
}
|
||||
const server = spawn('yarn', [startCommand], spawnOptions);
|
||||
|
||||
server.on('close', () => {
|
||||
resolve();
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче