From ec04f3e4a9edff2ded5f5839cd07e664f57e9fdc Mon Sep 17 00:00:00 2001 From: Andre Wiggins Date: Tue, 15 Sep 2020 14:27:53 -0700 Subject: [PATCH 01/29] Add some docs on using createLazy and createSuspender --- compat/test/browser/suspense.test.js | 57 ++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/compat/test/browser/suspense.test.js b/compat/test/browser/suspense.test.js index da3a8bf12d..53d7983a78 100644 --- a/compat/test/browser/suspense.test.js +++ b/compat/test/browser/suspense.test.js @@ -14,8 +14,32 @@ const h = React.createElement; /* eslint-env browser, mocha */ /** + * Create a Lazy component whose promise is controlled by by the test. This + * function returns 3 values: The Lazy component to render, a `resolve` + * function, and a `reject` function. Call `resolve` with the component the Lazy + * component should resolve with. Call `reject` with the error the Lazy + * component should reject with + * + * @example + * // 1. Create and render the Lazy component + * const [Lazy, resolve] = createLazy(); + * render( + * Suspended...}> + * + * , + * scratch + * ); + * rerender(); // Rerender is required so the fallback is displayed + * expect(scratch.innerHTML).to.eql(`
Suspended...
`); + * + * // 2. Resolve the Lazy with a new component to render + * return resolve(() =>
Hello
).then(() => { + * rerender(); + * expect(scratch.innerHTML).to.equal(`
Hello
`); + * }); + * * @typedef {import('../../../src').ComponentType} ComponentType - * @returns {[typeof Component, (c: ComponentType) => Promise, (c: ComponentType) => void]} + * @returns {[typeof Component, (c: ComponentType) => Promise, (e: Error) => Promise]} */ function createLazy() { /** @type {(c: ComponentType) => Promise} */ @@ -27,8 +51,8 @@ function createLazy() { return promise; }; - rejecter = () => { - reject(); + rejecter = e => { + reject(e); return promise; }; }); @@ -40,6 +64,33 @@ function createLazy() { } /** + * Returns a Component and a function (named `suspend`) that will suspend the component when called. + * `suspend` will return two functions, `resolve` and `reject`. Call `resolve` with a Component the + * suspended component should resume with or reject with the Error the suspended component should + * reject with + * + * @example + * // 1. Create a suspender with initial children (argument to createSuspender) and render it + * const [Suspender, suspend] = createSuspender(() =>
Hello
); + * render( + * Suspended...}> + * + * , + * scratch + * ); + * expect(scratch.innerHTML).to.eql(`div>Hello`); + * + * // 2. Cause the component to suspend and rerender the update (i.e. the fallback) + * const [resolve] = suspend(); + * rerender(); + * expect(scratch.innerHTML).to.eql(`div>Suspended...`); + * + * // 3. Resolve the suspended component with a new component and rerender + * return resolve(() =>
Hello2
).then(() => { + * rerender(); + * expect(scratch.innerHTML).to.eql(`div>Hello2`); + * }); + * * @typedef {[(c: ComponentType) => Promise, (error: Error) => Promise]} Resolvers * @param {ComponentType} DefaultComponent * @returns {[typeof Component, () => Resolvers]} From 5a88bfc0094473900711695dcd4f16643684ba34 Mon Sep 17 00:00:00 2001 From: Andre Wiggins Date: Tue, 15 Sep 2020 14:54:48 -0700 Subject: [PATCH 02/29] Fix assertion to pass on Windows --- debug/test/browser/debug.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debug/test/browser/debug.test.js b/debug/test/browser/debug.test.js index 7e75ef3605..032aa2e5c3 100644 --- a/debug/test/browser/debug.test.js +++ b/debug/test/browser/debug.test.js @@ -555,7 +555,7 @@ describe('debug', () => { // correctly. expect(console.error).to.have.been.calledOnceWith( sinon.match( - /^Failed prop type: Invalid prop `text` of type `number` supplied to `Foo`, expected `string`\.\n {2}in Foo \(at (.*)\/debug\/test\/browser\/debug\.test\.js:[0-9]+\)$/m + /^Failed prop type: Invalid prop `text` of type `number` supplied to `Foo`, expected `string`\.\n {2}in Foo \(at (.*)[\/\\]debug[\/\\]test[\/\\]browser[\/\\]debug\.test\.js:[0-9]+\)$/m ) ); }); From b0d75064c3b4e74bf399817f30cc1a3231f9dac1 Mon Sep 17 00:00:00 2001 From: Andre Wiggins Date: Tue, 15 Sep 2020 14:55:35 -0700 Subject: [PATCH 03/29] Move suspense utils to their own file --- compat/test/browser/suspense-utils.js | 116 ++++++++++++++++++++++++++ compat/test/browser/suspense.test.js | 113 +------------------------ 2 files changed, 117 insertions(+), 112 deletions(-) create mode 100644 compat/test/browser/suspense-utils.js diff --git a/compat/test/browser/suspense-utils.js b/compat/test/browser/suspense-utils.js new file mode 100644 index 0000000000..f8f380bed9 --- /dev/null +++ b/compat/test/browser/suspense-utils.js @@ -0,0 +1,116 @@ +import React, { Component, lazy } from 'preact/compat'; + +const h = React.createElement; + +/** + * Create a Lazy component whose promise is controlled by by the test. This + * function returns 3 values: The Lazy component to render, a `resolve` + * function, and a `reject` function. Call `resolve` with the component the Lazy + * component should resolve with. Call `reject` with the error the Lazy + * component should reject with + * + * @example + * // 1. Create and render the Lazy component + * const [Lazy, resolve] = createLazy(); + * render( + * Suspended...}> + * + * , + * scratch + * ); + * rerender(); // Rerender is required so the fallback is displayed + * expect(scratch.innerHTML).to.eql(`
Suspended...
`); + * + * // 2. Resolve the Lazy with a new component to render + * return resolve(() =>
Hello
).then(() => { + * rerender(); + * expect(scratch.innerHTML).to.equal(`
Hello
`); + * }); + * + * @typedef {import('../../../src').ComponentType} ComponentType + * @returns {[typeof Component, (c: ComponentType) => Promise, (e: Error) => Promise]} + */ +export function createLazy() { + /** @type {(c: ComponentType) => Promise} */ + let resolver, rejecter; + const Lazy = lazy(() => { + let promise = new Promise((resolve, reject) => { + resolver = c => { + resolve({ default: c }); + return promise; + }; + + rejecter = e => { + reject(e); + return promise; + }; + }); + + return promise; + }); + + return [Lazy, c => resolver(c), e => rejecter(e)]; +} + +/** + * Returns a Component and a function (named `suspend`) that will suspend the component when called. + * `suspend` will return two functions, `resolve` and `reject`. Call `resolve` with a Component the + * suspended component should resume with or reject with the Error the suspended component should + * reject with + * + * @example + * // 1. Create a suspender with initial children (argument to createSuspender) and render it + * const [Suspender, suspend] = createSuspender(() =>
Hello
); + * render( + * Suspended...}> + * + * , + * scratch + * ); + * expect(scratch.innerHTML).to.eql(`div>Hello`); + * + * // 2. Cause the component to suspend and rerender the update (i.e. the fallback) + * const [resolve] = suspend(); + * rerender(); + * expect(scratch.innerHTML).to.eql(`div>Suspended...`); + * + * // 3. Resolve the suspended component with a new component and rerender + * return resolve(() =>
Hello2
).then(() => { + * rerender(); + * expect(scratch.innerHTML).to.eql(`div>Hello2`); + * }); + * + * @typedef {Component<{}, any>} Suspender + * @typedef {[(c: ComponentType) => Promise, (error: Error) => Promise]} Resolvers + * @param {ComponentType} DefaultComponent + * @returns {[typeof Suspender, () => Resolvers]} + */ +export function createSuspender(DefaultComponent) { + /** @type {(lazy: typeof Component) => void} */ + let renderLazy; + class Suspender extends Component { + constructor(props, context) { + super(props, context); + this.state = { Lazy: null }; + + renderLazy = Lazy => this.setState({ Lazy }); + } + + render(props, state) { + return state.Lazy ? h(state.Lazy, props) : h(DefaultComponent, props); + } + } + + sinon.spy(Suspender.prototype, 'render'); + + /** + * @returns {Resolvers} + */ + function suspend() { + const [Lazy, resolve, reject] = createLazy(); + renderLazy(Lazy); + return [resolve, reject]; + } + + return [Suspender, suspend]; +} diff --git a/compat/test/browser/suspense.test.js b/compat/test/browser/suspense.test.js index 53d7983a78..cc5770568b 100644 --- a/compat/test/browser/suspense.test.js +++ b/compat/test/browser/suspense.test.js @@ -9,122 +9,11 @@ import React, { createContext } from 'preact/compat'; import { setupScratch, teardown } from '../../../test/_util/helpers'; +import { createLazy, createSuspender } from './suspense-utils'; const h = React.createElement; /* eslint-env browser, mocha */ -/** - * Create a Lazy component whose promise is controlled by by the test. This - * function returns 3 values: The Lazy component to render, a `resolve` - * function, and a `reject` function. Call `resolve` with the component the Lazy - * component should resolve with. Call `reject` with the error the Lazy - * component should reject with - * - * @example - * // 1. Create and render the Lazy component - * const [Lazy, resolve] = createLazy(); - * render( - * Suspended...}> - * - * , - * scratch - * ); - * rerender(); // Rerender is required so the fallback is displayed - * expect(scratch.innerHTML).to.eql(`
Suspended...
`); - * - * // 2. Resolve the Lazy with a new component to render - * return resolve(() =>
Hello
).then(() => { - * rerender(); - * expect(scratch.innerHTML).to.equal(`
Hello
`); - * }); - * - * @typedef {import('../../../src').ComponentType} ComponentType - * @returns {[typeof Component, (c: ComponentType) => Promise, (e: Error) => Promise]} - */ -function createLazy() { - /** @type {(c: ComponentType) => Promise} */ - let resolver, rejecter; - const Lazy = lazy(() => { - let promise = new Promise((resolve, reject) => { - resolver = c => { - resolve({ default: c }); - return promise; - }; - - rejecter = e => { - reject(e); - return promise; - }; - }); - - return promise; - }); - - return [Lazy, c => resolver(c), e => rejecter(e)]; -} - -/** - * Returns a Component and a function (named `suspend`) that will suspend the component when called. - * `suspend` will return two functions, `resolve` and `reject`. Call `resolve` with a Component the - * suspended component should resume with or reject with the Error the suspended component should - * reject with - * - * @example - * // 1. Create a suspender with initial children (argument to createSuspender) and render it - * const [Suspender, suspend] = createSuspender(() =>
Hello
); - * render( - * Suspended...}> - * - * , - * scratch - * ); - * expect(scratch.innerHTML).to.eql(`div>Hello`); - * - * // 2. Cause the component to suspend and rerender the update (i.e. the fallback) - * const [resolve] = suspend(); - * rerender(); - * expect(scratch.innerHTML).to.eql(`div>Suspended...`); - * - * // 3. Resolve the suspended component with a new component and rerender - * return resolve(() =>
Hello2
).then(() => { - * rerender(); - * expect(scratch.innerHTML).to.eql(`div>Hello2`); - * }); - * - * @typedef {[(c: ComponentType) => Promise, (error: Error) => Promise]} Resolvers - * @param {ComponentType} DefaultComponent - * @returns {[typeof Component, () => Resolvers]} - */ -export function createSuspender(DefaultComponent) { - /** @type {(lazy: React.JSX.Element) => void} */ - let renderLazy; - class Suspender extends Component { - constructor(props, context) { - super(props, context); - this.state = { Lazy: null }; - - renderLazy = Lazy => this.setState({ Lazy }); - } - - render(props, state) { - return state.Lazy ? h(state.Lazy, props) : h(DefaultComponent, props); - } - } - - sinon.spy(Suspender.prototype, 'render'); - - /** - * @returns {Resolvers} - */ - function suspend() { - const [Lazy, resolve, reject] = createLazy(); - renderLazy(Lazy); - return [resolve, reject]; - } - - return [Suspender, suspend]; -} - class Catcher extends Component { constructor(props) { super(props); From d153463d63dea9f06d86050fdba99b9b053d23f3 Mon Sep 17 00:00:00 2001 From: Andre Wiggins Date: Tue, 15 Sep 2020 15:32:05 -0700 Subject: [PATCH 04/29] Add some basic suspense hydration tests --- .../test/browser/suspense-hydration.test.js | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 compat/test/browser/suspense-hydration.test.js diff --git a/compat/test/browser/suspense-hydration.test.js b/compat/test/browser/suspense-hydration.test.js new file mode 100644 index 0000000000..eb4ed41d6b --- /dev/null +++ b/compat/test/browser/suspense-hydration.test.js @@ -0,0 +1,118 @@ +import { setupRerender } from 'preact/test-utils'; +import React, { + createElement, + hydrate, + Component, + Fragment, + Suspense +} from 'preact/compat'; +import { setupScratch, teardown } from '../../../test/_util/helpers'; +import { createLazy } from './suspense-utils'; + +/* eslint-env browser, mocha */ +describe('suspense hydration', () => { + /** @type {HTMLDivElement} */ + let scratch, + rerender, + unhandledEvents = []; + + function onUnhandledRejection(event) { + unhandledEvents.push(event); + } + + /** @type {() => void} */ + let increment; + class Counter extends Component { + constructor(props) { + super(props); + + this.state = { count: 0 }; + increment = () => this.setState({ count: ++this.state.count }); + } + + render(props, { count }) { + return
Count: {count}
; + } + } + + class ErrorBoundary { + componentDidCatch(err) { + if (err && err.then) this.__d = true; + } + + render(props) { + return props.children; + } + } + + beforeEach(() => { + scratch = setupScratch(); + rerender = setupRerender(); + + unhandledEvents = []; + if ('onunhandledrejection' in window) { + window.addEventListener('unhandledrejection', onUnhandledRejection); + } + }); + + afterEach(() => { + teardown(scratch); + + if ('onunhandledrejection' in window) { + window.removeEventListener('unhandledrejection', onUnhandledRejection); + + if (unhandledEvents.length) { + throw unhandledEvents[0].reason; + } + } + }); + + it('should leave DOM untouched when suspending while hydrating', () => { + scratch.innerHTML = '
Hello
'; + + const [Lazy, resolve] = createLazy(); + hydrate( + + + , + scratch + ); + rerender(); // Flush rerender queue to mimic what preact will really do + expect(scratch.innerHTML).to.equal('
Hello
'); + + return resolve(() =>
Hello
).then(() => { + rerender(); + expect(scratch.innerHTML).to.equal('
Hello
'); + }); + }); + + it('should allow siblings to update around suspense boundary', () => { + scratch.innerHTML = '
Count: 0
Hello
'; + + const [Lazy, resolve] = createLazy(); + hydrate( + + + + + + , + scratch + ); + rerender(); // Flush rerender queue to mimic what preact will really do + expect(scratch.innerHTML).to.equal('
Count: 0
Hello
'); + + increment(); + rerender(); + expect(scratch.innerHTML).to.equal('
Count: 1
Hello
'); + + return resolve(() =>
Hello
).then(() => { + rerender(); + expect(scratch.innerHTML).to.equal('
Count: 1
Hello
'); + }); + }); + + // TODO: + // 1. What if props change between when hydrate suspended and suspense resolves? + // 2. If using real Suspense, test re-suspending after hydrate suspense +}); From 36a30ae66620998100fc39d8b05e581227249ccd Mon Sep 17 00:00:00 2001 From: Andre Wiggins Date: Tue, 15 Sep 2020 16:32:38 -0700 Subject: [PATCH 05/29] Add another test --- .../test/browser/suspense-hydration.test.js | 56 ++++++++++++++++++- 1 file changed, 53 insertions(+), 3 deletions(-) diff --git a/compat/test/browser/suspense-hydration.test.js b/compat/test/browser/suspense-hydration.test.js index eb4ed41d6b..f0e1b91cd1 100644 --- a/compat/test/browser/suspense-hydration.test.js +++ b/compat/test/browser/suspense-hydration.test.js @@ -3,9 +3,9 @@ import React, { createElement, hydrate, Component, - Fragment, - Suspense + Fragment } from 'preact/compat'; +import { logCall, getLog, clearLog } from '../../../test/_util/logCall'; import { setupScratch, teardown } from '../../../test/_util/helpers'; import { createLazy } from './suspense-utils'; @@ -45,6 +45,13 @@ describe('suspense hydration', () => { } } + before(() => { + logCall(Element.prototype, 'appendChild'); + logCall(Element.prototype, 'insertBefore'); + logCall(Element.prototype, 'removeChild'); + logCall(Element.prototype, 'remove'); + }); + beforeEach(() => { scratch = setupScratch(); rerender = setupRerender(); @@ -69,6 +76,7 @@ describe('suspense hydration', () => { it('should leave DOM untouched when suspending while hydrating', () => { scratch.innerHTML = '
Hello
'; + clearLog(); const [Lazy, resolve] = createLazy(); hydrate( @@ -79,15 +87,18 @@ describe('suspense hydration', () => { ); rerender(); // Flush rerender queue to mimic what preact will really do expect(scratch.innerHTML).to.equal('
Hello
'); + expect(getLog()).to.deep.equal([]); return resolve(() =>
Hello
).then(() => { rerender(); expect(scratch.innerHTML).to.equal('
Hello
'); + expect(getLog()).to.deep.equal([]); }); }); it('should allow siblings to update around suspense boundary', () => { scratch.innerHTML = '
Count: 0
Hello
'; + clearLog(); const [Lazy, resolve] = createLazy(); hydrate( @@ -101,18 +112,57 @@ describe('suspense hydration', () => { ); rerender(); // Flush rerender queue to mimic what preact will really do expect(scratch.innerHTML).to.equal('
Count: 0
Hello
'); + // Re: DOM OP below - Known issue with hydrating merged text nodes + expect(getLog()).to.deep.equal(['
Count: .appendChild(#text)']); + clearLog(); increment(); rerender(); + expect(scratch.innerHTML).to.equal('
Count: 1
Hello
'); + expect(getLog()).to.deep.equal([]); + clearLog(); return resolve(() =>
Hello
).then(() => { rerender(); expect(scratch.innerHTML).to.equal('
Count: 1
Hello
'); + expect(getLog()).to.deep.equal([]); + }); + }); + + it('should properly hydrate when there is DOM and Components between Suspense and suspender', () => { + scratch.innerHTML = '
Hello
'; + clearLog(); + + const [Lazy, resolve] = createLazy(); + hydrate( + +
+ + + +
+
, + scratch + ); + rerender(); // Flush rerender queue to mimic what preact will really do + expect(scratch.innerHTML).to.equal('
Hello
'); + expect(getLog()).to.deep.equal([]); + clearLog(); + + return resolve(() =>
Hello
).then(() => { + rerender(); + expect(scratch.innerHTML).to.equal('
Hello
'); + expect(getLog()).to.deep.equal([]); }); }); // TODO: - // 1. What if props change between when hydrate suspended and suspense resolves? + // 1. What if props change between when hydrate suspended and suspense + // resolves? // 2. If using real Suspense, test re-suspending after hydrate suspense + // 3. Put some DOM and components with state and event listeners between + // suspender and Suspense boundary + // 4. Put some sibling DOM and components with state and event listeners + // sibling to suspender and under Suspense boundary }); From 4c04105d9b98060ca9c8248d7eb5ba759dbeeb0b Mon Sep 17 00:00:00 2001 From: Andre Wiggins Date: Tue, 15 Sep 2020 16:39:51 -0700 Subject: [PATCH 06/29] Fix linting errors --- debug/test/browser/debug.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debug/test/browser/debug.test.js b/debug/test/browser/debug.test.js index 032aa2e5c3..1e51515984 100644 --- a/debug/test/browser/debug.test.js +++ b/debug/test/browser/debug.test.js @@ -555,7 +555,7 @@ describe('debug', () => { // correctly. expect(console.error).to.have.been.calledOnceWith( sinon.match( - /^Failed prop type: Invalid prop `text` of type `number` supplied to `Foo`, expected `string`\.\n {2}in Foo \(at (.*)[\/\\]debug[\/\\]test[\/\\]browser[\/\\]debug\.test\.js:[0-9]+\)$/m + /^Failed prop type: Invalid prop `text` of type `number` supplied to `Foo`, expected `string`\.\n {2}in Foo \(at (.*)[/\\]debug[/\\]test[/\\]browser[/\\]debug\.test\.js:[0-9]+\)$/m ) ); }); From fe0aa75ef46a38c1b28755b309b78d3cff696b63 Mon Sep 17 00:00:00 2001 From: Andre Wiggins Date: Tue, 15 Sep 2020 16:44:49 -0700 Subject: [PATCH 07/29] Use suspense instead of custom ErrorBoundary in tests --- compat/src/internal.d.ts | 7 ++---- compat/src/suspense.js | 15 ++++++----- .../test/browser/suspense-hydration.test.js | 25 ++++++------------- 3 files changed, 19 insertions(+), 28 deletions(-) diff --git a/compat/src/internal.d.ts b/compat/src/internal.d.ts index 51578fdcd2..635f69f7f1 100644 --- a/compat/src/internal.d.ts +++ b/compat/src/internal.d.ts @@ -14,11 +14,8 @@ export interface Component

extends PreactComponent { isPureReactComponent?: true; _patchedLifecycles?: true; - _childDidSuspend?( - error: Promise, - suspendingComponent: Component, - oldVNode?: VNode - ): void; + _childDidSuspend?(error: Promise, suspendingVNode: VNode): void; + _suspended: (vnode: VNode) => (unsuspend: () => void) => void; _suspendedComponentWillUnmount?(): void; } diff --git a/compat/src/suspense.js b/compat/src/suspense.js index 87269d9734..ba0ccedcb4 100644 --- a/compat/src/suspense.js +++ b/compat/src/suspense.js @@ -15,7 +15,7 @@ options._catchError = function(error, newVNode, oldVNode) { newVNode._children = oldVNode._children; } // Don't call oldCatchError if we found a Suspense - return component._childDidSuspend(error, newVNode._component); + return component._childDidSuspend(error, newVNode); } } } @@ -54,9 +54,11 @@ Suspense.prototype = new Component(); /** * @param {Promise} promise The thrown promise - * @param {Component} suspendingComponent The suspending component + * @param {import('./internal').VNode} suspendingVNode The suspending component */ -Suspense.prototype._childDidSuspend = function(promise, suspendingComponent) { +Suspense.prototype._childDidSuspend = function(promise, suspendingVNode) { + const suspendingComponent = suspendingVNode._component; + /** @type {import('./internal').SuspenseComponent} */ const c = this; @@ -109,8 +111,7 @@ Suspense.prototype._childDidSuspend = function(promise, suspendingComponent) { * to remain on screen and hydrate it when the suspense actually gets resolved. * While in non-hydration cases the usual fallback -> component flow would occour. */ - const vnode = c._vnode; - const wasHydrating = vnode && vnode._hydrating === true; + const wasHydrating = suspendingVNode._hydrating === true; if (!wasHydrating && !c._pendingSuspensionCount++) { c.setState({ _suspended: (c._detachOnNextRender = c._vnode._children[0]) }); } @@ -132,6 +133,7 @@ Suspense.prototype.render = function(props, state) { } // Wrap fallback tree in a VNode that prevents itself from being marked as aborting mid-hydration: + /** @type {import('./internal').VNode} */ const fallback = state._suspended && createElement(Fragment, null, props.fallback); if (fallback) fallback._hydrating = null; @@ -156,10 +158,11 @@ Suspense.prototype.render = function(props, state) { * If the parent does not return a callback then the resolved vnode * gets unsuspended immediately when it resolves. * - * @param {import('../src/internal').VNode} vnode + * @param {import('./internal').VNode} vnode * @returns {((unsuspend: () => void) => void)?} */ export function suspended(vnode) { + /** @type {import('./internal').Component} */ let component = vnode._parent._component; return component && component._suspended && component._suspended(vnode); } diff --git a/compat/test/browser/suspense-hydration.test.js b/compat/test/browser/suspense-hydration.test.js index f0e1b91cd1..ba777c3b51 100644 --- a/compat/test/browser/suspense-hydration.test.js +++ b/compat/test/browser/suspense-hydration.test.js @@ -3,7 +3,8 @@ import React, { createElement, hydrate, Component, - Fragment + Fragment, + Suspense } from 'preact/compat'; import { logCall, getLog, clearLog } from '../../../test/_util/logCall'; import { setupScratch, teardown } from '../../../test/_util/helpers'; @@ -35,16 +36,6 @@ describe('suspense hydration', () => { } } - class ErrorBoundary { - componentDidCatch(err) { - if (err && err.then) this.__d = true; - } - - render(props) { - return props.children; - } - } - before(() => { logCall(Element.prototype, 'appendChild'); logCall(Element.prototype, 'insertBefore'); @@ -80,9 +71,9 @@ describe('suspense hydration', () => { const [Lazy, resolve] = createLazy(); hydrate( - + - , + , scratch ); rerender(); // Flush rerender queue to mimic what preact will really do @@ -104,9 +95,9 @@ describe('suspense hydration', () => { hydrate( - + - + , scratch ); @@ -136,13 +127,13 @@ describe('suspense hydration', () => { const [Lazy, resolve] = createLazy(); hydrate( - +

- , + , scratch ); rerender(); // Flush rerender queue to mimic what preact will really do From c7833487090798294e6b260b644c989d8f602eb1 Mon Sep 17 00:00:00 2001 From: Andre Wiggins Date: Tue, 15 Sep 2020 16:56:34 -0700 Subject: [PATCH 08/29] Skip failing fragment for the moment --- compat/test/browser/suspense-hydration.test.js | 4 ++++ test/browser/fragments.test.js | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/compat/test/browser/suspense-hydration.test.js b/compat/test/browser/suspense-hydration.test.js index ba777c3b51..e82863e2b8 100644 --- a/compat/test/browser/suspense-hydration.test.js +++ b/compat/test/browser/suspense-hydration.test.js @@ -79,11 +79,13 @@ describe('suspense hydration', () => { rerender(); // Flush rerender queue to mimic what preact will really do expect(scratch.innerHTML).to.equal('
Hello
'); expect(getLog()).to.deep.equal([]); + clearLog(); return resolve(() =>
Hello
).then(() => { rerender(); expect(scratch.innerHTML).to.equal('
Hello
'); expect(getLog()).to.deep.equal([]); + clearLog(); }); }); @@ -118,6 +120,7 @@ describe('suspense hydration', () => { rerender(); expect(scratch.innerHTML).to.equal('
Count: 1
Hello
'); expect(getLog()).to.deep.equal([]); + clearLog(); }); }); @@ -145,6 +148,7 @@ describe('suspense hydration', () => { rerender(); expect(scratch.innerHTML).to.equal('
Hello
'); expect(getLog()).to.deep.equal([]); + clearLog(); }); }); diff --git a/test/browser/fragments.test.js b/test/browser/fragments.test.js index cfe8e5c175..c0b430da97 100644 --- a/test/browser/fragments.test.js +++ b/test/browser/fragments.test.js @@ -2561,7 +2561,9 @@ describe('Fragment', () => { ]); }); - it('should properly place conditional elements around strictly equal vnodes', () => { + // TODO: Revisit why this test is failing. Likely due to some side effect of + // the logCalls in suspense-hydration.test.js + it.skip('should properly place conditional elements around strictly equal vnodes', () => { expectDomLog = true; let set; From a54ddaaf70788e33b06d200a4e9e0400f57fd3ba Mon Sep 17 00:00:00 2001 From: jdecroock Date: Sat, 2 May 2020 22:45:51 +0200 Subject: [PATCH 09/29] remove circular ref and golf implementation --- src/component.js | 2 +- src/create-element.js | 2 +- src/diff/index.js | 6 ------ 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/component.js b/src/component.js index ce99b13246..2a0838b02c 100644 --- a/src/component.js +++ b/src/component.js @@ -123,7 +123,7 @@ function renderComponent(component) { if (parentDom) { let commitQueue = []; const oldVNode = assign({}, vnode); - oldVNode._original = oldVNode; + oldVNode._original = {}; let newDom = diff( parentDom, diff --git a/src/create-element.js b/src/create-element.js index aae06c250e..b65e4b94a7 100644 --- a/src/create-element.js +++ b/src/create-element.js @@ -78,7 +78,7 @@ export function createVNode(type, props, key, ref, original) { _original: original }; - if (original == null) vnode._original = vnode; + if (original == null) vnode._original = {}; if (options.vnode != null) options.vnode(vnode); return vnode; diff --git a/src/diff/index.js b/src/diff/index.js index e06f3800ed..7654e8c297 100644 --- a/src/diff/index.js +++ b/src/diff/index.js @@ -247,12 +247,6 @@ export function diff( } c._force = false; - } else if ( - excessDomChildren == null && - newVNode._original === oldVNode._original - ) { - newVNode._children = oldVNode._children; - newVNode._dom = oldVNode._dom; } else { newVNode._dom = diffElementNodes( oldVNode._dom, From b03ea7df6d6f20ee66aea343dacdb193adbeec32 Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Fri, 16 Oct 2020 16:44:00 +0200 Subject: [PATCH 10/29] use number --- src/create-element.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/create-element.js b/src/create-element.js index b65e4b94a7..650c61b407 100644 --- a/src/create-element.js +++ b/src/create-element.js @@ -75,10 +75,9 @@ export function createVNode(type, props, key, ref, original) { _component: null, _hydrating: null, constructor: undefined, - _original: original + _original: original == null ? Math.random() : original }; - if (original == null) vnode._original = {}; if (options.vnode != null) options.vnode(vnode); return vnode; From 378d9891feb46512243d80f57c44f0092935efdd Mon Sep 17 00:00:00 2001 From: jdecroock Date: Sat, 31 Oct 2020 06:58:17 +0100 Subject: [PATCH 11/29] fix tests --- compat/test/browser/cloneElement.test.js | 9 ++++++++- jsx-runtime/src/index.js | 3 +-- src/component.js | 2 +- src/diff/index.js | 6 ++++++ 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/compat/test/browser/cloneElement.test.js b/compat/test/browser/cloneElement.test.js index dce5ec362b..460ef77745 100644 --- a/compat/test/browser/cloneElement.test.js +++ b/compat/test/browser/cloneElement.test.js @@ -20,12 +20,17 @@ describe('compat cloneElement', () => { ab ); - expect(cloneElement(element)).to.eql(element); + const clone = cloneElement(element); + delete clone._original; + delete element._original; + expect(clone).to.eql(element); }); it('should support props.children', () => { let element = b} />; let clone = cloneElement(element); + delete clone._original; + delete element._original; expect(clone).to.eql(element); expect(cloneElement(clone).props.children).to.eql(element.props.children); }); @@ -37,6 +42,8 @@ describe('compat cloneElement', () => { ); let clone = cloneElement(element); + delete clone._original; + delete element._original; expect(clone).to.eql(element); expect(clone.props.children.type).to.eql('div'); }); diff --git a/jsx-runtime/src/index.js b/jsx-runtime/src/index.js index 793b43813f..20414adac8 100644 --- a/jsx-runtime/src/index.js +++ b/jsx-runtime/src/index.js @@ -35,11 +35,10 @@ function createVNode(type, props, key, __source, __self) { _component: null, _hydrating: null, constructor: undefined, - _original: undefined, + _original: 0, __source, __self }; - vnode._original = vnode; // If a Component VNode, check for and apply defaultProps. // Note: `type` is often a String, and can be `undefined` in development. diff --git a/src/component.js b/src/component.js index 2a0838b02c..8c164c7448 100644 --- a/src/component.js +++ b/src/component.js @@ -123,7 +123,7 @@ function renderComponent(component) { if (parentDom) { let commitQueue = []; const oldVNode = assign({}, vnode); - oldVNode._original = {}; + oldVNode._original = vnode._original + 1; let newDom = diff( parentDom, diff --git a/src/diff/index.js b/src/diff/index.js index 7654e8c297..e06f3800ed 100644 --- a/src/diff/index.js +++ b/src/diff/index.js @@ -247,6 +247,12 @@ export function diff( } c._force = false; + } else if ( + excessDomChildren == null && + newVNode._original === oldVNode._original + ) { + newVNode._children = oldVNode._children; + newVNode._dom = oldVNode._dom; } else { newVNode._dom = diffElementNodes( oldVNode._dom, From bd4fb978d4a88323a29ad64ee89a8a04e3e4c6e2 Mon Sep 17 00:00:00 2001 From: jdecroock Date: Sat, 31 Oct 2020 07:05:12 +0100 Subject: [PATCH 12/29] fix shared tests --- test/shared/createElement.test.js | 56 +++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 17 deletions(-) diff --git a/test/shared/createElement.test.js b/test/shared/createElement.test.js index c5c8f2010e..c5ce5713eb 100644 --- a/test/shared/createElement.test.js +++ b/test/shared/createElement.test.js @@ -122,7 +122,11 @@ describe('createElement(jsx)', () => { }); it('should support nested children', () => { - const m = x => h(x); + const m = x => { + const result = h(x); + delete result._original; + return result; + }; expect(h('foo', null, m('a'), [m('b'), m('c')], m('d'))) .to.have.nested.property('props.children') .that.eql([m('a'), [m('b'), m('c')], m('d')]); @@ -158,34 +162,47 @@ describe('createElement(jsx)', () => { }); it('should NOT set children prop when null', () => { - let r = h('foo', {foo : 'bar'}, null); + let r = h('foo', { foo: 'bar' }, null); expect(r) .to.be.an('object') .to.have.nested.property('props.foo') - .not.to.have.nested.property('props.children') - - }) + .not.to.have.nested.property('props.children'); + }); it('should NOT set children prop when unspecified', () => { - let r = h('foo', {foo : 'bar'}); + let r = h('foo', { foo: 'bar' }); expect(r) .to.be.an('object') .to.have.nested.property('props.foo') - .not.to.have.nested.property('props.children') - }) + .not.to.have.nested.property('props.children'); + }); it('should NOT merge adjacent text children', () => { + const bar = h('bar'); + const barClone = h('bar'); + const baz = h('baz'); + const bazClone = h('baz'); + const baz2 = h('baz'); + const baz2Clone = h('baz'); + + delete bar._original; + delete barClone._original; + delete baz._original; + delete bazClone._original; + delete baz2._original; + delete baz2Clone._original; + let r = h( 'foo', null, 'one', 'two', - h('bar'), + bar, 'three', - h('baz'), - h('baz'), + baz, + baz2, 'four', null, 'five', @@ -198,10 +215,10 @@ describe('createElement(jsx)', () => { .that.deep.equals([ 'one', 'two', - h('bar'), + barClone, 'three', - h('baz'), - h('baz'), + bazClone, + baz2Clone, 'four', null, 'five', @@ -233,7 +250,7 @@ describe('createElement(jsx)', () => { null ]); }); - + it('should not merge children that are boolean values', () => { let r = h('foo', null, 'one', true, 'two', false, 'three'); @@ -254,10 +271,15 @@ describe('createElement(jsx)', () => { }); it('should ignore props.children if children are manually specified', () => { - expect( + const element = (
c
- ).to.eql(
c
); + ); + const childrenless =
c
; + delete element._original; + delete childrenless._original; + + expect(element).to.eql(childrenless); }); }); From 5bf39651fb5c42570a614e3f89ec29fe7be5dcb6 Mon Sep 17 00:00:00 2001 From: jdecroock Date: Sat, 31 Oct 2020 07:06:43 +0100 Subject: [PATCH 13/29] fix jsx-runtime by using random as well --- jsx-runtime/src/index.js | 2 +- jsx-runtime/test/browser/jsx-runtime.test.jsx | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/jsx-runtime/src/index.js b/jsx-runtime/src/index.js index 20414adac8..3f2f76055b 100644 --- a/jsx-runtime/src/index.js +++ b/jsx-runtime/src/index.js @@ -35,7 +35,7 @@ function createVNode(type, props, key, __source, __self) { _component: null, _hydrating: null, constructor: undefined, - _original: 0, + _original: Math.random(), __source, __self }; diff --git a/jsx-runtime/test/browser/jsx-runtime.test.jsx b/jsx-runtime/test/browser/jsx-runtime.test.jsx index bd6c34e6bd..c69501a3c3 100644 --- a/jsx-runtime/test/browser/jsx-runtime.test.jsx +++ b/jsx-runtime/test/browser/jsx-runtime.test.jsx @@ -62,6 +62,8 @@ describe('Babel jsx/jsxDEV', () => { const jsxVNode = jsx('div', { class: 'foo' }, 'key'); delete jsxVNode.__self; delete jsxVNode.__source; + delete jsxVNode._original; + delete elementVNode._original; expect(jsxVNode).to.deep.equal(elementVNode); }); }); From 9b4d0eff174dea7e58b9d6f3c764a51e862e5073 Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Sun, 1 Nov 2020 15:58:27 +0100 Subject: [PATCH 14/29] move to counting global --- jsx-runtime/src/index.js | 4 +++- src/create-element.js | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/jsx-runtime/src/index.js b/jsx-runtime/src/index.js index 3f2f76055b..b5e424ee6d 100644 --- a/jsx-runtime/src/index.js +++ b/jsx-runtime/src/index.js @@ -13,6 +13,8 @@ import { options, Fragment } from 'preact'; * Benchmarks: https://esbench.com/bench/5f6b54a0b4632100a7dcd2b3 */ +let vnodeCounter = 0; + /** * JSX.Element factory used by Babel's {runtime:"automatic"} JSX transform * @param {VNode['type']} type @@ -35,7 +37,7 @@ function createVNode(type, props, key, __source, __self) { _component: null, _hydrating: null, constructor: undefined, - _original: Math.random(), + _original: ++vnodeCounter, __source, __self }; diff --git a/src/create-element.js b/src/create-element.js index 650c61b407..8a59090f27 100644 --- a/src/create-element.js +++ b/src/create-element.js @@ -43,6 +43,8 @@ export function createElement(type, props, children) { return createVNode(type, normalizedProps, key, ref, null); } +let vnodeCounter = 0; + /** * Create a VNode (used internally by Preact) * @param {import('./internal').VNode["type"]} type The node name or Component @@ -75,7 +77,7 @@ export function createVNode(type, props, key, ref, original) { _component: null, _hydrating: null, constructor: undefined, - _original: original == null ? Math.random() : original + _original: original == null ? ++vnodeCounter : original }; if (options.vnode != null) options.vnode(vnode); From 6e6bf38712584dc653f87f1b19ab98e885e93723 Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Sun, 1 Nov 2020 22:28:50 +0100 Subject: [PATCH 15/29] refactor vnodeId to live on options --- jsx-runtime/src/index.js | 4 +--- mangle.json | 1 + src/create-element.js | 4 +--- src/internal.d.ts | 1 + src/options.js | 3 ++- 5 files changed, 6 insertions(+), 7 deletions(-) diff --git a/jsx-runtime/src/index.js b/jsx-runtime/src/index.js index b5e424ee6d..1027b392b8 100644 --- a/jsx-runtime/src/index.js +++ b/jsx-runtime/src/index.js @@ -13,8 +13,6 @@ import { options, Fragment } from 'preact'; * Benchmarks: https://esbench.com/bench/5f6b54a0b4632100a7dcd2b3 */ -let vnodeCounter = 0; - /** * JSX.Element factory used by Babel's {runtime:"automatic"} JSX transform * @param {VNode['type']} type @@ -37,7 +35,7 @@ function createVNode(type, props, key, __source, __self) { _component: null, _hydrating: null, constructor: undefined, - _original: ++vnodeCounter, + _original: ++options._vnodeId, __source, __self }; diff --git a/mangle.json b/mangle.json index 212298ed38..3acc512193 100644 --- a/mangle.json +++ b/mangle.json @@ -25,6 +25,7 @@ "props": { "cname": 6, "props": { + "$_vnodeId": "__i", "$_cleanup": "__c", "$_afterPaintQueued": "__a", "$__hooks": "__H", diff --git a/src/create-element.js b/src/create-element.js index 8a59090f27..c9334476de 100644 --- a/src/create-element.js +++ b/src/create-element.js @@ -43,8 +43,6 @@ export function createElement(type, props, children) { return createVNode(type, normalizedProps, key, ref, null); } -let vnodeCounter = 0; - /** * Create a VNode (used internally by Preact) * @param {import('./internal').VNode["type"]} type The node name or Component @@ -77,7 +75,7 @@ export function createVNode(type, props, key, ref, original) { _component: null, _hydrating: null, constructor: undefined, - _original: original == null ? ++vnodeCounter : original + _original: original == null ? ++options._vnodeId : original }; if (options.vnode != null) options.vnode(vnode); diff --git a/src/internal.d.ts b/src/internal.d.ts index 3f4cb6e86e..36d916badf 100644 --- a/src/internal.d.ts +++ b/src/internal.d.ts @@ -21,6 +21,7 @@ export interface DevSource { } export interface Options extends preact.Options { + _vnodeId: number; /** Attach a hook that is invoked before render, mainly to check the arguments. */ _root?( vnode: preact.ComponentChild, diff --git a/src/options.js b/src/options.js index 174f322705..cfce373101 100644 --- a/src/options.js +++ b/src/options.js @@ -10,7 +10,8 @@ import { _catchError } from './diff/catch-error'; * @type {import('./internal').Options} */ const options = { - _catchError + _catchError, + _vnodeId: 0 }; export default options; From 5cbb3080bb838c7e9932e1e31035914d8e26d410 Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Sun, 1 Nov 2020 22:54:05 +0100 Subject: [PATCH 16/29] rename to __v --- mangle.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mangle.json b/mangle.json index 3acc512193..cee96f5899 100644 --- a/mangle.json +++ b/mangle.json @@ -25,7 +25,7 @@ "props": { "cname": 6, "props": { - "$_vnodeId": "__i", + "$_vnodeId": "__v", "$_cleanup": "__c", "$_afterPaintQueued": "__a", "$__hooks": "__H", From e68d8de196192bfbe6bad3421231532875011acf Mon Sep 17 00:00:00 2001 From: Marvin Hagemeister Date: Thu, 5 Nov 2020 18:09:23 +0100 Subject: [PATCH 17/29] Throw when hook is invoked outside of render --- debug/test/browser/debug-hooks.test.js | 31 ++++++++++++++++++-------- hooks/src/index.js | 14 ++++++++++++ 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/debug/test/browser/debug-hooks.test.js b/debug/test/browser/debug-hooks.test.js index a566d15d3c..76891708e4 100644 --- a/debug/test/browser/debug-hooks.test.js +++ b/debug/test/browser/debug-hooks.test.js @@ -25,19 +25,18 @@ describe('debug with hooks', () => { teardown(scratch); }); - // TODO: Fix this test. It only passed before because App was the first component - // into render so currentComponent in hooks/index.js wasn't set yet. However, - // any children under App wouldn't have thrown the error if they did what App - // did because currentComponent would be set to App. - // In other words, hooks never clear currentComponent so once it is set, it won't - // be unset - it.skip('should throw an error when using a hook outside a render', () => { - const Foo = props => props.children; - class App extends Component { + it('should throw an error when using a hook outside a render', () => { + class Foo extends Component { componentWillMount() { useState(); } + render() { + return this.props.children; + } + } + + class App extends Component { render() { return

test

; } @@ -81,6 +80,20 @@ describe('debug with hooks', () => { expect(fn).to.throw(/Hook can only be invoked from render/); }); + it('should throw an error when invoked outside of a component after render', () => { + function Foo(props) { + useEffect(() => {}); // Pretend to use a hook + return props.children; + } + + const fn = () => + act(() => { + render(Hello!, scratch); + useState(); + }); + expect(fn).to.throw(/Hook can only be invoked from render/); + }); + it('should throw an error when invoked inside an effect callback', () => { function Foo(props) { useEffect(() => { diff --git a/hooks/src/index.js b/hooks/src/index.js index 57cfcbc50d..5a8c4fdeb9 100644 --- a/hooks/src/index.js +++ b/hooks/src/index.js @@ -5,6 +5,13 @@ let currentIndex; /** @type {import('./internal').Component} */ let currentComponent; +/** + * Keep track of the previous component so that we can set + * `currentComponent` to `null` and throw when a hook is invoked + * outside of render + * @type {import('./internal').Component} + */ +let previousComponent; /** @type {number} */ let currentHook = 0; @@ -12,6 +19,7 @@ let currentHook = 0; /** @type {Array} */ let afterPaintEffects = []; +let oldBeforeDiff = options._diff; let oldBeforeRender = options._render; let oldAfterDiff = options.diffed; let oldCommit = options._commit; @@ -20,6 +28,11 @@ let oldBeforeUnmount = options.unmount; const RAF_TIMEOUT = 100; let prevRaf; +options._diff = vnode => { + currentComponent = null; + if (oldBeforeDiff) oldBeforeDiff(vnode); +}; + options._render = vnode => { if (oldBeforeRender) oldBeforeRender(vnode); @@ -41,6 +54,7 @@ options.diffed = vnode => { if (c && c.__hooks && c.__hooks._pendingEffects.length) { afterPaint(afterPaintEffects.push(c)); } + currentComponent = previousComponent; }; options._commit = (vnode, commitQueue) => { From d52e9fecb6b52b70a64c67106feb2842e39e0937 Mon Sep 17 00:00:00 2001 From: Greg Craft <1685986+gcraftyg@users.noreply.github.com> Date: Thu, 5 Nov 2020 16:40:34 -0600 Subject: [PATCH 18/29] Ensuring Symbol is natively supported when determining if browser is ie11 --- compat/src/render.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/compat/src/render.js b/compat/src/render.js index eeacdbc474..43daeb668e 100644 --- a/compat/src/render.js +++ b/compat/src/render.js @@ -16,7 +16,9 @@ const CAMEL_PROPS = /^(?:accent|alignment|arabic|baseline|cap|clip(?!PathU)|colo // type="file|checkbox|radio", plus "range" in IE11. // (IE11 doesn't support Symbol, which we use here to turn `rad` into `ra` which matches "range") const ONCHANGE_INPUT_TYPES = - typeof Symbol != 'undefined' ? /fil|che|rad/i : /fil|che|ra/i; + typeof Symbol != 'undefined' && typeof Symbol() == 'symbol' + ? /fil|che|rad/i + : /fil|che|ra/i; // Some libraries like `react-virtualized` explicitly check for this. Component.prototype.isReactComponent = {}; From 9d6023cd34ef248753aa1d877f09f725f813139c Mon Sep 17 00:00:00 2001 From: Max Voronov Date: Sun, 8 Nov 2020 16:42:21 +0200 Subject: [PATCH 19/29] Add displayName to context --- src/index.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/index.d.ts b/src/index.d.ts index bfc7feeeda..26226fe630 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -299,6 +299,7 @@ declare namespace preact { interface Context { Consumer: Consumer; Provider: Provider; + displayName?: string; } interface PreactContext extends Context {} From 647ed4ebb2b97b4a1aec661c68e5ab4e960e323d Mon Sep 17 00:00:00 2001 From: HuHguZ Date: Wed, 11 Nov 2020 11:54:04 +0300 Subject: [PATCH 20/29] add animateTransform tag type --- src/jsx.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/jsx.d.ts b/src/jsx.d.ts index 52c1dc069b..355dd2537d 100644 --- a/src/jsx.d.ts +++ b/src/jsx.d.ts @@ -898,6 +898,7 @@ export namespace JSXInternal { svg: SVGAttributes; animate: SVGAttributes; circle: SVGAttributes; + animateTransform: SVGAttributes; clipPath: SVGAttributes; defs: SVGAttributes; desc: SVGAttributes; From c8bbe39abb74bd7dff4c54f2be39ee3c7a660f0e Mon Sep 17 00:00:00 2001 From: Marvin Hagemeister Date: Wed, 11 Nov 2020 12:25:22 +0100 Subject: [PATCH 21/29] Fix prototype spies not being reset in tests --- test/_util/logCall.js | 2 ++ test/browser/fragments.test.js | 18 +++++++++--- test/browser/hydrate.test.js | 28 +++++++++++++++---- test/browser/keys.test.js | 20 ++++++++++--- .../lifecycles/shouldComponentUpdate.test.js | 15 ++++++++-- test/browser/placeholders.test.js | 20 ++++++++++--- test/browser/render.test.js | 20 ++++++++++--- 7 files changed, 98 insertions(+), 25 deletions(-) diff --git a/test/_util/logCall.js b/test/_util/logCall.js index 288aded17a..0eff2821b0 100644 --- a/test/_util/logCall.js +++ b/test/_util/logCall.js @@ -37,6 +37,8 @@ export function logCall(obj, method) { log.push(operation); return old.apply(this, args); }; + + return () => (obj[method] = old); } /** diff --git a/test/browser/fragments.test.js b/test/browser/fragments.test.js index cfe8e5c175..0d9b4955d9 100644 --- a/test/browser/fragments.test.js +++ b/test/browser/fragments.test.js @@ -33,10 +33,14 @@ describe('Fragment', () => { } } + let resetInsertBefore; + let resetAppendChild; + let resetRemoveChild; + before(() => { - logCall(Node.prototype, 'insertBefore'); - logCall(Node.prototype, 'appendChild'); - logCall(Node.prototype, 'removeChild'); + resetInsertBefore = logCall(Node.prototype, 'insertBefore'); + resetAppendChild = logCall(Node.prototype, 'appendChild'); + resetRemoveChild = logCall(Node.prototype, 'removeChild'); // logCall(CharacterData.prototype, 'remove'); // TODO: Consider logging setting set data // ``` @@ -49,6 +53,12 @@ describe('Fragment', () => { // ``` }); + after(() => { + resetInsertBefore(); + resetAppendChild(); + resetRemoveChild(); + }); + beforeEach(() => { scratch = setupScratch(); rerender = setupRerender(); @@ -541,7 +551,7 @@ describe('Fragment', () => { expect(scratch.innerHTML).to.equal('
Hello
'); }); - it.skip('should not preserve state between array nested in fragment and double nested array', () => { + it('should not preserve state between array nested in fragment and double nested array', () => { function Foo({ condition }) { return condition ? ( {[]} diff --git a/test/browser/hydrate.test.js b/test/browser/hydrate.test.js index aebc40a493..1fe2062cae 100644 --- a/test/browser/hydrate.test.js +++ b/test/browser/hydrate.test.js @@ -17,13 +17,29 @@ describe('hydrate()', () => { const List = ({ children }) =>
    {children}
; const ListItem = ({ children }) =>
  • {children}
  • ; + let resetAppendChild; + let resetInsertBefore; + let resetRemoveChild; + let resetRemove; + let resetSetAttribute; + let resetRemoveAttribute; + before(() => { - logCall(Element.prototype, 'appendChild'); - logCall(Element.prototype, 'insertBefore'); - logCall(Element.prototype, 'removeChild'); - logCall(Element.prototype, 'remove'); - logCall(Element.prototype, 'setAttribute'); - logCall(Element.prototype, 'removeAttribute'); + resetAppendChild = logCall(Element.prototype, 'appendChild'); + resetInsertBefore = logCall(Element.prototype, 'insertBefore'); + resetRemoveChild = logCall(Element.prototype, 'removeChild'); + resetRemove = logCall(Element.prototype, 'remove'); + resetSetAttribute = logCall(Element.prototype, 'setAttribute'); + resetRemoveAttribute = logCall(Element.prototype, 'removeAttribute'); + }); + + after(() => { + resetAppendChild(); + resetInsertBefore(); + resetRemoveChild(); + resetRemove(); + resetSetAttribute(); + resetRemoveAttribute(); }); beforeEach(() => { diff --git a/test/browser/keys.test.js b/test/browser/keys.test.js index 385bec0947..7f6c0e8f5c 100644 --- a/test/browser/keys.test.js +++ b/test/browser/keys.test.js @@ -50,11 +50,23 @@ describe('keys', () => { values.splice(to, 0, value); } + let resetAppendChild; + let resetInsertBefore; + let resetRemoveChild; + let resetRemove; + before(() => { - logCall(Element.prototype, 'appendChild'); - logCall(Element.prototype, 'insertBefore'); - logCall(Element.prototype, 'removeChild'); - logCall(Element.prototype, 'remove'); + resetAppendChild = logCall(Element.prototype, 'appendChild'); + resetInsertBefore = logCall(Element.prototype, 'insertBefore'); + resetRemoveChild = logCall(Element.prototype, 'removeChild'); + resetRemove = logCall(Element.prototype, 'remove'); + }); + + after(() => { + resetAppendChild(); + resetInsertBefore(); + resetRemoveChild(); + resetRemove(); }); beforeEach(() => { diff --git a/test/browser/lifecycles/shouldComponentUpdate.test.js b/test/browser/lifecycles/shouldComponentUpdate.test.js index 62ef701f0b..df604fe576 100644 --- a/test/browser/lifecycles/shouldComponentUpdate.test.js +++ b/test/browser/lifecycles/shouldComponentUpdate.test.js @@ -15,11 +15,20 @@ describe('Lifecycle methods', () => { // function expectDomLogToBe(expectedOperations, message) { // expect(getLog()).to.deep.equal(expectedOperations, message); // } + let resetInsertBefore; + let resetRemoveChild; + let resetRemove; before(() => { - logCall(Node.prototype, 'insertBefore'); - logCall(Node.prototype, 'appendChild'); - logCall(Node.prototype, 'removeChild'); + resetInsertBefore = logCall(Node.prototype, 'insertBefore'); + resetRemoveChild = logCall(Node.prototype, 'appendChild'); + resetRemove = logCall(Node.prototype, 'removeChild'); + }); + + after(() => { + resetInsertBefore(); + resetRemoveChild(); + resetRemove(); }); beforeEach(() => { diff --git a/test/browser/placeholders.test.js b/test/browser/placeholders.test.js index 9f42617298..5b52ee62e4 100644 --- a/test/browser/placeholders.test.js +++ b/test/browser/placeholders.test.js @@ -54,11 +54,23 @@ describe('null placeholders', () => { return [Nullable, ref]; } + let resetAppendChild; + let resetInsertBefore; + let resetRemoveChild; + let resetRemove; + before(() => { - logCall(Element.prototype, 'appendChild'); - logCall(Element.prototype, 'insertBefore'); - logCall(Element.prototype, 'removeChild'); - logCall(Element.prototype, 'remove'); + resetAppendChild = logCall(Element.prototype, 'appendChild'); + resetInsertBefore = logCall(Element.prototype, 'insertBefore'); + resetRemoveChild = logCall(Element.prototype, 'removeChild'); + resetRemove = logCall(Element.prototype, 'remove'); + }); + + after(() => { + resetAppendChild(); + resetInsertBefore(); + resetRemoveChild(); + resetRemove(); }); beforeEach(() => { diff --git a/test/browser/render.test.js b/test/browser/render.test.js index 67d6085b81..23d4a9fbfc 100644 --- a/test/browser/render.test.js +++ b/test/browser/render.test.js @@ -27,6 +27,11 @@ function getAttributes(node) { describe('render()', () => { let scratch, rerender; + let resetAppendChild; + let resetInsertBefore; + let resetRemoveChild; + let resetRemove; + beforeEach(() => { scratch = setupScratch(); rerender = setupRerender(); @@ -37,10 +42,17 @@ describe('render()', () => { }); before(() => { - logCall(Element.prototype, 'appendChild'); - logCall(Element.prototype, 'insertBefore'); - logCall(Element.prototype, 'removeChild'); - logCall(Element.prototype, 'remove'); + resetAppendChild = logCall(Element.prototype, 'appendChild'); + resetInsertBefore = logCall(Element.prototype, 'insertBefore'); + resetRemoveChild = logCall(Element.prototype, 'removeChild'); + resetRemove = logCall(Element.prototype, 'remove'); + }); + + after(() => { + resetAppendChild(); + resetInsertBefore(); + resetRemoveChild(); + resetRemove(); }); it('should rerender when value from "" to 0', () => { From 813373856bb888f8ddc1807064f2ca5cb95ffbbb Mon Sep 17 00:00:00 2001 From: Marvin Hagemeister Date: Wed, 11 Nov 2020 12:46:28 +0100 Subject: [PATCH 22/29] Fix inconsistent test spy calls --- .../test/browser/suspense-hydration.test.js | 20 +++++++++++++++---- test/browser/fragments.test.js | 10 ++++------ .../lifecycles/shouldComponentUpdate.test.js | 6 +++--- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/compat/test/browser/suspense-hydration.test.js b/compat/test/browser/suspense-hydration.test.js index e82863e2b8..0e358774d8 100644 --- a/compat/test/browser/suspense-hydration.test.js +++ b/compat/test/browser/suspense-hydration.test.js @@ -36,11 +36,23 @@ describe('suspense hydration', () => { } } + let resetAppendChild; + let resetInsertBefore; + let resetRemoveChild; + let resetRemove; + before(() => { - logCall(Element.prototype, 'appendChild'); - logCall(Element.prototype, 'insertBefore'); - logCall(Element.prototype, 'removeChild'); - logCall(Element.prototype, 'remove'); + resetAppendChild = logCall(Element.prototype, 'appendChild'); + resetInsertBefore = logCall(Element.prototype, 'insertBefore'); + resetRemoveChild = logCall(Element.prototype, 'removeChild'); + resetRemove = logCall(Element.prototype, 'remove'); + }); + + after(() => { + resetAppendChild(); + resetInsertBefore(); + resetRemoveChild(); + resetRemove(); }); beforeEach(() => { diff --git a/test/browser/fragments.test.js b/test/browser/fragments.test.js index f11482d645..2a089f801b 100644 --- a/test/browser/fragments.test.js +++ b/test/browser/fragments.test.js @@ -38,9 +38,9 @@ describe('Fragment', () => { let resetRemoveChild; before(() => { - resetInsertBefore = logCall(Node.prototype, 'insertBefore'); - resetAppendChild = logCall(Node.prototype, 'appendChild'); - resetRemoveChild = logCall(Node.prototype, 'removeChild'); + resetInsertBefore = logCall(Element.prototype, 'insertBefore'); + resetAppendChild = logCall(Element.prototype, 'appendChild'); + resetRemoveChild = logCall(Element.prototype, 'removeChild'); // logCall(CharacterData.prototype, 'remove'); // TODO: Consider logging setting set data // ``` @@ -2571,9 +2571,7 @@ describe('Fragment', () => { ]); }); - // TODO: Revisit why this test is failing. Likely due to some side effect of - // the logCalls in suspense-hydration.test.js - it.skip('should properly place conditional elements around strictly equal vnodes', () => { + it('should properly place conditional elements around strictly equal vnodes', () => { expectDomLog = true; let set; diff --git a/test/browser/lifecycles/shouldComponentUpdate.test.js b/test/browser/lifecycles/shouldComponentUpdate.test.js index df604fe576..ae0a6a66e9 100644 --- a/test/browser/lifecycles/shouldComponentUpdate.test.js +++ b/test/browser/lifecycles/shouldComponentUpdate.test.js @@ -20,9 +20,9 @@ describe('Lifecycle methods', () => { let resetRemove; before(() => { - resetInsertBefore = logCall(Node.prototype, 'insertBefore'); - resetRemoveChild = logCall(Node.prototype, 'appendChild'); - resetRemove = logCall(Node.prototype, 'removeChild'); + resetInsertBefore = logCall(Element.prototype, 'insertBefore'); + resetRemoveChild = logCall(Element.prototype, 'appendChild'); + resetRemove = logCall(Element.prototype, 'removeChild'); }); after(() => { From 214f7cb6b56a6cce24449650a89a6aed57ed8522 Mon Sep 17 00:00:00 2001 From: Marvin Hagemeister Date: Thu, 12 Nov 2020 19:15:48 +0100 Subject: [PATCH 23/29] Use tab indentation in package.json --- package.json | 506 +++++++++++++++++++++++++-------------------------- 1 file changed, 253 insertions(+), 253 deletions(-) diff --git a/package.json b/package.json index 4c49f26eff..ae7e3c978b 100644 --- a/package.json +++ b/package.json @@ -1,255 +1,255 @@ { - "name": "preact", - "amdName": "preact", - "version": "10.5.5", - "private": false, - "description": "Fast 3kb React-compatible Virtual DOM library.", - "main": "dist/preact.js", - "module": "dist/preact.module.js", - "umd:main": "dist/preact.umd.js", - "unpkg": "dist/preact.min.js", - "source": "src/index.js", - "exports": { - ".": { - "browser": "./dist/preact.module.js", - "umd": "./dist/preact.umd.js", - "import": "./dist/preact.mjs", - "require": "./dist/preact.js" - }, - "./compat": { - "browser": "./compat/dist/compat.module.js", - "umd": "./compat/dist/compat.umd.js", - "require": "./compat/dist/compat.js", - "import": "./compat/dist/compat.mjs" - }, - "./debug": { - "browser": "./debug/dist/debug.module.js", - "umd": "./debug/dist/debug.umd.js", - "require": "./debug/dist/debug.js", - "import": "./debug/dist/debug.mjs" - }, - "./devtools": { - "browser": "./devtools/dist/devtools.module.js", - "umd": "./devtools/dist/devtools.umd.js", - "require": "./devtools/dist/devtools.js", - "import": "./devtools/dist/devtools.mjs" - }, - "./hooks": { - "browser": "./hooks/dist/hooks.module.js", - "umd": "./hooks/dist/hooks.umd.js", - "require": "./hooks/dist/hooks.js", - "import": "./hooks/dist/hooks.mjs" - }, - "./test-utils": { - "browser": "./test-utils/dist/testUtils.module.js", - "umd": "./test-utils/dist/testUtils.umd.js", - "require": "./test-utils/dist/testUtils.js", - "import": "./test-utils/dist/testUtils.mjs" - }, - "./jsx-runtime": { - "browser": "./jsx-runtime/dist/jsxRuntime.module.js", - "umd": "./jsx-runtime/dist/jsxRuntime.umd.js", - "require": "./jsx-runtime/dist/jsxRuntime.js", - "import": "./jsx-runtime/dist/jsxRuntime.mjs" - }, - "./jsx-dev-runtime": { - "browser": "./jsx-runtime/dist/jsxRuntime.module.js", - "umd": "./jsx-runtime/dist/jsxRuntime.umd.js", - "require": "./jsx-runtime/dist/jsxRuntime.js", - "import": "./jsx-runtime/dist/jsxRuntime.mjs" - }, - "./compat/server": { - "require": "./compat/server.js" - }, - "./package.json": "./package.json", - "./": "./" - }, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/preact" - }, - "types": "src/index.d.ts", - "scripts": { - "prepare": "run-s build && check-export-map", - "build": "npm-run-all --parallel build:*", - "build:core": "microbundle build --raw", - "build:core-min": "microbundle build --raw -f iife src/cjs.js -o dist/preact.min.js", - "build:debug": "microbundle build --raw --cwd debug", - "build:devtools": "microbundle build --raw --cwd devtools", - "build:hooks": "microbundle build --raw --cwd hooks", - "build:test-utils": "microbundle build --raw --cwd test-utils", - "build:compat": "microbundle build --raw --cwd compat --globals 'preact/hooks=preactHooks'", - "build:jsx": "microbundle build --raw --cwd jsx-runtime", - "postbuild": "node ./config/node-13-exports.js && node ./config/copy-csstype.js", - "dev": "microbundle watch --raw --format cjs", - "dev:hooks": "microbundle watch --raw --format cjs --cwd hooks", - "dev:compat": "microbundle watch --raw --format cjs --cwd compat --globals 'preact/hooks=preactHooks'", - "test": "npm-run-all build lint test:unit", - "test:unit": "run-p test:mocha test:karma:minify test:ts", - "test:ts": "run-p test:ts:*", - "test:ts:core": "tsc -p test/ts/ && mocha --require \"@babel/register\" test/ts/**/*-test.js", - "test:ts:compat": "tsc -p compat/test/ts/", - "test:mocha": "mocha --recursive --require \"@babel/register\" test/shared test/node", - "test:mocha:watch": "npm run test:mocha -- --watch", - "test:karma": "cross-env COVERAGE=true karma start karma.conf.js --single-run", - "test:karma:minify": "cross-env COVERAGE=true MINIFY=true karma start karma.conf.js --single-run", - "test:karma:watch": "karma start karma.conf.js --no-single-run", - "test:karma:hooks": "cross-env COVERAGE=false karma start karma.conf.js --grep=hooks/test/browser/**.js --no-single-run", - "test:karma:test-utils": "cross-env PERFORMANCE=false COVERAGE=false karma start karma.conf.js --grep=test-utils/test/shared/**.js --no-single-run", - "test:karma:bench": "cross-env PERFORMANCE=true COVERAGE=false karma start karma.conf.js --grep=test/benchmarks/**.js --single-run", - "benchmark": "npm run test:karma:bench -- no-single-run", - "lint": "eslint src test debug compat hooks test-utils" - }, - "eslintConfig": { - "extends": [ - "developit", - "prettier" - ], - "settings": { - "react": { - "pragma": "createElement" - } - }, - "rules": { - "camelcase": [ - 1, - { - "allow": [ - "__test__*", - "unstable_*", - "UNSAFE_*" - ] - } - ], - "no-unused-vars": [ - 2, - { - "args": "none", - "varsIgnorePattern": "^h|React$" - } - ], - "prefer-rest-params": 0, - "prefer-spread": 0, - "no-cond-assign": 0, - "react/jsx-no-bind": 0, - "react/no-danger": "off", - "react/prefer-stateless-function": 0, - "react/sort-comp": 0, - "jest/valid-expect": 0, - "jest/no-disabled-tests": 0, - "react/no-find-dom-node": 0 - } - }, - "eslintIgnore": [ - "test/fixtures", - "test/ts/", - "*.ts", - "dist" - ], - "prettier": { - "singleQuote": true, - "trailingComma": "none", - "useTabs": true, - "tabWidth": 2 - }, - "lint-staged": { - "**/*.{js,jsx,ts,tsx,yml}": [ - "prettier --write", - "git add" - ] - }, - "husky": { - "hooks": { - "pre-commit": "lint-staged" - } - }, - "files": [ - "src", - "dist", - "compat/dist", - "compat/src", - "compat/server.js", - "compat/package.json", - "debug/dist", - "debug/src", - "debug/package.json", - "devtools/dist", - "devtools/src", - "devtools/package.json", - "hooks/dist", - "hooks/src", - "hooks/package.json", - "jsx-runtime/dist", - "jsx-runtime/src", - "jsx-runtime/package.json", - "test-utils/src", - "test-utils/package.json", - "test-utils/dist" - ], - "keywords": [ - "preact", - "react", - "ui", - "user interface", - "virtual dom", - "vdom", - "components", - "dom diff" - ], - "authors": [ - "The Preact Authors (https://github.com/preactjs/preact/contributors)" - ], - "repository": "preactjs/preact", - "bugs": "https://github.com/preactjs/preact/issues", - "homepage": "https://preactjs.com", - "devDependencies": { - "@babel/core": "^7.7.0", - "@babel/plugin-proposal-object-rest-spread": "^7.6.2", - "@babel/plugin-transform-react-jsx": "^7.7.0", - "@babel/plugin-transform-react-jsx-source": "^7.7.4", - "@babel/preset-env": "^7.7.1", - "@babel/register": "^7.7.0", - "@types/chai": "^4.1.2", - "@types/mocha": "^5.0.0", - "@types/node": "^10.5.2", - "babel-loader": "^8.0.6", - "babel-plugin-istanbul": "^6.0.0", - "babel-plugin-transform-async-to-promises": "^0.8.15", - "babel-plugin-transform-rename-properties": "0.0.3", - "benchmark": "^2.1.4", - "chai": "^4.1.2", - "check-export-map": "^1.0.1", - "coveralls": "^3.0.0", - "cross-env": "^5.2.0", - "csstype": "^2.6.6", - "diff": "^3.5.0", - "eslint": "5.15.1", - "eslint-config-developit": "^1.1.1", - "eslint-config-prettier": "^6.5.0", - "eslint-plugin-react": "7.12.4", - "husky": "^3.0.9", - "karma": "^3.0.0", - "karma-chai-sinon": "^0.1.5", - "karma-chrome-launcher": "^2.2.0", - "karma-coverage": "^2.0.1", - "karma-mocha": "^1.3.0", - "karma-mocha-reporter": "^2.2.5", - "karma-sauce-launcher": "^1.2.0", - "karma-sinon": "^1.0.5", - "karma-sourcemap-loader": "^0.3.7", - "karma-webpack": "^3.0.5", - "lint-staged": "^9.4.2", - "lodash": "^4.17.10", - "microbundle": "^0.11.0", - "mocha": "^5.2.0", - "npm-merge-driver-install": "^1.1.1", - "npm-run-all": "^4.0.0", - "prettier": "^1.18.2", - "prop-types": "^15.7.2", - "sinon": "^6.1.3", - "sinon-chai": "^3.0.0", - "typescript": "3.5.3", - "webpack": "^4.3.0" - } + "name": "preact", + "amdName": "preact", + "version": "10.5.5", + "private": false, + "description": "Fast 3kb React-compatible Virtual DOM library.", + "main": "dist/preact.js", + "module": "dist/preact.module.js", + "umd:main": "dist/preact.umd.js", + "unpkg": "dist/preact.min.js", + "source": "src/index.js", + "exports": { + ".": { + "browser": "./dist/preact.module.js", + "umd": "./dist/preact.umd.js", + "import": "./dist/preact.mjs", + "require": "./dist/preact.js" + }, + "./compat": { + "browser": "./compat/dist/compat.module.js", + "umd": "./compat/dist/compat.umd.js", + "require": "./compat/dist/compat.js", + "import": "./compat/dist/compat.mjs" + }, + "./debug": { + "browser": "./debug/dist/debug.module.js", + "umd": "./debug/dist/debug.umd.js", + "require": "./debug/dist/debug.js", + "import": "./debug/dist/debug.mjs" + }, + "./devtools": { + "browser": "./devtools/dist/devtools.module.js", + "umd": "./devtools/dist/devtools.umd.js", + "require": "./devtools/dist/devtools.js", + "import": "./devtools/dist/devtools.mjs" + }, + "./hooks": { + "browser": "./hooks/dist/hooks.module.js", + "umd": "./hooks/dist/hooks.umd.js", + "require": "./hooks/dist/hooks.js", + "import": "./hooks/dist/hooks.mjs" + }, + "./test-utils": { + "browser": "./test-utils/dist/testUtils.module.js", + "umd": "./test-utils/dist/testUtils.umd.js", + "require": "./test-utils/dist/testUtils.js", + "import": "./test-utils/dist/testUtils.mjs" + }, + "./jsx-runtime": { + "browser": "./jsx-runtime/dist/jsxRuntime.module.js", + "umd": "./jsx-runtime/dist/jsxRuntime.umd.js", + "require": "./jsx-runtime/dist/jsxRuntime.js", + "import": "./jsx-runtime/dist/jsxRuntime.mjs" + }, + "./jsx-dev-runtime": { + "browser": "./jsx-runtime/dist/jsxRuntime.module.js", + "umd": "./jsx-runtime/dist/jsxRuntime.umd.js", + "require": "./jsx-runtime/dist/jsxRuntime.js", + "import": "./jsx-runtime/dist/jsxRuntime.mjs" + }, + "./compat/server": { + "require": "./compat/server.js" + }, + "./package.json": "./package.json", + "./": "./" + }, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + }, + "types": "src/index.d.ts", + "scripts": { + "prepare": "run-s build && check-export-map", + "build": "npm-run-all --parallel build:*", + "build:core": "microbundle build --raw", + "build:core-min": "microbundle build --raw -f iife src/cjs.js -o dist/preact.min.js", + "build:debug": "microbundle build --raw --cwd debug", + "build:devtools": "microbundle build --raw --cwd devtools", + "build:hooks": "microbundle build --raw --cwd hooks", + "build:test-utils": "microbundle build --raw --cwd test-utils", + "build:compat": "microbundle build --raw --cwd compat --globals 'preact/hooks=preactHooks'", + "build:jsx": "microbundle build --raw --cwd jsx-runtime", + "postbuild": "node ./config/node-13-exports.js && node ./config/copy-csstype.js", + "dev": "microbundle watch --raw --format cjs", + "dev:hooks": "microbundle watch --raw --format cjs --cwd hooks", + "dev:compat": "microbundle watch --raw --format cjs --cwd compat --globals 'preact/hooks=preactHooks'", + "test": "npm-run-all build lint test:unit", + "test:unit": "run-p test:mocha test:karma:minify test:ts", + "test:ts": "run-p test:ts:*", + "test:ts:core": "tsc -p test/ts/ && mocha --require \"@babel/register\" test/ts/**/*-test.js", + "test:ts:compat": "tsc -p compat/test/ts/", + "test:mocha": "mocha --recursive --require \"@babel/register\" test/shared test/node", + "test:mocha:watch": "npm run test:mocha -- --watch", + "test:karma": "cross-env COVERAGE=true karma start karma.conf.js --single-run", + "test:karma:minify": "cross-env COVERAGE=true MINIFY=true karma start karma.conf.js --single-run", + "test:karma:watch": "karma start karma.conf.js --no-single-run", + "test:karma:hooks": "cross-env COVERAGE=false karma start karma.conf.js --grep=hooks/test/browser/**.js --no-single-run", + "test:karma:test-utils": "cross-env PERFORMANCE=false COVERAGE=false karma start karma.conf.js --grep=test-utils/test/shared/**.js --no-single-run", + "test:karma:bench": "cross-env PERFORMANCE=true COVERAGE=false karma start karma.conf.js --grep=test/benchmarks/**.js --single-run", + "benchmark": "npm run test:karma:bench -- no-single-run", + "lint": "eslint src test debug compat hooks test-utils" + }, + "eslintConfig": { + "extends": [ + "developit", + "prettier" + ], + "settings": { + "react": { + "pragma": "createElement" + } + }, + "rules": { + "camelcase": [ + 1, + { + "allow": [ + "__test__*", + "unstable_*", + "UNSAFE_*" + ] + } + ], + "no-unused-vars": [ + 2, + { + "args": "none", + "varsIgnorePattern": "^h|React$" + } + ], + "prefer-rest-params": 0, + "prefer-spread": 0, + "no-cond-assign": 0, + "react/jsx-no-bind": 0, + "react/no-danger": "off", + "react/prefer-stateless-function": 0, + "react/sort-comp": 0, + "jest/valid-expect": 0, + "jest/no-disabled-tests": 0, + "react/no-find-dom-node": 0 + } + }, + "eslintIgnore": [ + "test/fixtures", + "test/ts/", + "*.ts", + "dist" + ], + "prettier": { + "singleQuote": true, + "trailingComma": "none", + "useTabs": true, + "tabWidth": 2 + }, + "lint-staged": { + "**/*.{js,jsx,ts,tsx,yml}": [ + "prettier --write", + "git add" + ] + }, + "husky": { + "hooks": { + "pre-commit": "lint-staged" + } + }, + "files": [ + "src", + "dist", + "compat/dist", + "compat/src", + "compat/server.js", + "compat/package.json", + "debug/dist", + "debug/src", + "debug/package.json", + "devtools/dist", + "devtools/src", + "devtools/package.json", + "hooks/dist", + "hooks/src", + "hooks/package.json", + "jsx-runtime/dist", + "jsx-runtime/src", + "jsx-runtime/package.json", + "test-utils/src", + "test-utils/package.json", + "test-utils/dist" + ], + "keywords": [ + "preact", + "react", + "ui", + "user interface", + "virtual dom", + "vdom", + "components", + "dom diff" + ], + "authors": [ + "The Preact Authors (https://github.com/preactjs/preact/contributors)" + ], + "repository": "preactjs/preact", + "bugs": "https://github.com/preactjs/preact/issues", + "homepage": "https://preactjs.com", + "devDependencies": { + "@babel/core": "^7.7.0", + "@babel/plugin-proposal-object-rest-spread": "^7.6.2", + "@babel/plugin-transform-react-jsx": "^7.7.0", + "@babel/plugin-transform-react-jsx-source": "^7.7.4", + "@babel/preset-env": "^7.7.1", + "@babel/register": "^7.7.0", + "@types/chai": "^4.1.2", + "@types/mocha": "^5.0.0", + "@types/node": "^10.5.2", + "babel-loader": "^8.0.6", + "babel-plugin-istanbul": "^6.0.0", + "babel-plugin-transform-async-to-promises": "^0.8.15", + "babel-plugin-transform-rename-properties": "0.0.3", + "benchmark": "^2.1.4", + "chai": "^4.1.2", + "check-export-map": "^1.0.1", + "coveralls": "^3.0.0", + "cross-env": "^5.2.0", + "csstype": "^2.6.6", + "diff": "^3.5.0", + "eslint": "5.15.1", + "eslint-config-developit": "^1.1.1", + "eslint-config-prettier": "^6.5.0", + "eslint-plugin-react": "7.12.4", + "husky": "^3.0.9", + "karma": "^3.0.0", + "karma-chai-sinon": "^0.1.5", + "karma-chrome-launcher": "^2.2.0", + "karma-coverage": "^2.0.1", + "karma-mocha": "^1.3.0", + "karma-mocha-reporter": "^2.2.5", + "karma-sauce-launcher": "^1.2.0", + "karma-sinon": "^1.0.5", + "karma-sourcemap-loader": "^0.3.7", + "karma-webpack": "^3.0.5", + "lint-staged": "^9.4.2", + "lodash": "^4.17.10", + "microbundle": "^0.11.0", + "mocha": "^5.2.0", + "npm-merge-driver-install": "^1.1.1", + "npm-run-all": "^4.0.0", + "prettier": "^1.18.2", + "prop-types": "^15.7.2", + "sinon": "^6.1.3", + "sinon-chai": "^3.0.0", + "typescript": "3.5.3", + "webpack": "^4.3.0" + } } From 271bc43ee7e151b63c91bc7d7ea8f6a4c929c99c Mon Sep 17 00:00:00 2001 From: Marvin Hagemeister Date: Thu, 12 Nov 2020 19:19:32 +0100 Subject: [PATCH 24/29] Release 10.5.6 --- devtools/src/devtools.js | 2 +- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/devtools/src/devtools.js b/devtools/src/devtools.js index c949597b54..3abda32a9f 100644 --- a/devtools/src/devtools.js +++ b/devtools/src/devtools.js @@ -2,7 +2,7 @@ import { options, Fragment, Component } from 'preact'; export function initDevTools() { if (typeof window != 'undefined' && window.__PREACT_DEVTOOLS__) { - window.__PREACT_DEVTOOLS__.attachPreact('10.5.5', options, { + window.__PREACT_DEVTOOLS__.attachPreact('10.5.6', options, { Fragment, Component }); diff --git a/package-lock.json b/package-lock.json index c70baf6847..5b64073b2b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "preact", - "version": "10.5.5", + "version": "10.5.6", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index ae7e3c978b..1d3f79ca7f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "preact", "amdName": "preact", - "version": "10.5.5", + "version": "10.5.6", "private": false, "description": "Fast 3kb React-compatible Virtual DOM library.", "main": "dist/preact.js", From 2215f8d6c8c6e96b7780979bd100c6179072ed35 Mon Sep 17 00:00:00 2001 From: Marvin Hagemeister Date: Thu, 12 Nov 2020 22:23:06 +0100 Subject: [PATCH 25/29] Fix compat/jsx-runtime missing in npm package --- package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.json b/package.json index 1d3f79ca7f..4c292c4b9b 100644 --- a/package.json +++ b/package.json @@ -170,6 +170,8 @@ "compat/dist", "compat/src", "compat/server.js", + "compat/jsx-runtime.js", + "compat/jsx-runtime.mjs", "compat/package.json", "debug/dist", "debug/src", From af510ad2d642b8055f845c6db29dd8a376ab62f5 Mon Sep 17 00:00:00 2001 From: Marvin Hagemeister Date: Thu, 12 Nov 2020 22:48:03 +0100 Subject: [PATCH 26/29] Release 10.5.7 --- devtools/src/devtools.js | 2 +- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/devtools/src/devtools.js b/devtools/src/devtools.js index 3abda32a9f..834a462682 100644 --- a/devtools/src/devtools.js +++ b/devtools/src/devtools.js @@ -2,7 +2,7 @@ import { options, Fragment, Component } from 'preact'; export function initDevTools() { if (typeof window != 'undefined' && window.__PREACT_DEVTOOLS__) { - window.__PREACT_DEVTOOLS__.attachPreact('10.5.6', options, { + window.__PREACT_DEVTOOLS__.attachPreact('10.5.7', options, { Fragment, Component }); diff --git a/package-lock.json b/package-lock.json index 5b64073b2b..52cfe99754 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "preact", - "version": "10.5.6", + "version": "10.5.7", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 4c292c4b9b..6d76ab11df 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "preact", "amdName": "preact", - "version": "10.5.6", + "version": "10.5.7", "private": false, "description": "Fast 3kb React-compatible Virtual DOM library.", "main": "dist/preact.js", From 1b5d158380c67361a8db0fa30c9013d1e692602c Mon Sep 17 00:00:00 2001 From: Greg Craft <1685986+gcraftyg@users.noreply.github.com> Date: Thu, 12 Nov 2020 23:37:40 -0600 Subject: [PATCH 27/29] Testing that ie11 does not normalize onChange when Symbol polyfill exists --- compat/src/render.js | 9 +++++---- compat/test/browser/events.test.js | 29 +++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/compat/src/render.js b/compat/src/render.js index 43daeb668e..fa8c79d0ce 100644 --- a/compat/src/render.js +++ b/compat/src/render.js @@ -15,10 +15,11 @@ const CAMEL_PROPS = /^(?:accent|alignment|arabic|baseline|cap|clip(?!PathU)|colo // Input types for which onchange should not be converted to oninput. // type="file|checkbox|radio", plus "range" in IE11. // (IE11 doesn't support Symbol, which we use here to turn `rad` into `ra` which matches "range") -const ONCHANGE_INPUT_TYPES = - typeof Symbol != 'undefined' && typeof Symbol() == 'symbol' +const onChangeInputType = type => + (typeof Symbol != 'undefined' && typeof Symbol() == 'symbol' ? /fil|che|rad/i - : /fil|che|ra/i; + : /fil|che|ra/i + ).test(type); // Some libraries like `react-virtualized` explicitly check for this. Component.prototype.isReactComponent = {}; @@ -131,7 +132,7 @@ options.vnode = vnode => { i = 'ondblclick'; } else if ( /^onchange(textarea|input)/i.test(i + type) && - !ONCHANGE_INPUT_TYPES.test(props.type) + !onChangeInputType(props.type) ) { i = 'oninput'; } else if (/^on(Ani|Tra|Tou|BeforeInp)/.test(i)) { diff --git a/compat/test/browser/events.test.js b/compat/test/browser/events.test.js index fd8cf26356..0f0e7aa228 100644 --- a/compat/test/browser/events.test.js +++ b/compat/test/browser/events.test.js @@ -75,6 +75,7 @@ describe('preact/compat events', () => { const eventType = /Trident\//.test(navigator.userAgent) ? 'change' : 'input'; + render( null} />, scratch); expect(proto.addEventListener).to.have.been.calledOnce; expect(proto.addEventListener).to.have.been.calledWithExactly( @@ -84,6 +85,34 @@ describe('preact/compat events', () => { ); }); + it('should normalize onChange for range, except in IE11, including when IE11 has Symbol polyfill', () => { + // NOTE: we don't normalize `onchange` for range inputs in IE11. + // This test mimics a specific scenario when a Symbol polyfill may + // be present, in which case onChange should still not be normalized + + const isIE11 = /Trident\//.test(navigator.userAgent); + const eventType = isIE11 ? 'change' : 'input'; + + if (isIE11) { + window.Symbol = () => 'mockSymbolPolyfill'; + } + sinon.spy(window, 'Symbol'); + + render( null} />, scratch); + expect(window.Symbol).to.have.been.calledOnce; + expect(proto.addEventListener).to.have.been.calledOnce; + expect(proto.addEventListener).to.have.been.calledWithExactly( + eventType, + sinon.match.func, + false + ); + + window.Symbol.restore(); + if (isIE11) { + window.Symbol = undefined; + } + }); + it('should support onAnimationEnd', () => { const func = sinon.spy(() => {}); render(
    , scratch); From ced60c69c1dc7a5abb2443cf4f9eb396500ccf32 Mon Sep 17 00:00:00 2001 From: Martin Packman Date: Fri, 13 Nov 2020 20:29:05 +0000 Subject: [PATCH 28/29] Update csstype from version 2 to version 3 Improved typing, and smaller type file with fewer internals exposed. There are changes to the re-exported types but this feature exposing csstype has only just been released in preact. Expose TLength and TTime generics, and adapt length only to accept numbers of any kind, to match the 'px' appending logic in preact. --- package-lock.json | 6 +++--- package.json | 2 +- src/jsx.d.ts | 5 ++++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 52cfe99754..d5952dd791 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3548,9 +3548,9 @@ } }, "csstype": { - "version": "2.6.13", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.13.tgz", - "integrity": "sha512-ul26pfSQTZW8dcOnD2iiJssfXw0gdNVX9IJDH/X3K5DGPfj+fUYe3kB+swUY6BF3oZDxaID3AJt+9/ojSAE05A==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.5.tgz", + "integrity": "sha512-uVDi8LpBUKQj6sdxNaTetL6FpeCqTjOvAQuQUa/qAqq8oOd4ivkbhgnqayl0dnPal8Tb/yB1tF+gOvCBiicaiQ==", "dev": true }, "currently-unhandled": { diff --git a/package.json b/package.json index 6d76ab11df..00c6cd7eaf 100644 --- a/package.json +++ b/package.json @@ -224,7 +224,7 @@ "check-export-map": "^1.0.1", "coveralls": "^3.0.0", "cross-env": "^5.2.0", - "csstype": "^2.6.6", + "csstype": "^3.0.5", "diff": "^3.5.0", "eslint": "5.15.1", "eslint-config-developit": "^1.1.1", diff --git a/src/jsx.d.ts b/src/jsx.d.ts index 355dd2537d..cc014ce9c9 100644 --- a/src/jsx.d.ts +++ b/src/jsx.d.ts @@ -33,7 +33,10 @@ export namespace JSXInternal { children: any; } - interface CSSProperties extends CSS.Properties { + interface CSSProperties< + TLength = (string & {}) | number, + TTime = string & {} + > extends CSS.Properties { /** * The index signature was removed to enable closed typing for style * using CSSType. You're able to use type assertion or module augmentation From 2d53cfa26b02b064f257f1fc85e23fcb6cd87049 Mon Sep 17 00:00:00 2001 From: Marvin Hagemeister Date: Sat, 14 Nov 2020 09:32:38 +0100 Subject: [PATCH 29/29] TS: Update _original typings --- src/internal.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/internal.d.ts b/src/internal.d.ts index 36d916badf..24c0c122ce 100644 --- a/src/internal.d.ts +++ b/src/internal.d.ts @@ -84,7 +84,7 @@ export interface VNode

    extends preact.VNode

    { _component: Component | null; _hydrating: boolean | null; constructor: undefined; - _original?: VNode | null; + _original: number; } export interface Component

    extends preact.Component {