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
…4253)

This pr implements the examples for jslib/dependencies.

#3927

Checklist:
- [x] **example/jslib/testing**
     - [x]  3-integration-suite-cypress
     - [x]  3-integration-suite-playwright
  • Loading branch information
monyedavid authored Jan 7, 2025
1 parent b60ad32 commit 31d03bb
Show file tree
Hide file tree
Showing 16 changed files with 437 additions and 2 deletions.
11 changes: 9 additions & 2 deletions docs/modules/ROOT/pages/javascriptlib/testing.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,14 @@ This page will discuss common topics around working with test suites using the M

include::partial$example/javascriptlib/testing/1-test-suite.adoc[]


== Test Dependencies

include::partial$example/javascriptlib/testing/2-test-deps.adoc[]
include::partial$example/javascriptlib/testing/2-test-deps.adoc[]

== Integration Suite with Cypress

include::partial$example/javascriptlib/testing/3-integration-suite-cypress.adoc[]

== Integration Suite with PlayWright

include::partial$example/javascriptlib/testing/3-integration-suite-playwright.adoc[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package build

import mill._, javascriptlib._

object client extends ReactScriptsModule

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")

/** Bundle client as resource */
def resources = Task {
os.copy(client.bundle().path, Task.dest / "build")
super.resources() ++ Seq(PathRef(Task.dest))
}

override def forkEnv = super.forkEnv() + ("PORT" -> "4000")

object test extends TypeScriptTests with TestModule.Cypress {
def service = server
def port = "4000"
}
}

// Documentation for mill.example.javascriptlib
// In this example we demonstrate integration testing using cypress
// `mill server.test` will start the service on the speicifed port, run tests with configurations defined in cypress.config.ts
// and kill the service once completed

/** Usage

> mill server.test
...
...Server listening on port 4000
... app.cy.ts...
... All specs passed!...
...
*/
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react';

function App() {
return (
<div className="App">
<header className="App-header">
<h1 data-testid="heading">Hello, Cypress & PlayWright</h1>
<p>Brought to you by ✨✨mill.✨✨</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}

export default App;
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './app/App';

const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// @ts-nocheck
import { defineConfig } from 'node_modules/cypress';

export default defineConfig({
e2e: {
specPattern: '**/e2e/*.cy.ts',
baseUrl: 'http://localhost:4000',
supportFile: false
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import express, {Express} from 'express';
import cors from 'cors';

const Resources: string = (process.env.RESOURCESDEST || "@server/resources.dest") + "/build" // `RESOURCES` is generated on bundle
const Client = require.resolve(`${Resources}/index.html`);

const app: Express = express();
const port = process.env.PORT || 3001;
const BuildPath = Client.replace(/index\.html$/, "");

app.use(cors());
app.use(express.json());

// Middleware to serve static files from the "build" directory
app.use(express.static(BuildPath));

app.listen(port, () => {
console.log(`Server listening on port ${port}`);
});

export default app;
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
describe('React App', () => {
it('displays the heading', () => {
// Visit the base URL
cy.visit('/');

// Check if the heading is visible and contains "Hello, Cypress!"
cy.get('[data-testid="heading"]').should('be.visible').and('contain.text', 'Hello, Cypress & PlayWright');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package build

import mill._, javascriptlib._

object client extends ReactScriptsModule

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")

/** Bundle client as resource */
def resources = Task {
os.copy(client.bundle().path, Task.dest / "build")
super.resources() ++ Seq(PathRef(Task.dest))
}

def forkEnv = super.forkEnv() + ("PORT" -> "3000")

object test extends TypeScriptTests with TestModule.PlayWright {
def service = server
def port = "6000"
}
}

// Documentation for mill.example.javascriptlib
// In this example we demonstrate integration testing using playwright
// `mill server.test` will start the service on the speicifed port, run tests with configurations defined in playwright.config.ts
// and kill the service once completed

/** Usage

> mill server.test
...
...Server listening on port 6000
...
...1 passed...
...
*/
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react';

function App() {
return (
<div className="App">
<header className="App-header">
<h1 data-testid="heading">Hello, Cypress & PlayWright</h1>
<p>Brought to you by ✨✨mill.✨✨</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}

export default App;
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './app/App';

const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import {defineConfig} from '@playwright/test';
import * as glob from 'node_modules/glob';
import * as path from 'path';

const testFiles = glob.sync('**/playwright/*.test.ts', {absolute: true});

export default defineConfig({
testDir: './',
testMatch: testFiles.map(file => path.relative(process.cwd(), file)),
timeout: 30000,
retries: 1,
use: {
baseURL: 'http://localhost:6000',
headless: true,
trace: 'on-first-retry',
launchOptions: {
args: ['--explicitly-allowed-ports=6000']
},
channel: 'chrome', // Use the stable Chrome channel
},
projects: [
{
name: 'chromium',
use: {browserName: 'chromium'}
}
]
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import express, {Express} from 'express';
import cors from 'cors';

const Resources: string = (process.env.RESOURCESDEST || "@server/resources.dest") + "/build" // `RESOURCES` is generated on bundle
const Client = require.resolve(`${Resources}/index.html`);

const app: Express = express();
const port = process.env.PORT || 3001;
const BuildPath = Client.replace(/index\.html$/, "");

app.use(cors());
app.use(express.json());

// Middleware to serve static files from the "build" directory
app.use(express.static(BuildPath));

app.listen(port, () => {
console.log(`Server listening on port ${port}`);
});

export default app;
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { test, expect } from 'node_modules/@playwright/test';

test.describe('React App', () => {
test('displays the heading', async ({ page }) => {
// Visit the base URL
await page.goto('/');

// Check if the heading is visible
const heading = page.locator('[data-testid="heading"]');
await expect(heading).toBeVisible();
await expect(heading).toHaveText('Hello, Cypress & PlayWright');
});
});
Loading

0 comments on commit 31d03bb

Please sign in to comment.