mirror of
https://github.com/withastro/astro.git
synced 2024-12-30 22:03:56 -05:00
Improve templates test (#377)
This commit is contained in:
parent
e7b5ad362c
commit
9d3cd49410
5 changed files with 131 additions and 96 deletions
19
.github/workflows/nodejs.yml
vendored
19
.github/workflows/nodejs.yml
vendored
|
@ -70,4 +70,21 @@ jobs:
|
||||||
- run: yarn test
|
- run: yarn test
|
||||||
env:
|
env:
|
||||||
CI: true
|
CI: true
|
||||||
GITHUB_SHA: $GITHUB_SHA
|
# test-templates:
|
||||||
|
# if: ${{ github.head_ref }} == "changeset-release/main" # only run on version PRs (this test is too slow to run every PR)
|
||||||
|
# runs-on: ubuntu-latest
|
||||||
|
# steps:
|
||||||
|
# - uses: actions/checkout@v2
|
||||||
|
# - uses: actions/setup-node@v2
|
||||||
|
# with:
|
||||||
|
# node-version: 14.x
|
||||||
|
# - run: yarn --frozen-lockfile --ignore-engines
|
||||||
|
# env:
|
||||||
|
# CI: true
|
||||||
|
# - run: yarn build
|
||||||
|
# env:
|
||||||
|
# CI: true
|
||||||
|
# - run: yarn test:templates
|
||||||
|
# env:
|
||||||
|
# GITHUB_SHA: $GITHUB_SHA
|
||||||
|
# CI: true
|
||||||
|
|
|
@ -7,8 +7,7 @@
|
||||||
"build": "astro build"
|
"build": "astro build"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"astro": "^0.13.0",
|
"astro": "^0.13.0"
|
||||||
"nodemon": "^2.0.7"
|
|
||||||
},
|
},
|
||||||
"snowpack": {
|
"snowpack": {
|
||||||
"workspaceRoot": "../.."
|
"workspaceRoot": "../.."
|
||||||
|
|
|
@ -14,7 +14,8 @@
|
||||||
"format": "prettier -w \"**/*.{js,jsx,ts,tsx,md,json}\"",
|
"format": "prettier -w \"**/*.{js,jsx,ts,tsx,md,json}\"",
|
||||||
"lint": "eslint \"packages/**/*.ts\"",
|
"lint": "eslint \"packages/**/*.ts\"",
|
||||||
"test": "lerna run test --scope astro --stream",
|
"test": "lerna run test --scope astro --stream",
|
||||||
"test:core": "yarn workspace astro run test"
|
"test:core": "yarn workspace astro run test",
|
||||||
|
"test:templates": "lerna run test --scope create-astro --stream"
|
||||||
},
|
},
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/renderers/*",
|
"packages/renderers/*",
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "astro-scripts build \"src/*.ts\"",
|
"build": "astro-scripts build \"src/*.ts\"",
|
||||||
"prepare": "yarn build",
|
"prepare": "yarn build",
|
||||||
"test": "uvu -i test/fixtures"
|
"test": "rm -rf test/fixtures && mkdir test/fixtures && node --unhandled-rejections=strict test/create-astro.test.js"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist",
|
"dist",
|
||||||
|
|
|
@ -2,24 +2,27 @@ import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
import http from 'http';
|
import http from 'http';
|
||||||
import { suite } from 'uvu';
|
import { green, red } from 'kleur/colors';
|
||||||
import execa from 'execa';
|
import execa from 'execa';
|
||||||
import del from 'del';
|
|
||||||
import glob from 'tiny-glob';
|
import glob from 'tiny-glob';
|
||||||
import * as assert from 'uvu/assert';
|
|
||||||
import { TEMPLATES } from '../dist/templates.js';
|
import { TEMPLATES } from '../dist/templates.js';
|
||||||
|
|
||||||
// config
|
// 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 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 MAX_TEST_TIME = 60000; // maximum time a test may take (60s)
|
|
||||||
const TIMER = {}; // keep track of every test’s run time (uvu requires manual setup for this)
|
|
||||||
const FIXTURES_DIR = path.join(fileURLToPath(path.dirname(import.meta.url)), 'fixtures');
|
const FIXTURES_DIR = path.join(fileURLToPath(path.dirname(import.meta.url)), 'fixtures');
|
||||||
|
|
||||||
// helper
|
// helpers
|
||||||
async function fetch(url) {
|
async function fetch(url) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve, reject) => {
|
||||||
http
|
http
|
||||||
.get(url, (res) => {
|
.get(url, (res) => {
|
||||||
|
// not OK
|
||||||
|
if (res.statusCode !== 200) {
|
||||||
|
reject(res.statusCode);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// OK
|
||||||
let body = '';
|
let body = '';
|
||||||
res.on('data', (chunk) => {
|
res.on('data', (chunk) => {
|
||||||
body += chunk;
|
body += chunk;
|
||||||
|
@ -27,94 +30,109 @@ async function fetch(url) {
|
||||||
res.on('end', () => resolve({ statusCode: res.statusCode, body }));
|
res.on('end', () => resolve({ statusCode: res.statusCode, body }));
|
||||||
})
|
})
|
||||||
.on('error', (err) => {
|
.on('error', (err) => {
|
||||||
|
// other error
|
||||||
reject(err);
|
reject(err);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// test
|
function assert(a, b, message) {
|
||||||
const CreateAstro = suite('npm init astro');
|
if (a !== b) throw new Error(red(`✘ ${message}`));
|
||||||
|
// console.log(green(`✔ ${message}`)); // don’t show successes
|
||||||
CreateAstro.before(async () => {
|
|
||||||
// clean install dir
|
|
||||||
await del(FIXTURES_DIR);
|
|
||||||
await fs.promises.mkdir(FIXTURES_DIR);
|
|
||||||
|
|
||||||
// install all templates & deps before running tests
|
|
||||||
await Promise.all(
|
|
||||||
TEMPLATES.map(async ({ value: template }) => {
|
|
||||||
const templateDir = path.join(FIXTURES_DIR, template);
|
|
||||||
await execa('../../create-astro.mjs', [templateDir, '--template', template, '--commit', GITHUB_SHA, '--force-overwrite'], {
|
|
||||||
cwd: FIXTURES_DIR,
|
|
||||||
});
|
|
||||||
await execa('yarn', ['--frozen-lockfile', '--silent'], { cwd: templateDir });
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// enforce MAX_TEST_TIME
|
|
||||||
CreateAstro.before.each(({ __test__ }) => {
|
|
||||||
if (TIMER[__test__]) throw new Error(`Test "${__test__}" already declared`);
|
|
||||||
TIMER[__test__] = setTimeout(() => {
|
|
||||||
throw new Error(`"${__test__}" did not finish within allowed time`);
|
|
||||||
}, MAX_TEST_TIME);
|
|
||||||
});
|
|
||||||
CreateAstro.after.each(({ __test__ }) => {
|
|
||||||
clearTimeout(TIMER[__test__]);
|
|
||||||
});
|
|
||||||
|
|
||||||
for (let n = 0; n < TEMPLATES.length; n++) {
|
|
||||||
const template = TEMPLATES[n].value;
|
|
||||||
const templateDir = path.join(FIXTURES_DIR, template);
|
|
||||||
|
|
||||||
CreateAstro(`${template} (install)`, async () => {
|
|
||||||
const DOES_HAVE = ['.gitignore', 'package.json', 'public', 'src'];
|
|
||||||
const DOES_NOT_HAVE = ['.git', 'meta.json'];
|
|
||||||
|
|
||||||
// test: template contains essential files & folders
|
|
||||||
for (const file of DOES_HAVE) {
|
|
||||||
assert.ok(fs.existsSync(path.join(templateDir, file)), `missing ${file}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// test: template DOES NOT contain files supposed to be stripped away
|
|
||||||
for (const file of DOES_NOT_HAVE) {
|
|
||||||
assert.not.ok(fs.existsSync(path.join(templateDir, file)), `failed to clean up ${file}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
CreateAstro(`${template} (dev)`, async () => {
|
|
||||||
// start dev server
|
|
||||||
const port = 3000 + n; // start new port per test
|
|
||||||
const devServer = execa('yarn', ['start', '--port', port], { cwd: templateDir });
|
|
||||||
await new Promise((resolve) => {
|
|
||||||
setTimeout(() => resolve(), 15000);
|
|
||||||
}); // give dev server flat 15s to set up
|
|
||||||
// TODO: try to ping dev server ASAP rather than waiting flat 15s
|
|
||||||
|
|
||||||
// ping dev server
|
|
||||||
const { statusCode, body } = await fetch(`http://localhost:${port}`);
|
|
||||||
|
|
||||||
// expect 200 to be returned with some response
|
|
||||||
assert.equal(statusCode, 200, 'didn’t respond with 200');
|
|
||||||
assert.ok(body, 'returned empty response');
|
|
||||||
|
|
||||||
// clean up
|
|
||||||
devServer.kill();
|
|
||||||
});
|
|
||||||
|
|
||||||
CreateAstro(`${template} (build)`, async () => {
|
|
||||||
const MUST_HAVE_FILES = ['index.html', '_astro'];
|
|
||||||
|
|
||||||
// build template
|
|
||||||
await execa('yarn', ['build'], { cwd: templateDir });
|
|
||||||
|
|
||||||
// scan build dir
|
|
||||||
const builtFiles = await glob('**/*', { cwd: path.join(templateDir, 'dist') });
|
|
||||||
for (const file of MUST_HAVE_FILES) {
|
|
||||||
assert.ok(builtFiles.includes(file), `didn’t build ${file}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// run tests
|
async function testTemplate(template) {
|
||||||
CreateAstro.run();
|
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`);
|
||||||
|
|
||||||
|
// clean up
|
||||||
|
devServer.kill();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function testAll() {
|
||||||
|
// setup
|
||||||
|
await Promise.all(
|
||||||
|
TEMPLATES.map(async ({ value: template }) => {
|
||||||
|
// setup: `npm init astro`
|
||||||
|
await execa('../../create-astro.mjs', [template, '--template', template, '--commit', GITHUB_SHA, '--force-overwrite'], {
|
||||||
|
cwd: FIXTURES_DIR,
|
||||||
|
});
|
||||||
|
// 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) });
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await testTemplate(template);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(red(`✘ [${template}]`));
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.info(green(`✔ [${template}] All tests passed (${n + 1}/${TEMPLATES.length})`));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
testAll();
|
||||||
|
|
Loading…
Reference in a new issue