Skip to content

Commit 49e8ab1

Browse files
committed
feat(cli-test-utils): add declaration for test utilities
fix vuejs#5324
1 parent fc5fc9f commit 49e8ab1

11 files changed

+306
-0
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { PromptModuleAPI } from '@vue/cli'
2+
3+
interface CliPromptModule {
4+
(api: PromptModuleAPI): void
5+
}
6+
7+
declare function assertPromptModule(
8+
module: CliPromptModule | CliPromptModule[],
9+
expectedPrompts: object[],
10+
expectedOptions: object,
11+
opts?: {
12+
pluginsOnly?: boolean
13+
},
14+
): Promise<void>
15+
16+
export = assertPromptModule
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { Application } from 'express'
2+
3+
declare function createJSONServer(
4+
/**
5+
* Either a path to a json file (e.g. 'db.json') or an object in memory
6+
*/
7+
data?: string | object,
8+
): Application
9+
10+
export = createJSONServer
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import http from 'http'
2+
3+
type Options = {
4+
[props: string]: any
5+
/**
6+
* Set a sub directory to be served
7+
*/
8+
root: string
9+
}
10+
11+
declare function createServer(options: Options): http.Server
12+
13+
export = createServer
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import execa = require('execa') // [email protected] needs @types/execa
2+
import { GeneratorRootOptions } from '@vue/cli'
3+
4+
/**
5+
* create project at path `cwd`
6+
*/
7+
declare function createTestProject(
8+
/**
9+
* project name
10+
*/
11+
name: string,
12+
/**
13+
* manual preset used to generate project.
14+
*
15+
* Example:
16+
* {
17+
* plugins: {
18+
* '@vue/cli-plugin-babel': {}
19+
* }
20+
* }
21+
*/
22+
preset: GeneratorRootOptions,
23+
cwd?: string | null,
24+
/**
25+
* if init git repo
26+
*
27+
* Default:`true`
28+
*/
29+
initGit?: boolean,
30+
): Promise<{
31+
/** test project's root path */
32+
dir: string
33+
/** test if project contains the file */
34+
has: (file: string) => boolean
35+
/** read the content for the file */
36+
read: (file: string) => Promise<string>
37+
/** write file to project */
38+
write: (file: string, content: string) => Promise<void>
39+
/** execa command at root path of project */
40+
run: (command: string, args?: ReadonlyArray<string>) => execa.ExecaChildProcess
41+
/** delete the file of project */
42+
rm: (file: string) => Promise<void>
43+
}>
44+
45+
export = createTestProject
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { GeneratorAPI, GeneratorRootOptions } from '@vue/cli'
2+
3+
type ApplyFn = (
4+
api: GeneratorAPI,
5+
options: Record<string, any>,
6+
rootOptions: GeneratorRootOptions,
7+
invoking: boolean,
8+
) => any
9+
interface Plugin {
10+
/** package name from plugin */
11+
id: string
12+
/** generator function from plugin */
13+
apply: ApplyFn
14+
/** parameter passed to generator function */
15+
options: Record<string, any>
16+
}
17+
18+
/**
19+
* invoke generator function, and generate file tree in memory
20+
*/
21+
declare function generateWithPlugin(
22+
plugin: Plugin | Plugin[],
23+
): Promise<{
24+
/** package.json Object */
25+
pkg: object
26+
/** virtual file tree, file path is the key of Object */
27+
files: {
28+
[filePath: string]: string | Buffer
29+
}
30+
}>
31+
32+
export = generateWithPlugin
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Browser, Page } from 'puppeteer'
2+
3+
declare function launchPuppeteer(
4+
url: string,
5+
): Promise<{
6+
browser: Browser
7+
page: Page
8+
logs: string[]
9+
requestUrls: string[]
10+
}>
11+
12+
export = launchPuppeteer

packages/@vue/cli-test-utils/package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@
2222
"access": "public"
2323
},
2424
"dependencies": {
25+
"@types/execa": "^0.9.0",
26+
"@types/express": "^4.17.4",
27+
"@types/puppeteer": "^1.11.0",
28+
"@vue/cli": "^4.3.0",
2529
"execa": "^1.0.0",
2630
"fs-extra": "^7.0.1",
2731
"json-server": "^0.15.0",
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import execa = require('execa')
2+
import { Browser, Page } from 'puppeteer'
3+
4+
interface Helpers {
5+
getText: (selector: string) => Promise<string>
6+
hasElement: (selector: string) => Promise<boolean>
7+
hasClass: (selector: string, cls: string) => Promise<boolean>
8+
}
9+
10+
interface Utils {
11+
url: string
12+
browser: Browser
13+
page: Page
14+
/** wait for hot replacement */
15+
nextUpdate: () => Promise<string>
16+
helpers: Helpers
17+
requestUrls: string[]
18+
}
19+
20+
declare function serveWithPuppeteer(
21+
serve: () => execa.ExecaChildProcess,
22+
/** Function which executes test codes*/
23+
test: (arg: Utils) => any,
24+
/**
25+
* don't launch puppeteer.
26+
* Defaults to `false`.
27+
*/
28+
noPuppeteer?: boolean,
29+
): Promise<void>
30+
31+
export = serveWithPuppeteer
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import assertPromptModule from '@vue/cli-test-utils/assertPromptModule'
2+
import createJSONServer from '@vue/cli-test-utils/createJSONServer'
3+
import createServer from '@vue/cli-test-utils/createServer'
4+
import createTestProject from '@vue/cli-test-utils/createTestProject'
5+
import generateWithPlugin from '@vue/cli-test-utils/generateWithPlugin'
6+
import launchPuppeteer from '@vue/cli-test-utils/launchPuppeteer'
7+
import serveWithPuppeteer from '@vue/cli-test-utils/serveWithPuppeteer'
8+
import path from 'path'
9+
10+
const expectedPrompts = [{ choose: 0 }]
11+
12+
const expectedOptions = {
13+
useConfigFiles: false,
14+
plugins: {
15+
foo: {}
16+
}
17+
}
18+
19+
assertPromptModule(
20+
api => {
21+
api.injectFeature({
22+
name: 'Foo',
23+
value: 'foo'
24+
})
25+
api.injectFeature({
26+
name: 'Bar',
27+
value: 'bar'
28+
})
29+
api.onPromptComplete((answers, options) => {
30+
if (answers.features.includes('foo')) {
31+
options.plugins.foo = {}
32+
}
33+
})
34+
},
35+
expectedPrompts,
36+
expectedOptions
37+
)
38+
39+
const mockServer1 = createJSONServer({
40+
posts: [{ id: 1, title: 'server-one', author: 'typicode' }]
41+
}).listen(3000, () => {})
42+
43+
const server = createServer({ root: path.resolve(__dirname, 'temp') })
44+
45+
async function createTest() {
46+
const project = await createTestProject(
47+
'eslint',
48+
{
49+
plugins: {
50+
'@vue/cli-plugin-babel': {},
51+
'@vue/cli-plugin-eslint': {
52+
config: 'airbnb',
53+
lintOn: 'commit'
54+
}
55+
}
56+
},
57+
null,
58+
true
59+
)
60+
const { dir, has, read, write, run, rm } = project
61+
62+
if (!has('src/main.js')) return
63+
64+
const main = await read('src/main.js')
65+
66+
const updatedMain = main.replace(/;/g, '')
67+
68+
await write('src/main.js', updatedMain)
69+
70+
await project.rm(`src/test.js`)
71+
72+
const { stdout } = await run('vue-cli-service lint')
73+
74+
await serveWithPuppeteer(
75+
() => project.run('vue-cli-service serve'),
76+
async ({ url, browser, page, nextUpdate, helpers, requestUrls }) => {
77+
await helpers.getText('h1')
78+
}
79+
)
80+
}
81+
82+
async function testGenerate() {
83+
const { pkg, files } = await generateWithPlugin({
84+
id: 'test-plugin',
85+
apply: (api, options, rootOptions, invoking) => {},
86+
options: {}
87+
})
88+
}
89+
90+
async function testLaunchPuppeteer() {
91+
const { browser, page, logs, requestUrls } = await launchPuppeteer(`http://localhost:8080/`)
92+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
2+
{
3+
"files": [
4+
"cli-test-utils-test.ts"
5+
],
6+
"compilerOptions": {
7+
"module": "commonjs",
8+
"lib": [
9+
"es6"
10+
],
11+
"noImplicitAny": true,
12+
"noImplicitThis": true,
13+
"strictNullChecks": true,
14+
"esModuleInterop": true,
15+
"strictFunctionTypes": true,
16+
"types": [],
17+
"noEmit": true,
18+
"forceConsistentCasingInFileNames": true,
19+
"baseUrl": ".",
20+
"skipLibCheck": true
21+
}
22+
}

yarn.lock

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2788,6 +2788,13 @@
27882788
resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7"
27892789
integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==
27902790

2791+
"@types/execa@^0.9.0":
2792+
version "0.9.0"
2793+
resolved "https://registry.npmjs.org/@types/execa/-/execa-0.9.0.tgz#9b025d2755f17e80beaf9368c3f4f319d8b0fb93"
2794+
integrity sha512-mgfd93RhzjYBUHHV532turHC2j4l/qxsF/PbfDmprHDEUHmNZGlDn1CEsulGK3AfsPdhkWzZQT/S/k0UGhLGsA==
2795+
dependencies:
2796+
"@types/node" "*"
2797+
27912798
"@types/express-serve-static-core@*":
27922799
version "4.17.2"
27932800
resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.2.tgz#f6f41fa35d42e79dbf6610eccbb2637e6008a0cf"
@@ -2805,6 +2812,16 @@
28052812
"@types/express-serve-static-core" "*"
28062813
"@types/serve-static" "*"
28072814

2815+
"@types/express@^4.17.4":
2816+
version "4.17.4"
2817+
resolved "https://registry.npmjs.org/@types/express/-/express-4.17.4.tgz#e78bf09f3f530889575f4da8a94cd45384520aac"
2818+
integrity sha512-DO1L53rGqIDUEvOjJKmbMEQ5Z+BM2cIEPy/eV3En+s166Gz+FeuzRerxcab757u/U4v4XF4RYrZPmqKa+aY/2w==
2819+
dependencies:
2820+
"@types/body-parser" "*"
2821+
"@types/express-serve-static-core" "*"
2822+
"@types/qs" "*"
2823+
"@types/serve-static" "*"
2824+
28082825
"@types/fs-capacitor@*":
28092826
version "2.0.0"
28102827
resolved "https://registry.yarnpkg.com/@types/fs-capacitor/-/fs-capacitor-2.0.0.tgz#17113e25817f584f58100fb7a08eed288b81956e"
@@ -2971,11 +2988,23 @@
29712988
resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e"
29722989
integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==
29732990

2991+
"@types/puppeteer@^1.11.0":
2992+
version "1.20.4"
2993+
resolved "https://registry.npmjs.org/@types/puppeteer/-/puppeteer-1.20.4.tgz#30cb0a4ee5394c420119cbdf9f079d6595a07f67"
2994+
integrity sha512-T/kFgyLnYWk0H94hxI0HbOLnqHvzBRpfS0F0oo9ESGI24oiC2fEjDcMbBjuK3wH7VLsaIsp740vVXVzR1dsMNg==
2995+
dependencies:
2996+
"@types/node" "*"
2997+
29742998
"@types/q@^1.5.1":
29752999
version "1.5.2"
29763000
resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.2.tgz#690a1475b84f2a884fd07cd797c00f5f31356ea8"
29773001
integrity sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==
29783002

3003+
"@types/qs@*":
3004+
version "6.9.1"
3005+
resolved "https://registry.npmjs.org/@types/qs/-/qs-6.9.1.tgz#937fab3194766256ee09fcd40b781740758617e7"
3006+
integrity sha512-lhbQXx9HKZAPgBkISrBcmAcMpZsmpe/Cd/hY7LGZS5OfkySUBItnPZHgQPssWYUET8elF+yCFBbP1Q0RZPTdaw==
3007+
29793008
"@types/range-parser@*":
29803009
version "1.2.3"
29813010
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c"

0 commit comments

Comments
 (0)