Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fast Refresh] Module re-execution tests from Metro #12059

Merged
merged 6 commits into from
Apr 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
355 changes: 355 additions & 0 deletions test/acceptance/ReactRefreshRequire.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,355 @@
/* global jasmine */
/* eslint-env jest */
import { sandbox } from './helpers'

jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 60 * 5

// https://github.com/facebook/metro/blob/b651e535cd0fc5df6c0803b9aa647d664cb9a6c3/packages/metro/src/lib/polyfills/__tests__/require-test.js#L989-L1048
test('re-runs accepted modules', async () => {
const [session, cleanup] = await sandbox()

await session.patch(
'index.js',
`export default function Noop() { return null; };`
)

await session.write(
'./foo.js',
`window.log.push('init FooV1'); require('./bar');`
)
await session.write(
'./bar.js',
`window.log.push('init BarV1'); export default function Bar() { return null; };`
)

await session.evaluate(() => (window.log = []))
await session.patch(
'index.js',
`require('./foo'); export default function Noop() { return null; };`
)
expect(await session.evaluate(() => window.log)).toEqual([
'init FooV1',
'init BarV1',
])

// We only edited Bar, and it accepted.
// So we expect it to re-run alone.
await session.evaluate(() => (window.log = []))
await session.patch(
'./bar.js',
`window.log.push('init BarV2'); export default function Bar() { return null; };`
)
expect(await session.evaluate(() => window.log)).toEqual(['init BarV2'])

// We only edited Bar, and it accepted.
// So we expect it to re-run alone.
await session.evaluate(() => (window.log = []))
await session.patch(
'./bar.js',
`window.log.push('init BarV3'); export default function Bar() { return null; };`
)
expect(await session.evaluate(() => window.log)).toEqual(['init BarV3'])

// TODO:
// expect(Refresh.performReactRefresh).toHaveBeenCalled();
// expect(Refresh.performFullRefresh).not.toHaveBeenCalled();

await cleanup()
})

// https://github.com/facebook/metro/blob/b651e535cd0fc5df6c0803b9aa647d664cb9a6c3/packages/metro/src/lib/polyfills/__tests__/require-test.js#L1050-L1137
test('propagates a hot update to closest accepted module', async () => {
const [session, cleanup] = await sandbox()

await session.patch(
'index.js',
`export default function Noop() { return null; };`
)

await session.write(
'./foo.js',
`
window.log.push('init FooV1');
require('./bar');

// Exporting a component marks it as auto-accepting.
export default function Foo() {};
`
)

await session.write('./bar.js', `window.log.push('init BarV1');`)

await session.evaluate(() => (window.log = []))
await session.patch(
'index.js',
`require('./foo'); export default function Noop() { return null; };`
)

expect(await session.evaluate(() => window.log)).toEqual([
'init FooV1',
'init BarV1',
])

// We edited Bar, but it doesn't accept.
// So we expect it to re-run together with Foo which does.
await session.evaluate(() => (window.log = []))
await session.patch('./bar.js', `window.log.push('init BarV2');`)
expect(await session.evaluate(() => window.log)).toEqual([
// // FIXME: Metro order:
// 'init BarV2',
// 'init FooV1',
'init FooV1',
'init BarV2',
// Webpack runs in this order because it evaluates modules parent down, not
// child up. Parents will re-run child modules in the order that they're
// imported from the parent.
])

// We edited Bar, but it doesn't accept.
// So we expect it to re-run together with Foo which does.
await session.evaluate(() => (window.log = []))
await session.patch('./bar.js', `window.log.push('init BarV3');`)
expect(await session.evaluate(() => window.log)).toEqual([
// // FIXME: Metro order:
// 'init BarV3',
// 'init FooV1',
'init FooV1',
'init BarV3',
// Webpack runs in this order because it evaluates modules parent down, not
// child up. Parents will re-run child modules in the order that they're
// imported from the parent.
])

// We edited Bar so that it accepts itself.
// We still re-run Foo because the exports of Bar changed.
await session.evaluate(() => (window.log = []))
await session.patch(
'./bar.js',
`
window.log.push('init BarV3');
// Exporting a component marks it as auto-accepting.
export default function Bar() {};
`
)
expect(await session.evaluate(() => window.log)).toEqual([
// // FIXME: Metro order:
// 'init BarV3',
// 'init FooV1',
'init FooV1',
'init BarV3',
// Webpack runs in this order because it evaluates modules parent down, not
// child up. Parents will re-run child modules in the order that they're
// imported from the parent.
])

// Further edits to Bar don't re-run Foo.
await session.evaluate(() => (window.log = []))
await session.patch(
'./bar.js',
`
window.log.push('init BarV4');
export default function Bar() {};
`
)
expect(await session.evaluate(() => window.log)).toEqual(['init BarV4'])

// TODO:
// expect(Refresh.performReactRefresh).toHaveBeenCalled();
// expect(Refresh.performFullRefresh).not.toHaveBeenCalled();

await cleanup()
})
// https://github.com/facebook/metro/blob/b651e535cd0fc5df6c0803b9aa647d664cb9a6c3/packages/metro/src/lib/polyfills/__tests__/require-test.js#L1139-L1307
test('propagates hot update to all inverse dependencies', async () => {
const [session, cleanup] = await sandbox()

await session.patch(
'index.js',
`export default function Noop() { return null; };`
)

// This is the module graph:
// MiddleA*
// / \
// Root* - MiddleB* - Leaf
// \
// MiddleC
//
// * - accepts update
//
// We expect that editing Leaf will propagate to
// MiddleA and MiddleB both of which can handle updates.

await session.write(
'root.js',
`
window.log.push('init RootV1');

import './middleA';
import './middleB';
import './middleC';

export default function Root() {};
`
)
await session.write(
'middleA.js',
`
log.push('init MiddleAV1');

import './leaf';

export default function MiddleA() {};
`
)
await session.write(
'middleB.js',
`
log.push('init MiddleBV1');

import './leaf';

export default function MiddleB() {};
`
)
// This one doesn't import leaf and also doesn't export a component (so it
// doesn't accept updates).
await session.write(
'middleC.js',
`log.push('init MiddleCV1'); export default {};`
)

// Doesn't accept its own updates; they will propagate.
await session.write('leaf.js', `log.push('init LeafV1'); export default {};`)

// Bootstrap:
await session.evaluate(() => (window.log = []))
await session.patch(
'index.js',
`require('./root'); export default function Noop() { return null; };`
)

expect(await session.evaluate(() => window.log)).toEqual([
'init LeafV1',
'init MiddleAV1',
'init MiddleBV1',
'init MiddleCV1',
'init RootV1',
])

// We edited Leaf, but it doesn't accept.
// So we expect it to re-run together with MiddleA and MiddleB which do.
await session.evaluate(() => (window.log = []))
await session.patch('leaf.js', `log.push('init LeafV2'); export default {};`)
expect(await session.evaluate(() => window.log)).toEqual([
'init LeafV2',
'init MiddleAV1',
'init MiddleBV1',
])

// Let's try the same one more time.
await session.evaluate(() => (window.log = []))
await session.patch('leaf.js', `log.push('init LeafV3'); export default {};`)
expect(await session.evaluate(() => window.log)).toEqual([
'init LeafV3',
'init MiddleAV1',
'init MiddleBV1',
])

// Now edit MiddleB. It should accept and re-run alone.
await session.evaluate(() => (window.log = []))
await session.patch(
'middleB.js',
`
log.push('init MiddleBV2');

import './leaf';

export default function MiddleB() {};
`
)
expect(await session.evaluate(() => window.log)).toEqual(['init MiddleBV2'])

// Finally, edit MiddleC. It didn't accept so it should bubble to Root.
await session.evaluate(() => (window.log = []))

await session.patch(
'middleC.js',
`log.push('init MiddleCV2'); export default {};`
)
expect(await session.evaluate(() => window.log)).toEqual([
'init MiddleCV2',
'init RootV1',
])

// TODO:
// expect(Refresh.performReactRefresh).toHaveBeenCalled()
// expect(Refresh.performFullRefresh).not.toHaveBeenCalled()

await cleanup()
})

// https://github.com/facebook/metro/blob/b651e535cd0fc5df6c0803b9aa647d664cb9a6c3/packages/metro/src/lib/polyfills/__tests__/require-test.js#L1309-L1406
test('runs dependencies before dependents', async () => {
// TODO:
})

// https://github.com/facebook/metro/blob/b651e535cd0fc5df6c0803b9aa647d664cb9a6c3/packages/metro/src/lib/polyfills/__tests__/require-test.js#L1408-L1498
test('provides fresh value for module.exports in parents', async () => {
// TODO:
})

// https://github.com/facebook/metro/blob/b651e535cd0fc5df6c0803b9aa647d664cb9a6c3/packages/metro/src/lib/polyfills/__tests__/require-test.js#L1500-L1590
test('provides fresh value for exports.* in parents', async () => {
// TODO:
})

// https://github.com/facebook/metro/blob/b651e535cd0fc5df6c0803b9aa647d664cb9a6c3/packages/metro/src/lib/polyfills/__tests__/require-test.js#L1592-L1688
test('provides fresh value for ES6 named import in parents', async () => {
// TODO:
})

// https://github.com/facebook/metro/blob/b651e535cd0fc5df6c0803b9aa647d664cb9a6c3/packages/metro/src/lib/polyfills/__tests__/require-test.js#L1690-L1786
test('provides fresh value for ES6 default import in parents', async () => {
// TODO:
})

// https://github.com/facebook/metro/blob/b651e535cd0fc5df6c0803b9aa647d664cb9a6c3/packages/metro/src/lib/polyfills/__tests__/require-test.js#L1788-L1899
test('stops update propagation after module-level errors', async () => {
// TODO:
})

// https://github.com/facebook/metro/blob/b651e535cd0fc5df6c0803b9aa647d664cb9a6c3/packages/metro/src/lib/polyfills/__tests__/require-test.js#L1901-L2010
test('can continue hot updates after module-level errors with module.exports', async () => {
// TODO:
})

// https://github.com/facebook/metro/blob/b651e535cd0fc5df6c0803b9aa647d664cb9a6c3/packages/metro/src/lib/polyfills/__tests__/require-test.js#L2012-L2123
test('can continue hot updates after module-level errors with ES6 exports', async () => {
// TODO:
})

// https://github.com/facebook/metro/blob/b651e535cd0fc5df6c0803b9aa647d664cb9a6c3/packages/metro/src/lib/polyfills/__tests__/require-test.js#L2125-L2233
test('does not accumulate stale exports over time', async () => {
// TODO:
})

// https://github.com/facebook/metro/blob/b651e535cd0fc5df6c0803b9aa647d664cb9a6c3/packages/metro/src/lib/polyfills/__tests__/require-test.js#L2235-L2279
test('bails out if update bubbles to the root via the only path', async () => {
// TODO:
})

// https://github.com/facebook/metro/blob/b651e535cd0fc5df6c0803b9aa647d664cb9a6c3/packages/metro/src/lib/polyfills/__tests__/require-test.js#L2281-L2371
test('bails out if the update bubbles to the root via one of the paths', async () => {
// TODO:
})

// https://github.com/facebook/metro/blob/b651e535cd0fc5df6c0803b9aa647d664cb9a6c3/packages/metro/src/lib/polyfills/__tests__/require-test.js#L2373-L2472
test('propagates a module that stops accepting in next version', async () => {
// TODO:
})

// https://github.com/facebook/metro/blob/b651e535cd0fc5df6c0803b9aa647d664cb9a6c3/packages/metro/src/lib/polyfills/__tests__/require-test.js#L2474-L2521
test('can replace a module before it is loaded', async () => {
// TODO:
})
15 changes: 9 additions & 6 deletions test/acceptance/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,25 +28,28 @@ export async function sandbox(id = nanoid()) {

return [
{
async write(fileName, content) {
// Update the file on filesystem
const fullFileName = path.join(sandboxDirectory, fileName)
const dir = path.dirname(fullFileName)
await fs.mkdirp(dir)
await fs.writeFile(fullFileName, content)
},
async patch(fileName, content) {
// Register an event for HMR completion
await browser.executeScript(function() {
window.__HMR_STATE = 'pending'

var timeout = setTimeout(() => {
window.__HMR_STATE = 'timeout'
}, 5250)
}, 10000)
window.__NEXT_HMR_CB = function() {
clearTimeout(timeout)
window.__HMR_STATE = 'success'
}
})

// Update the file on filesystem
const fullFileName = path.join(sandboxDirectory, fileName)
const dir = path.dirname(fullFileName)
await fs.mkdirp(dir)
await fs.writeFile(fullFileName, content)
await this.write(fileName, content)

for (;;) {
const status = await browser.executeScript(() => window.__HMR_STATE)
Expand Down