Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
dist/
node_modules/
19 changes: 19 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"extends": "@artus/eslint-config-artus/typescript",
"parserOptions": {
"project": "./tsconfig.json",
"createDefaultProgram": true
},
"rules": {
"prefer-spread": "off",
"no-return-assign": "off",
"no-case-declarations": "off",
"prefer-const": "off",
"no-regex-spaces": "off",
"no-return-await": "off",
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/no-use-before-define": "off",
"@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/no-var-requires": "off"
}
}
22 changes: 22 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: CI

on:
push:
branches: [ master, main ]

pull_request:
branches: [ master, main, next, beta, "*.x" ]

schedule:
- cron: '0 2 * * *'

workflow_dispatch:

jobs:
Job:
name: Node.js
uses: artusjs/github-actions/.github/workflows/node-test.yml@master
# pass these inputs only if you need to custom
# with:
# os: 'ubuntu-latest, macos-latest, windows-latest'
# version: '16, 18'
47 changes: 0 additions & 47 deletions .github/workflows/nodejs.yml

This file was deleted.

17 changes: 5 additions & 12 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
logs/
npm-debug.log
node_modules/
dist/
coverage/
.idea/
run/
.DS_Store
*.swp
*-lock.json
*-lock.yaml
.vscode/history
*-lock*[.yaml, .json]
**/*.js
**/*.js.map
**/*.d.ts
.tmp
.vscode
.tempCodeRunnerFile.js
/snippet/
19 changes: 19 additions & 0 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// prepare/prerun
// - init dir, init env, init ctx
// run
// - run cli
// - collect stdout/stderr, emit event
// - stdin (expect)
// postrun
// - wait event(end, message, error, stdout, stderr)
// - check assert
// end
// - clean up, kill, log result, error hander

// console.log(this.middlewares);
// return Promise.all(this.middlewares.map(fn => fn()));

// prepare: [],
// prerun: [],
// run: [],
// postrun: [],
4 changes: 2 additions & 2 deletions lib/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import stripAnsi from 'strip-ansi';
import stripFinalNewline from 'strip-final-newline';
import { pEvent } from 'p-event';
import { compose } from 'throwback';
import consola from 'consola';

import * as utils from './utils.js';
import { assert } from './assert.js';
import { Logger, LogLevel } from './logger.js';
import * as validatorPlugin from './validator.js';
import * as operationPlugin from './operation.js';

Expand All @@ -22,7 +22,7 @@ class TestRunner extends EventEmitter {

this.assert = assert;
this.utils = utils;
this.logger = new Logger({ tag: 'CLET' });
this.logger = consola.withDefaults({ tag: 'CLET' });
this.childLogger = this.logger.child('PROC', { indent: 4, showTag: false });

// middleware.pre -> before -> fork -> running -> after -> end -> middleware.post -> cleanup
Expand Down
4 changes: 2 additions & 2 deletions lib/validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export function expect(fn) {
}

const extractPathRegex = /\s+at.*[(\s](.*):\d+:\d+\)?/;
const __filename = filename(import.meta);
const currentFileName = types.isObject(meta) ? filename(import.meta) : __filename;

function mergeError(buildError, runError) {
buildError.message = runError.message;
Expand All @@ -38,7 +38,7 @@ function mergeError(buildError, runError) {
if (line.trim() === '') return false;
const pathMatches = line.match(extractPathRegex);
if (pathMatches === null || !pathMatches[1]) return true;
if (pathMatches[1] === __filename) return false;
if (pathMatches[ 1 ] === currentFileName) return false;
return true;
})
.join('\n');
Expand Down
60 changes: 20 additions & 40 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,73 +2,53 @@
"name": "clet",
"version": "1.0.1",
"description": "Command Line E2E Testing",
"type": "module",
"type": "commonjs",
"main": "./lib/runner.js",
"exports": "./lib/runner.js",
"types": "./lib/index.d.ts",
"author": "TZ <atian25@qq.com> (https://github.com/atian25)",
"homepage": "https://github.com/node-modules/clet",
"repository": "git@github.com:node-modules/clet.git",
"dependencies": {
"consola": "^2.15.3",
"dirname-filename-esm": "^1.1.1",
"dot-prop": "^7.2.0",
"execa": "^6.1.0",
"execa": "^5",
"lodash.ismatch": "^4.4.0",
"p-event": "^5.0.1",
"strip-ansi": "^7.0.1",
"strip-final-newline": "^3.0.0",
"p-event": "^4",
"strip-ansi": "^6",
"strip-final-newline": "^2",
"throwback": "^4.1.0",
"trash": "^8.1.0"
},
"devDependencies": {
"@artus/eslint-config-artus": "^0.0.1",
"@artus/tsconfig": "^1",
"@types/mocha": "^9.1.1",
"@types/node": "^18.7.14",
"@vitest/coverage-c8": "^0.22.1",
"@vitest/ui": "^0.22.1",
"cross-env": "^7.0.3",
"egg-ci": "^1.19.0",
"enquirer": "^2.3.6",
"eslint": "^7",
"eslint-config-egg": "^9",
"supertest": "^6.2.3",
"ts-node": "^10.9.1",
"tslib": "^2.4.0",
"typescript": "^4.8.2",
"vitest": "^0.22.1"
},
"files": [
"bin",
"lib",
"index.js"
"dist"
],
"scripts": {
"lint": "eslint .",
"test": "vitest",
"lint": "eslint . --ext .ts",
"postlint": "tsc --noEmit",
"test": "vitest run",
"cov": "vitest run --coverage",
"ci": "npm run lint && npm run cov"
},
"ci": {
"version": "14, 16, 18",
"type": "github",
"npminstall": false
},
"eslintConfig": {
"extends": "eslint-config-egg",
"root": true,
"env": {
"node": true,
"browser": false,
"jest": true
},
"rules": {
"node/file-extension-in-import": [
"error",
"always"
]
},
"parserOptions": {
"sourceType": "module"
},
"ignorePatterns": [
"dist",
"coverage",
"node_modules"
]
"ci": "npm run cov",
"tsc": "rm -rf dist && tsc",
"prepack": "npm run tsc"
},
"license": "MIT"
}
123 changes: 123 additions & 0 deletions src/lib/assert.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import fs from 'node:fs/promises';
import assert from 'node:assert/strict';
import { match, doesNotMatch, AssertionError } from 'node:assert/strict';

import isMatch from 'lodash.ismatch';
import { types, exists } from './utils';

type Actual = string | number | Record<string, any>;
type Expected = string | RegExp | Record<string, any>;

/**
* assert the `actual` is match `expected`
* - when `expected` is regexp, detect by `RegExp.test`
* - when `expected` is json, detect by `lodash.ismatch`
* - when `expected` is string, detect by `String.includes`
*/
export function matchRule(actual: Actual, expected: Expected) {
if (types.isRegExp(expected)) {
match(actual.toString(), expected);
} else if (types.isObject(expected)) {
// if pattern is `json`, then convert actual to json and check whether contains pattern
const content = types.isString(actual) ? JSON.parse(actual) : actual;
const result = isMatch(content, expected);
if (!result) {
// print diff
throw new AssertionError({
operator: 'should partial includes',
actual: content,
expected,
stackStartFn: matchRule,
});
}
} else if (actual === undefined || !actual.includes(expected)) {
throw new AssertionError({
operator: 'should includes',
actual,
expected,
stackStartFn: matchRule,
});
}
}

/**
* assert the `actual` is not match `expected`
* - when `expected` is regexp, detect by `RegExp.test`
* - when `expected` is json, detect by `lodash.ismatch`
* - when `expected` is string, detect by `String.includes`
*/
export function doesNotMatchRule(actual: Actual, expected: Expected) {
if (types.isRegExp(expected)) {
doesNotMatch(actual.toString(), expected);
} else if (types.isObject(expected)) {
// if pattern is `json`, then convert actual to json and check whether contains pattern
const content = types.isString(actual) ? JSON.parse(actual) : actual;
const result = isMatch(content, expected);
if (result) {
// print diff
throw new AssertionError({
operator: 'should not partial includes',
actual: content,
expected,
stackStartFn: doesNotMatchRule,
});
}
} else if (actual === undefined || actual.includes(expected)) {
throw new AssertionError({
operator: 'should not includes',
actual,
expected,
stackStartFn: doesNotMatchRule,
});
}
}

/**
* validate file
*
* - `matchFile('/path/to/file')`: check whether file exists
* - `matchFile('/path/to/file', /\w+/)`: check whether file match regexp
* - `matchFile('/path/to/file', 'usage')`: check whether file includes specified string
* - `matchFile('/path/to/file', { version: '1.0.0' })`: checke whether file content partial includes specified JSON
*/
export async function matchFile(filePath: string, expected?: Expected) {
// check whether file exists
const isExists = await exists(filePath);
assert(isExists, `Expected ${filePath} to be exists`);

// compare content, support string/json/regex
if (expected) {
const content = await fs.readFile(filePath, 'utf-8');
try {
matchRule(content, expected);
} catch (err) {
err.message = `file(${filePath}) with content: ${err.message}`;
throw err;
}
}
}

/**
* validate file with opposite rule
*
* - `doesNotMatchFile('/path/to/file')`: check whether file don't exists
* - `doesNotMatchFile('/path/to/file', /\w+/)`: check whether file don't match regex
* - `doesNotMatchFile('/path/to/file', 'usage')`: check whether file don't includes specified string
* - `doesNotMatchFile('/path/to/file', { version: '1.0.0' })`: checke whether file content don't partial includes specified JSON
*/
export async function doesNotMatchFile(filePath: string, expected?: Expected) {
// check whether file exists
const isExists = await exists(filePath);
if (!expected) {
assert(!isExists, `Expected ${filePath} to not be exists`);
} else {
assert(isExists, `Expected file(${filePath}) not to match \`${expected}\` but file not exists`);
const content = await fs.readFile(filePath, 'utf-8');
try {
doesNotMatchRule(content, expected);
} catch (err) {
err.message = `file(${filePath}) with content: ${err.message}`;
throw err;
}
}
}
Loading