Skip to content

Commit

Permalink
Add first class Javascript/Typescript support to the Mill build tool (#…
Browse files Browse the repository at this point in the history
…4098)

#3927

### Checklist
- [x] **example/jslib/testing**
    - [x] 1-test-suite
    - [x] 2-test-deps
  • Loading branch information
monyedavid authored Dec 13, 2024
1 parent 0d879c5 commit da37921
Show file tree
Hide file tree
Showing 32 changed files with 606 additions and 120 deletions.
12 changes: 3 additions & 9 deletions example/javascriptlib/basic/1-simple/build.mill
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ package build

import mill._, javascriptlib._

object foo extends JestModule {
object foo extends TypeScriptModule {
def npmDeps = Seq("[email protected]")

object test extends TypeScriptTests with TestModule.Jest
}

// Documentation for mill.example.javascriptlib
Expand All @@ -15,16 +15,10 @@ Hello James Bond Professor

> mill foo.test
PASS .../foo.test.ts
...generateUser function
...should generate a user with all specified fields...
...should default lastName and role when they are not provided...
...should default all fields when args is empty...
...
Test Suites:...1 passed, 1 total...
Tests:...3 passed, 3 total...
Snapshots:...
Time:...
Ran all test suites matching ...
...

> mill show foo.bundle
Build succeeded!
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {generateUser, defaultRoles} from "../src/foo";
import {generateUser, defaultRoles} from "foo/foo";
import {Map} from 'node_modules/immutable';

// Define the type roles object
Expand Down
25 changes: 22 additions & 3 deletions example/javascriptlib/basic/1-simple/jest.config.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,32 @@
// @ts-nocheck
import {pathsToModuleNameMapper} from 'node_modules/ts-jest';
import {compilerOptions} from './tsconfig.json'; // this is a generated file.

// Remove unwanted keys
const moduleDeps = {...compilerOptions.paths};
delete moduleDeps['*'];
delete moduleDeps['typeRoots'];

// moduleNameMapper evaluates in order they appear,
// sortedModuleDeps makes sure more specific path mappings always appear first
const sortedModuleDeps = Object.keys(moduleDeps)
.sort((a, b) => b.length - a.length) // Sort by descending length
.reduce((acc, key) => {
acc[key] = moduleDeps[key];
return acc;
}, {});

export default {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: [
'<rootDir>/**/test/**/*.test.ts',
'<rootDir>/**/test/**/*.test.js',
'<rootDir>/**/**/**/*.test.ts',
'<rootDir>/**/**/**/*.test.js',
],
transform: {
'^.+\\.(ts|tsx)$': ['ts-jest', { tsconfig: 'tsconfig.json' }],
'^.+\\.(js|jsx)$': 'babel-jest', // Use babel-jest for JS/JSX files
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node']
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
moduleNameMapper: pathsToModuleNameMapper(sortedModuleDeps) // use absolute paths generated in tsconfig.
};
11 changes: 4 additions & 7 deletions example/javascriptlib/basic/3-custom-build-logic/build.mill
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package build

import mill._, javascriptlib._

object foo extends JestModule {
object foo extends TypeScriptModule {

/** Total number of lines in module source files */
def lineCount = Task {
Expand All @@ -20,6 +20,8 @@ object foo extends JestModule {
super.mkENV() ++ Map("RESOURCE_PATH" -> resources().path.toString)
}

object test extends TypeScriptTests with TestModule.Jest

}

// Documentation for mill.example.javascriptlib
Expand All @@ -28,15 +30,10 @@ object foo extends JestModule {

> mill foo.test
PASS .../foo.test.ts
...Foo.getLineCount
...should return the content of the line-count.txt file...
...should return null if the file cannot be read...
...
Test Suites:...1 passed, 1 total...
Tests:...2 passed, 2 total...
Snapshots:...
Time:...
Ran all test suites matching...
...

> mill foo.run
[Reading file:] .../out/foo/resources.dest/line-count.txt
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as fs from 'fs';
import * as path from 'path';
import Foo from '../src/foo';
import Foo from 'foo/foo';

// Mock the 'fs' module
jest.mock('fs');
Expand Down
28 changes: 23 additions & 5 deletions example/javascriptlib/basic/3-custom-build-logic/jest.config.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,32 @@
// @ts-nocheck
import {pathsToModuleNameMapper} from 'node_modules/ts-jest';
import {compilerOptions} from './tsconfig.json'; // this is a generated file.

// Remove unwanted keys
const moduleDeps = {...compilerOptions.paths};
delete moduleDeps['*'];
delete moduleDeps['typeRoots'];

// moduleNameMapper evaluates in order they appear,
// sortedModuleDeps makes sure more specific path mappings always appear first
const sortedModuleDeps = Object.keys(moduleDeps)
.sort((a, b) => b.length - a.length) // Sort by descending length
.reduce((acc, key) => {
acc[key] = moduleDeps[key];
return acc;
}, {});

export default {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: [
'<rootDir>/**/test/**/*.test.ts',
'<rootDir>/**/test/**/*.test.js',
'<rootDir>/**/**/**/*.test.ts',
'<rootDir>/**/**/**/*.test.js',
],
transform: {
'^.+\\.(ts|tsx)$': ['ts-jest', {tsconfig: 'tsconfig.json'}],
'^.+\\.(js|jsx)$': 'babel-jest',
'^.+\\.(ts|tsx)$': ['ts-jest', { tsconfig: 'tsconfig.json' }],
'^.+\\.(js|jsx)$': 'babel-jest', // Use babel-jest for JS/JSX files
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
moduleNameMapper: {}
moduleNameMapper: pathsToModuleNameMapper(sortedModuleDeps) // use absolute paths generated in tsconfig.
};
3 changes: 2 additions & 1 deletion example/javascriptlib/basic/4-multi-modules/build.mill
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ object foo extends TypeScriptModule {

}

object qux extends JestModule {
object qux extends TypeScriptModule {
def moduleDeps = Seq(foo, foo.bar)
object test extends TypeScriptTests with TestModule.Jest
}

// Documentation for mill.example.javascriptlib
Expand Down
4 changes: 2 additions & 2 deletions example/javascriptlib/basic/4-multi-modules/jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ export default {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: [
'<rootDir>/**/test/**/*.test.ts',
'<rootDir>/**/test/**/*.test.js',
'<rootDir>/**/**/**/*.test.ts',
'<rootDir>/**/**/**/*.test.js',
],
transform: {
'^.+\\.(ts|tsx)$': ['ts-jest', { tsconfig: 'tsconfig.json' }],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { generateUser } from "../src/qux";
import { generateUser } from "qux/qux";

// Define the type roles object
type RoleKeys = "admin" | "user";
Expand Down
7 changes: 6 additions & 1 deletion example/javascriptlib/basic/5-client-server-hello/build.mill
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import mill._, javascriptlib._

object client extends ReactScriptsModule

object server extends JestModule {
object server extends TypeScriptModule {
override def mkENV = Task {
super.mkENV() ++ Map("PORT" -> "3000")
}
Expand All @@ -13,6 +13,8 @@ object server extends JestModule {
os.copy(clientBundle, Task.dest / "build")
PathRef(Task.dest)
}

object test extends TypeScriptTests with TestModule.Jest
}

// Documentation for mill.example.javascriptlib
Expand All @@ -33,6 +35,9 @@ PASS .../App.test.tsx
...
PASS .../server.test.ts
...
Test Suites:...1 passed, 1 total...
Tests:...1 passed, 1 total...
...

> mill show server.bundle # bundle the express server
Build succeeded!
Expand Down
25 changes: 22 additions & 3 deletions example/javascriptlib/basic/5-client-server-hello/jest.config.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,32 @@
// @ts-nocheck
import {pathsToModuleNameMapper} from 'node_modules/ts-jest';
import {compilerOptions} from './tsconfig.json'; // this is a generated file.

// Remove unwanted keys
const moduleDeps = {...compilerOptions.paths};
delete moduleDeps['*'];
delete moduleDeps['typeRoots'];

// moduleNameMapper evaluates in order they appear,
// sortedModuleDeps makes sure more specific path mappings always appear first
const sortedModuleDeps = Object.keys(moduleDeps)
.sort((a, b) => b.length - a.length) // Sort by descending length
.reduce((acc, key) => {
acc[key] = moduleDeps[key];
return acc;
}, {});

export default {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: [
'<rootDir>/**/test/**/*.test.ts',
'<rootDir>/**/test/**/*.test.js',
'<rootDir>/**/**/**/*.test.ts',
'<rootDir>/**/**/**/*.test.js',
],
transform: {
'^.+\\.(ts|tsx)$': ['ts-jest', {tsconfig: 'tsconfig.json'}],
'^.+\\.(ts|tsx)$': ['ts-jest', { tsconfig: 'tsconfig.json' }],
'^.+\\.(js|jsx)$': 'babel-jest', // Use babel-jest for JS/JSX files
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
moduleNameMapper: pathsToModuleNameMapper(sortedModuleDeps) // use absolute paths generated in tsconfig.
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ describe('Server Tests', () => {
let server: http.Server;

beforeAll(() => {
server = require('../src/server').default;
server = require('server/server').default;
process.env.NODE_ENV = "test";
});

Expand Down
11 changes: 8 additions & 3 deletions example/javascriptlib/basic/6-client-server-realistic/build.mill
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,13 @@ import mill._, javascriptlib._

object client extends ReactScriptsModule

object server extends JestModule {
object server extends TypeScriptModule {
def npmDeps =
Seq("@types/cors@^2.8.17", "@types/express@^5.0.0", "cors@^2.8.5", "express@^4.21.1")

def npmDevDeps =
super.npmDevDeps() ++ Seq("@types/supertest@^6.0.2", "supertest@^7.0.0")

override def testConfigSource = Task.Source(millSourcePath / "jest.config.ts")

override def bundleFlags = Map("external" -> Seq("express", "cors"))

override def mkENV = Task {
Expand All @@ -23,6 +21,10 @@ object server extends JestModule {
os.copy(clientBundle, Task.dest / "build")
PathRef(Task.dest)
}

object test extends TypeScriptTests with TestModule.Jest {
override def testConfigSource = Task.Source(millSourcePath / os.up / "jest.config.ts")
}
}

// Documentation for mill.example.javascriptlib
Expand All @@ -37,6 +39,9 @@ object server extends JestModule {
> mill server.test
PASS .../server.test.ts
...
Test Suites:...1 passed, 1 total...
Tests:...3 passed, 3 total...
...

> mill show server.bundle # bundle the express server
Build succeeded!
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,32 @@
// @ts-nocheck
import {pathsToModuleNameMapper} from 'node_modules/ts-jest';
import {compilerOptions} from './tsconfig.json'; // this is a generated file.

// Remove unwanted keys
const moduleDeps = {...compilerOptions.paths};
delete moduleDeps['*'];
delete moduleDeps['typeRoots'];

// moduleNameMapper evaluates in order they appear,
// sortedModuleDeps makes sure more specific path mappings always appear first
const sortedModuleDeps = Object.keys(moduleDeps)
.sort((a, b) => b.length - a.length) // Sort by descending length
.reduce((acc, key) => {
acc[key] = moduleDeps[key];
return acc;
}, {});

export default {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: [
'<rootDir>/**/test/**/*.test.ts',
'<rootDir>/**/test/**/*.test.js',
'<rootDir>/**/**/**/*.test.ts',
'<rootDir>/**/**/**/*.test.js',
],
transform: {
'^.+\\.(ts|tsx)$': ['ts-jest', {tsconfig: 'tsconfig.json'}],
'^.+\\.(ts|tsx)$': ['ts-jest', { tsconfig: 'tsconfig.json' }],
'^.+\\.(js|jsx)$': 'babel-jest', // Use babel-jest for JS/JSX files
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node']
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
moduleNameMapper: pathsToModuleNameMapper(sortedModuleDeps) // use absolute paths generated in tsconfig.
};
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ describe('Server Tests', () => {
beforeAll((done) => {
process.env.PORT = '3002';
process.env.NODE_ENV = 'test';
app = require('../src/server').default;
app = require('server/server').default;
server = app.listen(process.env.PORT, done);
});

Expand Down
12 changes: 12 additions & 0 deletions example/javascriptlib/testing/1-test-suite/bar/src/calculator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export class Calculator {
add(a: number, b: number): number {
return a + b;
}

divide(a: number, b: number): number {
if (b === 0) {
throw new Error("Division by zero is not allowed");
}
return a / b;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {Calculator} from 'bar/calculator';

describe('Calculator', () => {
const calculator = new Calculator();

describe('Addition', () => {
test('should return the sum of two numbers', () => {
const result = calculator.add(2, 3);
expect(result).toEqual(5);
});

test('should return the correct sum for negative numbers', () => {
const result = calculator.add(-2, -3);
expect(result).toEqual(-5);
});
});

describe('Division', () => {
test('should return the quotient of two numbers', () => {
const result = calculator.divide(6, 3);
expect(result).toEqual(2);
});

it('should throw an error when dividing by zero', () => {
expect(() => calculator.divide(6, 0)).toThrow("Division by zero is not allowed");
});
});
});
Loading

0 comments on commit da37921

Please sign in to comment.