Skip to content

Commit 61d78bd

Browse files
authored
test: add initial integration test suite (#371)
* test: add initial integration test harness - some additional ts-jest config is needed to parse `index.ts` - since Rollup isn't used when importing files during testing, we need to enable `esModuleInterop` - the import of `findCacheDir` was immediately erroring without this (and maybe more, but `esModuleInterop` made the import work) - add `tsconfig.test.json` for this purpose - use `ts-jest` JSDoc types to get types for the ts-jest config as well - add an integration/ dir in the __tests__/ dir for all integration tests - add a fixtures/ dir in there as well for integration test fixtures - add a `tsconfig.json` for all fixtures to use - basically so that rpt2 source code is not `include`d, though also bc this may be good to customize for fixtures - add a simple fixture with "no-errors" that does an import and a type-only import - add a simple integration test that just ensures that all files are part of the bundle - the bundled `index` file and each file's declaration and declaration map - and no _other_ files! - for Rollup, need to use paths relative to the root of the whole project - probably because that is `cwd` when running `jest` from root - will probably abstract out helpers here in the future as the test suite grows - 70% coverage of `index.ts` with just this one test! - update CONTRIBUTING.md now that an integration test exists * refactor: use `local` func for path resolution - similar to the unit test suite - also remove the `.only` that I was using during development * add comparison test for cache / no cache - should probably do this for each integration test suite - refactor: split out a `genBundle` func to DRY things up - this was bound to happen eventually - don't think testing other output formats is necessary since we don't have any specific logic for that, so that is just Rollup behavior - take `input` path and rpt2 options as params - and add the `tsconfig` as a default - add a hacky workaround so that Rollup doesn't output a bunch of warnings in the logs - format: use double quotes for strings consistently (my bad, I normally use single quotes in my own repos) * refactor: use a temp cacheRoot - so we can clean it up after testing to ensure consistency - and so we're not filling up the cache in `node_modules` with testing caches - also add a comment to one the of the tests * test: check w/ and w/o declarations + declaration maps - make sure they don't get output when not asked for * fix(test): actually test the cache - `clean: true` also means that no cache is created - which is a bit confusing naming, but was requested by a few users and implemented in f15cb84 - so have to create the bundle one _more_ time to create the cache - note that `tscache`'s `getCached` and `isDirty` functions are now actually covered in the coverage report * fix(ci): increase integration test timeout - double it bc it was occassionally failing on CI due to timeouts * test: ensure that JS files are part of bundle too - ensures that TS understands the JS w/ DTS w/o error - and that rpt2 filters out JS while Rollup still resolves it on its own (since Rollup understands ESM) - similar to testing w/ a different plugin (i.e. literally testing an "integration"), but this is native Rollup behavior in this case where it doesn't need a plugin to understand ESM - also the first test of actual code contents - reformat the cache test a bit into its own block since we now have ~3 different sets of tests in the suite * optim(test): don't need to check cache each time - this doesn't test a new code path and the first test already tests the entire bundle for the fixture, so the other tests just repeat that * test: add initial error checking integration tests - refactor: rename `integration/index.spec` -> `integration/no-errors.spec` - refactor: split `genBundle` func out into a helper file to be used by multiple integration test suites - simplify the `genBundle` within `no-errors` as such - create new `errors` fixture just with some simple code that doesn't type-check for now - add test to check that the exact semantic error pops up - and it even tests colors 😮 ...though I was confused for some time why the strings weren't matching... 😐 - add test to make sure it doesn't pop up when `check: false` or `abortOnError: false` - when `abortOnError: false`, detect that a warning is created instead - due to the new `errors` dir in the fixtures dir, the fixtures `tsconfig` now picks up two dirs - which changes the `rootDir` etc - so create two tiny `tsconfig`s in both fixture dirs that just extend that one - and now tests all run simiarly to before * fix(ci): increase integration test `errors` timeout - this too timed out, probably due to the checks that _don't_ error * optim(test): split non-erroring error tests into a different suite - so that Jest can parallelize these - CI was timing out on this, so splitting it out should help as it'll be 10s each * fix(ci): bump integration test timeout to 15s - bc it was still timing out in some cases 😕 - this time the `no-errors` suite was timing out, so just increase all around * fix(test): output a `.js` bundle, not `.ts` - woooops... was wondering why it was `.ts`; turns out because I wrote the `output.file` setting that way 😅 😅 😅 - also add a missing comment in errors for consistency - and put code checks _after_ file structure checks, since that's a deeper check * test: check that `emitDeclarationOnly` works as expected - should output declaration and declaration map for file - code + declaration should contain the one-line function - code _shouldn't_ contain anything from TS files - since this is plain ESM code, we don't need another plugin to process this - nice workaround to installing another plugin that I hadn't thought of till now! * test: add a syntactic error, refactor a bit - add a file to `errors` with a syntax error in it - apparently this does throw syntax err, but does not cause `emitSkipped: true`... odd... - so may need another test for that... - `abortOnError: false` / `check: false` both cause Rollup to error out instead - rename `errors/index` -> `errors/semantic` since we have different kinds now - change the `include` to only take a single file, so that we don't unnecessarily generate declarations or type-check other error files - refactor(test): rewrite `genBundle` in both files to take a relative path to a file - simplifies the `emitDeclarationOnly` test as we can now just reuse the local `genBundle` instead of calling `helpers.genBundle` directly - use the same structure for `errors`'s different files as well - refactor(test): split `errors.spec` into `tsconfig` errors, semantic errors, and syntactic errors (for now) - add a slightly hacky workaround to get `fs.remove` to not error - seems to be a race condition due to the thrown errors and file handles not being closed immediately on throw (seems like they close during garbage collection instead?) - see linked issue in the comment; workaround is to just give it some more time - not sure that there is a true fix for this, since an improper close may cause indeterminate behavior * fix(test): normalize as arguments to `include` - got a Windows error in CI for the `errors` test suite
1 parent 9c50898 commit 61d78bd

17 files changed

+261
-5
lines changed

.gitignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@
99
*.swp
1010
*.swo
1111
coverage
12-
__tests__/__temp/
12+
__temp

CONTRIBUTING.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ Use the [standard GitHub process](https://docs.github.com/en/pull-requests/colla
3030
1. `npm run test:watch` to run tests in watch mode while developing
3131
1. `npm run test:coverage` to run tests and output a test coverage report
3232

33-
While this repo now has an assortment of unit tests, it still badly needs integration tests with various scenarios and expected outcomes.
34-
Test coverage improvements for existing files and untested is needed as well.
33+
While this repo now has an assortment of unit tests and integration tests, it still needs more integration tests with various scenarios and expected outcomes.
34+
Test coverage improvements for existing files and untested files is needed as well.
3535

3636
### Building and Self-Build
3737

@@ -72,7 +72,7 @@ A useful resource as you dive deeper are the [unit tests](__tests__/). They're g
7272
- [Using the Language Service API](https://github.com/microsoft/TypeScript/wiki/Using-the-Language-Service-API)
7373
- _NOTE_: These are fairly short and unfortunately leave a lot to be desired... especially when you consider that this plugin is actually one of the simpler integrations out there.
7474
1. At this point, you may be ready to read the more complicated bits of [`index`](src/index.ts) in detail and see how it interacts with the other modules.
75-
- The integration tests [TBD] could be useful to review at this point as well.
75+
- The [integration tests](__tests__/integration/) could be useful to review at this point as well.
7676
1. Once you're pretty familiar with `index`, you can dive into some of the cache code in [`tscache`](src/tscache.ts) and [`rollingcache`](src/rollingcache.ts).
7777
1. And finally, you can see some of the Rollup logging nuances in [`context`](src/context.ts) and [`rollupcontext`](src/rollupcontext.ts), and then the TS logging nuances in [`print-diagnostics`](src/print-diagnostics.ts), and [`diagnostics-format-host`](src/diagnostics-format-host.ts)
7878
- While these are necessary to the implementation, they are fairly ancillary to understanding and working with the codebase.

__tests__/integration/errors.spec.ts

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { jest, afterAll, test, expect } from "@jest/globals";
2+
import * as path from "path";
3+
import { normalizePath as normalize } from "@rollup/pluginutils";
4+
import * as fs from "fs-extra";
5+
import { red } from "colors/safe";
6+
7+
import { RPT2Options } from "../../src/index";
8+
import * as helpers from "./helpers";
9+
10+
// increase timeout to 15s for whole file since CI occassionally timed out -- these are integration and cache tests, so longer timeout is warranted
11+
jest.setTimeout(15000);
12+
13+
const local = (x: string) => path.resolve(__dirname, x);
14+
const cacheRoot = local("__temp/errors/rpt2-cache"); // don't use the one in node_modules
15+
const onwarn = jest.fn();
16+
17+
afterAll(async () => {
18+
// workaround: there seems to be some race condition causing fs.remove to fail, so give it a sec first (c.f. https://github.com/jprichardson/node-fs-extra/issues/532)
19+
await new Promise(resolve => setTimeout(resolve, 1000));
20+
await fs.remove(cacheRoot);
21+
});
22+
23+
async function genBundle(relInput: string, extraOpts?: RPT2Options) {
24+
const input = normalize(local(`fixtures/errors/${relInput}`));
25+
return helpers.genBundle({
26+
input,
27+
tsconfig: local("fixtures/errors/tsconfig.json"),
28+
cacheRoot,
29+
extraOpts: { include: [input], ...extraOpts }, // only include the input itself, not other error files (to only generate types and type-check the one file)
30+
onwarn,
31+
});
32+
}
33+
34+
test("integration - tsconfig errors", async () => {
35+
// TODO: move to parse-tsconfig unit tests?
36+
expect(genBundle("semantic.ts", {
37+
tsconfig: "non-existent-tsconfig",
38+
})).rejects.toThrow("rpt2: failed to open 'undefined'"); // FIXME: bug: this should be "non-existent-tsconfig", not "undefined"
39+
});
40+
41+
test("integration - semantic error", async () => {
42+
expect(genBundle("semantic.ts")).rejects.toThrow(`semantic error TS2322: ${red("Type 'string' is not assignable to type 'number'.")}`);
43+
});
44+
45+
test("integration - semantic error - abortOnError: false / check: false", async () => {
46+
// either warning or not type-checking should result in the same bundle
47+
const { output } = await genBundle("semantic.ts", { abortOnError: false });
48+
const { output: output2 } = await genBundle("semantic.ts", { check: false });
49+
expect(output).toEqual(output2);
50+
51+
expect(output[0].fileName).toEqual("index.js");
52+
expect(output[1].fileName).toEqual("semantic.d.ts");
53+
expect(output[2].fileName).toEqual("semantic.d.ts.map");
54+
expect(output.length).toEqual(3); // no other files
55+
expect(onwarn).toBeCalledTimes(1);
56+
});
57+
58+
test("integration - syntax error", () => {
59+
expect(genBundle("syntax.ts")).rejects.toThrow(`syntax error TS1005: ${red("';' expected.")}`);
60+
});
61+
62+
test("integration - syntax error - abortOnError: false / check: false", () => {
63+
const err = "Unexpected token (Note that you need plugins to import files that are not JavaScript)";
64+
expect(genBundle("syntax.ts", { abortOnError: false })).rejects.toThrow(err);
65+
expect(genBundle("syntax.ts", { check: false })).rejects.toThrow(err);
66+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const sum = (a: number, b: number): number => {
2+
return "a + b";
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const incorrectSyntax => {
2+
return "a + b";
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"extends": "../tsconfig.json",
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export function sum(a: number, b: number) {
2+
return a + b;
3+
}
4+
5+
export { difference } from "./some-import"
6+
export type { num } from "./type-only-import"
7+
8+
export { identity } from "./some-js-import"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export function difference(a: number, b: number) {
2+
return a - b;
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// TS needs a declaration in order to understand the import
2+
// but this is ambient, and so should not be directly imported into rpt2, just found by TS
3+
4+
export function identity(a: any): any;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// should be filtered out by rpt2, but still bundled by Rollup itself (as this is ESM, no need for a plugin)
2+
3+
export function identity(a) {
4+
return a;
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"extends": "../tsconfig.json",
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export type num = number;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"compilerOptions": {
3+
"forceConsistentCasingInFileNames": true,
4+
"moduleResolution": "node",
5+
"sourceMap": true,
6+
"declaration": true,
7+
"declarationMap": true,
8+
"target": "ES6",
9+
"strict": true,
10+
},
11+
}

__tests__/integration/helpers.ts

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { rollup, RollupOptions, OutputAsset } from "rollup";
2+
3+
import rpt2, { RPT2Options } from "../../src/index";
4+
5+
type Params = {
6+
input: string,
7+
tsconfig: string,
8+
cacheRoot: string,
9+
extraOpts?: RPT2Options,
10+
onwarn?: RollupOptions['onwarn'],
11+
};
12+
13+
export async function genBundle ({ input, tsconfig, cacheRoot, extraOpts, onwarn }: Params) {
14+
const bundle = await rollup({
15+
input,
16+
plugins: [rpt2({
17+
tsconfig,
18+
cacheRoot,
19+
...extraOpts,
20+
})],
21+
onwarn,
22+
});
23+
24+
const esm = await bundle.generate({
25+
file: "./dist/index.js",
26+
format: "esm",
27+
exports: "named",
28+
});
29+
30+
// Rollup has some deprecated properties like `get isAsset`, so enumerating them with, e.g. `.toEqual`, causes a bunch of warnings to be output
31+
// delete the `isAsset` property for (much) cleaner logs
32+
const { output: files } = esm;
33+
for (const file of files) {
34+
if ("isAsset" in file) {
35+
const optIsAsset = file as Partial<Pick<OutputAsset, "isAsset">> & Omit<OutputAsset, "isAsset">;
36+
delete optIsAsset["isAsset"];
37+
}
38+
}
39+
40+
return esm;
41+
}
+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { jest, afterAll, test, expect } from "@jest/globals";
2+
import * as path from "path";
3+
import * as fs from "fs-extra";
4+
5+
import { RPT2Options } from "../../src/index";
6+
import * as helpers from "./helpers";
7+
8+
// increase timeout to 15s for whole file since CI occassionally timed out -- these are integration and cache tests, so longer timeout is warranted
9+
jest.setTimeout(15000);
10+
11+
const local = (x: string) => path.resolve(__dirname, x);
12+
const cacheRoot = local("__temp/no-errors/rpt2-cache"); // don't use the one in node_modules
13+
14+
afterAll(() => fs.remove(cacheRoot));
15+
16+
async function genBundle(relInput: string, extraOpts?: RPT2Options) {
17+
return helpers.genBundle({
18+
input: local(`fixtures/no-errors/${relInput}`),
19+
tsconfig: local("fixtures/no-errors/tsconfig.json"),
20+
cacheRoot,
21+
extraOpts,
22+
});
23+
}
24+
25+
test("integration - no errors", async () => {
26+
const { output } = await genBundle("index.ts", { clean: true });
27+
28+
// populate the cache
29+
await genBundle("index.ts");
30+
const { output: outputWithCache } = await genBundle("index.ts");
31+
expect(output).toEqual(outputWithCache);
32+
33+
expect(output[0].fileName).toEqual("index.js");
34+
expect(output[1].fileName).toEqual("index.d.ts");
35+
expect(output[2].fileName).toEqual("index.d.ts.map");
36+
expect(output[3].fileName).toEqual("some-import.d.ts");
37+
expect(output[4].fileName).toEqual("some-import.d.ts.map");
38+
expect(output[5].fileName).toEqual("type-only-import.d.ts");
39+
expect(output[6].fileName).toEqual("type-only-import.d.ts.map");
40+
expect(output.length).toEqual(7); // no other files
41+
42+
// JS file should be bundled by Rollup, even though rpt2 does not resolve it (as Rollup natively understands ESM)
43+
expect(output[0].code).toEqual(expect.stringContaining("identity"));
44+
});
45+
46+
test("integration - no errors - no declaration maps", async () => {
47+
const noDeclarationMaps = { compilerOptions: { declarationMap: false } };
48+
const { output } = await genBundle("index.ts", {
49+
tsconfigOverride: noDeclarationMaps,
50+
clean: true,
51+
});
52+
53+
expect(output[0].fileName).toEqual("index.js");
54+
expect(output[1].fileName).toEqual("index.d.ts");
55+
expect(output[2].fileName).toEqual("some-import.d.ts");
56+
expect(output[3].fileName).toEqual("type-only-import.d.ts");
57+
expect(output.length).toEqual(4); // no other files
58+
});
59+
60+
61+
test("integration - no errors - no declarations", async () => {
62+
const noDeclarations = { compilerOptions: { declaration: false, declarationMap: false } };
63+
const { output } = await genBundle("index.ts", {
64+
tsconfigOverride: noDeclarations,
65+
clean: true,
66+
});
67+
68+
expect(output[0].fileName).toEqual("index.js");
69+
expect(output.length).toEqual(1); // no other files
70+
});
71+
72+
test("integration - no errors - allowJs + emitDeclarationOnly", async () => {
73+
const { output } = await genBundle("some-js-import.js", {
74+
include: ["**/*.js"],
75+
tsconfigOverride: {
76+
compilerOptions: {
77+
allowJs: true,
78+
emitDeclarationOnly: true,
79+
},
80+
},
81+
});
82+
83+
expect(output[0].fileName).toEqual("index.js");
84+
expect(output[1].fileName).toEqual("some-js-import.d.ts");
85+
expect(output[2].fileName).toEqual("some-js-import.d.ts.map");
86+
expect(output.length).toEqual(3); // no other files
87+
88+
expect(output[0].code).toEqual(expect.stringContaining("identity"));
89+
expect(output[0].code).not.toEqual(expect.stringContaining("sum")); // no TS files included
90+
expect("source" in output[1] && output[1].source).toEqual(expect.stringContaining("identity"));
91+
});

jest.config.js

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
1-
/** @type {import("@jest/types").Config.InitialOptions} */
1+
/** @type {import("ts-jest").InitialOptionsTsJest} */
22
const config = {
3+
// ts-jest settings
34
preset: "ts-jest",
5+
globals: {
6+
"ts-jest": {
7+
tsconfig: "./tsconfig.test.json",
8+
}
9+
},
10+
11+
// jest settings
412
injectGlobals: false, // use @jest/globals instead
513
restoreMocks: true,
614
// only use *.spec.ts files in __tests__, no auto-generated files

tsconfig.test.json

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"extends": "./tsconfig",
3+
"compilerOptions": {
4+
"esModuleInterop": true, // needed to parse some imports with ts-jest (since Rollup isn't used when importing during testing)
5+
},
6+
}

0 commit comments

Comments
 (0)