From e6c1a54164e75df2cf836afed888acbb9af6dc44 Mon Sep 17 00:00:00 2001 From: mrkishi Date: Wed, 8 May 2019 00:57:59 -0300 Subject: [PATCH] Harden tests (...a bit) --- .gitignore | 4 - test/apps/AppRunner.ts | 169 +++++++++---- test/apps/basics/src/server.js | 7 +- test/apps/basics/test.ts | 229 +++++++++--------- test/apps/common.js | 42 ++++ test/apps/credentials/src/server.js | 7 +- test/apps/credentials/test.ts | 47 ++-- test/apps/css/src/server.js | 9 +- test/apps/css/test.ts | 67 ++--- test/apps/encoding/src/server.js | 9 +- test/apps/encoding/test.ts | 51 ++-- test/apps/errors/src/server.js | 9 +- test/apps/errors/test.ts | 108 ++++----- test/apps/export-webpack/src/server.js | 12 +- test/apps/export-webpack/test.ts | 8 +- test/apps/export/src/server.js | 12 +- test/apps/export/test.ts | 9 +- test/apps/ignore/src/server.js | 4 +- test/apps/ignore/test.ts | 38 +-- test/apps/layout/src/server.js | 9 +- test/apps/layout/test.ts | 40 ++- test/apps/preloading/src/server.js | 9 +- test/apps/preloading/test.ts | 116 +++++---- test/apps/redirects/src/server.js | 9 +- test/apps/redirects/test.ts | 123 +++++----- test/apps/scroll/src/server.js | 9 +- test/apps/scroll/test.ts | 84 +++---- test/apps/session/src/server.js | 9 +- test/apps/session/test.ts | 50 ++-- test/apps/with-basepath/src/server.js | 13 +- test/apps/with-basepath/test.ts | 67 +++-- .../with-sourcemaps-webpack/src/server.js | 9 +- test/apps/with-sourcemaps-webpack/test.ts | 7 +- test/apps/with-sourcemaps/src/server.js | 9 +- test/apps/with-sourcemaps/test.ts | 7 +- 35 files changed, 726 insertions(+), 685 deletions(-) create mode 100644 test/apps/common.js diff --git a/.gitignore b/.gitignore index 910b5f3..e25b13b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,11 +3,7 @@ yarn.lock yarn-error.log node_modules cypress/screenshots -test/app/.sapper -test/app/src/manifest __sapper__ -test/app/export -test/app/build sapper runtime.js dist diff --git a/test/apps/AppRunner.ts b/test/apps/AppRunner.ts index 0a3d51b..9874583 100644 --- a/test/apps/AppRunner.ts +++ b/test/apps/AppRunner.ts @@ -1,49 +1,77 @@ import * as path from 'path'; import puppeteer from 'puppeteer'; -import * as ports from 'port-authority'; import { fork, ChildProcess } from 'child_process'; +import { AddressInfo } from 'net'; + +import { wait } from '../utils' + +const DEFAULT_ENTRY = '__sapper__/build/server/server.js'; +const DELAY = parseInt(process.env.SAPPER_TEST_DELAY) || 50; declare const start: () => Promise; declare const prefetchRoutes: () => Promise; declare const prefetch: (href: string) => Promise; declare const goto: (href: string) => Promise; -type StartOpts = { - requestInterceptor?: (interceptedRequst: puppeteer.Request) => any -}; - export class AppRunner { - cwd: string; - entry: string; - port: number; - proc: ChildProcess; + exiting: boolean; + terminate: Promise; + + server: ChildProcess; + address: AddressInfo; + base: string; messages: any[]; + errors: Error[]; browser: puppeteer.Browser; page: puppeteer.Page; - constructor(cwd: string, entry: string) { - this.cwd = cwd; - this.entry = path.join(cwd, entry); + sapper = { + start: () => this.page.evaluate(() => start()).then(() => void 0), + prefetchRoutes: () => this.page.evaluate(() => prefetchRoutes()).then(() => void 0), + prefetch: (href: string) => this.page.evaluate((href: string) => prefetch(href), href).then(() => void 0), + goto: (href: string) => this.page.evaluate((href: string) => goto(href), href).then(() => void 0) + }; + + constructor() { this.messages = []; + this.errors = []; } - async start({ requestInterceptor }: StartOpts = {}) { - this.port = await ports.find(3000); + async start(cwd: string, entry: string = DEFAULT_ENTRY) { + const server_listening = deferred(); + const server_closed = deferred(); + const browser_closed = deferred(); - this.proc = fork(this.entry, [], { - cwd: this.cwd, - env: { - PORT: String(this.port) + this.terminate = Promise.all([server_closed, browser_closed]); + + this.server = fork(path.join(cwd, entry), [], { cwd }); + this.server.on('exit', () => { + server_listening.reject(); + server_closed.settle(this.exiting); + }); + this.server.on('message', message => { + if (!message.__sapper__) return; + + switch (message.event) { + case 'listening': + this.address = message.address; + this.base = `http://localhost:${this.address.port}`; + + server_listening.resolve(); + break; + + case 'error': + this.errors.push(Object.assign(new Error(), message.error)); + break; + + default: + this.messages.push(message); } }); - this.proc.on('message', message => { - if (!message.__sapper__) return; - this.messages.push(message); - }); - this.browser = await puppeteer.launch({ args: ['--no-sandbox'] }); + this.browser.on('disconnected', () => browser_closed.settle(this.exiting)); this.page = await this.browser.newPage(); this.page.on('console', msg => { @@ -54,25 +82,28 @@ export class AppRunner { } }); - if (requestInterceptor) { - await this.page.setRequestInterception(true); - this.page.on('request', requestInterceptor); - } + await server_listening; - return { - page: this.page, - base: `http://localhost:${this.port}`, - - // helpers - start: () => this.page.evaluate(() => start()).then(() => void 0), - prefetchRoutes: () => this.page.evaluate(() => prefetchRoutes()).then(() => void 0), - prefetch: (href: string) => this.page.evaluate((href: string) => prefetch(href), href).then(() => void 0), - goto: (href: string) => this.page.evaluate((href: string) => goto(href), href).then(() => void 0), - title: () => this.page.$eval('h1', node => node.textContent).then(serializable => String(serializable)) - }; + return this; } - capture(fn: () => any): Promise { + load(url: string) { + if (url[0] === '/') { + url = `${this.base}${url}`; + } + + return this.page.goto(url); + } + + text(selector: string) { + return this.page.$eval(selector, node => node.textContent); + } + + wait(extra_ms: number = 0) { + return wait(DELAY + extra_ms); + } + + capture_requests(fn: () => any): Promise { return new Promise((fulfil, reject) => { const requests: string[] = []; const pending: Set = new Set(); @@ -120,13 +151,55 @@ export class AppRunner { }); } - end() { - return Promise.all([ - this.browser.close(), - new Promise(fulfil => { - this.proc.once('exit', fulfil); - this.proc.kill(); - }) - ]); + async intercept_requests(interceptor: (request: puppeteer.Request) => void, fn: () => any): Promise { + const unique_interceptor = request => interceptor(request); + + this.page.prependListener('request', unique_interceptor); + await this.page.setRequestInterception(true); + + const result = await Promise.resolve(fn()); + + await this.page.setRequestInterception(false); + this.page.removeListener('request', unique_interceptor); + + return result; } -} \ No newline at end of file + + end() { + this.exiting = true; + + this.server.kill(); + this.browser.close(); + + return this.terminate; + } +} + +interface Deferred extends Promise { + resolve: (value?: T | PromiseLike) => void; + reject: (reason?: any) => void; + settle: (result?: boolean) => void; +} + +function settle(this: Deferred, result: boolean) { + if (result) { + this.resolve(); + } else { + this.reject(); + } +} + +function deferred() { + let resolve, reject; + + const deferred = new Promise((_resolve, _reject) => { + resolve = _resolve; + reject = _reject; + }) as Deferred; + + deferred.resolve = resolve; + deferred.reject = reject; + deferred.settle = settle; + + return deferred; +} diff --git a/test/apps/basics/src/server.js b/test/apps/basics/src/server.js index 7f090b8..c839a8b 100644 --- a/test/apps/basics/src/server.js +++ b/test/apps/basics/src/server.js @@ -1,8 +1,9 @@ import polka from 'polka'; import * as sapper from '@sapper/server'; -const { PORT } = process.env; +import { start } from '../../common.js'; -polka() +const app = polka() .use(sapper.middleware()) - .listen(PORT); + +start(app); diff --git a/test/apps/basics/test.ts b/test/apps/basics/test.ts index 53cced3..9f24a8c 100644 --- a/test/apps/basics/test.ts +++ b/test/apps/basics/test.ts @@ -1,23 +1,23 @@ import * as assert from 'assert'; -import * as puppeteer from 'puppeteer'; import * as http from 'http'; import { build } from '../../../api'; import { AppRunner } from '../AppRunner'; -import { wait } from '../../utils'; declare let deleted: { id: number }; declare let el: any; -function get(url: string, opts?: any): Promise<{ headers: Record, body: string }> { +type Response = { headers: http.IncomingHttpHeaders, body: string }; + +function get(url: string, opts: http.RequestOptions = {}): Promise { return new Promise((fulfil, reject) => { - const req = http.get(url, opts || {}, res => { + const req = http.get(url, opts, res => { res.on('error', reject); let body = ''; res.on('data', chunk => body += chunk); res.on('end', () => { fulfil({ - headers: res.headers as Record, + headers: res.headers, body }); }); @@ -30,114 +30,104 @@ function get(url: string, opts?: any): Promise<{ headers: Record describe('basics', function() { this.timeout(10000); - let runner: AppRunner; - let page: puppeteer.Page; - let base: string; - - // helpers - let start: () => Promise; - let prefetchRoutes: () => Promise; - let prefetch: (href: string) => Promise; - let goto: (href: string) => Promise; - let title: () => Promise; + let r: AppRunner; // hooks - before(async () => { - await build({ cwd: __dirname }); - - runner = new AppRunner(__dirname, '__sapper__/build/server/server.js'); - ({ base, page, start, prefetchRoutes, prefetch, goto, title } = await runner.start()); + before('build app', () => build({ cwd: __dirname })); + before('start runner', async () => { + r = await new AppRunner().start(__dirname); }); - after(() => runner.end()); + after(() => r && r.end()); + // tests it('serves /', async () => { - await page.goto(base); + await r.load('/'); assert.equal( - await title(), + await r.text('h1'), 'Great success!' ); }); it('serves /?', async () => { - await page.goto(`${base}?`); + await r.load('/?'); assert.equal( - await title(), + await r.text('h1'), 'Great success!' ); }); it('serves static route', async () => { - await page.goto(`${base}/a`); + await r.load('/a'); assert.equal( - await title(), + await r.text('h1'), 'a' ); }); it('serves static route from dir/index.html file', async () => { - await page.goto(`${base}/b`); + await r.load('/b'); assert.equal( - await title(), + await r.text('h1'), 'b' ); }); it('serves dynamic route', async () => { - await page.goto(`${base}/test-slug`); + await r.load('/test-slug'); assert.equal( - await title(), + await r.text('h1'), 'TEST-SLUG' ); }); it('navigates to a new page without reloading', async () => { - await page.goto(base); - await start(); - await prefetchRoutes(); + await r.load('/'); + await r.sapper.start(); + await r.sapper.prefetchRoutes(); - const requests: string[] = await runner.capture(async () => { - await page.click('a[href="a"]'); + const requests: string[] = await r.capture_requests(async () => { + await r.page.click('a[href="a"]'); + await r.wait(); }); assert.deepEqual(requests, []); assert.equal( - await title(), + await r.text('h1'), 'a' ); }); it('navigates programmatically', async () => { - await page.goto(`${base}/a`); - await start(); - - await goto('b'); + await r.load('/a'); + await r.sapper.start(); + await r.sapper.goto('b'); assert.equal( - await title(), + await r.text('h1'), 'b' ); }); it('prefetches programmatically', async () => { - await page.goto(`${base}/a`); - await start(); + await r.load(`/a`); + await r.sapper.start(); - const requests = await runner.capture(() => prefetch('b')); + const requests = await r.capture_requests(() => r.sapper.prefetch('b')); assert.equal(requests.length, 2); - assert.equal(requests[1], `${base}/b.json`); + assert.equal(requests[1], `${r.base}/b.json`); }); // TODO equivalent test for a webpack app it('sets Content-Type, Link...modulepreload, and Cache-Control headers', async () => { - const { headers } = await get(base); + const { headers } = await get(r.base); assert.equal( headers['content-type'], @@ -157,162 +147,163 @@ describe('basics', function() { }); it('calls a delete handler', async () => { - await page.goto(`${base}/delete-test`); - await start(); + await r.load('/delete-test'); + await r.sapper.start(); - await page.click('.del'); - await page.waitForFunction(() => deleted); + await r.page.click('.del'); + await r.page.waitForFunction(() => deleted); - assert.equal(await page.evaluate(() => deleted.id), 42); + assert.equal(await r.page.evaluate(() => deleted.id), 42); }); it('hydrates initial route', async () => { - await page.goto(base); + await r.load('/'); - await page.evaluate(() => { + await r.page.evaluate(() => { el = document.querySelector('.hydrate-test'); }); - await start(); + await r.sapper.start(); - assert.ok(await page.evaluate(() => { + assert.ok(await r.page.evaluate(() => { return document.querySelector('.hydrate-test') === el; })); }); it('does not attempt client-side navigation to server routes', async () => { - await page.goto(base); - await start(); - await prefetchRoutes(); + await r.load('/'); + await r.sapper.start(); + await r.sapper.prefetchRoutes(); - await page.click(`[href="ambiguous/ok.json"]`); - await wait(50); + await r.page.click('[href="ambiguous/ok.json"]'); + await r.wait(); assert.equal( - await page.evaluate(() => document.body.textContent), + await r.text('body'), 'ok' ); }); it('allows reserved words as route names', async () => { - await page.goto(`${base}/const`); - await start(); + await r.load('/const'); + await r.sapper.start(); assert.equal( - await title(), + await r.text('h1'), 'reserved words are okay as routes' ); }); it('accepts value-less query string parameter on server', async () => { - await page.goto(`${base}/echo-query?message`); + await r.load('/echo-query?message'); assert.equal( - await title(), + await r.text('h1'), '{"message":""}' ); }); it('accepts value-less query string parameter on client', async () => { - await page.goto(base); - await start(); - await prefetchRoutes(); + await r.load('/'); + await r.sapper.start(); + await r.sapper.prefetchRoutes(); - await page.click('a[href="echo-query?message"]') + await r.page.click('a[href="echo-query?message"]'); + await r.wait(); assert.equal( - await title(), + await r.text('h1'), '{"message":""}' ); }); it('accepts duplicated query string parameter on server', async () => { - await page.goto(`${base}/echo-query?p=one&p=two`); + await r.load('/echo-query?p=one&p=two'); assert.equal( - await title(), + await r.text('h1'), '{"p":["one","two"]}' ); }); it('accepts duplicated query string parameter on client', async () => { - await page.goto(base); - await start(); - await prefetchRoutes(); + await r.load('/'); + await r.sapper.start(); + await r.sapper.prefetchRoutes(); - await page.click('a[href="echo-query?p=one&p=two"]') + await r.page.click('a[href="echo-query?p=one&p=two"]') assert.equal( - await title(), + await r.text('h1'), '{"p":["one","two"]}' ); }); // skipped because Nightmare doesn't seem to focus the correctly it('resets the active element after navigation', async () => { - await page.goto(base); - await start(); - await prefetchRoutes(); + await r.load('/'); + await r.sapper.start(); + await r.sapper.prefetchRoutes(); - await page.click('[href="a"]'); - await wait(50); + await r.page.click('[href="a"]'); + await r.wait(); assert.equal( - await page.evaluate(() => document.activeElement.nodeName), + await r.page.evaluate(() => document.activeElement.nodeName), 'BODY' ); }); it('replaces %sapper.xxx% tags safely', async () => { - await page.goto(`${base}/unsafe-replacement`); - await start(); + await r.load('/unsafe-replacement'); + await r.sapper.start(); - const html = String(await page.evaluate(() => document.body.innerHTML)); + const html = String(await r.page.evaluate(() => document.body.innerHTML)); assert.equal(html.indexOf('%sapper'), -1); }); it('navigates between routes with empty parts', async () => { - await page.goto(`${base}/dirs/foo`); - await start(); + await r.load('/dirs/foo'); + await r.sapper.start(); - assert.equal(await title(), 'foo'); + assert.equal(await r.text('h1'), 'foo'); - await page.click('[href="dirs/bar"]'); - await wait(50); - assert.equal(await title(), 'bar'); + await r.page.click('[href="dirs/bar"]'); + await r.wait(); + assert.equal(await r.text('h1'), 'bar'); }); it('navigates to ...rest', async () => { - await page.goto(`${base}/abc/xyz`); - await start(); + await r.load('/abc/xyz'); + await r.sapper.start(); - assert.equal(await title(), 'abc,xyz'); + assert.equal(await r.text('h1'), 'abc,xyz'); - await page.click('[href="xyz/abc/deep"]'); - await wait(50); - assert.equal(await title(), 'xyz,abc'); + await r.page.click('[href="xyz/abc/deep"]'); + await r.wait(); + assert.equal(await r.text('h1'), 'xyz,abc'); - await page.click(`[href="xyz/abc/qwe/deep.json"]`); - await wait(50); + await r.page.click('[href="xyz/abc/qwe/deep.json"]'); + await r.wait(); assert.equal( - await page.evaluate(() => document.body.textContent), + await r.text('body'), 'xyz,abc,qwe' ); }); it('navigates between dynamic routes with same segments', async () => { - await page.goto(`${base}/dirs/bar/xyz`); - await start(); + await r.load('/dirs/bar/xyz'); + await r.sapper.start(); - assert.equal(await title(), 'A page'); + assert.equal(await r.text('h1'), 'A page'); - await page.click('[href="dirs/foo/xyz"]'); - await wait(50); - assert.equal(await title(), 'B page'); + await r.page.click('[href="dirs/foo/xyz"]'); + await r.wait(); + assert.equal(await r.text('h1'), 'B page'); }); it('runs server route handlers before page handlers, if they match', async () => { - const json = await get(`${base}/middleware`, { + const json = await get(`${r.base}/middleware`, { headers: { 'Accept': 'application/json' } @@ -320,22 +311,26 @@ describe('basics', function() { assert.equal(json.body, '{"json":true}'); - const html = await get(`${base}/middleware`); + const html = await get(`${r.base}/middleware`); assert.ok(html.body.indexOf('

HTML

') !== -1); }); it('invalidates page when a segment is skipped', async () => { - await page.goto(`${base}/skipped/x/1`); - await start(); - await prefetchRoutes(); + await r.load('/skipped/x/1'); + await r.sapper.start(); + await r.sapper.prefetchRoutes(); - await page.click('a[href="skipped/y/1"]'); - await wait(50); + await r.page.click('a[href="skipped/y/1"]'); + await r.wait(); assert.equal( - await title(), + await r.text('h1'), 'y:1' ); }); + + it('survives the tests with no server errors', () => { + assert.deepEqual(r.errors, []); + }); }); diff --git a/test/apps/common.js b/test/apps/common.js new file mode 100644 index 0000000..5425f42 --- /dev/null +++ b/test/apps/common.js @@ -0,0 +1,42 @@ +const { NODE_ENV, PORT } = process.env; + +export const dev = NODE_ENV === 'development'; + +export function start(app) { + const port = parseInt(PORT) || 0; + + app.listen(port, () => { + const address = app.server.address(); + + process.env.PORT = address.port; + + send({ + __sapper__: true, + event: 'listening', + address + }); + }); +} + +const properties = ['name', 'message', 'stack', 'code', 'lineNumber', 'fileName']; + +function send(message) { + process.send && process.send(message); +} + +function send_error(error) { + send({ + __sapper__: true, + event: 'error', + error: properties.reduce((object, key) => ({...object, [key]: error[key]}), {}) + }) +} + +process.on('unhandledRejection', (reason, p) => { + send_error(reason); +}); + +process.on('uncaughtException', err => { + send_error(err); + process.exitCode = 1; +}); diff --git a/test/apps/credentials/src/server.js b/test/apps/credentials/src/server.js index 951243e..1067cb3 100644 --- a/test/apps/credentials/src/server.js +++ b/test/apps/credentials/src/server.js @@ -1,13 +1,14 @@ import polka from 'polka'; import * as sapper from '@sapper/server'; -const { PORT } = process.env; +import { start } from '../../common.js'; -polka() +const app = polka() .use((req, res, next) => { // set test cookie res.setHeader('Set-Cookie', ['a=1; Max-Age=3600', 'b=2; Max-Age=3600']); next(); }) .use(sapper.middleware()) - .listen(PORT); + +start(app); diff --git a/test/apps/credentials/test.ts b/test/apps/credentials/test.ts index 665f46c..67f39c0 100644 --- a/test/apps/credentials/test.ts +++ b/test/apps/credentials/test.ts @@ -1,59 +1,54 @@ import * as assert from 'assert'; -import * as puppeteer from 'puppeteer'; import { build } from '../../../api'; -import { wait } from '../../utils'; import { AppRunner } from '../AppRunner'; describe('credentials', function() { this.timeout(10000); - let runner: AppRunner; - let page: puppeteer.Page; - let base: string; - - // helpers - let start: () => Promise; - let prefetchRoutes: () => Promise; + let r: AppRunner; // hooks - before(async () => { - await build({ cwd: __dirname }); - - runner = new AppRunner(__dirname, '__sapper__/build/server/server.js'); - ({ base, page, start, prefetchRoutes } = await runner.start()); + before('build app', () => build({ cwd: __dirname })); + before('start runner', async () => { + r = await new AppRunner().start(__dirname); }); - after(() => runner.end()); + after(() => r && r.end()); + // tests it('sends cookies when using this.fetch with credentials: "include"', async () => { - await page.goto(`${base}/credentials?creds=include`); + await r.load('/credentials?creds=include'); assert.equal( - await page.$eval('h1', node => node.textContent), + await r.text('h1'), 'a: 1, b: 2, max-age: undefined' ); }); it('does not send cookies when using this.fetch without credentials', async () => { - await page.goto(`${base}/credentials`); + await r.load('/credentials'); assert.equal( - await page.$eval('h1', node => node.textContent), + await r.text('h1'), 'unauthorized' ); }); it('delegates to fetch on the client', async () => { - await page.goto(base) - await start(); - await prefetchRoutes(); + await r.load('/') + await r.sapper.start(); + await r.sapper.prefetchRoutes(); - await page.click('[href="credentials?creds=include"]'); - await wait(50); + await r.page.click('[href="credentials?creds=include"]'); + await r.wait(); assert.equal( - await page.$eval('h1', node => node.textContent), + await r.text('h1'), 'a: 1, b: 2, max-age: undefined' ); }); -}); \ No newline at end of file + + it('survives the tests with no server errors', () => { + assert.deepEqual(r.errors, []); + }); +}); diff --git a/test/apps/css/src/server.js b/test/apps/css/src/server.js index 7f090b8..e33d804 100644 --- a/test/apps/css/src/server.js +++ b/test/apps/css/src/server.js @@ -1,8 +1,9 @@ import polka from 'polka'; import * as sapper from '@sapper/server'; -const { PORT } = process.env; +import { start } from '../../common.js'; -polka() - .use(sapper.middleware()) - .listen(PORT); +const app = polka() + .use(sapper.middleware()); + +start(app); diff --git a/test/apps/css/test.ts b/test/apps/css/test.ts index c6539af..01e7095 100644 --- a/test/apps/css/test.ts +++ b/test/apps/css/test.ts @@ -1,78 +1,61 @@ import * as assert from 'assert'; -import * as puppeteer from 'puppeteer'; import { build } from '../../../api'; import { AppRunner } from '../AppRunner'; -import { wait } from '../../utils'; describe('css', function() { this.timeout(10000); - let runner: AppRunner; - let page: puppeteer.Page; - let base: string; - - // helpers - let start: () => Promise; - let prefetchRoutes: () => Promise; - let prefetch: (href: string) => Promise; - let goto: (href: string) => Promise; - let title: () => Promise; + let r: AppRunner; // hooks - before(async () => { - await build({ cwd: __dirname }); - - runner = new AppRunner(__dirname, '__sapper__/build/server/server.js'); - ({ base, page, start, prefetchRoutes, prefetch, goto, title } = await runner.start()); + before('build app', () => build({ cwd: __dirname })); + before('start runner', async () => { + r = await new AppRunner().start(__dirname); }); - after(() => runner.end()); + after(() => r && r.end()); + // tests it('includes critical CSS with server render', async () => { - await page.goto(base); + await r.load('/'); assert.equal( - await page.evaluate(() => { - const h1 = document.querySelector('h1'); - return getComputedStyle(h1).color; - }), + await r.page.$eval('h1', node => getComputedStyle(node).color), 'rgb(255, 0, 0)' ); }); it('loads CSS when navigating client-side', async () => { - await page.goto(base); + await r.load('/'); - await start(); - await prefetchRoutes(); + await r.sapper.start(); + await r.sapper.prefetchRoutes(); - await page.click(`[href="foo"]`); - await wait(50); + await r.page.click(`[href="foo"]`); + await r.wait(); assert.equal( - await page.evaluate(() => { - const h1 = document.querySelector('h1'); - return getComputedStyle(h1).color; - }), + await r.page.$eval('h1', node => getComputedStyle(node).color), 'rgb(0, 0, 255)' ); }); it('loads CSS for a lazily-rendered component', async () => { - await page.goto(base); + await r.load('/'); - await start(); - await prefetchRoutes(); + await r.sapper.start(); + await r.sapper.prefetchRoutes(); - await page.click(`[href="bar"]`); - await wait(50); + await r.page.click(`[href="bar"]`); + await r.wait(); assert.equal( - await page.evaluate(() => { - const h1 = document.querySelector('h1'); - return getComputedStyle(h1).color; - }), + await r.page.$eval('h1', node => getComputedStyle(node).color), 'rgb(0, 128, 0)' ); }); -}); \ No newline at end of file + + it('survives the tests with no server errors', () => { + assert.deepEqual(r.errors, []); + }); +}); diff --git a/test/apps/encoding/src/server.js b/test/apps/encoding/src/server.js index 7f090b8..e33d804 100644 --- a/test/apps/encoding/src/server.js +++ b/test/apps/encoding/src/server.js @@ -1,8 +1,9 @@ import polka from 'polka'; import * as sapper from '@sapper/server'; -const { PORT } = process.env; +import { start } from '../../common.js'; -polka() - .use(sapper.middleware()) - .listen(PORT); +const app = polka() + .use(sapper.middleware()); + +start(app); diff --git a/test/apps/encoding/test.ts b/test/apps/encoding/test.ts index 157615a..0d51b64 100644 --- a/test/apps/encoding/test.ts +++ b/test/apps/encoding/test.ts @@ -1,68 +1,63 @@ import * as assert from 'assert'; -import * as puppeteer from 'puppeteer'; import { build } from '../../../api'; import { AppRunner } from '../AppRunner'; -import { wait } from '../../utils'; describe('encoding', function() { this.timeout(10000); - let runner: AppRunner; - let page: puppeteer.Page; - let base: string; - - // helpers - let start: () => Promise; - let prefetchRoutes: () => Promise; + let r: AppRunner; // hooks - before(async () => { - await build({ cwd: __dirname }); - - runner = new AppRunner(__dirname, '__sapper__/build/server/server.js'); - ({ base, page, start, prefetchRoutes } = await runner.start()); + before('build app', () => build({ cwd: __dirname })); + before('start runner', async () => { + r = await new AppRunner().start(__dirname); }); - after(() => runner.end()); + after(() => r && r.end()); + // tests it('encodes routes', async () => { - await page.goto(`${base}/fünke`); + await r.load('/fünke'); assert.equal( - await page.$eval('h1', node => node.textContent), + await r.text('h1'), `I'm afraid I just blue myself` ); }); it('encodes req.params and req.query for server-rendered pages', async () => { - await page.goto(`${base}/echo/page/encöded?message=hëllö+wörld&föo=bar&=baz&tel=%2B123456789`); + await r.load('/echo/page/encöded?message=hëllö+wörld&föo=bar&=baz&tel=%2B123456789'); assert.equal( - await page.$eval('h1', node => node.textContent), + await r.text('h1'), 'encöded {"message":"hëllö wörld","föo":"bar","":"baz","tel":"+123456789"}' ); }); it('encodes req.params and req.query for client-rendered pages', async () => { - await page.goto(base); - await start(); - await prefetchRoutes(); + await r.load('/'); + await r.sapper.start(); + await r.sapper.prefetchRoutes(); - await page.click('a'); - await wait(50); + await r.page.click('a'); + await r.wait(); assert.equal( - await page.$eval('h1', node => node.textContent), + await r.text('h1'), 'encöded {"message":"hëllö wörld","föo":"bar","":"baz","tel":"+123456789"}' ); }); it('encodes req.params for server routes', async () => { - await page.goto(`${base}/echo/server-route/encöded`); + await r.load('/echo/server-route/encöded'); assert.equal( - await page.$eval('h1', node => node.textContent), + await r.text('h1'), 'encöded' ); }); -}); \ No newline at end of file + + it('survives the tests with no server errors', () => { + assert.deepEqual(r.errors, []); + }); +}); diff --git a/test/apps/errors/src/server.js b/test/apps/errors/src/server.js index 7f090b8..e33d804 100644 --- a/test/apps/errors/src/server.js +++ b/test/apps/errors/src/server.js @@ -1,8 +1,9 @@ import polka from 'polka'; import * as sapper from '@sapper/server'; -const { PORT } = process.env; +import { start } from '../../common.js'; -polka() - .use(sapper.middleware()) - .listen(PORT); +const app = polka() + .use(sapper.middleware()); + +start(app); diff --git a/test/apps/errors/test.ts b/test/apps/errors/test.ts index d69470c..181c591 100644 --- a/test/apps/errors/test.ts +++ b/test/apps/errors/test.ts @@ -1,5 +1,4 @@ import * as assert from 'assert'; -import * as puppeteer from 'puppeteer'; import { build } from '../../../api'; import { AppRunner } from '../AppRunner'; import { wait } from '../../utils'; @@ -7,142 +6,137 @@ import { wait } from '../../utils'; describe('errors', function() { this.timeout(10000); - let runner: AppRunner; - let page: puppeteer.Page; - let base: string; - - // helpers - let start: () => Promise; - let prefetchRoutes: () => Promise; - let title: () => Promise; + let r: AppRunner; // hooks - before(async () => { - await build({ cwd: __dirname }); - - runner = new AppRunner(__dirname, '__sapper__/build/server/server.js'); - ({ base, page, start, prefetchRoutes, title } = await runner.start()); + before('build app', () => build({ cwd: __dirname })); + before('start runner', async () => { + r = await new AppRunner().start(__dirname); }); - after(() => runner.end()); + after(() => r && r.end()); + // tests it('handles missing route on server', async () => { - await page.goto(`${base}/nope`); + await r.load('/nope'); assert.equal( - await page.$eval('h1', node => node.textContent), + await r.text('h1'), '404' ); }); it('handles missing route on client', async () => { - await page.goto(base); - await start(); + await r.load('/'); + await r.sapper.start(); - await page.click('[href="nope"]'); - await wait(50); + await r.page.click('[href="nope"]'); + await r.wait(); assert.equal( - await page.$eval('h1', node => node.textContent), + await r.text('h1'), '404' ); }); it('handles explicit 4xx on server', async () => { - await page.goto(`${base}/blog/nope`); + await r.load('/blog/nope'); assert.equal( - await page.$eval('h1', node => node.textContent), + await r.text('h1'), '404' ); }); it('handles explicit 4xx on client', async () => { - await page.goto(base); - await start(); - await prefetchRoutes(); + await r.load('/'); + await r.sapper.start(); + await r.sapper.prefetchRoutes(); - await page.click('[href="blog/nope"]'); - await wait(50); + await r.page.click('[href="blog/nope"]'); + await r.wait(); assert.equal( - await page.$eval('h1', node => node.textContent), + await r.text('h1'), '404' ); }); it('handles error on server', async () => { - await page.goto(`${base}/throw`); + await r.load('/throw'); assert.equal( - await page.$eval('h1', node => node.textContent), + await r.text('h1'), '500' ); }); it('handles error on client', async () => { - await page.goto(base); - await start(); - await prefetchRoutes(); + await r.load('/'); + await r.sapper.start(); + await r.sapper.prefetchRoutes(); - await page.click('[href="throw"]'); - await wait(50); + await r.page.click('[href="throw"]'); + await r.wait(); assert.equal( - await page.$eval('h1', node => node.textContent), + await r.text('h1'), '500' ); }); it('does not serve error page for explicit non-page errors', async () => { - await page.goto(`${base}/nope.json`); + await r.load('/nope.json'); assert.equal( - await page.evaluate(() => document.body.textContent), + await r.text('body'), 'nope' ); }); it('does not serve error page for thrown non-page errors', async () => { - await page.goto(`${base}/throw.json`); + await r.load('/throw.json'); assert.equal( - await page.evaluate(() => document.body.textContent), + await r.text('body'), 'oops' ); }); it('execute error page hooks', async () => { - await page.goto(`${base}/some-throw-page`); - await start(); - await wait(50); + await r.load('/some-throw-page'); + await r.sapper.start(); assert.equal( - await page.$eval('h2', node => node.textContent), + await r.text('h2'), 'success' ); }) it('does not serve error page for async non-page error', async () => { - await page.goto(`${base}/async-throw.json`); + await r.load('/async-throw.json'); assert.equal( - await page.evaluate(() => document.body.textContent), + await r.text('body'), 'oops' ); }); it('clears props.error on successful render', async () => { - await page.goto(`${base}/no-error`); - await start(); - await prefetchRoutes(); + await r.load('/no-error'); + await r.sapper.start(); + await r.sapper.prefetchRoutes(); - await page.click('[href="enhance-your-calm"]'); - await wait(50); - assert.equal(await title(), '420'); + await r.page.click('[href="enhance-your-calm"]'); + await r.wait(); + assert.equal(await r.text('h1'), '420'); - await page.goBack(); - await wait(50); - assert.equal(await title(), 'No error here'); + await r.page.goBack(); + await r.wait(); + assert.equal(await r.text('h1'), 'No error here'); + }); + + it('survives the tests with no server errors', () => { + assert.deepEqual(r.errors, []); }); }); diff --git a/test/apps/export-webpack/src/server.js b/test/apps/export-webpack/src/server.js index 2c6932d..4ccaf34 100644 --- a/test/apps/export-webpack/src/server.js +++ b/test/apps/export-webpack/src/server.js @@ -2,14 +2,12 @@ import sirv from 'sirv'; import polka from 'polka'; import * as sapper from '@sapper/server'; -const { PORT, NODE_ENV } = process.env; -const dev = NODE_ENV === 'development'; +import { start, dev } from '../../common.js'; -polka() +const app = polka() .use( sirv('static', { dev }), sapper.middleware() - ) - .listen(PORT, err => { - if (err) console.log('error', err); - }); + ); + +start(app); diff --git a/test/apps/export-webpack/test.ts b/test/apps/export-webpack/test.ts index 44abb78..30c3f49 100644 --- a/test/apps/export-webpack/test.ts +++ b/test/apps/export-webpack/test.ts @@ -6,14 +6,12 @@ describe('export-webpack', function() { this.timeout(10000); // hooks - before(async () => { - await api.build({ cwd: __dirname, bundler: 'webpack' }); - await api.export({ cwd: __dirname, bundler: 'webpack' }); - }); + before('build app', () => api.build({ cwd: __dirname, bundler: 'webpack' })); + before('export app', () => api.export({ cwd: __dirname })); + // tests it('injects tags', () => { const index = fs.readFileSync(`${__dirname}/__sapper__/export/index.html`, 'utf8'); assert.ok(/rel=preload/.test(index)); }); - }); diff --git a/test/apps/export/src/server.js b/test/apps/export/src/server.js index 2c6932d..4ccaf34 100644 --- a/test/apps/export/src/server.js +++ b/test/apps/export/src/server.js @@ -2,14 +2,12 @@ import sirv from 'sirv'; import polka from 'polka'; import * as sapper from '@sapper/server'; -const { PORT, NODE_ENV } = process.env; -const dev = NODE_ENV === 'development'; +import { start, dev } from '../../common.js'; -polka() +const app = polka() .use( sirv('static', { dev }), sapper.middleware() - ) - .listen(PORT, err => { - if (err) console.log('error', err); - }); + ); + +start(app); diff --git a/test/apps/export/test.ts b/test/apps/export/test.ts index 98e7601..181096c 100644 --- a/test/apps/export/test.ts +++ b/test/apps/export/test.ts @@ -6,11 +6,10 @@ describe('export', function() { this.timeout(10000); // hooks - before(async () => { - await api.build({ cwd: __dirname }); - await api.export({ cwd: __dirname }); - }); + before('build app', () => api.build({ cwd: __dirname })); + before('export app', () => api.export({ cwd: __dirname })); + // tests it('crawls a site', () => { const files = walk(`${__dirname}/__sapper__/export`); @@ -45,4 +44,4 @@ describe('export', function() { }); // TODO test timeout, basepath -}); \ No newline at end of file +}); diff --git a/test/apps/ignore/src/server.js b/test/apps/ignore/src/server.js index 4a66815..950c214 100644 --- a/test/apps/ignore/src/server.js +++ b/test/apps/ignore/src/server.js @@ -1,7 +1,7 @@ import polka from 'polka'; import * as sapper from '@sapper/server'; -const { PORT } = process.env; +import { start } from '../../common.js'; const app = polka().use(sapper.middleware({ ignore: [ @@ -16,4 +16,4 @@ const app = polka().use(sapper.middleware({ app.get('/'+uri, (req, res) => res.end(uri)); }); -app.listen(PORT); +start(app); diff --git a/test/apps/ignore/test.ts b/test/apps/ignore/test.ts index 6ec8616..641c5a4 100644 --- a/test/apps/ignore/test.ts +++ b/test/apps/ignore/test.ts @@ -1,58 +1,58 @@ import * as assert from 'assert'; -import * as puppeteer from 'puppeteer'; import { build } from '../../../api'; import { AppRunner } from '../AppRunner'; describe('ignore', function() { this.timeout(10000); - let runner: AppRunner; - let page: puppeteer.Page; - let base: string; + let r: AppRunner; // hooks - before(async () => { - await build({ cwd: __dirname }); - - runner = new AppRunner(__dirname, '__sapper__/build/server/server.js'); - ({ base, page } = await runner.start()); + before('build app', () => build({ cwd: __dirname })); + before('start runner', async () => { + r = await new AppRunner().start(__dirname); }); - after(() => runner.end()); + after(() => r && r.end()); + // tests it('respects `options.ignore` values (RegExp)', async () => { - await page.goto(`${base}/foobar`); + await r.load('/foobar'); assert.equal( - await page.evaluate(() => document.documentElement.textContent), + await r.text('body'), 'foobar' ); }); it('respects `options.ignore` values (String #1)', async () => { - await page.goto(`${base}/buzz`); + await r.load('/buzz'); assert.equal( - await page.evaluate(() => document.documentElement.textContent), + await r.text('body'), 'buzz' ); }); it('respects `options.ignore` values (String #2)', async () => { - await page.goto(`${base}/fizzer`); + await r.load('/fizzer'); assert.equal( - await page.evaluate(() => document.documentElement.textContent), + await r.text('body'), 'fizzer' ); }); it('respects `options.ignore` values (Function)', async () => { - await page.goto(`${base}/hello`); + await r.load('/hello'); assert.equal( - await page.evaluate(() => document.documentElement.textContent), + await r.text('body'), 'hello' ); }); -}); \ No newline at end of file + + it('survives the tests with no server errors', () => { + assert.deepEqual(r.errors, []); + }); +}); diff --git a/test/apps/layout/src/server.js b/test/apps/layout/src/server.js index 7f090b8..e33d804 100644 --- a/test/apps/layout/src/server.js +++ b/test/apps/layout/src/server.js @@ -1,8 +1,9 @@ import polka from 'polka'; import * as sapper from '@sapper/server'; -const { PORT } = process.env; +import { start } from '../../common.js'; -polka() - .use(sapper.middleware()) - .listen(PORT); +const app = polka() + .use(sapper.middleware()); + +start(app); diff --git a/test/apps/layout/test.ts b/test/apps/layout/test.ts index 86a6d3a..7f57408 100644 --- a/test/apps/layout/test.ts +++ b/test/apps/layout/test.ts @@ -1,33 +1,25 @@ import * as assert from 'assert'; -import * as puppeteer from 'puppeteer'; import { build } from '../../../api'; import { AppRunner } from '../AppRunner'; -import { wait } from '../../utils'; describe('layout', function() { this.timeout(10000); - let runner: AppRunner; - let page: puppeteer.Page; - let base: string; - - // helpers - let start: () => Promise; + let r: AppRunner; // hooks - before(async () => { - await build({ cwd: __dirname }); - - runner = new AppRunner(__dirname, '__sapper__/build/server/server.js'); - ({ base, page, start } = await runner.start()); + before('build app', () => build({ cwd: __dirname })); + before('start runner', async () => { + r = await new AppRunner().start(__dirname); }); - after(() => runner.end()); + after(() => r && r.end()); + // tests it('only recreates components when necessary', async () => { - await page.goto(`${base}/foo/bar/baz`); + await r.load('/foo/bar/baz'); - const text1 = String(await page.evaluate(() => document.querySelector('#sapper').textContent)); + const text1 = await r.text('#sapper'); assert.deepEqual(text1.split('\n').map(str => str.trim()).filter(Boolean), [ 'y: bar 1', 'z: baz 1', @@ -35,8 +27,8 @@ describe('layout', function() { 'child segment: baz' ]); - await start(); - const text2 = String(await page.evaluate(() => document.querySelector('#sapper').textContent)); + await r.sapper.start(); + const text2 = await r.text('#sapper'); assert.deepEqual(text2.split('\n').map(str => str.trim()).filter(Boolean), [ 'y: bar 1', 'z: baz 1', @@ -44,10 +36,10 @@ describe('layout', function() { 'child segment: baz' ]); - await page.click('[href="foo/bar/qux"]'); - await wait(50); + await r.page.click('[href="foo/bar/qux"]'); + await r.wait(); - const text3 = String(await page.evaluate(() => document.querySelector('#sapper').textContent)); + const text3 = await r.text('#sapper'); assert.deepEqual(text3.split('\n').map(str => str.trim()).filter(Boolean), [ 'y: bar 1', 'z: qux 2', @@ -55,4 +47,8 @@ describe('layout', function() { 'child segment: qux' ]); }); -}); \ No newline at end of file + + it('survives the tests with no server errors', () => { + assert.deepEqual(r.errors, []); + }); +}); diff --git a/test/apps/preloading/src/server.js b/test/apps/preloading/src/server.js index 7f090b8..e33d804 100644 --- a/test/apps/preloading/src/server.js +++ b/test/apps/preloading/src/server.js @@ -1,8 +1,9 @@ import polka from 'polka'; import * as sapper from '@sapper/server'; -const { PORT } = process.env; +import { start } from '../../common.js'; -polka() - .use(sapper.middleware()) - .listen(PORT); +const app = polka() + .use(sapper.middleware()); + +start(app); diff --git a/test/apps/preloading/test.ts b/test/apps/preloading/test.ts index 97f68b7..4aa05fb 100644 --- a/test/apps/preloading/test.ts +++ b/test/apps/preloading/test.ts @@ -1,7 +1,5 @@ import * as assert from 'assert'; -import * as puppeteer from 'puppeteer'; import { build } from '../../../api'; -import { wait } from '../../utils'; import { AppRunner } from '../AppRunner'; declare const fulfil: () => Promise; @@ -9,112 +7,106 @@ declare const fulfil: () => Promise; describe('preloading', function() { this.timeout(10000); - let runner: AppRunner; - let page: puppeteer.Page; - let base: string; - - // helpers - let start: () => Promise; - let prefetchRoutes: () => Promise; - let title: () => Promise; + let r: AppRunner; // hooks - before(async () => { - await build({ cwd: __dirname }); - - runner = new AppRunner(__dirname, '__sapper__/build/server/server.js'); - ({ base, page, start, prefetchRoutes, title } = await runner.start()); + before('build app', () => build({ cwd: __dirname })); + before('start runner', async () => { + r = await new AppRunner().start(__dirname); }); - after(() => runner.end()); + after(() => r && r.end()); + // tests it('serializes Set objects returned from preload', async () => { - await page.goto(`${base}/preload-values/set`); + await r.load('/preload-values/set'); - assert.equal(await title(), 'true'); + assert.equal(await r.text('h1'), 'true'); - await start(); - assert.equal(await title(), 'true'); + await r.sapper.start(); + assert.equal(await r.text('h1'), 'true'); }); it('prevent crash if preload return nothing', async () => { - await page.goto(`${base}/preload-nothing`); + await r.load('/preload-nothing'); - await start(); - await wait(50); + await r.sapper.start(); - assert.equal(await title(), 'Page loaded'); + assert.equal(await r.text('h1'), 'Page loaded'); }); it('bails on custom classes returned from preload', async () => { - await page.goto(`${base}/preload-values/custom-class`); + await r.load('/preload-values/custom-class'); - assert.equal(await title(), '42'); + assert.equal(await r.text('h1'), '42'); - await start(); - assert.equal(await title(), '42'); + await r.sapper.start(); + assert.equal(await r.text('h1'), '42'); }); it('sets preloading true when appropriate', async () => { - await page.goto(base); - await start(); - await prefetchRoutes(); + await r.load('/'); + await r.sapper.start(); + await r.sapper.prefetchRoutes(); - await page.click('a[href="slow-preload"]'); + await r.page.click('a[href="slow-preload"]'); - assert.ok(await page.evaluate(() => !!document.querySelector('progress'))); + assert.ok(await r.page.evaluate(() => !!document.querySelector('progress'))); - await page.evaluate(() => fulfil()); - assert.ok(await page.evaluate(() => !document.querySelector('progress'))); + await r.page.evaluate(() => fulfil()); + assert.ok(await r.page.evaluate(() => !document.querySelector('progress'))); }); it('runs preload in root component', async () => { - await page.goto(`${base}/preload-root`); - assert.equal(await title(), 'root preload function ran: true'); + await r.load('/preload-root'); + assert.equal(await r.text('h1'), 'root preload function ran: true'); }); it('cancels navigation if subsequent navigation occurs during preload', async () => { - await page.goto(base); - await start(); - await prefetchRoutes(); + await r.load('/'); + await r.sapper.start(); + await r.sapper.prefetchRoutes(); - await page.click('a[href="slow-preload"]'); - await wait(100); - await page.click('a[href="foo"]'); + await r.page.click('a[href="slow-preload"]'); + await r.wait(); + await r.page.click('a[href="foo"]'); - assert.equal(page.url(), `${base}/foo`); - assert.equal(await title(), 'foo'); + assert.equal(r.page.url(), `${r.base}/foo`); + assert.equal(await r.text('h1'), 'foo'); - await page.evaluate(() => fulfil()); - await wait(100); - assert.equal(page.url(), `${base}/foo`); - assert.equal(await title(), 'foo'); + await r.page.evaluate(() => fulfil()); + await r.wait(); + assert.equal(r.page.url(), `${r.base}/foo`); + assert.equal(await r.text('h1'), 'foo'); }); it('navigates to prefetched urls', async () => { - await page.goto(base); - await start(); - await prefetchRoutes(); + await r.load('/'); + await r.sapper.start(); + await r.sapper.prefetchRoutes(); - await page.hover('a[href="prefetch/qwe"]'); - await wait(100); - await page.hover('a[href="prefetch/xyz"]'); - await wait(100); + await r.page.hover('a[href="prefetch/qwe"]'); + await r.wait(50); + await r.page.hover('a[href="prefetch/xyz"]'); + await r.wait(50); - await page.click('a[href="prefetch/qwe"]'); - await wait(50); + await r.page.click('a[href="prefetch/qwe"]'); + await r.wait(); assert.equal( - await title(), + await r.text('h1'), 'qwe' ); - await page.goto(`${base}/prefetch`); - await wait(50); + await r.load('/prefetch'); assert.equal( - await title(), + await r.text('h1'), 'prefetch' ); }); + + it('survives the tests with no server errors', () => { + assert.deepEqual(r.errors, []); + }); }); diff --git a/test/apps/redirects/src/server.js b/test/apps/redirects/src/server.js index 7f090b8..e33d804 100644 --- a/test/apps/redirects/src/server.js +++ b/test/apps/redirects/src/server.js @@ -1,8 +1,9 @@ import polka from 'polka'; import * as sapper from '@sapper/server'; -const { PORT } = process.env; +import { start } from '../../common.js'; -polka() - .use(sapper.middleware()) - .listen(PORT); +const app = polka() + .use(sapper.middleware()); + +start(app); diff --git a/test/apps/redirects/test.ts b/test/apps/redirects/test.ts index 50d6507..1d8f125 100644 --- a/test/apps/redirects/test.ts +++ b/test/apps/redirects/test.ts @@ -2,138 +2,137 @@ import * as assert from 'assert'; import * as puppeteer from 'puppeteer'; import { build } from '../../../api'; import { AppRunner } from '../AppRunner'; -import { wait } from '../../utils'; describe('redirects', function() { this.timeout(10000); - let runner: AppRunner; - let page: puppeteer.Page; - let base: string; - - // helpers - let start: () => Promise; - let prefetchRoutes: () => Promise; - let title: () => Promise; + let r: AppRunner; // hooks - before(async () => { - await build({ cwd: __dirname }); - - runner = new AppRunner(__dirname, '__sapper__/build/server/server.js'); - ({ base, page, start, prefetchRoutes, title } = await runner.start({ - requestInterceptor: (interceptedRequest) => { - if (/example\.com/.test(interceptedRequest.url())) { - interceptedRequest.respond({ - status: 200, - contentType: 'text/html', - body: `

external

` - }); - } else { - interceptedRequest.continue(); - } - } - })); + before('build app', () => build({ cwd: __dirname })); + before('start runner', async () => { + r = await new AppRunner().start(__dirname); }); - after(() => runner.end()); + after(() => r && r.end()); + // tests it('redirects on server', async () => { - await page.goto(`${base}/redirect-from`); + await r.load('/redirect-from'); assert.equal( - page.url(), - `${base}/redirect-to` + r.page.url(), + `${r.base}/redirect-to` ); assert.equal( - await title(), + await r.text('h1'), 'redirected' ); }); it('redirects in client', async () => { - await page.goto(base); - await start(); - await prefetchRoutes(); + await r.load('/'); + await r.sapper.start(); + await r.sapper.prefetchRoutes(); - await page.click('[href="redirect-from"]'); - await wait(50); + await r.page.click('[href="redirect-from"]'); + await r.wait(); assert.equal( - page.url(), - `${base}/redirect-to` + r.page.url(), + `${r.base}/redirect-to` ); assert.equal( - await title(), + await r.text('h1'), 'redirected' ); }); it('redirects to root on server', async () => { - await page.goto(`${base}/redirect-to-root`); + await r.load('/redirect-to-root'); assert.equal( - page.url(), - `${base}/` + r.page.url(), + `${r.base}/` ); assert.equal( - await title(), + await r.text('h1'), 'root' ); }); it('redirects to root in client', async () => { - await page.goto(base); - await start(); - await prefetchRoutes(); + await r.load('/'); + await r.sapper.start(); + await r.sapper.prefetchRoutes(); - await page.click('[href="redirect-to-root"]'); - await wait(50); + await r.page.click('[href="redirect-to-root"]'); + await r.wait(); assert.equal( - page.url(), - `${base}/` + r.page.url(), + `${r.base}/` ); assert.equal( - await title(), + await r.text('h1'), 'root' ); }); + const interceptor = (request: puppeteer.Request) => { + if (/example\.com/.test(request.url())) { + request.respond({ + status: 200, + contentType: 'text/html', + body: `

external

` + }); + } else { + request.continue(); + } + }; + it('redirects to external URL on server', async () => { - await page.goto(`${base}/redirect-to-external`); + await r.intercept_requests(interceptor, async () => { + await r.load('/redirect-to-external'); + }); assert.equal( - page.url(), + r.page.url(), `https://example.com/` ); assert.equal( - await title(), + await r.text('h1'), 'external' ); }); it('redirects to external URL in client', async () => { - await page.goto(base); - await start(); - await prefetchRoutes(); + await r.load('/'); + await r.sapper.start(); + await r.sapper.prefetchRoutes(); - await page.click('[href="redirect-to-external"]'); - await wait(50); + await r.intercept_requests(interceptor, async () => { + await r.page.click('[href="redirect-to-external"]'); + await r.wait(); + }); assert.equal( - page.url(), + r.page.url(), `https://example.com/` ); assert.equal( - await title(), + await r.text('h1'), 'external' ); }); -}); \ No newline at end of file + + it('survives the tests with no server errors', () => { + assert.deepEqual(r.errors, []); + }); +}); diff --git a/test/apps/scroll/src/server.js b/test/apps/scroll/src/server.js index 7f090b8..e33d804 100644 --- a/test/apps/scroll/src/server.js +++ b/test/apps/scroll/src/server.js @@ -1,8 +1,9 @@ import polka from 'polka'; import * as sapper from '@sapper/server'; -const { PORT } = process.env; +import { start } from '../../common.js'; -polka() - .use(sapper.middleware()) - .listen(PORT); +const app = polka() + .use(sapper.middleware()); + +start(app); diff --git a/test/apps/scroll/test.ts b/test/apps/scroll/test.ts index 11130ff..f787d18 100644 --- a/test/apps/scroll/test.ts +++ b/test/apps/scroll/test.ts @@ -1,93 +1,87 @@ import * as assert from 'assert'; -import * as puppeteer from 'puppeteer'; import { build } from '../../../api'; import { AppRunner } from '../AppRunner'; -import { wait } from '../../utils'; describe('scroll', function() { this.timeout(10000); - let runner: AppRunner; - let page: puppeteer.Page; - let base: string; - - // helpers - let start: () => Promise; - let prefetchRoutes: () => Promise; - let title: () => Promise; + let r: AppRunner; // hooks - before(async () => { - await build({ cwd: __dirname }); - - runner = new AppRunner(__dirname, '__sapper__/build/server/server.js'); - ({ base, page, start, prefetchRoutes, title } = await runner.start()); + before('build app', () => build({ cwd: __dirname })); + before('start runner', async () => { + r = await new AppRunner().start(__dirname); }); - after(() => runner.end()); + after(() => r && r.end()); + // tests it('scrolls to active deeplink', async () => { - await page.goto(`${base}/tall-page#foo`); - await start(); + await r.load('/tall-page#foo'); + await r.sapper.start(); - const scrollY = await page.evaluate(() => window.scrollY); + const scrollY = await r.page.evaluate(() => window.scrollY); assert.ok(scrollY > 0, String(scrollY)); }); it('scrolls to any deeplink if it was already active', async () => { - await page.goto(`${base}/tall-page#foo`); - await start(); + await r.load('/tall-page#foo'); + await r.sapper.start(); - let scrollY = await page.evaluate(() => window.scrollY); + let scrollY = await r.page.evaluate(() => window.scrollY); assert.ok(scrollY > 0, String(scrollY)); - scrollY = await page.evaluate(() => { + scrollY = await r.page.evaluate(() => { window.scrollTo(0, 0) return window.scrollY }); assert.ok(scrollY === 0, String(scrollY)); - await page.click('[href="tall-page#foo"]'); - scrollY = await page.evaluate(() => window.scrollY); + await r.page.click('[href="tall-page#foo"]'); + scrollY = await r.page.evaluate(() => window.scrollY); assert.ok(scrollY > 0, String(scrollY)); }); it('resets scroll when a link is clicked', async () => { - await page.goto(`${base}/tall-page#foo`); - await start(); - await prefetchRoutes(); + await r.load('/tall-page#foo'); + await r.sapper.start(); + await r.sapper.prefetchRoutes(); - await page.click('[href="another-tall-page"]'); - await wait(50); + await r.page.click('[href="another-tall-page"]'); + await r.wait(); assert.equal( - await page.evaluate(() => window.scrollY), + await r.page.evaluate(() => window.scrollY), 0 ); }); it('preserves scroll when a link with sapper-noscroll is clicked', async () => { - await page.goto(`${base}/tall-page#foo`); - await start(); - await prefetchRoutes(); + await r.load('/tall-page#foo'); + await r.sapper.start(); + await r.sapper.prefetchRoutes(); - await page.click('[href="another-tall-page"][sapper-noscroll]'); - await wait(50); + await r.page.click('[href="another-tall-page"][sapper-noscroll]'); + await r.wait(); - const scrollY = await page.evaluate(() => window.scrollY); + const scrollY = await r.page.evaluate(() => window.scrollY); assert.ok(scrollY > 0); }); it('scrolls into a deeplink on a new page', async () => { - await page.goto(`${base}/tall-page#foo`); - await start(); - await prefetchRoutes(); + await r.load('/tall-page#foo'); + await r.sapper.start(); + await r.sapper.prefetchRoutes(); - await page.click('[href="another-tall-page#bar"]'); - await wait(50); - assert.equal(await title(), 'Another tall page'); - const scrollY = await page.evaluate(() => window.scrollY); + await r.page.click('[href="another-tall-page#bar"]'); + await r.wait(); + assert.equal(await r.text('h1'), 'Another tall page'); + const scrollY = await r.page.evaluate(() => window.scrollY); assert.ok(scrollY > 0); }); -}); \ No newline at end of file + + it('survives the tests with no server errors', () => { + assert.deepEqual(r.errors, []); + }); +}); diff --git a/test/apps/session/src/server.js b/test/apps/session/src/server.js index 16d904e..9e1cdeb 100644 --- a/test/apps/session/src/server.js +++ b/test/apps/session/src/server.js @@ -1,9 +1,9 @@ import polka from 'polka'; import * as sapper from '@sapper/server'; -const { PORT } = process.env; +import { start } from '../../common.js'; -polka() +const app = polka() .use((req, res, next) => { req.hello = 'hello'; res.locals = { name: 'world' }; @@ -15,5 +15,6 @@ polka() title: `${req.hello} ${res.locals.name}` }) }) - ) - .listen(PORT); + ); + +start(app); diff --git a/test/apps/session/test.ts b/test/apps/session/test.ts index 3fd910f..6dbfec7 100644 --- a/test/apps/session/test.ts +++ b/test/apps/session/test.ts @@ -1,50 +1,46 @@ import * as assert from 'assert'; -import * as puppeteer from 'puppeteer'; import { build } from '../../../api'; import { AppRunner } from '../AppRunner'; describe('session', function() { this.timeout(10000); - let runner: AppRunner; - let page: puppeteer.Page; - let base: string; - - // helpers - let start: () => Promise; - let title: () => Promise; + let r: AppRunner; // hooks - before(async () => { - await build({ cwd: __dirname }); - - runner = new AppRunner(__dirname, '__sapper__/build/server/server.js'); - ({ base, page, start, title } = await runner.start()); + before('build app', () => build({ cwd: __dirname })); + before('start runner', async () => { + r = await new AppRunner().start(__dirname); }); - after(() => runner.end()); + after(() => r && r.end()); + // tests it('renders session props', async () => { - await page.goto(`${base}/session`); + await r.load('/session'); - assert.equal(await title(), 'hello world'); + assert.equal(await r.text('h1'), 'hello world'); - await start(); - assert.equal(await title(), 'hello world'); + await r.sapper.start(); + assert.equal(await r.text('h1'), 'hello world'); - await page.click('button'); - assert.equal(await title(), 'changed'); + await r.page.click('button'); + assert.equal(await r.text('h1'), 'changed'); }); it('preloads session props', async () => { - await page.goto(`${base}/preloaded`); + await r.load('/preloaded'); - assert.equal(await title(), 'hello world'); + assert.equal(await r.text('h1'), 'hello world'); - await start(); - assert.equal(await title(), 'hello world'); + await r.sapper.start(); + assert.equal(await r.text('h1'), 'hello world'); - await page.click('button'); - assert.equal(await title(), 'changed'); + await r.page.click('button'); + assert.equal(await r.text('h1'), 'changed'); }); -}); \ No newline at end of file + + it('survives the tests with no server errors', () => { + assert.deepEqual(r.errors, []); + }); +}); diff --git a/test/apps/with-basepath/src/server.js b/test/apps/with-basepath/src/server.js index 22da3e9..8e8a064 100644 --- a/test/apps/with-basepath/src/server.js +++ b/test/apps/with-basepath/src/server.js @@ -2,15 +2,14 @@ import sirv from 'sirv'; import polka from 'polka'; import * as sapper from '@sapper/server'; -const { PORT, NODE_ENV } = process.env; -const dev = NODE_ENV === 'development'; +import { start, dev } from '../../common.js'; -polka() +const app = polka() .use( 'custom-basepath', sirv('static', { dev }), sapper.middleware() - ) - .listen(PORT, err => { - if (err) console.log('error', err); - }); + ); + +start(app); + diff --git a/test/apps/with-basepath/test.ts b/test/apps/with-basepath/test.ts index a81875d..0aae2fe 100644 --- a/test/apps/with-basepath/test.ts +++ b/test/apps/with-basepath/test.ts @@ -1,51 +1,36 @@ import * as assert from 'assert'; -import * as puppeteer from 'puppeteer'; import * as api from '../../../api'; import { walk } from '../../utils'; import { AppRunner } from '../AppRunner'; -import { wait } from '../../utils'; - describe('with-basepath', function() { this.timeout(10000); - let runner: AppRunner; - let page: puppeteer.Page; - let base: string; - - // helpers - let start: () => Promise; - let prefetchRoutes: () => Promise; - let title: () => Promise; + let r: AppRunner; // hooks - before(async () => { - await api.build({ cwd: __dirname }); - - await api.export({ - cwd: __dirname, - basepath: '/custom-basepath' - }); - - runner = new AppRunner(__dirname, '__sapper__/build/server/server.js'); - ({ base, start, page, prefetchRoutes, title } = await runner.start()); + before('build app', () => api.build({ cwd: __dirname })); + before('export app', () => api.export({ cwd: __dirname, basepath: '/custom-basepath' })); + before('start runner', async () => { + r = await new AppRunner().start(__dirname); }); - after(() => runner.end()); + after(() => r && r.end()); + // tests it('serves /custom-basepath', async () => { - await page.goto(`${base}/custom-basepath`); + await r.load('/custom-basepath'); assert.equal( - await page.$eval('h1', node => node.textContent), + await r.text('h1'), 'Great success!' ); }); it('emits a basepath message', async () => { - await page.goto(`${base}/custom-basepath`); + await r.load('/custom-basepath'); - assert.deepEqual(runner.messages, [{ + assert.deepEqual(r.messages, [{ __sapper__: true, event: 'basepath', basepath: '/custom-basepath' @@ -71,35 +56,39 @@ describe('with-basepath', function() { }); it('redirects on server', async () => { - await page.goto(`${base}/custom-basepath/redirect-from`); + await r.load('/custom-basepath/redirect-from'); assert.equal( - page.url(), - `${base}/custom-basepath/redirect-to` + r.page.url(), + `${r.base}/custom-basepath/redirect-to` ); assert.equal( - await title(), + await r.text('h1'), 'redirected' ); }); it('redirects in client', async () => { - await page.goto(`${base}/custom-basepath`); - await start(); - await prefetchRoutes(); + await r.load('/custom-basepath'); + await r.sapper.start(); + await r.sapper.prefetchRoutes(); - await page.click('[href="redirect-from"]'); - await wait(50); + await r.page.click('[href="redirect-from"]'); + await r.wait(); assert.equal( - page.url(), - `${base}/custom-basepath/redirect-to` + r.page.url(), + `${r.base}/custom-basepath/redirect-to` ); assert.equal( - await title(), + await r.text('h1'), 'redirected' ); }); -}); \ No newline at end of file + + it('survives the tests with no server errors', () => { + assert.deepEqual(r.errors, []); + }); +}); diff --git a/test/apps/with-sourcemaps-webpack/src/server.js b/test/apps/with-sourcemaps-webpack/src/server.js index 7f090b8..e33d804 100644 --- a/test/apps/with-sourcemaps-webpack/src/server.js +++ b/test/apps/with-sourcemaps-webpack/src/server.js @@ -1,8 +1,9 @@ import polka from 'polka'; import * as sapper from '@sapper/server'; -const { PORT } = process.env; +import { start } from '../../common.js'; -polka() - .use(sapper.middleware()) - .listen(PORT); +const app = polka() + .use(sapper.middleware()); + +start(app); diff --git a/test/apps/with-sourcemaps-webpack/test.ts b/test/apps/with-sourcemaps-webpack/test.ts index d270919..15b4fd1 100644 --- a/test/apps/with-sourcemaps-webpack/test.ts +++ b/test/apps/with-sourcemaps-webpack/test.ts @@ -7,10 +7,9 @@ describe('with-sourcemaps-webpack', function() { this.timeout(10000); // hooks - before(async () => { - await build({ cwd: __dirname, bundler: 'webpack' }); - }); + before('build app', () => build({ cwd: __dirname, bundler: 'webpack' })); + // tests it('does not put sourcemap files in service worker shell', async () => { const service_worker_source = fs.readFileSync(`${__dirname}/src/node_modules/@sapper/service-worker.js`, 'utf-8'); const shell_source = /shell = (\[[\s\S]+?\])/.exec(service_worker_source)[1]; @@ -23,4 +22,4 @@ describe('with-sourcemaps-webpack', function() { const sourcemapFiles = fs.readdirSync(clientShellDir).filter(_ => _.endsWith('.map')); assert.ok(sourcemapFiles.length > 0, 'sourcemap files exist'); }); -}); \ No newline at end of file +}); diff --git a/test/apps/with-sourcemaps/src/server.js b/test/apps/with-sourcemaps/src/server.js index 7f090b8..e33d804 100644 --- a/test/apps/with-sourcemaps/src/server.js +++ b/test/apps/with-sourcemaps/src/server.js @@ -1,8 +1,9 @@ import polka from 'polka'; import * as sapper from '@sapper/server'; -const { PORT } = process.env; +import { start } from '../../common.js'; -polka() - .use(sapper.middleware()) - .listen(PORT); +const app = polka() + .use(sapper.middleware()); + +start(app); diff --git a/test/apps/with-sourcemaps/test.ts b/test/apps/with-sourcemaps/test.ts index 2d05958..969bf9e 100644 --- a/test/apps/with-sourcemaps/test.ts +++ b/test/apps/with-sourcemaps/test.ts @@ -7,10 +7,9 @@ describe('with-sourcemaps', function() { this.timeout(10000); // hooks - before(async () => { - await build({ cwd: __dirname }); - }); + before('build app', () => build({ cwd: __dirname })); + // tests it('does not put sourcemap files in service worker shell', async () => { const service_worker_source = fs.readFileSync(`${__dirname}/src/node_modules/@sapper/service-worker.js`, 'utf-8'); const shell_source = /shell = (\[[\s\S]+?\])/.exec(service_worker_source)[1]; @@ -23,4 +22,4 @@ describe('with-sourcemaps', function() { const sourcemapFiles = fs.readdirSync(clientShellDir).filter(_ => _.endsWith('.map')); assert.ok(sourcemapFiles.length > 0, 'sourcemap files exist'); }); -}); \ No newline at end of file +});