From a660e49f80accbc4e18f48cc10bf23a239f711fe Mon Sep 17 00:00:00 2001 From: Drew Powers <1369770+drwpow@users.noreply.github.com> Date: Thu, 10 Jun 2021 10:30:48 -0600 Subject: [PATCH] Add integration test for templates (#372) --- .github/workflows/nodejs.yml | 1 + examples/blog/meta.json | 5 - examples/starter/meta.json | 5 - package.json | 2 +- packages/create-astro/README.md | 12 +- packages/create-astro/package.json | 2 +- packages/create-astro/src/index.ts | 22 +++- .../create-astro/test/create-astro.test.js | 108 +++++++++++++++--- tools/astro-vscode/package.json | 2 +- www/package.json | 1 + 10 files changed, 128 insertions(+), 32 deletions(-) delete mode 100644 examples/blog/meta.json delete mode 100644 examples/starter/meta.json diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 1e958547b..8b362e7a1 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -70,3 +70,4 @@ jobs: - run: yarn test env: CI: true + GITHUB_SHA: $GITHUB_SHA diff --git a/examples/blog/meta.json b/examples/blog/meta.json deleted file mode 100644 index caa6852e5..000000000 --- a/examples/blog/meta.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "title": "Blog", - "description": "an SEO-optimized blog starter", - "rank": 1 -} diff --git a/examples/starter/meta.json b/examples/starter/meta.json deleted file mode 100644 index e9f3778c7..000000000 --- a/examples/starter/meta.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "title": "Getting Started", - "description": "a friendly starting point for new astronauts", - "rank": 999 -} diff --git a/package.json b/package.json index 171dd94d6..d2535653a 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "build": "yarn build:core", "build:all": "lerna run build", "build:one": "lerna run build --scope", - "build:core": "lerna run build --scope astro --scope @astrojs/parser --scope @astrojs/markdown-support", + "build:core": "lerna run build --scope astro --scope @astrojs/parser --scope @astrojs/markdown-support --scope create-astro", "build:vscode": "lerna run build --scope astro-languageserver --scope astro-vscode --scope @astrojs/parser", "dev:vscode": "lerna run dev --scope astro-languageserver --scope astro-vscode --scope @astrojs/parser --parallel --stream", "format": "prettier -w \"**/*.{js,jsx,ts,tsx,md,json}\"", diff --git a/packages/create-astro/README.md b/packages/create-astro/README.md index 0c3982ed5..4a90b7eb4 100644 --- a/packages/create-astro/README.md +++ b/packages/create-astro/README.md @@ -26,5 +26,15 @@ npm init astro my-astro-project -- --template starter # yarn yarn create astro my-astro-project --template starter ``` +[Check out the full list][examples] of example starter templates, available on GitHub. -[Check out the full list](https://github.com/snowpackjs/astro/tree/main/examples) of example starter templates, available on GitHub. +### CLI Flags + +May be provided in place of prompts + +| Name | Description | +|:-------------|:----------------------------------------------------| +| `--template` | Specify the template name ([list][examples]) | +| `--commit` | Specify a specific Git commit or branch to use from this repo (by default, `main` branch of this repo will be used) | + +[examples]: https://github.com/snowpackjs/astro/tree/main/examples diff --git a/packages/create-astro/package.json b/packages/create-astro/package.json index 04df2fdde..b4cc2a7e9 100644 --- a/packages/create-astro/package.json +++ b/packages/create-astro/package.json @@ -11,7 +11,7 @@ "scripts": { "build": "astro-scripts build \"src/*.ts\"", "prepare": "yarn build", - "test": "uvu" + "test": "uvu -i test/fixtures" }, "files": [ "dist", diff --git a/packages/create-astro/src/index.ts b/packages/create-astro/src/index.ts index 0d1d834f3..240faf7c0 100644 --- a/packages/create-astro/src/index.ts +++ b/packages/create-astro/src/index.ts @@ -19,6 +19,8 @@ export function mkdirp(dir: string) { const { version } = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf-8')); +const POSTPROCESS_FILES = ['package.json']; // some files need processing after copying. + export async function main() { console.log('\n' + bold('Welcome to Astro!') + gray(` (create-astro v${version})`)); console.log(`If you encounter a problem, visit ${cyan('https://github.com/snowpack/astro/issues')} to search or file a new issue.\n`); @@ -52,12 +54,14 @@ export async function main() { }, ]); - const emitter = degit(`snowpackjs/astro/examples/${options.template}`, { + const hash = args.commit ? `#${args.commit}` : ''; + const emitter = degit(`snowpackjs/astro/examples/${options.template}${hash}`, { cache: false, force: true, verbose: false, }); + // Copy try { // emitter.on('info', info => { console.log(info.message) }); console.log(green(`>`) + gray(` Copying project files...`)); @@ -68,6 +72,22 @@ export async function main() { process.exit(1); } + // Post-process in parallel + await Promise.all( + POSTPROCESS_FILES.map(async (file) => { + const fileLoc = path.join(cwd, file); + + switch (file) { + case 'package.json': { + const packageJSON = JSON.parse(await fs.promises.readFile(fileLoc, 'utf8')); + delete packageJSON.snowpack; // delete snowpack config only needed in monorepo (can mess up projects) + await fs.promises.writeFile(fileLoc, JSON.stringify(packageJSON, undefined, 2)); + break; + } + } + }) + ); + console.log(bold(green('✔ Copied project files'))); console.log('\nNext steps:'); diff --git a/packages/create-astro/test/create-astro.test.js b/packages/create-astro/test/create-astro.test.js index 3b9e4f013..4335f77e1 100644 --- a/packages/create-astro/test/create-astro.test.js +++ b/packages/create-astro/test/create-astro.test.js @@ -1,46 +1,120 @@ import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; +import http from 'http'; import { suite } from 'uvu'; import execa from 'execa'; import del from 'del'; +import glob from 'tiny-glob'; import * as assert from 'uvu/assert'; import { TEMPLATES } from '../dist/templates.js'; +// 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 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'); + +// helper +async function fetch(url) { + return new Promise((resolve) => { + http + .get(url, (res) => { + let body = ''; + res.on('data', (chunk) => { + body += chunk; + }); + res.on('end', () => resolve({ statusCode: res.statusCode, body })); + }) + .on('error', (err) => { + reject(err); + }); + }); +} + +// test const CreateAstro = suite('npm init astro'); -const cwd = fileURLToPath(path.dirname(import.meta.url)); -const fixturesDir = path.join(cwd, 'fixtures'); - CreateAstro.before(async () => { - await del(fixturesDir); - await fs.promises.mkdir(fixturesDir); + // 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 }); + }) + ); }); -for (const { value: template } of TEMPLATES) { - // TODO: Unskip once repo is made public. Because the repo is private, the templates can't yet be downloaded. - CreateAstro.skip(template, async () => { - const testDirectory = path.join(fixturesDir, template); - const { stdout } = await execa('../../create-astro.mjs', [testDirectory, '--template', template, '--force-overwrite'], { cwd: path.join(cwd, 'fixtures') }); +// 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__]); +}); - console.log(stdout); - // test: path should formatted as './{dirName}' - assert.not.match(stdout, '././'); +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 = ['meta.json', 'node_modules', 'yarn.lock']; + const DOES_NOT_HAVE = ['.git', 'meta.json']; // test: template contains essential files & folders for (const file of DOES_HAVE) { - console.log(path.join(testDirectory, file)); - assert.ok(fs.existsSync(path.join(testDirectory, file)), `has ${file}`); + 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(testDirectory, file)), `does not have ${file}`); + 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 CreateAstro.run(); diff --git a/tools/astro-vscode/package.json b/tools/astro-vscode/package.json index be3460977..d947c90ee 100644 --- a/tools/astro-vscode/package.json +++ b/tools/astro-vscode/package.json @@ -24,7 +24,7 @@ "onLanguage:astro" ], "dependencies": { - "astro-languageserver": "file:../astro-languageserver" + "astro-languageserver": "^0.4.0" }, "devDependencies": { "@types/vscode": "^1.52.0", diff --git a/www/package.json b/www/package.json index e9426b700..3734e3461 100644 --- a/www/package.json +++ b/www/package.json @@ -1,6 +1,7 @@ { "name": "www", "version": "1.1.0", + "private": true, "scripts": { "start": "astro dev", "build": "astro build"