use nightmare for testing

This commit is contained in:
Rich Harris
2017-12-21 16:48:53 -05:00
committed by GitHub
parent 43a12a8331
commit 0bcb61650b
23 changed files with 1351 additions and 4461 deletions

View File

@@ -1,10 +1,21 @@
sudo: false
language: node_js
node_js:
- "stable"
env:
global:
- BUILD_TIMEOUT=10000
addons:
apt:
packages:
- xvfb
install:
- export DISPLAY=':99.0'
- Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &
- npm install
- (cd test/app && npm install)

View File

@@ -9,7 +9,7 @@ const generate_asset_cache = require('./utils/generate_asset_cache.js');
const { dest } = require('./config.js');
module.exports = () => {
mkdirp(dest);
mkdirp.sync(dest);
rimraf.sync(path.join(dest, '**/*'));
// create main.js and server-routes.js

View File

@@ -14,7 +14,7 @@ exports.dest = path.resolve(
);
if (exports.dev) {
mkdirp(exports.dest);
mkdirp.sync(exports.dest);
rimraf.sync(path.join(exports.dest, '**/*'));
}

View File

@@ -20,7 +20,7 @@ function connect_dev() {
let asset_cache;
return compose_handlers([
const middleware = compose_handlers([
require('webpack-hot-middleware')(compilers.client, {
reload: true,
path: '/__webpack_hmr',
@@ -59,6 +59,13 @@ function connect_dev() {
get_not_found_handler(() => asset_cache)
]);
middleware.close = () => {
watcher.close();
// TODO shut down chokidar
};
return middleware;
}
function connect_prod() {
@@ -67,7 +74,7 @@ function connect_prod() {
read_json(path.join(dest, 'stats.server.json'))
);
return compose_handlers([
const middleware = compose_handlers([
set_req_pathname,
get_asset_handler({
@@ -95,6 +102,12 @@ function connect_prod() {
get_not_found_handler(() => asset_cache)
]);
// here for API consistency between dev, and prod, but
// doesn't actually need to do anything
middleware.close = () => {};
return middleware;
}
module.exports = dev ? connect_dev : connect_prod;

View File

@@ -21,7 +21,8 @@ update();
if (dev) {
const watcher = chokidar.watch(`${src}/**/*.+(html|js|mjs)`, {
ignoreInitial: true
ignoreInitial: true,
persistent: false
});
watcher.on('add', update);

View File

@@ -66,7 +66,8 @@ create_templates();
if (dev) {
const watcher = chokidar.watch('templates/**.html', {
ignoreInitial: true
ignoreInitial: true,
persistent: false
});
watcher.on('add', create_templates);

View File

@@ -71,7 +71,8 @@ if (dev) {
route_manager.onchange(create_app);
const watcher = chokidar.watch(`templates/main.js`, {
ignoreInitial: true
ignoreInitial: true,
persistent: false
});
watcher.on('add', create_app);

View File

@@ -38,10 +38,6 @@ module.exports = function create_watcher() {
);
});
watcher = {
ready: invalidate()
};
function watch_compiler(type) {
const compiler = compilers[type];
@@ -55,7 +51,7 @@ module.exports = function create_watcher() {
deferreds[type].reject(err);
});
compiler.watch({}, (err, stats) => {
return compiler.watch({}, (err, stats) => {
if (stats.hasErrors()) {
deferreds[type].reject(stats.toJson().errors[0]);
} else {
@@ -64,8 +60,16 @@ module.exports = function create_watcher() {
});
}
watch_compiler('client');
watch_compiler('server');
watcher = {
ready: invalidate(),
client: watch_compiler('client'),
server: watch_compiler('server'),
close: () => {
watcher.client.close();
watcher.server.close();
}
};
return watcher;
};

View File

@@ -1,2 +1,3 @@
--recursive
test/unit/**/*.js
test/unit/**/*.js
test/common/test.js

2038
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -21,22 +21,18 @@
"webpack-hot-middleware": "^2.21.0"
},
"devDependencies": {
"cypress": "^1.3.0",
"eslint": "^4.13.1",
"express": "^4.16.2",
"get-port": "^3.2.0",
"mocha": "^4.0.1",
"nightmare": "^2.10.0",
"node-fetch": "^1.7.3",
"npm-run-all": "^4.1.2",
"wait-on": "^2.0.2"
},
"scripts": {
"cy:open": "cypress open",
"test": "run-s test:unit test:dev test:prod",
"test:unit": "mocha --opts mocha.opts",
"test:dev": "run-p --race test:launch:dev cy:run:dev",
"test:launch:dev": "node test/launch.js --dev",
"cy:run:dev": "wait-on http://localhost:3000 && cypress run -s test/cypress/integration/dev.js",
"test:prod": "run-p --race test:launch:prod cy:run:prod",
"test:launch:prod": "node test/launch.js --prod",
"cy:run:prod": "wait-on http://localhost:3000 && cypress run -s test/cypress/integration/prod.js"
"test": "mocha --opts mocha.opts"
},
"repository": "https://github.com/sveltejs/sapper",
"keywords": [

1
test/app/.gitignore vendored
View File

@@ -3,3 +3,4 @@ node_modules
.sapper
yarn.lock
cypress/screenshots
templates/.*

View File

@@ -10,9 +10,9 @@
<link rel='icon' type='image/png' href='/favicon.png'>
<script>
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js');
}
// if ('serviceWorker' in navigator) {
// navigator.serviceWorker.register('/service-worker.js');
// }
</script>
<!-- Sapper generates a <style> tag containing critical CSS

View File

@@ -3,8 +3,4 @@ import { init } from '__app__';
// `routes` is an array of route objects injected by Sapper
init(document.querySelector('#sapper'), __routes__);
// if (__dev__) {
// // Enable hot-module reloading
// import('sapper/webpack/hmr');
// if (module.hot) module.hot.accept();
// }
window.READY = true;

210
test/common/test.js Normal file
View File

@@ -0,0 +1,210 @@
const fs = require('fs');
const path = require('path');
const assert = require('assert');
const express = require('express');
const serve = require('serve-static');
const Nightmare = require('nightmare');
const getPort = require('get-port');
const fetch = require('node-fetch');
run('production');
run('development');
function run(env) {
describe(`env=${env}`, function () {
this.timeout(30000);
let PORT;
let server;
let nightmare;
let middleware;
let capture;
let base;
function get(url) {
return new Promise((fulfil, reject) => {
const req = {
url,
method: 'GET'
};
const result = {
headers: {},
body: ''
};
const res = {
set: (headers, value) => {
if (typeof headers === 'string') {
return res.set({ [headers]: value });
}
Object.assign(result.headers, headers);
},
status: code => {
result.status = code;
},
write: data => {
result.body += data;
},
end: data => {
result.body += data;
fulfil(result);
}
};
middleware(req, res, () => {
fulfil(result);
});
});
}
before(async () => {
process.chdir(path.resolve(__dirname, '../app'));
process.env.NODE_ENV = env;
if (env === 'production') {
const cli = path.resolve(__dirname, '../../cli/index.js');
await exec(`${cli} build`);
}
const resolved = require.resolve('../..');
delete require.cache[resolved];
const sapper = require(resolved);
PORT = await getPort();
base = `http://localhost:${PORT}`;
global.fetch = (url, opts) => {
if (url[0] === '/') url = `${base}${url}`;
return fetch(url, opts);
};
let captured;
capture = async fn => {
const result = captured = [];
await fn();
captured = null;
return result;
};
app = express();
app.use(serve('assets'));
app.use((req, res, next) => {
if (captured) captured.push(req);
next();
});
middleware = sapper();
app.use(middleware);
return new Promise((fulfil, reject) => {
server = app.listen(PORT, err => {
if (err) reject(err);
else fulfil();
});
});
});
after(() => {
server.close();
middleware.close();
// give a chance to clean up
return new Promise(fulfil => setTimeout(fulfil, 500));
});
describe('basic functionality', () => {
beforeEach(() => {
nightmare = new Nightmare();
nightmare.on('console', (type, ...args) => {
console[type](...args);
});
});
afterEach(async () => {
await nightmare.end();
});
it('serves /', async () => {
const title = await nightmare
.goto(base)
.evaluate(() => document.querySelector('h1').textContent);
assert.equal(title, 'Great success!');
});
it('serves static route', async () => {
const title = await nightmare
.goto(`${base}/about`)
.evaluate(() => document.querySelector('h1').textContent);
assert.equal(title, 'About this site');
});
it('serves dynamic route', async () => {
const title = await nightmare
.goto(`${base}/blog/what-is-sapper`)
.evaluate(() => document.querySelector('h1').textContent);
assert.equal(title, 'What is Sapper?');
});
it('navigates to a new page without reloading', async () => {
await nightmare.goto(base);
const requests = await capture(async () => {
await nightmare.click('a[href="/about"]');
});
assert.equal(
await nightmare.path(),
'/about'
);
assert.equal(
await nightmare.evaluate(() => document.title),
'About'
);
assert.deepEqual(requests.map(r => r.url), []);
});
});
describe('headers', () => {
it('sets Content-Type and Link...preload headers', async () => {
const { headers } = await get('/');
assert.equal(
headers['Content-Type'],
'text/html'
);
assert.ok
(/<\/client\/main.\w+\.js\>;rel="preload";as="script", <\/client\/_.\d+.\w+.js>;rel="preload";as="script"/.test(headers['Link']),
headers['Link']
);
});
});
});
}
function exec(cmd) {
return new Promise((fulfil, reject) => {
require('child_process').exec(cmd, (err, stdout, stderr) => {
if (err) return reject(err);
process.stdout.write(stdout);
process.stderr.write(stderr);
fulfil();
});
});
}

View File

@@ -1,5 +0,0 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}

View File

@@ -1,19 +0,0 @@
describe('dev mode', () => {
beforeEach(() => {
cy.visit('/')
});
it('has the correct <h1>', () => {
cy.contains('h1', 'Great success!')
});
it('navigates to /about', () => {
cy.get('nav a').contains('about').click();
cy.url().should('include', '/about');
});
it('navigates to /blog', () => {
cy.get('nav a').contains('blog').click();
cy.url().should('include', '/blog');
});
});

View File

@@ -1,19 +0,0 @@
describe('prod mode', () => {
beforeEach(() => {
cy.visit('/')
});
it('has the correct <h1>', () => {
cy.contains('h1', 'Great success!')
});
it('navigates to /about', () => {
cy.get('nav a').contains('about').click();
cy.url().should('include', '/about');
});
it('navigates to /blog', () => {
cy.get('nav a').contains('blog').click();
cy.url().should('include', '/blog');
});
});

View File

@@ -1,17 +0,0 @@
// ***********************************************************
// This example plugins/index.js can be used to load plugins
//
// You can change the location of this file or turn off loading
// the plugins file with the 'pluginsFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/plugins-guide
// ***********************************************************
// This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing)
module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 331 KiB

View File

@@ -1,25 +0,0 @@
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add("login", (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This is will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })

View File

@@ -1,20 +0,0 @@
// ***********************************************************
// This example support/index.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import './commands'
// Alternatively you can use CommonJS syntax:
// require('./commands')

3370
yarn.lock

File diff suppressed because it is too large Load Diff