Tests: Add custom attribute getter tests to the selector module
[jquery.git] / test / runner / createTestServer.js
blob9b9810bcb8bfa97645ec3f5d8da73791183aca0c
1 import http from "node:http";
2 import { readFile, stat } from "node:fs/promises";
3 import { createReadStream } from "node:fs";
4 import mockServer from "../middleware-mockserver.cjs";
5 import getRawBody from "raw-body";
7 export async function createTestServer( report, { quiet } = {} ) {
8         const indexHTML = await readFile( "./test/index.html", "utf8" );
10         // Support connect-style middleware
11         const middlewares = [];
12         function use( middleware ) {
13                 middlewares.push( middleware );
14         }
16         function run( req, res ) {
17                 let i = 0;
19                 // Log responses unless quiet is set
20                 if ( !quiet ) {
21                         const originalEnd = res.end;
22                         res.end = function( ...args ) {
23                                 console.log( `${ req.method } ${ req.url } ${ this.statusCode }` );
24                                 originalEnd.call( this, ...args );
25                         };
26                 }
28                 // Add a parsed URL object to the request object
29                 req.parsedUrl = new URL(
30                         `http://${ process.env.HOST ?? "localhost" }${ req.url }`
31                 );
33                 // Add a simplified redirect helper to the response object
34                 res.redirect = ( status, location ) => {
35                         if ( !location ) {
36                                 location = status;
37                                 status = 303;
38                         }
40                         res.writeHead( status, { Location: location } );
41                         res.end();
42                 };
44                 const next = () => {
45                         const middleware = middlewares[ i++ ];
46                         if ( middleware ) {
47                                 try {
48                                         middleware( req, res, next );
49                                 } catch ( error ) {
50                                         console.error( error );
51                                         res.writeHead( 500, { "Content-Type": "application/json" } );
52                                         res.end( "Internal Server Error" );
53                                 }
54                         } else {
55                                 res.writeHead( 404 );
56                                 res.end();
57                         }
58                 };
60                 next();
61         }
63         // Redirect home to test page
64         use( ( req, res, next ) => {
65                 if ( req.parsedUrl.pathname === "/" ) {
66                         res.redirect( "/test/" );
67                 } else {
68                         next();
69                 }
70         } );
72         // Redirect to trailing slash
73         use( ( req, res, next ) => {
74                 if ( req.parsedUrl.pathname === "/test" ) {
75                         res.redirect( 308, `${ req.parsedUrl.pathname }/${ req.parsedUrl.search }` );
76                 } else {
77                         next();
78                 }
79         } );
81         // Add a script tag to the index.html to load the QUnit listeners
82         use( ( req, res, next ) => {
83                 if (
84                         ( req.method === "GET" || req.method === "HEAD" ) &&
85                         ( req.parsedUrl.pathname === "/test/" ||
86                                 req.parsedUrl.pathname === "/test/index.html" )
87                 ) {
88                         res.writeHead( 200, { "Content-Type": "text/html" } );
89                         res.end(
90                                 indexHTML.replace(
91                                         "</head>",
92                                         "<script src=\"/test/runner/listeners.js\"></script></head>"
93                                 )
94                         );
95                 } else {
96                         next();
97                 }
98         } );
100         // Bind the reporter
101         use( async( req, res, next ) => {
102                 if ( req.url !== "/api/report" || req.method !== "POST" ) {
103                         return next();
104                 }
105                 let body;
106                 try {
107                         body = JSON.parse( await getRawBody( req ) );
108                 } catch ( error ) {
109                         if ( error.code === "ECONNABORTED" ) {
110                                 return;
111                         }
112                         console.error( error );
113                         res.writeHead( 400, { "Content-Type": "application/json" } );
114                         res.end( JSON.stringify( { error: "Invalid JSON" } ) );
115                         return;
116                 }
117                 const response = await report( body );
118                 if ( response ) {
119                         res.writeHead( 200, { "Content-Type": "application/json" } );
120                         res.end( JSON.stringify( response ) );
121                 } else {
122                         res.writeHead( 204 );
123                         res.end();
124                 }
125         } );
127         // Hook up mock server
128         use( mockServer() );
130         // Serve static files
131         const validMimeTypes = {
133                 // No .mjs or .cjs files are used in tests
134                 ".js": "application/javascript",
135                 ".css": "text/css",
136                 ".html": "text/html",
137                 ".xml": "application/xml",
138                 ".xhtml": "application/xhtml+xml",
139                 ".jpg": "image/jpeg",
140                 ".png": "image/png",
141                 ".svg": "image/svg+xml",
142                 ".ico": "image/x-icon",
143                 ".map": "application/json",
144                 ".txt": "text/plain",
145                 ".log": "text/plain"
146         };
147         use( async( req, res, next ) => {
148                 if (
149                         !req.url.startsWith( "/dist/" ) &&
150                         !req.url.startsWith( "/src/" ) &&
151                         !req.url.startsWith( "/test/" ) &&
152                         !req.url.startsWith( "/external/" )
153                 ) {
154                         return next();
155                 }
156                 const file = req.parsedUrl.pathname.slice( 1 );
157                 const ext = file.slice( file.lastIndexOf( "." ) );
159                 // Allow POST to .html files in tests
160                 if (
161                         req.method !== "GET" &&
162                         req.method !== "HEAD" &&
163                         ( ext !== ".html" || req.method !== "POST" )
164                 ) {
165                         return next();
166                 }
167                 const mimeType = validMimeTypes[ ext ];
168                 if ( mimeType ) {
169                         try {
170                                 await stat( file );
171                         } catch ( error ) {
172                                 res.writeHead( 404 );
173                                 res.end();
174                                 return;
175                         }
176                         res.writeHead( 200, { "Content-Type": mimeType } );
177                         createReadStream( file )
178                                 .pipe( res )
179                                 .on( "error", ( error ) => {
180                                         console.error( error );
181                                         res.writeHead( 500 );
182                                         res.end();
183                                 } );
184                 } else {
185                         console.error( `Invalid file extension: ${ ext }` );
186                         res.writeHead( 404 );
187                         res.end();
188                 }
189         } );
191         return http.createServer( run );