2021-05-24 17:18:56 -05:00
import fs from 'fs' ;
import path from 'path' ;
import { fileURLToPath } from 'url' ;
2021-06-10 11:30:48 -05:00
import http from 'http' ;
2021-06-11 16:07:30 -05:00
import { green , red } from 'kleur/colors' ;
2021-05-24 17:18:56 -05:00
import execa from 'execa' ;
2021-06-10 11:30:48 -05:00
import glob from 'tiny-glob' ;
2021-06-08 10:12:07 -05:00
import { TEMPLATES } from '../dist/templates.js' ;
2021-05-24 17:18:56 -05:00
2021-06-10 11:30:48 -05:00
// config
const GITHUB _SHA = process . env . GITHUB _SHA || execa . sync ( 'git' , [ 'rev-parse' , 'HEAD' ] ) . stdout ; // process.env.GITHUB_SHA will be set in CI; if testing locally execa() will gather this
const FIXTURES _DIR = path . join ( fileURLToPath ( path . dirname ( import . meta . url ) ) , 'fixtures' ) ;
2021-05-24 17:18:56 -05:00
2021-06-11 16:07:30 -05:00
// helpers
2021-06-10 11:30:48 -05:00
async function fetch ( url ) {
2021-06-11 16:07:30 -05:00
return new Promise ( ( resolve , reject ) => {
2021-06-10 11:30:48 -05:00
http
. get ( url , ( res ) => {
2021-06-11 16:07:30 -05:00
// not OK
if ( res . statusCode !== 200 ) {
reject ( res . statusCode ) ;
return ;
}
// OK
2021-06-10 11:30:48 -05:00
let body = '' ;
res . on ( 'data' , ( chunk ) => {
body += chunk ;
} ) ;
res . on ( 'end' , ( ) => resolve ( { statusCode : res . statusCode , body } ) ) ;
} )
. on ( 'error' , ( err ) => {
2021-06-11 16:07:30 -05:00
// other error
2021-06-10 11:30:48 -05:00
reject ( err ) ;
} ) ;
} ) ;
}
2021-06-11 16:07:30 -05:00
function assert ( a , b , message ) {
if ( a !== b ) throw new Error ( red ( ` ✘ ${ message } ` ) ) ;
// console.log(green(`✔ ${message}`)); // don’ t show successes
}
async function testTemplate ( template ) {
const templateDir = path . join ( FIXTURES _DIR , template ) ;
// test 1: install
const DOES _HAVE = [ '.gitignore' , 'package.json' , 'public' , 'src' ] ;
const DOES _NOT _HAVE = [ '.git' , 'meta.json' ] ;
// test 1a: expect template contains essential files & folders
for ( const file of DOES _HAVE ) {
assert ( fs . existsSync ( path . join ( templateDir , file ) ) , true , ` [ ${ template } ] has ${ file } ` ) ;
}
// test 1b: expect template DOES NOT contain files supposed to be stripped away
for ( const file of DOES _NOT _HAVE ) {
assert ( fs . existsSync ( path . join ( templateDir , file ) ) , false , ` [ ${ template } ] cleaned up ${ file } ` ) ;
}
// test 2: build
const MUST _HAVE _FILES = [ 'index.html' , '_astro' ] ;
await execa ( 'npm' , [ 'run' , 'build' ] , { cwd : templateDir } ) ;
const builtFiles = await glob ( '**/*' , { cwd : path . join ( templateDir , 'dist' ) } ) ;
// test 2a: expect all files built successfully
for ( const file of MUST _HAVE _FILES ) {
assert ( builtFiles . includes ( file ) , true , ` [ ${ template } ] built ${ file } ` ) ;
}
// test 3: dev server (should happen after build so dependency install can be reused)
// TODO: fix dev server test in CI
if ( process . env . CI === true ) {
return ;
}
// start dev server in background & wait until ready
const templateIndex = TEMPLATES . findIndex ( ( { value } ) => value === template ) ;
const port = 3000 + templateIndex ; // use different port per-template
const devServer = execa ( 'npm' , [ 'run' , 'start' , '--' , '--port' , port ] , { cwd : templateDir } ) ;
let sigkill = setTimeout ( ( ) => {
throw new Error ( ` Dev server failed to start ` ) ; // if 10s has gone by with no update, kill process
} , 10000 ) ;
// read stdout until "Server started" appears
await new Promise ( ( resolve , reject ) => {
devServer . stdout . on ( 'data' , ( data ) => {
clearTimeout ( sigkill ) ;
sigkill = setTimeout ( ( ) => {
reject ( ` Dev server failed to start ` ) ;
} , 10000 ) ;
if ( data . toString ( 'utf8' ) . includes ( 'Server started' ) ) resolve ( ) ;
} ) ;
devServer . stderr . on ( 'data' , ( data ) => {
reject ( data . toString ( 'utf8' ) ) ;
} ) ;
} ) ;
clearTimeout ( sigkill ) ; // done!
// send request to dev server that should be ready
const { statusCode , body } = ( await fetch ( ` http://localhost: ${ port } ` ) ) || { } ;
// test 3a: expect 200 status code
assert ( statusCode , 200 , ` [ ${ template } ] 200 response ` ) ;
// test 3b: expect non-empty response
assert ( body . length > 0 , true , ` [ ${ template } ] non-empty response ` ) ;
2021-05-24 17:18:56 -05:00
2021-06-11 16:07:30 -05:00
// clean up
devServer . kill ( ) ;
}
2021-06-10 11:30:48 -05:00
2021-06-11 16:07:30 -05:00
async function testAll ( ) {
// setup
2021-06-10 11:30:48 -05:00
await Promise . all (
TEMPLATES . map ( async ( { value : template } ) => {
2021-06-11 16:07:30 -05:00
// setup: `npm init astro`
await execa ( '../../create-astro.mjs' , [ template , '--template' , template , '--commit' , GITHUB _SHA , '--force-overwrite' ] , {
2021-06-10 11:30:48 -05:00
cwd : FIXTURES _DIR ,
} ) ;
2021-06-11 16:07:30 -05:00
// setup: `npm install` (note: running multiple `yarn`s in parallel in CI will conflict)
await execa ( 'npm' , [ 'install' , '--no-package-lock' , '--silent' ] , { cwd : path . join ( FIXTURES _DIR , template ) } ) ;
2021-06-10 11:30:48 -05:00
} )
) ;
2021-05-24 17:18:56 -05:00
2021-06-11 16:07:30 -05:00
// test (note: not parallelized because Snowpack HMR reuses same port in dev)
for ( let n = 0 ; n < TEMPLATES . length ; n += 1 ) {
const template = TEMPLATES [ n ] . value ;
2021-05-24 17:18:56 -05:00
2021-06-11 16:07:30 -05:00
try {
await testTemplate ( template ) ;
} catch ( err ) {
console . error ( red ( ` ✘ [ ${ template } ] ` ) ) ;
throw err ;
2021-05-24 17:18:56 -05:00
}
2021-06-11 16:07:30 -05:00
console . info ( green ( ` ✔ [ ${ template } ] All tests passed ( ${ n + 1 } / ${ TEMPLATES . length } ) ` ) ) ;
}
2021-05-24 17:18:56 -05:00
}
2021-06-11 16:07:30 -05:00
testAll ( ) ;