Skip to content

Commit

Permalink
Merge branch 'master' into fix-force-flag
Browse files Browse the repository at this point in the history
  • Loading branch information
developit committed Mar 11, 2019
2 parents c7f126f + dc2e810 commit b44c142
Show file tree
Hide file tree
Showing 13 changed files with 202 additions and 10 deletions.
2 changes: 1 addition & 1 deletion debug/mangle.json
Expand Up @@ -31,4 +31,4 @@
"$_processingException": "__p"
}
}
}
}
8 changes: 7 additions & 1 deletion hooks/src/index.js
Expand Up @@ -206,7 +206,13 @@ function scheduleFlushAfterPaint() {
if (typeof window !== 'undefined') {
afterPaint = (component) => {
if (!component._afterPaintQueued && (component._afterPaintQueued = true) && afterPaintEffects.push(component) === 1) {
requestAnimationFrame(scheduleFlushAfterPaint);
/* istanbul ignore next */
if (options.requestAnimationFrame) {
options.requestAnimationFrame(flushAfterPaintEffects);
}
else {
requestAnimationFrame(scheduleFlushAfterPaint);
}
}
};
}
Expand Down
4 changes: 2 additions & 2 deletions hooks/test/browser/useLayoutEffect.test.js
@@ -1,4 +1,4 @@
import { createElement as h, render } from 'preact';
import { createElement as h, render, options } from 'preact';
import { spy } from 'sinon';
import { setupScratch, teardown, setupRerender } from '../../../test/_util/helpers';
import { useEffectAssertions } from './useEffectAssertions.test';
Expand Down Expand Up @@ -61,7 +61,7 @@ describe('useLayoutEffect', () => {
<div>
<Child />
</div>
)
);
}

function Child() {
Expand Down
6 changes: 3 additions & 3 deletions karma.conf.js
Expand Up @@ -100,11 +100,11 @@ module.exports = function(config) {

files: [
{ pattern: 'test/polyfills.js', watched: false },
{ pattern: config.grep || '{debug,hooks,compat,}/test/{browser,shared}/**.test.js', watched: false }
{ pattern: config.grep || '{debug,hooks,compat,test-utils,}/test/{browser,shared}/**.test.js', watched: false }
],

preprocessors: {
'{debug,hooks,compat,}/test/**/*': ['webpack', 'sourcemap']
'{debug,hooks,compat,test-utils,}/test/**/*': ['webpack', 'sourcemap']
},

webpack: {
Expand Down Expand Up @@ -139,7 +139,7 @@ module.exports = function(config) {
'**/__tests__/**',
'**/node_modules/**',
// Our custom extension
'{debug,hooks,compat,}/test/**/*'
'{debug,hooks,compat,test-utils}/test/**/*'
]
}]] : []
}
Expand Down
7 changes: 6 additions & 1 deletion package.json
Expand Up @@ -15,6 +15,7 @@
"build:core": "microbundle build --raw",
"build:debug": "microbundle build --raw --cwd debug",
"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'",
"dev": "microbundle watch --raw --format cjs,umd",
"dev:hooks": "microbundle watch --raw --format cjs --cwd hooks",
Expand All @@ -26,6 +27,7 @@
"test:mocha:watch": "npm run test:mocha -- --watch",
"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",
"test:size": "bundlesize",
Expand Down Expand Up @@ -63,7 +65,10 @@
"debug/package.json",
"hooks/dist",
"hooks/src",
"hooks/package.json"
"hooks/package.json",
"test-utils/src",
"test-utils/package.json",
"test-utils/dist"
],
"keywords": [
"preact",
Expand Down
5 changes: 3 additions & 2 deletions src/diff/index.js
Expand Up @@ -102,6 +102,7 @@ export function diff(dom, parentDom, newVNode, oldVNode, context, isSvg, excessD
else {
if (newType.getDerivedStateFromProps==null && force==null && c.componentWillReceiveProps!=null) {
c.componentWillReceiveProps(newVNode.props, cctx);
s = c._nextState || c.state;
}

if (!force && c.shouldComponentUpdate!=null && c.shouldComponentUpdate(newVNode.props, s, cctx)===false) {
Expand Down Expand Up @@ -216,7 +217,7 @@ function diffElementNodes(dom, newVNode, oldVNode, context, isSvg, excessDomChil
let d = dom;

// Tracks entering and exiting SVG namespace when descending through the tree.
isSvg = isSvg ? newVNode.type !== 'foreignObject' : newVNode.type === 'svg';
isSvg = newVNode.type==='svg' || isSvg;

if (dom==null && excessDomChildren!=null) {
for (let i=0; i<excessDomChildren.length; i++) {
Expand Down Expand Up @@ -260,7 +261,7 @@ function diffElementNodes(dom, newVNode, oldVNode, context, isSvg, excessDomChil
diffProps(dom, newVNode.props, oldProps, isSvg);
}

diffChildren(dom, newVNode, oldVNode, context, isSvg, excessDomChildren, mounts, ancestorComponent);
diffChildren(dom, newVNode, oldVNode, context, newVNode.type==='foreignObject' ? false : isSvg, excessDomChildren, mounts, ancestorComponent);
}

return dom;
Expand Down
3 changes: 3 additions & 0 deletions src/jsx.d.ts
Expand Up @@ -493,7 +493,9 @@ declare namespace JSX {
alt?: string;
async?: boolean;
autocomplete?: string;
autoComplete?: string;
autofocus?: boolean;
autoFocus?: boolean;
autoPlay?: boolean;
capture?: boolean;
cellPadding?: number | string;
Expand Down Expand Up @@ -535,6 +537,7 @@ declare namespace JSX {
href?: string;
hrefLang?: string;
for?: string;
htmlFor?: string;
httpEquiv?: string;
icon?: string;
id?: string;
Expand Down
19 changes: 19 additions & 0 deletions test-utils/package.json
@@ -0,0 +1,19 @@
{
"name": "test-utils",
"amdName": "preactTestUtils",
"version": "0.1.0",
"private": true,
"description": "Test-utils for Preact",
"main": "dist/testUtils.js",
"module": "dist/testUtils.mjs",
"umd:main": "dist/testUtils.umd.js",
"source": "src/index.js",
"license": "MIT",
"types": "src/index.d.ts",
"peerDependencies": {
"preact": "^10.0.0-alpha.0"
},
"mangle": {
"regex": "^_"
}
}
2 changes: 2 additions & 0 deletions test-utils/src/index.d.ts
@@ -0,0 +1,2 @@
export function setupRerender(): void;
export function act(callback: () => void): void;
26 changes: 26 additions & 0 deletions test-utils/src/index.js
@@ -0,0 +1,26 @@
import { Component, options } from 'preact';

/**
* Setup a rerender function that will drain the queue of pending renders
* @returns {() => void}
*/
export function setupRerender() {
Component.__test__previousDebounce = options.debounceRendering;
options.debounceRendering = cb => Component.__test__drainQueue = cb;
return () => Component.__test__drainQueue && Component.__test__drainQueue();
}

export function act(cb) {
const previousDebounce = options.debounceRendering;
const previousRequestAnimationFrame = options.requestAnimationFrame;
const rerender = setupRerender();
let flush;
options.requestAnimationFrame = (fc) => flush = fc;
cb();
if (flush) {
flush();
}
rerender();
options.debounceRendering = previousDebounce;
options.requestAnimationFrame = previousRequestAnimationFrame;
}
77 changes: 77 additions & 0 deletions test-utils/test/shared/act.test.js
@@ -0,0 +1,77 @@
import { options, createElement as h, render } from 'preact';
import sinon from 'sinon';
import { useEffect, useState } from 'preact/hooks';

import { setupScratch, teardown } from '../../../test/_util/helpers';
import { act } from '../../src';

/** @jsx h */
describe('act', () => {

/** @type {HTMLDivElement} */
let scratch;

beforeEach(() => {
scratch = setupScratch();
});

afterEach(() => {
teardown(scratch);
});

it('should reset options after act finishes', () => {
expect(options.requestAnimationFrame).to.equal(undefined);
act(() => null);
expect(options.requestAnimationFrame).to.equal(undefined);
});

it('should drain the queue of hooks', () => {
function StateContainer() {
const [count, setCount] = useState(0);
return (<div>
<p>Count: {count}</p>
<button onClick={() => setCount(c => c + 11)} />
</div>);
}

render(<StateContainer />, scratch);
expect(scratch.textContent).to.include('Count: 0');
act(() => {
const button = scratch.querySelector('button');
button.click();
expect(scratch.textContent).to.include('Count: 0');
});
expect(scratch.textContent).to.include('Count: 1');
});

it('should flush pending effects', () => {
let spy = sinon.spy();
function StateContainer() {
useEffect(spy);
return <div />;
}

act(() => render(<StateContainer />, scratch));
expect(spy).to.be.calledOnce;
});

it('should restore options.requestAnimationFrame', () => {
const spy = sinon.spy();

options.requestAnimationFrame = spy;
act(() => null);

expect(options.requestAnimationFrame).to.equal(spy);
expect(spy).to.not.be.called;
});

it('should restore options.debounceRendering after act', () => {
const spy = sinon.spy();

options.debounceRendering = spy;
act(() => null);

expect(options.debounceRendering).to.equal(spy);
expect(spy).to.not.be.called;
});
});
39 changes: 39 additions & 0 deletions test/browser/lifecycle.test.js
Expand Up @@ -885,6 +885,45 @@ describe('Lifecycle methods', () => {
});

describe('#componentWillReceiveProps', () => {
it('should update state when called setState in componentWillReceiveProps', () => {
let componentState;

class Foo extends Component {
constructor(props) {
super(props);
this.state = {
dummy: 0
};
}
componentDidMount() {
// eslint-disable-next-line react/no-did-mount-set-state
this.setState({ dummy: 1 });
}
render() {
return <Bar dummy={this.state.dummy} />;
}
}
class Bar extends Component {
constructor(props) {
super(props);
this.state = {
value: 0
};
}
componentWillReceiveProps() {
this.setState({ value: 1 });
}
render() {
componentState = this.state;
return <div />;
}
}

render(<Foo />, scratch);
rerender();
expect(componentState).to.deep.equal({ value: 1 });
});

it('should NOT be called on initial render', () => {
class ReceivePropsComponent extends Component {
componentWillReceiveProps() {}
Expand Down
14 changes: 14 additions & 0 deletions test/browser/svg.test.js
Expand Up @@ -118,6 +118,20 @@ describe('svg', () => {
.that.is.a('HTMLAnchorElement');
});

it('should render foreignObject as an svg element', () => {
render((
<svg>
<g>
<foreignObject>
<a href="#foo">test</a>
</foreignObject>
</g>
</svg>
), scratch);

expect(scratch.querySelector('foreignObject').localName).to.equal('foreignObject');
});

it('should transition from DOM to SVG and back', () => {
render((
<div>
Expand Down

0 comments on commit b44c142

Please sign in to comment.