Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(compas-convert): get tests running, fix some issues while running the tests #312

Merged
merged 1 commit into from
Nov 22, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
20 changes: 17 additions & 3 deletions packages/compas-convert/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,28 @@ rm -rf ../compas-convert-test && npx compas-convert ../some-local-test-project .
- [x] Pass: @compas/test to Vitest
- [x] Pass: inline JSDoc blocks to inline types
- Pass: Run TypeScript, ESLint and tests to find common errors
- [ ] Type of `AppErrror` in Compas should accept anything for 'cause' and 'info'.
- [x] Type of `AppErrror` in Compas should accept anything for 'cause' and 'info'.
- `TS2345: Argument of type 'unknown' is not assignable to parameter of type 'Error | undefined'`
- Used in all variants of `AppError` construction
- [ ] Insert `assertIsAppError` in test files
- On `TS18046: 'e' is of type 'unknown'`
- AND `.key` or `.info` is accessed
- Implement like `not-nil-checks-in-test-flows`
- [ ] Convert JSDOC `function(number, string): string` to `(number, string) => string`
- [x] ~Convert JSDOC `function(number, string): string` to `(number, string) => string`~
- [x] Pass: query-builder types
- Improve query-builder types by inferring the types based on the passed in builder.
- [ ] Pass: build step. Fixup CI, Dockerfile, docs, etc

Checklist:

- [ ] Any TS errors we need to fix still?
- Start documenting common errors and their fixes below
- [ ] Any ESLint rules we can fix?
- Start documenting common errors and their fixes below
- [ ] Can we run the API, Queue, etc?
- [ ] Does the docker build work?
- [ ] Collect common knowledge:
- New QueryBuilder types
- TSX usage
- Vitest usage

## Migration docs
2 changes: 2 additions & 0 deletions packages/compas-convert/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import consola from "consola";
import { createEmptyContext } from "./context.js";
import type { GlobalPass, Pass } from "./pass.js";
import { addCommonImports } from "./passes/add-common-imports.js";
import { convertTestConfig } from "./passes/convert-test-config.js";
import { convertTestFiles } from "./passes/convert-test-files.js";
import { copyRename } from "./passes/copy-rename.js";
import { finalizePendingImports } from "./passes/finalize-pending-imports.js";
Expand Down Expand Up @@ -56,6 +57,7 @@ const passes: Array<Pass> = [
fixTypesOfAllFunctions,
updateGenerateOptions,
convertTestFiles,
convertTestConfig,

transformModuleJsDoc,
transformExpressionJsDoc,
Expand Down
55 changes: 29 additions & 26 deletions packages/compas-convert/src/passes/add-common-imports.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { SourceFile } from "ts-morph";
import { addNamedImportIfNotExists, resolveRelativeImport } from "../shared/import.js";
import { addPendingImport, resolveRelativeImport } from "../shared/imports.js";
import type { Context } from "./../context.js";
import { CONVERT_UTIL, CONVERT_UTIL_PATH } from "./init-ts-morph.js";

Expand All @@ -15,49 +15,52 @@ export function addCommonImports(context: Context, sourceFile: SourceFile) {
return;
}

addNamedImportIfNotExists(
addPendingImport(
jurriaanr marked this conversation as resolved.
Show resolved Hide resolved
context,
sourceFile,
resolveRelativeImport(context, sourceFile, CONVERT_UTIL_PATH),
CONVERT_UTIL.any,
true,
);
addNamedImportIfNotExists(
addPendingImport(
context,
sourceFile,
resolveRelativeImport(context, sourceFile, CONVERT_UTIL_PATH),
CONVERT_UTIL.assertNotNil,
false,
);

addNamedImportIfNotExists(sourceFile, "@compas/stdlib", "InsightEvent", true);
addNamedImportIfNotExists(sourceFile, "@compas/stdlib", "Logger", true);
addNamedImportIfNotExists(sourceFile, "@compas/stdlib", "AppError", false);
addNamedImportIfNotExists(sourceFile, "@compas/stdlib", "Either", true);
addNamedImportIfNotExists(sourceFile, "@compas/stdlib", "EitherN", true);
addPendingImport(context, sourceFile, "@compas/stdlib", "InsightEvent", true);
addPendingImport(context, sourceFile, "@compas/stdlib", "Logger", true);
addPendingImport(context, sourceFile, "@compas/stdlib", "AppError", false);
addPendingImport(context, sourceFile, "@compas/stdlib", "Either", true);
addPendingImport(context, sourceFile, "@compas/stdlib", "EitherN", true);

addNamedImportIfNotExists(sourceFile, "@compas/code-gen", "Generator", false);
addNamedImportIfNotExists(sourceFile, "@compas/code-gen", "TypeCreator", false);
addNamedImportIfNotExists(sourceFile, "@compas/code-gen", "RouteCreator", true);
addNamedImportIfNotExists(sourceFile, "@compas/code-gen", "TypeBuilder", true);
addNamedImportIfNotExists(sourceFile, "@compas/code-gen", "TypeBuilderLike", true);
addPendingImport(context, sourceFile, "@compas/code-gen", "Generator", false);
addPendingImport(context, sourceFile, "@compas/code-gen", "TypeCreator", false);
addPendingImport(context, sourceFile, "@compas/code-gen", "RouteCreator", true);
addPendingImport(context, sourceFile, "@compas/code-gen", "TypeBuilder", true);
addPendingImport(context, sourceFile, "@compas/code-gen", "TypeBuilderLike", true);

addNamedImportIfNotExists(sourceFile, "@compas/server", "Application", true);
addNamedImportIfNotExists(sourceFile, "@compas/server", "Next", true);
addNamedImportIfNotExists(sourceFile, "@compas/server", "Middleware", true);
addNamedImportIfNotExists(sourceFile, "@compas/server", "Context", true);
addNamedImportIfNotExists(sourceFile, "@compas/server", "Context", true);
addPendingImport(context, sourceFile, "@compas/server", "Application", true);
addPendingImport(context, sourceFile, "@compas/server", "Next", true);
addPendingImport(context, sourceFile, "@compas/server", "Middleware", true);
addPendingImport(context, sourceFile, "@compas/server", "Context", true);
addPendingImport(context, sourceFile, "@compas/server", "Context", true);

addNamedImportIfNotExists(sourceFile, "@compas/store", "Postgres", true);
addNamedImportIfNotExists(sourceFile, "@compas/store", "S3Client", true);
addNamedImportIfNotExists(sourceFile, "@compas/store", "QueryPart", true);
addNamedImportIfNotExists(sourceFile, "@compas/store", "SessionStoreSettings", true);
addNamedImportIfNotExists(
addPendingImport(context, sourceFile, "@compas/store", "Postgres", true);
addPendingImport(context, sourceFile, "@compas/store", "S3Client", true);
addPendingImport(context, sourceFile, "@compas/store", "QueryPart", true);
addPendingImport(context, sourceFile, "@compas/store", "SessionStoreSettings", true);
addPendingImport(
context,
sourceFile,
"@compas/store",
"SessionTransportSettings",
true,
);

addNamedImportIfNotExists(sourceFile, "axios", "AxiosInstance", true);
addNamedImportIfNotExists(sourceFile, "axios", "AxiosRequestConfig", true);
addNamedImportIfNotExists(sourceFile, "axios", "AxiosError", true);
addPendingImport(context, sourceFile, "axios", "AxiosInstance", true);
addPendingImport(context, sourceFile, "axios", "AxiosRequestConfig", true);
addPendingImport(context, sourceFile, "axios", "AxiosError", true);
}
49 changes: 49 additions & 0 deletions packages/compas-convert/src/passes/convert-test-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import path from "node:path";
import type { Context } from "../context.js";
import { getTypescriptProgram } from "./init-ts-morph.js";

/**
* Apply .env loading in the test setup + rename methods
*/
export async function convertTestConfig(context: Context) {
const configFilePath = path.join(context.outputDirectory, "test/config.ts");

const program = getTypescriptProgram(context);
const configFile = program.getSourceFile(configFilePath);

if (configFile) {
configFile.replaceWithText(`
import { readFileSync } from "node:fs";
import { parseEnv } from "node:util";
import { refreshEnvironmentCache, loggerSetGlobalDestination } from "@compas/stdlib";

await setupTestEnvironment();
jurriaanr marked this conversation as resolved.
Show resolved Hide resolved

${configFile
.getFullText()
.replace(
"export async function setup(): Promise<void> {",
`
async function setupTestEnvironment(): Promise<void> {
// Compas-compat: Load default .env values.
Object.assign(process.env, parseEnv(readFileSync(".env", "utf-8")));
refreshEnvironmentCache();
jurriaanr marked this conversation as resolved.
Show resolved Hide resolved

// Compas-compat: use console.dir to only print error logs
loggerSetGlobalDestination({
write(msg) {
if (msg.includes(\`"level":"error"\`)) {
console.dir(msg, { colors: true, depth: null });
}
},
});
`,
)
.replace(
`export async function teardown()`,
`async function teardownTestEnvironment()`,
)}
`);
await configFile.save();
}
}
47 changes: 14 additions & 33 deletions packages/compas-convert/src/passes/convert-test-files.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import type { CallExpression, SourceFile } from "ts-morph";
import { ts } from "ts-morph";
import type { Context } from "../context.js";
import { addNamedImportIfNotExists } from "../shared/import.js";
import { addPendingImport } from "../shared/imports.js";
import SyntaxKind = ts.SyntaxKind;

/**
* TODO:
* - Handle other usages of "t" like seedTestValuator (not now)
* - Do something with: test("teardown", ... (not now)
* - Handle newTestEvent(t) (not now)
*
* - Handle `seedXyz(t, ...args)`
*/

enum TestCommand {
Expand All @@ -35,7 +34,11 @@ export function convertTestFiles(context: Context, sourceFile: SourceFile) {
return;
}

if (!filePath.includes("/src/") && !filePath.includes("/plugins/")) {
if (
!filePath.includes("/src/") &&
!filePath.includes("/plugins/") &&
!filePath.includes("/test/")
) {
return;
}

Expand All @@ -47,7 +50,11 @@ export function convertTestFiles(context: Context, sourceFile: SourceFile) {
?.remove();

// re-add import for newTestEvent
addNamedImportIfNotExists(sourceFile, "@compas/cli", "newTestEvent", false);
addPendingImport(context, sourceFile, "@compas/cli", "newTestEvent", false);
jurriaanr marked this conversation as resolved.
Show resolved Hide resolved
addPendingImport(context, sourceFile, "vitest", "expect", false);
addPendingImport(context, sourceFile, "vitest", "beforeAll", false);
addPendingImport(context, sourceFile, "vitest", "describe", false);
addPendingImport(context, sourceFile, "vitest", "test", false);

// go over each expression statement in file
for (const statement of sourceFile.getStatements()) {
Expand Down Expand Up @@ -91,9 +98,6 @@ function handleNestedTest(expression: CallExpression) {
if (testIsParent(expression)) {
// the test functions as grouping, so instead we use describe to define a separate suite
expression.getFirstChild()?.replaceWithText("describe");
addNamedImportIfNotExists(expression.getSourceFile(), "vitest", "describe", false);
} else {
addNamedImportIfNotExists(expression.getSourceFile(), "vitest", "test", false);
}

removeTestHandlerParameter(expression);
Expand Down Expand Up @@ -128,7 +132,7 @@ function handleNestedTest(expression: CallExpression) {
const expected = args[1]?.getText() ?? "true";
const message = args[2]?.getText() ?? "";
it.expression = it.expression.replaceWithText(
`expect(${actual}${message ? `, ${message}` : ""}).toStrictEqual(${expected})`,
`expect(${actual}${message ? `, ${message}` : ""}).toEqual(${expected})`,
jurriaanr marked this conversation as resolved.
Show resolved Hide resolved
) as CallExpression;
break;
}
Expand Down Expand Up @@ -167,15 +171,8 @@ function handleNestedTest(expression: CallExpression) {
if (testIsParent(it.expression)) {
// the test functions as grouping, so instead we use describe to define a separate suite
it.expression.getFirstChild()?.replaceWithText("describe");
addNamedImportIfNotExists(
expression.getSourceFile(),
"vitest",
"describe",
false,
);
} else {
it.expression.getFirstChild()?.replaceWithText("test");
addNamedImportIfNotExists(expression.getSourceFile(), "vitest", "test", false);
}

if (!testUsesContext(it)) {
Expand All @@ -187,29 +184,13 @@ function handleNestedTest(expression: CallExpression) {
}
}
}

if (
usage.find((it) =>
[
TestCommand.equal,
TestCommand.deepEqual,
TestCommand.notEqual,
TestCommand.ok,
TestCommand.notOk,
TestCommand.pass,
TestCommand.fail,
].includes(it.command),
)
) {
addNamedImportIfNotExists(expression.getSourceFile(), "vitest", "expect", false);
}
}

/**
* Check if the expression only contains other tests
*/
function testIsParent(expression: CallExpression): boolean {
return getDirectDescendants(expression).every((child) =>
return getDirectDescendants(expression).some((child) =>
jurriaanr marked this conversation as resolved.
Show resolved Hide resolved
child
?.getExpression()
.asKind(SyntaxKind.CallExpression)
Expand Down
2 changes: 2 additions & 0 deletions packages/compas-convert/src/passes/generate-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,11 @@ export async function updateGenerateOptions(context: Context) {
// 5. Add a new command parser
generateCommandFile.addStatements(`
import { newLogger, environment } from "@compas/stdlib";
import { register } from "tsx/esm/api";

// TODO(compas-convert): cleanup this;
environment.NODE_ENV = "development";
register();
const opts = {
logger: newLogger(),
skipLint: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ export async function initTypescriptInProject(context: Context) {
packageJson.devDependencies["vitest"] = "2.0.5";
packageJson.devDependencies["@total-typescript/tsconfig"] = "1.0.4";
packageJson.devDependencies["@types/node"] = "latest";
packageJson.devDependencies["@compas/code-gen"] = "0.15.5";
packageJson.devDependencies["@compas/code-gen"] = "0.15.6";

packageJson.dependencies ??= {};
packageJson.dependencies["@compas/cli"] = "0.15.5";
packageJson.dependencies["@compas/server"] = "0.15.5";
packageJson.dependencies["@compas/stdlib"] = "0.15.5";
packageJson.dependencies["@compas/store"] = "0.15.5";
packageJson.dependencies["@compas/cli"] = "0.15.6";
packageJson.dependencies["@compas/server"] = "0.15.6";
packageJson.dependencies["@compas/stdlib"] = "0.15.6";
packageJson.dependencies["@compas/store"] = "0.15.6";

packageJson.scripts ??= {};
packageJson.scripts["build"] = `tsc -p ./tsconfig.json`;
Expand Down Expand Up @@ -49,10 +49,10 @@ export async function initTypescriptInProject(context: Context) {

export default defineConfig({
test: {
globalSetup: "src/test/config.ts",
setupFiles: "./test/config.ts",
jurriaanr marked this conversation as resolved.
Show resolved Hide resolved
expect: {
requireAssertions: true,
}
},
},
})`,
);
Expand Down
Loading