diff --git a/addon-test-support/index.js b/addon-test-support/index.js index a29e5b58..85168c54 100644 --- a/addon-test-support/index.js +++ b/addon-test-support/index.js @@ -2,6 +2,7 @@ import { fetch } from 'whatwg-fetch'; import { setupContext, teardownContext } from '@ember/test-helpers'; import { mockServer } from './-private/mock-server'; import param from 'jquery-param'; +import * as Comlink from 'comlink'; export function setup(hooks) { hooks.beforeEach(async function() { @@ -39,6 +40,35 @@ export async function visit(url, options = {}) { export { mockServer }; +export const nock = Comlink.wrapChain({ + listeners: [], + + async postMessage(message) { + let response = await fetch('/__nock-proxy', { + method: 'post', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(message), + }); + + let result = await response.json(); + + for (let listener of this.listeners) { + listener({ data: result }); + } + }, + + addEventListener(type, listener) { + this.listeners.push(listener); + }, + + removeEventListener(type, listener) { + let index = this.listeners.indexOf(listener); + this.listeners.splice(index, 1); + }, +}); + // private let fetchFromEmberCli = async function(url, headers) { @@ -50,17 +80,19 @@ let fetchFromEmberCli = async function(url, headers) { response = await fetch(endpoint); } catch (e) { if (e.message && e.message.match(/^Mirage:/)) { - error = `Ember CLI FastBoot Testing: It looks like Mirage is intercepting ember-cli-fastboot-testing's attempt to render ${url}. Please disable Mirage when running FastBoot tests.`; + error = `It looks like Mirage is intercepting ember-cli-fastboot-testing's attempt to render ${url}. Please disable Mirage when running FastBoot tests.`; } else { - error = `Ember CLI FastBoot Testing: We were unable to render ${url}. Is your test suite blocking or intercepting HTTP requests? Error: ${e.message ? e.message : e}.` + error = `We were unable to render ${url}. Is your test suite blocking or intercepting HTTP requests? Error: ${e.message ? e.message : e}.` } } if (response && response.headers && response.headers.get && response.headers.get('x-fastboot-testing') !== 'true') { - error = `Ember CLI FastBoot Testing: We were unable to render ${url}. Is your test suite blocking or intercepting HTTP requests?`; + error = `We were unable to render ${url}. Is your test suite blocking or intercepting HTTP requests?`; } if (error) { + error = `Ember CLI FastBoot Testing: ${error}`; + // eslint-disable-next-line no-console console.error(error); throw new Error(error); diff --git a/index.js b/index.js index 382f1a8a..b4feea4a 100644 --- a/index.js +++ b/index.js @@ -4,7 +4,46 @@ let FastBoot = require('fastboot'); let url = require('url'); let resolve = require('resolve'); let nock = require('nock'); -let bodyParser = require('body-parser') +let bodyParser = require('body-parser'); +let Comlink = require('comlink'); + +let nockInterface = { + listeners: [], + lastMessage: null, + + dispatchEvent(message) { + let listener = this.listeners[0]; + return listener({ data: message }); + }, + + postMessage(message) { + this.lastMessage = message; + }, + + addEventListener(type, listener) { + this.listeners.push(listener); + }, + + removeEventListener(type, listener) { + let index = this.listeners.indexOf(listener); + this.listeners.splice(index, 1); + }, +} + +Comlink.expose(nock, nockInterface); + +let getCircularReplacer = () => { + let seen = new WeakSet(); + return (key, value) => { + if (typeof value === "object" && value !== null) { + if (seen.has(value)) { + return; + } + seen.add(value); + } + return value; + }; +} module.exports = { name: 'ember-cli-fastboot-testing', @@ -41,6 +80,16 @@ module.exports = { }, _fastbootRenderingMiddleware(app) { + app.post('/__nock-proxy', bodyParser.json({ limit: '50mb' }), (req, res) => { + nockInterface.dispatchEvent(req.body).then(() => { + let body = JSON.stringify( + nockInterface.lastMessage, + getCircularReplacer() + ); + + res.send(body); + }); + }); app.post('/__mock-request', bodyParser.json({ limit: '50mb' }), (req, res) => { let mock = nock(req.headers.origin) diff --git a/package.json b/package.json index 7fc4f388..ea6b8d2a 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ }, "scripts": { "build": "ember build", + "install": "cd node_modules/comlink && npm install && npm run build", "lint:js": "eslint .", "start": "ember serve", "test": "ember test", @@ -22,6 +23,7 @@ }, "dependencies": { "body-parser": "^1.18.3", + "comlink": "CvX/comlink#wrap-chain", "ember-auto-import": "^1.2.15", "ember-cli-babel": "^6.6.0", "fastboot": "^1.2.1", diff --git a/tests/fastboot/network-mocking-test.js b/tests/fastboot/network-mocking-test.js index e7978179..45b28c31 100644 --- a/tests/fastboot/network-mocking-test.js +++ b/tests/fastboot/network-mocking-test.js @@ -69,7 +69,7 @@ module('Fastboot | network mocking', function(hooks) { test('it can mock a get request', async function(assert) { await mockServer.get('/api/notes', [ - { id: 1, title: 'get note'}, + { id: 1, title: 'get note' }, ]); await visit('/examples/network/other/get-request'); @@ -79,7 +79,7 @@ module('Fastboot | network mocking', function(hooks) { test('it can mock a post request', async function(assert) { await mockServer.post('/api/notes', [ - { id: 1, title: 'post note'}, + { id: 1, title: 'post note' }, ]); await visit('/examples/network/other/post-request'); diff --git a/tests/fastboot/nock-proxy-test.js b/tests/fastboot/nock-proxy-test.js new file mode 100644 index 00000000..94159cf7 --- /dev/null +++ b/tests/fastboot/nock-proxy-test.js @@ -0,0 +1,95 @@ +import { module, test } from 'qunit'; +import { setup, visit, nock } from 'ember-cli-fastboot-testing/test-support'; + +module('Fastboot | nock proxy', function(hooks) { + setup(hooks); + + test('it will not change an endpoint that already exists', async function(assert) { + await visit('/examples/network/other/echo?message=hello%20world'); + assert.dom('[data-test-id="echo"]').hasText("hello world"); + }); + + test('it can mock an array of models', async function(assert) { + await nock('http://localhost:7357') + .intercept('/api/notes', 'GET') + .reply(200, { + data: [ + { + type: 'note', + id: '1', + attributes: { + title: 'test note' + } + }, + { + type: 'note', + id: '2', + attributes: { + title: 'test 2' + } + } + ] + }); + + await visit('/examples/network/notes'); + + assert.dom('[data-test-id="title-1"]').hasText("test note") + assert.dom('[data-test-id="title-2"]').hasText("test 2") + }); + + test('it can mock a single model', async function(assert) { + await nock('http://localhost:7357') + .intercept('/api/notes/1', 'GET') + .reply(200, { + data: { + type: "notes", + id: "1", + attributes: { + title: 'test note' + } + } + }); + + await visit('/examples/network/notes/1'); + + assert.dom('[data-test-id="title"]').hasText("test note"); + }); + + test('it can mock 404s', async function(assert) { + await nock('http://localhost:7357') + .intercept('/api/notes/1', 'GET') + .reply(404, { + errors: [ + { title: "Not found" } + ] + }); + + await visit('/examples/network/notes/1'); + + assert.dom().includesText('Ember Data Request GET /api/notes/1 returned a 404'); + }); + + test('it can mock a get request', async function(assert) { + await nock('http://localhost:7357') + .intercept('/api/notes', 'GET') + .reply(200, [ + { id: 1, title: 'get note' }, + ]); + + await visit('/examples/network/other/get-request'); + + assert.dom('[data-test-id="title-1"]').hasText("get note") + }); + + test('it can mock a post request', async function(assert) { + await nock('http://localhost:7357') + .intercept('/api/notes', 'POST') + .reply(200, [ + { id: 1, title: 'post note' }, + ]); + + await visit('/examples/network/other/post-request'); + + assert.dom('[data-test-id="title-1"]').hasText("post note") + }); +}); diff --git a/yarn.lock b/yarn.lock index d1f92208..06d81ae3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3663,6 +3663,10 @@ combined-stream@~0.0.4: dependencies: delayed-stream "0.0.5" +comlink@CvX/comlink#wrap-chain: + version "4.0.1" + resolved "https://codeload.github.com/CvX/comlink/tar.gz/f7913e3a02905389eeee4fda441356f3dbb19688" + commander@2.12.2: version "2.12.2" resolved "https://registry.yarnpkg.com/commander/-/commander-2.12.2.tgz#0f5946c427ed9ec0d91a46bb9def53e54650e555"