import { getRedboxDescription, getRedboxHeader, getRedboxSource, hasRedbox, } from 'next-test-utils' import webdriver from 'next-webdriver' import { NextInstance } from 'test/lib/next-modes/base' export async function sandbox( next: NextInstance, initialFiles?: Map, defaultFiles = true ) { await next.stop() await next.clean() if (defaultFiles) { await next.patchFile( 'pages/index.js', `export { default } from '../index';` ) await next.patchFile('index.js', `export default () => 'new sandbox';`) } if (initialFiles) { for (const [k, v] of initialFiles.entries()) { await next.patchFile(k, v) } } await next.start() const browser = await webdriver(next.appPort, '/') return { session: { async write(filename, content) { // Update the file on filesystem await next.patchFile(filename, content) }, async patch(filename, content) { // Register an event for HMR completion await browser.eval(function () { ;(window as any).__HMR_STATE = 'pending' var timeout = setTimeout(() => { ;(window as any).__HMR_STATE = 'timeout' }, 30 * 1000) ;(window as any).__NEXT_HMR_CB = function () { clearTimeout(timeout) ;(window as any).__HMR_STATE = 'success' } }) await this.write(filename, content) for (;;) { const status = await browser.eval(() => (window as any).__HMR_STATE) if (!status) { await new Promise((resolve) => setTimeout(resolve, 750)) // Wait for application to re-hydrate: await browser.evalAsync(function () { var callback = arguments[arguments.length - 1] if ((window as any).__NEXT_HYDRATED) { callback() } else { var timeout = setTimeout(callback, 30 * 1000) ;(window as any).__NEXT_HYDRATED_CB = function () { clearTimeout(timeout) callback() } } }) console.log('Application re-loaded.') // Slow down tests a bit: await new Promise((resolve) => setTimeout(resolve, 750)) return false } if (status === 'success') { console.log('Hot update complete.') break } if (status !== 'pending') { throw new Error(`Application is in inconsistent state: ${status}.`) } await new Promise((resolve) => setTimeout(resolve, 30)) } // Slow down tests a bit (we don't know how long re-rendering takes): await new Promise((resolve) => setTimeout(resolve, 750)) return true }, async remove(filename) { await next.deleteFile(filename) }, async evaluate(snippet: () => any) { if (typeof snippet === 'function') { const result = await browser.eval(snippet) await new Promise((resolve) => setTimeout(resolve, 30)) return result } else { throw new Error( `You must pass a function to be evaluated in the browser.` ) } }, async hasRedbox(expected = false) { return hasRedbox(browser, expected) }, async getRedboxDescription() { return getRedboxDescription(browser) }, async getRedboxSource(includeHeader = false) { const header = includeHeader ? await getRedboxHeader(browser) : '' const source = await getRedboxSource(browser) if (includeHeader) { return `${header}\n\n${source}` } return source }, }, async cleanup() { await browser.close() await next.stop() await next.clean() }, } }