Harden tests (...a bit)

This commit is contained in:
mrkishi
2019-05-08 00:57:59 -03:00
parent 8bdd363a19
commit e6c1a54164
35 changed files with 726 additions and 685 deletions

4
.gitignore vendored
View File

@@ -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

View File

@@ -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<void>;
declare const prefetchRoutes: () => Promise<void>;
declare const prefetch: (href: string) => Promise<void>;
declare const goto: (href: string) => Promise<void>;
type StartOpts = {
requestInterceptor?: (interceptedRequst: puppeteer.Request) => any
};
export class AppRunner {
cwd: string;
entry: string;
port: number;
proc: ChildProcess;
exiting: boolean;
terminate: Promise<any>;
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<string[]> {
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<string[]> {
return new Promise((fulfil, reject) => {
const requests: string[] = [];
const pending: Set<string> = 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<void> {
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;
}
}
end() {
this.exiting = true;
this.server.kill();
this.browser.close();
return this.terminate;
}
}
interface Deferred<T> extends Promise<T> {
resolve: (value?: T | PromiseLike<T>) => void;
reject: (reason?: any) => void;
settle: (result?: boolean) => void;
}
function settle<T>(this: Deferred<T>, result: boolean) {
if (result) {
this.resolve();
} else {
this.reject();
}
}
function deferred<T>() {
let resolve, reject;
const deferred = new Promise((_resolve, _reject) => {
resolve = _resolve;
reject = _reject;
}) as Deferred<T>;
deferred.resolve = resolve;
deferred.reject = reject;
deferred.settle = settle;
return deferred;
}

View File

@@ -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);

View File

@@ -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<string, string>, body: string }> {
type Response = { headers: http.IncomingHttpHeaders, body: string };
function get(url: string, opts: http.RequestOptions = {}): Promise<Response> {
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<string, string>,
headers: res.headers,
body
});
});
@@ -30,114 +30,104 @@ function get(url: string, opts?: any): Promise<{ headers: Record<string, string>
describe('basics', function() {
this.timeout(10000);
let runner: AppRunner;
let page: puppeteer.Page;
let base: string;
// helpers
let start: () => Promise<void>;
let prefetchRoutes: () => Promise<void>;
let prefetch: (href: string) => Promise<void>;
let goto: (href: string) => Promise<void>;
let title: () => Promise<string>;
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 <a> 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('<h1>HTML</h1>') !== -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, []);
});
});

42
test/apps/common.js Normal file
View File

@@ -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;
});

View File

@@ -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);

View File

@@ -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<void>;
let prefetchRoutes: () => Promise<void>;
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'
);
});
});
it('survives the tests with no server errors', () => {
assert.deepEqual(r.errors, []);
});
});

View File

@@ -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);

View File

@@ -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<void>;
let prefetchRoutes: () => Promise<void>;
let prefetch: (href: string) => Promise<void>;
let goto: (href: string) => Promise<void>;
let title: () => Promise<string>;
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)'
);
});
});
it('survives the tests with no server errors', () => {
assert.deepEqual(r.errors, []);
});
});

View File

@@ -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);

View File

@@ -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<void>;
let prefetchRoutes: () => Promise<void>;
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'
);
});
});
it('survives the tests with no server errors', () => {
assert.deepEqual(r.errors, []);
});
});

View File

@@ -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);

View File

@@ -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<void>;
let prefetchRoutes: () => Promise<void>;
let title: () => Promise<string>;
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, []);
});
});

View File

@@ -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);

View File

@@ -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 <link rel=preload> tags', () => {
const index = fs.readFileSync(`${__dirname}/__sapper__/export/index.html`, 'utf8');
assert.ok(/rel=preload/.test(index));
});
});

View File

@@ -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);

View File

@@ -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
});
});

View File

@@ -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);

View File

@@ -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'
);
});
});
it('survives the tests with no server errors', () => {
assert.deepEqual(r.errors, []);
});
});

View File

@@ -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);

View File

@@ -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<void>;
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'
]);
});
});
it('survives the tests with no server errors', () => {
assert.deepEqual(r.errors, []);
});
});

View File

@@ -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);

View File

@@ -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<void>;
@@ -9,112 +7,106 @@ declare const fulfil: () => Promise<void>;
describe('preloading', function() {
this.timeout(10000);
let runner: AppRunner;
let page: puppeteer.Page;
let base: string;
// helpers
let start: () => Promise<void>;
let prefetchRoutes: () => Promise<void>;
let title: () => Promise<string>;
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, []);
});
});

View File

@@ -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);

View File

@@ -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<void>;
let prefetchRoutes: () => Promise<void>;
let title: () => Promise<string>;
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: `<h1>external</h1>`
});
} 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: `<h1>external</h1>`
});
} 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'
);
});
});
it('survives the tests with no server errors', () => {
assert.deepEqual(r.errors, []);
});
});

View File

@@ -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);

View File

@@ -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<void>;
let prefetchRoutes: () => Promise<void>;
let title: () => Promise<string>;
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);
});
});
it('survives the tests with no server errors', () => {
assert.deepEqual(r.errors, []);
});
});

View File

@@ -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);

View File

@@ -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<void>;
let title: () => Promise<string>;
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');
});
});
it('survives the tests with no server errors', () => {
assert.deepEqual(r.errors, []);
});
});

View File

@@ -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);

View File

@@ -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<void>;
let prefetchRoutes: () => Promise<void>;
let title: () => Promise<string>;
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'
);
});
});
it('survives the tests with no server errors', () => {
assert.deepEqual(r.errors, []);
});
});

View File

@@ -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);

View File

@@ -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');
});
});
});

View File

@@ -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);

View File

@@ -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');
});
});
});