Skip to content

Commit bb4d160

Browse files
Upgrade runner for type-tests (#73)
* Upgrade runner for type-tests wip * Make .ts import work again * Fix fallback check, fix tsconfig unlinking * Clean-up formatting * Implement tstyche support * Use tsconfig.solutions.json * Update expected results * Inline the config files * Support for skipping only when the exercise allows it * Add flags to todos
1 parent bb05b2a commit bb4d160

File tree

26 files changed

+640
-103
lines changed

26 files changed

+640
-103
lines changed

bin/run.sh

Lines changed: 392 additions & 95 deletions
Large diffs are not rendered by default.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
"prepublish": "corepack yarn test:bare && corepack yarn lint",
2525
"lint": "corepack yarn eslint src -c eslint.config.mjs && corepack yarn eslint test -c eslint.config.mjs",
2626
"test": "corepack yarn build && corepack yarn test:bare",
27-
"test:bare": "corepack yarn node test/smoke.test.mjs && corepack yarn node test/skip.test.mjs && corepack yarn node test/import.test.mjs"
27+
"test:bare": "corepack yarn node test/smoke.test.mjs && corepack yarn node test/skip.test.mjs && corepack yarn node test/import.test.mjs && corepack yarn node test/types.test.mjs"
2828
},
2929
"dependencies": {
3030
"@exercism/babel-preset-typescript": "^0.5.0",
@@ -40,6 +40,7 @@
4040
"core-js": "^3.37.1",
4141
"jest": "^29.7.0",
4242
"shelljs": "^0.8.5",
43+
"tstyche": "^2.1.1",
4344
"typescript": "~5.5.4"
4445
},
4546
"devDependencies": {

src/output.ts

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ type ExerciseConfig = {
3131
'flag.tests.task-per-describe': boolean
3232
'flag.tests.may-run-long': boolean
3333
'flag.tests.includes-optional': boolean
34+
'flag.tests.jest'?: boolean
35+
'flag.tests.tstyche'?: boolean
3436
}
3537
}
3638

@@ -66,11 +68,9 @@ export class Output {
6668
this.results.status =
6769
aggregatedResults.numRuntimeErrorTestSuites === 0 &&
6870
aggregatedResults.numFailedTestSuites === 0 &&
69-
// Pending tests are skipped tests. test.skip tests are fine in our
70-
// reporter and should not be forced to have ran here. So the next
71-
// line is commented out.
71+
// Pending tests are skipped tests. test.skip tests are fine if the
72+
// exercise reports that there are optional tests.
7273
//
73-
// aggregatedResults.numPendingTests === 0 &&
7474
aggregatedResults.numFailedTests === 0
7575
? 'pass'
7676
: 'fail'
@@ -88,6 +88,24 @@ export class Output {
8888
'an issue if the problem persists.'
8989
)
9090
}
91+
92+
if (
93+
this.results.status === 'pass' &&
94+
(aggregatedResults.numPendingTests !== 0 ||
95+
aggregatedResults.numPendingTestSuites !== 0)
96+
) {
97+
if (
98+
!this.configFlag('flag.tests.includes-optional') &&
99+
this.config?.custom
100+
) {
101+
this.results.status = 'fail'
102+
this.error(
103+
'Expected to see 0 skipped tests and 0 skipped test suites. ' +
104+
'None of the tests in this exercise are optional. The skipped ' +
105+
'tests will not show up, but were found during the last run.'
106+
)
107+
}
108+
}
91109
}
92110

93111
const parsedSources: Record<
File renamed without changes.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"authors": [
3+
"SleeplessByte"
4+
],
5+
"files": {
6+
"solution": [
7+
],
8+
"test": [
9+
"__typetests__/docs.tst.ts"
10+
],
11+
"example": [
12+
]
13+
}
14+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { expect, test } from "tstyche";
2+
3+
function firstItem<T>(target: Array<T>): T | undefined {
4+
return target[0];
5+
}
6+
7+
test("first item requires a parameter", () => {
8+
expect(firstItem(["a", "b", "c"])).type.toBe<string | undefined>();
9+
10+
expect(firstItem()).type.toRaiseError("Expected 1 argument");
11+
});
12+
13+
function secondItem<T>(target: Array<T>): T | undefined {
14+
return target[1];
15+
}
16+
17+
test("handles numbers", () => {
18+
expect(secondItem([1, 2, 3])).type.toBe<number | undefined>();
19+
});
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"extends": "../tsconfig.json",
3+
"compilerOptions": {
4+
"noEmit": true,
5+
"strict": true,
6+
"types": []
7+
},
8+
"include": ["./"],
9+
"exclude": []
10+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export {}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"compilerOptions": {
3+
"target": "es2016",
4+
"module": "commonjs",
5+
"forceConsistentCasingInFileNames": true,
6+
"strict": true,
7+
"skipLibCheck": true
8+
},
9+
"exclude": ["./__typetests__"]
10+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"authors": [
3+
"SleeplessByte"
4+
],
5+
"files": {
6+
"solution": [
7+
"fire.ts"
8+
],
9+
"test": [
10+
"__typetests__/fire.tst.ts"
11+
],
12+
"example": [
13+
]
14+
}
15+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { test, expect, describe } from 'tstyche'
2+
import type { MethodLikeKeys } from '../fire.js'
3+
4+
interface Sample {
5+
description: string
6+
getLength: () => number
7+
getWidth?: () => number
8+
}
9+
10+
describe('fire', () => {
11+
test('all method keys are found', () => {
12+
expect<MethodLikeKeys<Sample>>().type.toBe<'getLength' | 'getWidth'>()
13+
})
14+
})
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"extends": "../tsconfig.json",
3+
"compilerOptions": {
4+
"noEmit": true,
5+
"strict": true,
6+
"types": []
7+
},
8+
"include": ["./"],
9+
"exclude": []
10+
}

test/fixtures/tstyche/fire/fire.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
type MethodLike = (...args: any) => any;
2+
3+
export type MethodLikeKeys<T> = keyof {
4+
[K in keyof T as T[K] extends MethodLike ? K : never]: T[K];
5+
};
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"compilerOptions": {
3+
"target": "es2016",
4+
"module": "commonjs",
5+
"forceConsistentCasingInFileNames": true,
6+
"strict": true,
7+
"skipLibCheck": true
8+
},
9+
"exclude": ["./__typetests__"]
10+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"authors": [
3+
"SleeplessByte"
4+
],
5+
"files": {
6+
"solution": [
7+
"fire.ts"
8+
],
9+
"test": [
10+
"__typetests__/fire.tst.ts"
11+
],
12+
"example": [
13+
]
14+
}
15+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { describe, test, expect } from "tstyche";
2+
import type { MethodLikeKeys } from "../fire.js";
3+
4+
interface Sample {
5+
description: string;
6+
getLength: () => number;
7+
getWidth?: () => number;
8+
}
9+
10+
describe('fire', () => {
11+
test('all method keys are found', () => {
12+
expect<MethodLikeKeys<Sample>>().type.toBe<"getLength" | "getWidth">();
13+
})
14+
})
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"extends": "../tsconfig.json",
3+
"compilerOptions": {
4+
"noEmit": true,
5+
"strict": true,
6+
"types": []
7+
},
8+
"include": ["./"],
9+
"exclude": []
10+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
type MethodLike = (...args: any) => any;
2+
3+
export type MethodLikeKeys<T> = keyof {
4+
[K in keyof T as Required<T>[K] extends MethodLike ? K : never]: T[K];
5+
};
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"compilerOptions": {
3+
"target": "es2016",
4+
"module": "commonjs",
5+
"forceConsistentCasingInFileNames": true,
6+
"strict": true,
7+
"skipLibCheck": true
8+
},
9+
"exclude": ["./__typetests__"]
10+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{ "version": 1, "status": "error", "message": "The submitted code didn't compile. We have collected the errors encountered during compilation. At this moment the error messages are not very read-friendly, but it's a start. We are working on a more helpful output.\n-------------------------------\ntwo-fer.ts(1,14): error TS1389: 'const' is not allowed as a variable declaration name.\ntwo-fer.ts(1,20): error TS1134: Variable declaration expected.\ntwo-fer.ts(1,29): error TS1109: Expression expected.\ntwo-fer.ts(2,1): error TS1005: ')' expected." }
1+
{ "version": 1, "status": "error", "message": "The submitted code didn't compile. We have collected the errors encountered during compilation. At this moment the error messages are not very read-friendly, but it's a start. We are working on a more helpful output.\n-------------------------------\ntwo-fer.ts(1,14): error TS1389: 'const' is not allowed as a variable declaration name.\ntwo-fer.ts(1,20): error TS1134: Variable declaration expected.\ntwo-fer.ts(1,29): error TS1109: Expression expected.\ntwo-fer.ts(2,1): error TS1005: ')' expected.\n" }

test/smoke.test.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ assertPass(
1212
)
1313

1414
shelljs.echo(
15-
'typescript-test-runner > passing solution > with output directory'
15+
'typescript-test-runner > failing solution > with output directory'
1616
)
17-
assertPass('clock', join(fixtures, 'clock', 'pass'))
17+
assertError('clock', join(fixtures, 'clock', 'fail'))
1818

1919
/** Test failures */
2020
const failures = ['tests', 'empty']

test/types.test.mjs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { join } from 'node:path'
2+
import shelljs from 'shelljs'
3+
import { assertError, assertPass } from './asserts.mjs'
4+
import { fixtures } from './paths.mjs'
5+
6+
shelljs.echo('type tests (only) > documentation solution (smoke test)')
7+
assertPass('tstyche', join(fixtures, 'tstyche', 'documentation'))
8+
9+
shelljs.echo('type tests (only) > failing solution')
10+
assertError('tstyche', join(fixtures, 'tstyche', 'fire'))
11+
12+
shelljs.echo('type tests (only) > passing solution')
13+
assertPass('tstyche', join(fixtures, 'tstyche', 'firefought'))

tsconfig.solutions.json

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"extends": "@tsconfig/node20/tsconfig.json",
3+
"compilerOptions": {
4+
// Allows you to use the newest syntax, and have access to console.log
5+
// https://www.typescriptlang.org/tsconfig#lib
6+
"lib": ["ES2020", "dom"],
7+
// Make sure typescript is configured to output ESM
8+
// https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm
9+
"module": "Node16",
10+
// Since this project is using babel, TypeScript may target something very
11+
// high, and babel will make sure it runs on your local Node version.
12+
// https://babeljs.io/docs/en/
13+
"target": "ES2020", // ESLint doesn't support this yet: "es2022",
14+
15+
"strict": true,
16+
"esModuleInterop": true,
17+
"skipLibCheck": true,
18+
"forceConsistentCasingInFileNames": true,
19+
20+
// Because jest-resolve isn't like node resolve, the absolute path must be .ts
21+
"allowImportingTsExtensions": true,
22+
"noEmit": true,
23+
24+
// Because we'll be using babel: ensure that Babel can safely transpile
25+
// files in the TypeScript project.
26+
//
27+
// https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats
28+
"isolatedModules": true
29+
},
30+
"exclude": [".meta/*", "__typetests__/*", "*.test.ts", "*.tst.ts"]
31+
}

yarn.lock

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1546,6 +1546,7 @@ __metadata:
15461546
prettier: "npm:^3.3.3"
15471547
rimraf: "npm:^6.0.1"
15481548
shelljs: "npm:^0.8.5"
1549+
tstyche: "npm:^2.1.1"
15491550
typescript: "npm:~5.5.4"
15501551
bin:
15511552
typescript-test-runner: bin/run.sh
@@ -6274,6 +6275,20 @@ __metadata:
62746275
languageName: node
62756276
linkType: hard
62766277

6278+
"tstyche@npm:^2.1.1":
6279+
version: 2.1.1
6280+
resolution: "tstyche@npm:2.1.1"
6281+
peerDependencies:
6282+
typescript: 4.x || 5.x
6283+
peerDependenciesMeta:
6284+
typescript:
6285+
optional: true
6286+
bin:
6287+
tstyche: ./build/bin.js
6288+
checksum: 10/f30e7d782e51c262528ededf383c9daf39af8dea063d483667e3ff9f4800434891589c294c4b4f69802dd06daf8fb1d2a10553316d2f4631ba1413d3e48dab81
6289+
languageName: node
6290+
linkType: hard
6291+
62776292
"type-check@npm:^0.4.0, type-check@npm:~0.4.0":
62786293
version: 0.4.0
62796294
resolution: "type-check@npm:0.4.0"

0 commit comments

Comments
 (0)