feat: setup testing for CTA (#1615)

* feat: setup testing for CTA

* install with yarn

* build before test

* add yarn to npm runs for install / test start

* add dev mode to link cli.js and api locally

* remove fixtures

* run tests serially

* cli.js build-release avoids webpack error

* assert on package.json contents as first check

* run tauri build and split out custom asserts

* add changefile

* shorten workflow name

* too short

* exclude npm@6 on node@16

* increase timeout, tauri build takes a bit of time

* only assert that the tauri script exists
This commit is contained in:
Jacob Bolda 2021-04-27 09:14:24 -05:00 committed by GitHub
parent ddcd9233bd
commit af6411d5f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 331 additions and 29 deletions

View File

@ -0,0 +1,5 @@
---
"create-tauri-app": patch
---
We setup an e2e type test suite for CTA. It is mostly an internal change, but should help with stability moving forward.

97
.github/workflows/test-cta.yml vendored Normal file
View File

@ -0,0 +1,97 @@
# Copyright 2019-2021 Tauri Programme within The Commons Conservancy
# SPDX-License-Identifier: Apache-2.0
# SPDX-License-Identifier: MIT
name: test create-tauri-app
on:
workflow_dispatch:
inputs:
branch:
default: "dev"
pull_request:
paths:
- "tooling/create-tauri-app/**"
env:
RUST_BACKTRACE: 1
jobs:
create-recipe-with-npm:
name: "node@${{ matrix.node }} + npm@${{ matrix.manager }}: ${{ matrix.recipe }}"
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node: ["14", "16"]
manager: ["6", "7"]
recipe: ["vanillajs", "reactjs", "reactts", "vite", "vuecli"]
exclude:
- node: "16"
manager: "6"
steps:
- uses: actions/checkout@v2
with:
ref: ${{ github.head_ref || github.event.inputs.branch }}
- name: install stable
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- uses: volta-cli/action@v1
with:
node-version: ${{ matrix.node }}
npm-version: ${{ matrix.manager }}
yarn-version: 1.22.5
- name: install webkit2gtk (ubuntu only)
if: matrix.platform == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -y webkit2gtk-4.0
- run: yarn
working-directory: tooling/create-tauri-app
- run: yarn build
working-directory: tooling/create-tauri-app
- run: yarn test
working-directory: tooling/create-tauri-app
env:
TAURI_RECIPE: ${{ matrix.recipe }}
TAURI_RUN_MANAGER: "npm"
create-recipe-with-yarn:
name: "node@${{ matrix.node }} + yarn@1: ${{ matrix.recipe }}"
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node: ["14", "16"]
recipe: ["vanillajs", "reactjs", "reactts", "vite", "vuecli"]
steps:
- uses: actions/checkout@v2
with:
ref: ${{ github.head_ref || github.event.inputs.branch }}
- name: install stable
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- uses: volta-cli/action@v1
with:
node-version: ${{ matrix.node }}
yarn-version: 1.22.5
- name: install webkit2gtk (ubuntu only)
if: matrix.platform == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -y webkit2gtk-4.0
- run: yarn
working-directory: tooling/create-tauri-app
- run: yarn build
working-directory: tooling/create-tauri-app
- run: yarn test
working-directory: tooling/create-tauri-app
env:
TAURI_RECIPE: ${{ matrix.recipe }}
TAURI_RUN_MANAGER: "yarn"

View File

@ -7,7 +7,7 @@ module.exports = {
},
parser: '@typescript-eslint/parser',
ignorePatterns: ['.eslintrc.js', 'jest.config.js', 'test/**/*'],
extends: [
'standard-with-typescript',
'plugin:@typescript-eslint/recommended-requiring-type-checking',

View File

@ -29,6 +29,7 @@ const {
* @property {boolean} log
* @property {boolean} d
* @property {boolean} directory
* @property {boolean} dev
* @property {string} r
* @property {string} recipe
*/
@ -41,7 +42,7 @@ const createTauriApp = async (cliArgs) => {
l: 'log',
m: 'manager',
d: 'directory',
b: 'binary',
dev: 'dev',
t: 'tauri-path',
A: 'app-name',
W: 'window-title',
@ -49,7 +50,7 @@ const createTauriApp = async (cliArgs) => {
P: 'dev-path',
r: 'recipe'
},
boolean: ['h', 'l', 'ci']
boolean: ['h', 'l', 'ci', 'dev']
})
if (argv.help) {
@ -62,13 +63,9 @@ const createTauriApp = async (cliArgs) => {
return false // do this for node consumers and tests
}
if (argv.ci) {
return runInit(argv)
} else {
return getOptionsInteractive(argv).then((responses) =>
runInit(argv, responses)
)
}
return getOptionsInteractive(argv, !argv.ci).then((responses) =>
runInit(argv, responses)
)
}
function printUsage() {
@ -95,8 +92,12 @@ function printUsage() {
`)
}
const getOptionsInteractive = (argv) => {
let defaultAppName = argv.A || 'tauri-app'
const getOptionsInteractive = (argv, ask) => {
const defaults = {
appName: argv.A || 'tauri-app',
tauri: { window: { title: 'Tauri App' } },
recipeName: argv.r || 'vanillajs'
}
return inquirer
.prompt([
@ -104,29 +105,33 @@ const getOptionsInteractive = (argv) => {
type: 'input',
name: 'appName',
message: 'What is your app name?',
default: defaultAppName,
when: !argv.A
default: defaults.appName,
when: ask && !argv.A
},
{
type: 'input',
name: 'tauri.window.title',
message: 'What should the window title be?',
default: 'Tauri App',
when: () => !argv.W
default: defaults.tauri.window.title,
when: ask && !argv.W
},
{
type: 'list',
name: 'recipeName',
message: 'Would you like to add a UI recipe?',
choices: recipeDescriptiveNames,
default: 'No recipe',
when: () => !argv.r
default: defaults.recipeName,
when: ask && !argv.r
}
])
.then((answers) => ({
...defaults,
...answers
}))
.catch((error) => {
if (error.isTtyError) {
// Prompt couldn't be rendered in the current environment
console.log(
console.warn(
'It appears your terminal does not support interactive prompts. Using default values.'
)
runInit()
@ -150,10 +155,10 @@ async function runInit(argv, config = {}) {
let recipe
if (recipeName !== undefined) {
recipe = recipeByDescriptiveName(recipeName)
} else if (argv.r) {
if (argv.r) {
recipe = recipeByShortName(argv.r)
} else if (recipeName !== undefined) {
recipe = recipeByDescriptiveName(recipeName)
}
let buildConfig = {
@ -201,10 +206,21 @@ async function runInit(argv, config = {}) {
// Vue CLI plugin automatically runs these
if (recipe.shortName !== 'vuecli') {
console.log('===== installing any additional needed deps =====')
if (argv.dev) {
await shell('yarn', ['link', '@tauri-apps/cli'], {
cwd: appDirectory
})
await shell('yarn', ['link', '@tauri-apps/api'], {
cwd: appDirectory
})
}
await install({
appDir: appDirectory,
dependencies: recipe.extraNpmDependencies,
devDependencies: ['@tauri-apps/cli', ...recipe.extraNpmDevDependencies],
devDependencies: argv.dev
? [...recipe.extraNpmDevDependencies]
: ['@tauri-apps/cli'].concat(recipe.extraNpmDevDependencies),
packageManager
})
@ -216,7 +232,7 @@ async function runInit(argv, config = {}) {
packageManager === 'npm' && !argv.b
? ['run', 'tauri', '--', 'init']
: ['tauri', 'init']
await shell(binary, [...runTauriArgs, ...initArgs], {
await shell(binary, [...runTauriArgs, ...initArgs, '--ci'], {
cwd: appDirectory
})
}

View File

@ -0,0 +1,16 @@
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
modulePathIgnorePatterns: ['__fixtures__'],
testMatch: ['<rootDir>/test/**/*.spec.ts'],
moduleFileExtensions: ['ts', 'js', 'json'],
globals: {
'ts-jest': {
tsconfig: 'tsconfig.json'
}
}
}

View File

@ -29,7 +29,8 @@
"lint-fix": "eslint --fix --ext ts \"./src/**/*.ts\"",
"lint:lockfile": "lockfile-lint --path yarn.lock --type yarn --validate-https --allowed-hosts npm yarn",
"format": "prettier --write --end-of-line=auto \"./**/*.{js,jsx,ts,tsx,html,css,json}\" --ignore-path .gitignore",
"format:check": "prettier --check --end-of-line=auto \"./**/*.{js,jsx,ts,tsx,html,css,json}\" --ignore-path .gitignore"
"format:check": "prettier --check --end-of-line=auto \"./**/*.{js,jsx,ts,tsx,html,css,json}\" --ignore-path .gitignore",
"test": "jest --runInBand"
},
"dependencies": {
"execa": "^5.0.0",
@ -41,11 +42,12 @@
"@rollup/plugin-commonjs": "18.0.0",
"@rollup/plugin-node-resolve": "11.2.1",
"@rollup/plugin-typescript": "8.2.1",
"@typescript-eslint/eslint-plugin": "4.22.0",
"@typescript-eslint/parser": "4.22.0",
"@types/cross-spawn": "6.0.2",
"@types/inquirer": "7.3.1",
"@types/jest": "^26.0.22",
"@types/semver": "7.3.4",
"@typescript-eslint/eslint-plugin": "4.22.0",
"@typescript-eslint/parser": "4.22.0",
"eslint": "7.24.0",
"eslint-config-prettier": "8.2.0",
"eslint-config-standard-with-typescript": "20.0.0",
@ -54,8 +56,11 @@
"eslint-plugin-node": "11.1.0",
"eslint-plugin-promise": "5.1.0",
"eslint-plugin-security": "1.4.0",
"fixturez": "^1.1.0",
"jest": "^26.6.3",
"prettier": "2.2.1",
"rollup": "2.45.1",
"ts-jest": "^26.5.5",
"tslib": "2.2.0",
"typescript": "4.2.4"
}

View File

@ -0,0 +1,161 @@
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
import execa from 'execa'
import fixtures from 'fixturez'
const f = fixtures(__dirname)
import path from 'path'
import fs from 'fs'
const ctaBinary = path.resolve('./bin/create-tauri-app.js')
const clijs = path.resolve('../cli.js/')
const api = path.resolve('../api/')
const manager = process.env.TAURI_RUN_MANAGER ?? 'npm'
const recipes = process.env.TAURI_RECIPE
? [process.env.TAURI_RECIPE]
: ['vanillajs', 'reactjs', 'reactts', 'vite', 'vuecli']
const timeoutLong = 900000
const timeoutLittleLonger = 930000
const logOut = false ? 'inherit' : 'pipe'
beforeAll(async () => {
const installCLI = await execa('yarn', [], {
stdio: logOut,
cwd: clijs,
timeout: timeoutLong
})
const buildCLI = await execa('yarn', ['build-release'], {
stdio: logOut,
cwd: clijs,
timeout: timeoutLong
})
const linkCLI = await execa('yarn', ['link'], {
stdio: logOut,
cwd: clijs,
timeout: timeoutLong
})
const installAPI = await execa('yarn', [], {
stdio: logOut,
cwd: api,
timeout: timeoutLong
})
const buildAPI = await execa('yarn', ['build'], {
stdio: logOut,
cwd: api,
timeout: timeoutLong
})
const linkAPI = await execa('yarn', ['link'], {
stdio: logOut,
cwd: api,
timeout: timeoutLong
})
}, timeoutLittleLonger)
describe('CTA', () => {
describe.each(recipes.map((recipe) => [recipe, 'tauri-app']))(
`%s recipe`,
(recipe: string, appName: string) => {
it(
'runs',
async () => {
// creates a temp folder to run CTA within (this is our cwd)
const folder = f.temp()
const appFolder = path.join(folder, appName)
// runs CTA with all args set to avoid any prompts
const cta = await execa(
'node',
[
ctaBinary,
'--manager',
manager,
'--recipe',
recipe,
'--ci',
'--dev'
],
{
all: true,
stdio: logOut,
cwd: folder,
timeout: timeoutLong
}
)
// check to make certain it didn't fail anywhere
expect(cta.failed).toBe(false)
expect(cta.timedOut).toBe(false)
expect(cta.isCanceled).toBe(false)
expect(cta.killed).toBe(false)
expect(cta.signal).toBe(undefined)
// run a tauri build to check if what we produced
// can actually create an app
// TODO long term we will want to hook this up to a real test harness
// and then run that test suite instead
let opts: string[] = []
if (manager === 'npm') {
opts = ['run', 'tauri', '--', 'build']
} else if (manager === 'yarn') {
opts = ['tauri', 'build']
}
const tauriBuild = await execa(manager, opts, {
all: true,
stdio: logOut,
cwd: appFolder,
timeout: timeoutLong
})
expect(tauriBuild.failed).toBe(false)
expect(tauriBuild.timedOut).toBe(false)
expect(tauriBuild.isCanceled).toBe(false)
expect(tauriBuild.killed).toBe(false)
expect(tauriBuild.signal).toBe(undefined)
const packageFileOutput: {
[k: string]: string | object
} = JSON.parse(
await fs.promises.readFile(
path.join(appFolder, 'package.json'),
'utf-8'
)
)
expect(packageFileOutput['name']).toBe(appName)
const assertCustom: { [k: string]: Function } = {
vanillajs: () => {
expect(packageFileOutput['scripts']).toMatchObject({
tauri: 'tauri'
})
},
reactjs: () => {
expect(packageFileOutput['scripts']).toEqual(
expect.objectContaining({
tauri: 'tauri'
})
)
},
reactts: () => {
expect(packageFileOutput['scripts']).toEqual(
expect.objectContaining({
tauri: 'tauri'
})
)
}
}
const getCustomAsserts = assertCustom[recipe]
if (getCustomAsserts) getCustomAsserts()
},
timeoutLittleLonger
)
}
)
})

View File

@ -7,8 +7,9 @@
"pretty": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"moduleResolution": "node"
"moduleResolution": "node",
"typeRoots": ["./types", "node_modules/@types"]
},
"include": ["src"],
"exclude": ["src/templates"]
"exclude": ["src/templates", "types", "test", "__fixtures__"]
}

View File

@ -0,0 +1 @@
declare module 'fixturez'