Skip to content

Commit

Permalink
Merge branch 'master' into update/eslint-jsdoc
Browse files Browse the repository at this point in the history
  • Loading branch information
ntwb committed Jan 18, 2018
2 parents 596ff36 + f9486f1 commit 48780ab
Show file tree
Hide file tree
Showing 7 changed files with 185 additions and 67 deletions.
11 changes: 0 additions & 11 deletions .eslines.json

This file was deleted.

42 changes: 40 additions & 2 deletions components/higher-order/with-filters/index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
/**
* External dependencies
*/
import { debounce, uniqueId } from 'lodash';

/**
* WordPress dependencies
*/
import { Component, getWrapperDisplayName } from '@wordpress/element';
import { applyFilters } from '@wordpress/hooks';
import { addAction, applyFilters, removeAction } from '@wordpress/hooks';

const ANIMATION_FRAME_PERIOD = 16;

/**
* Creates a higher-order component which adds filtering capability to the
* wrapped component. Filters get applied when the original component is about
* to be mounted.
* to be mounted. When a filter is added or removed that matches the hook name,
* the wrapped component re-renders.
*
* @param {string} hookName Hook name exposed to be used by filters.
*
Expand All @@ -16,11 +24,41 @@ import { applyFilters } from '@wordpress/hooks';
export default function withFilters( hookName ) {
return ( OriginalComponent ) => {
class FilteredComponent extends Component {
/** @inheritdoc */
constructor( props ) {
super( props );

this.onHooksUpdated = this.onHooksUpdated.bind( this );
this.Component = applyFilters( hookName, OriginalComponent );
this.namespace = uniqueId( 'core/with-filters/component-' );
this.throttledForceUpdate = debounce( () => {
this.Component = applyFilters( hookName, OriginalComponent );
this.forceUpdate();
}, ANIMATION_FRAME_PERIOD );

addAction( 'hookRemoved', this.namespace, this.onHooksUpdated );
addAction( 'hookAdded', this.namespace, this.onHooksUpdated );
}

/** @inheritdoc */
componentWillUnmount() {
this.throttledForceUpdate.cancel();
removeAction( 'hookRemoved', this.namespace );
removeAction( 'hookAdded', this.namespace );
}

/**
* When a filter is added or removed for the matching hook name, the wrapped component should re-render.
*
* @param {string} updatedHookName Name of the hook that was updated.
*/
onHooksUpdated( updatedHookName ) {
if ( updatedHookName === hookName ) {
this.throttledForceUpdate();
}
}

/** @inheritdoc */
render() {
return <this.Component { ...this.props } />;
}
Expand Down
139 changes: 133 additions & 6 deletions components/higher-order/with-filters/test/index.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,33 @@
/**
* External dependencies
*/
import { shallow } from 'enzyme';
import { mount, shallow } from 'enzyme';

/**
* WordPress dependencies
*/
import { addFilter, removeAllFilters } from '@wordpress/hooks';
import { addFilter, removeAllFilters, removeFilter } from '@wordpress/hooks';

/**
* Internal dependencies
*/
import withFilters from '../';
import withFilters from '..';

describe( 'withFilters', () => {
let wrapper;

const hookName = 'EnhancedComponent';
const MyComponent = () => <div>My component</div>;

afterEach( () => {
wrapper.unmount();
removeAllFilters( hookName );
} );

it( 'should display original component when no filters applied', () => {
const EnhancedComponent = withFilters( hookName )( MyComponent );
const wrapper = shallow( <EnhancedComponent /> );

wrapper = shallow( <EnhancedComponent /> );

expect( wrapper.html() ).toBe( '<div>My component</div>' );
} );
Expand All @@ -37,7 +41,7 @@ describe( 'withFilters', () => {
);
const EnhancedComponent = withFilters( hookName )( MyComponent );

const wrapper = shallow( <EnhancedComponent /> );
wrapper = shallow( <EnhancedComponent /> );

expect( wrapper.html() ).toBe( '<div>Overridden component</div>' );
} );
Expand All @@ -56,8 +60,131 @@ describe( 'withFilters', () => {
);
const EnhancedComponent = withFilters( hookName )( MyComponent );

const wrapper = shallow( <EnhancedComponent /> );
wrapper = shallow( <EnhancedComponent /> );

expect( wrapper.html() ).toBe( '<div><div>My component</div><div>Composed component</div></div>' );
} );

it( 'should re-render component once when new filter added after component was mounted', () => {
const spy = jest.fn();
const SpiedComponent = () => {
spy();
return <div>Spied component</div>;
};
const EnhancedComponent = withFilters( hookName )( SpiedComponent );

wrapper = mount( <EnhancedComponent /> );

spy.mockClear();
addFilter(
hookName,
'test/enhanced-component-spy-1',
FilteredComponent => () => (
<blockquote>
<FilteredComponent />
</blockquote>
),
);
jest.runAllTimers();

expect( spy ).toHaveBeenCalledTimes( 1 );
expect( wrapper.html() ).toBe( '<blockquote><div>Spied component</div></blockquote>' );
} );

it( 'should re-render component once when two filters added in the same animation frame', () => {
const spy = jest.fn();
const SpiedComponent = () => {
spy();
return <div>Spied component</div>;
};
const EnhancedComponent = withFilters( hookName )( SpiedComponent );
wrapper = mount( <EnhancedComponent /> );

spy.mockClear();

addFilter(
hookName,
'test/enhanced-component-spy-1',
FilteredComponent => () => (
<blockquote>
<FilteredComponent />
</blockquote>
),
);
addFilter(
hookName,
'test/enhanced-component-spy-2',
FilteredComponent => () => (
<section>
<FilteredComponent />
</section>
),
);
jest.runAllTimers();

expect( spy ).toHaveBeenCalledTimes( 1 );
expect( wrapper.html() ).toBe( '<section><blockquote><div>Spied component</div></blockquote></section>' );
} );

it( 'should re-render component twice when new filter added and removed in two different animation frames', () => {
const spy = jest.fn();
const SpiedComponent = () => {
spy();
return <div>Spied component</div>;
};
const EnhancedComponent = withFilters( hookName )( SpiedComponent );
wrapper = mount( <EnhancedComponent /> );

spy.mockClear();
addFilter(
hookName,
'test/enhanced-component-spy',
FilteredComponent => () => (
<div>
<FilteredComponent />
</div>
),
);
jest.runAllTimers();

removeFilter(
hookName,
'test/enhanced-component-spy',
);
jest.runAllTimers();

expect( spy ).toHaveBeenCalledTimes( 2 );
expect( wrapper.html() ).toBe( '<div>Spied component</div>' );
} );

it( 'should re-render both components once each when one filter added', () => {
const spy = jest.fn();
const SpiedComponent = () => {
spy();
return <div>Spied component</div>;
};
const EnhancedComponent = withFilters( hookName )( SpiedComponent );
const CombinedComponents = () => (
<div>
<EnhancedComponent />
<EnhancedComponent />
</div>
);
wrapper = mount( <CombinedComponents /> );

spy.mockClear();
addFilter(
hookName,
'test/enhanced-component-spy-1',
FilteredComponent => () => (
<blockquote>
<FilteredComponent />
</blockquote>
),
);
jest.runAllTimers();

expect( spy ).toHaveBeenCalledTimes( 2 );
expect( wrapper.html() ).toBe( '<div><blockquote><div>Spied component</div></blockquote><blockquote><div>Spied component</div></blockquote></div>' );
} );
} );
17 changes: 10 additions & 7 deletions docs/templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,14 @@ Sometimes the intention might be to lock the template on the UI so that the bloc
It is also possible to assign a template to an existing post type like "posts" and "pages":

```php
$post_type_object = get_post_type_object( 'post' );
$post_type_object->template = array(
array( 'core/paragraph', array(
'placeholder' => 'Add Description...',
) ),
);
$post_type_object->template_lock = 'all';
function my_add_template_to_posts() {
$post_type_object = get_post_type_object( 'post' );
$post_type_object->template = array(
array( 'core/paragraph', array(
'placeholder' => 'Add Description...',
) ),
);
$post_type_object->template_lock = 'all';
}
add_action( 'init', 'my_add_template_to_posts' );
```
2 changes: 1 addition & 1 deletion gutenberg.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
/**
* Plugin Name: Gutenberg
* Plugin URI: https://github.com/WordPress/gutenberg
* Description: Printing since 1440. This is the development plugin for the new block editor in core. <strong>Meant for development, do not run on real sites.</strong>
* Description: Printing since 1440. This is the development plugin for the new block editor in core.
* Version: 2.0.0
* Author: Gutenberg Team
*
Expand Down
38 changes: 0 additions & 38 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@
"deep-freeze": "0.0.1",
"enzyme": "3.2.0",
"enzyme-adapter-react-16": "1.1.0",
"eslines": "1.1.0",
"eslint": "4.9.0",
"eslint-config-wordpress": "2.0.0",
"eslint-plugin-jest": "21.5.0",
Expand Down Expand Up @@ -137,7 +136,7 @@
"prebuild": "check-node-version --package",
"build": "cross-env BABEL_ENV=default NODE_ENV=production webpack",
"gettext-strings": "cross-env BABEL_ENV=gettext webpack",
"lint": "eslint -f json . | eslines",
"lint": "eslint .",
"lint-php": "docker-compose run --rm composer run-script lint",
"predev": "check-node-version --package",
"dev": "cross-env BABEL_ENV=default webpack --watch",
Expand Down

0 comments on commit 48780ab

Please sign in to comment.