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 }> { return new Promise((fulfil, reject) => { 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, body }); }); }); req.on('error', reject); }); } 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; // 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()); }); after(() => runner.end()); it('serves /', async () => { await page.goto(base); assert.equal( await title(), 'Great success!' ); }); it('serves /?', async () => { await page.goto(`${base}?`); assert.equal( await title(), 'Great success!' ); }); it('serves static route', async () => { await page.goto(`${base}/a`); assert.equal( await title(), 'a' ); }); it('serves static route from dir/index.html file', async () => { await page.goto(`${base}/b`); assert.equal( await title(), 'b' ); }); it('serves dynamic route', async () => { await page.goto(`${base}/test-slug`); assert.equal( await title(), 'TEST-SLUG' ); }); it('navigates to a new page without reloading', async () => { await page.goto(base); await start(); await prefetchRoutes(); const requests: string[] = await runner.capture(async () => { await page.click('a[href="a"]'); }); assert.deepEqual(requests, []); assert.equal( await title(), 'a' ); }); it('navigates programmatically', async () => { await page.goto(`${base}/a`); await start(); await goto('b'); assert.equal( await title(), 'b' ); }); it('prefetches programmatically', async () => { await page.goto(`${base}/a`); await start(); const requests = await runner.capture(() => prefetch('b')); assert.equal(requests.length, 2); assert.equal(requests[1], `${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); assert.equal( headers['content-type'], 'text/html' ); assert.equal( headers['cache-control'], 'max-age=600' ); // TODO preload more than just the entry point const regex = /<\/client\/client\.\w+\.js>;rel="modulepreload"/; const link = headers['link']; assert.ok(regex.test(link), link); }); it('calls a delete handler', async () => { await page.goto(`${base}/delete-test`); await start(); await page.click('.del'); await page.waitForFunction(() => deleted); assert.equal(await page.evaluate(() => deleted.id), 42); }); it('hydrates initial route', async () => { await page.goto(base); await page.evaluate(() => { el = document.querySelector('.hydrate-test'); }); await start(); assert.ok(await 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 page.click(`[href="ambiguous/ok.json"]`); await wait(50); assert.equal( await page.evaluate(() => document.body.textContent), 'ok' ); }); it('allows reserved words as route names', async () => { await page.goto(`${base}/const`); await start(); assert.equal( await title(), 'reserved words are okay as routes' ); }); it('accepts value-less query string parameter on server', async () => { await page.goto(`${base}/echo-query?message`); assert.equal( await title(), '{"message":""}' ); }); it('accepts value-less query string parameter on client', async () => { await page.goto(base); await start(); await prefetchRoutes(); await page.click('a[href="echo-query?message"]') assert.equal( await title(), '{"message":""}' ); }); it('accepts duplicated query string parameter on server', async () => { await page.goto(`${base}/echo-query?p=one&p=two`); assert.equal( await title(), '{"p":["one","two"]}' ); }); it('accepts duplicated query string parameter on client', async () => { await page.goto(base); await start(); await prefetchRoutes(); await page.click('a[href="echo-query?p=one&p=two"]') assert.equal( await title(), '{"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 page.click('[href="a"]'); await wait(50); assert.equal( await page.evaluate(() => document.activeElement.nodeName), 'BODY' ); }); it('replaces %sapper.xxx% tags safely', async () => { await page.goto(`${base}/unsafe-replacement`); await start(); const html = String(await 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(); assert.equal(await title(), 'foo'); await page.click('[href="dirs/bar"]'); await wait(50); assert.equal(await title(), 'bar'); }); it('navigates to ...rest', async () => { await page.goto(`${base}/abc/xyz`); await start(); assert.equal(await title(), 'abc,xyz'); await page.click('[href="xyz/abc/deep"]'); await wait(50); assert.equal(await title(), 'xyz,abc'); await page.click(`[href="xyz/abc/qwe/deep.json"]`); await wait(50); assert.equal( await page.evaluate(() => document.body.textContent), 'xyz,abc,qwe' ); }); it('runs server route handlers before page handlers, if they match', async () => { const json = await get(`${base}/middleware`, { headers: { 'Accept': 'application/json' } }); assert.equal(json.body, '{"json":true}'); const html = await get(`${base}/middleware`); assert.ok(html.body.indexOf('

HTML

') !== -1); }); });