Skip to content

Commit

Permalink
Merge pull request #9 from HarvestProfit/add-array-support
Browse files Browse the repository at this point in the history
Add array support
  • Loading branch information
humphreyja committed Jun 18, 2019
2 parents 1ff8467 + ce0c8c3 commit 1549b2f
Show file tree
Hide file tree
Showing 13 changed files with 297 additions and 110 deletions.
115 changes: 115 additions & 0 deletions README.md
Expand Up @@ -5,6 +5,121 @@ Flux/React framework for creating any document, just define a few DOM components

See an example of how to generate Spreadsheets https://github.com/humphreyja/sample-doc-flux-spreadsheets

# Examples

### Documents
To start, you must define a document. Think of this as the root. You will define a few document metadata options and specify which component it will render. Below I am using the [DocFlux PDFS](https://github.com/HarvestProfit/DocFlux-PDFs) package to create a pdf that uses the `Table` component specified in the next section. `documentSettings` takes options specified in [PDFMake](https://pdfmake.github.io/docs/document-definition-object/document-medatadata/) document metadata docs.

```js
import PropTypes from 'prop-types';
import { Document } from '@harvest-profit/doc-flux-pdfs';
import Table from './Table';

class TablePDF extends Document {
static propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number.isRequired,
};

static styleSheet() {
return {
td: {
fontSize: 11,
marginTop: 2,
marginBottom: 2,
}
};
}

static documentSettings(props) {
return {
name: `People: ${props.name}`,
pageMargins: [30, 50, 30, 50],
pageOrientation: 'portrait',
};
}

static component = Table;
}

export default TablePDF;
```

### Components
The following is a sample component that will render a table. Notice, the `tname` tag. This is a special tag created from the [DocFlux Spreadsheets](https://github.com/HarvestProfit/DocFlux-Spreadsheets) package. It names the tab to `People` in excel. For PDFs, this will be ignored. **NOTICE:** For this to work, you must either import `doc-flux` as `React` or change the babel parser like the following:

```js
import DocFlux from '@harvest-profit/doc-flux';
/** @jsx DocFlux.createElement */

//... rest of component file
```
It is easier to just specify it as `React`

```js
import PropTypes from 'prop-types';
import React from '@harvest-profit/doc-flux';
import RandomRow from './RandomRow';

const Table = (props) => (
<table>
<tname>People</tname>
<thead>
<th>Name</th>
<th>Age</th>
</thead>
<tbody>
<tr>
<td>{props.name}</td>
<td>{props.age}</td>
</tr>
<RandomRow />
</tbody>
</table>
)

Table.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number.isRequired,
};

export default Table;
```
### Testing

For testing, this uses a similar API to `enzyme`. You can shallow render the component (which only renders the component and not any child components). Then you can actively `find` or get `text` from the rendered component. You can `find` by tag name or component name.

Additionally, you can use `at(index)`, `first()`, or `last()` on any `find` results.

```js
import React, { shallow } from '@harvest-profit/doc-flux';
import Table from './Table';
import RandomRow from './RandomRow';

describe('<Table />', () => {
it('should render', () => {
const wrapper = shallow(
<Table
name="Jake"
age={100}
/>
);
expect(wrapper.find('tr').text()).toContain('Jake');
expect(wrapper.find('tr').first().text()).toContain('Jake');
});

it('should find the RandomRow component', () => {
const wrapper = shallow(
<Table
name="Jake"
age={100}
/>
);
expect(wrapper.find(RandomRow).length).toEqual(1);
});
});
```

## Development
[Clone](https://help.github.com/articles/cloning-a-repository/) this repo, and begin committing changes. PRs are preferred over committing directly to master.

Expand Down
2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "@harvest-profit/doc-flux",
"version": "1.1.0",
"version": "1.1.1",
"description": "Flux/React framework for creating any document, just define a few DOM components to transform into the document.",
"main": "dist/index.js",
"repository": "https://github.com/HarvestProfit/DocFlux",
Expand Down
58 changes: 49 additions & 9 deletions src/DocFluxTest.js
@@ -1,6 +1,9 @@
import DocFlux from './DocFlux';
import Wrapper from './TestWrapper';

// Used to filter out invalid rendered items.
const isValidSubValue = value => (value !== undefined && value !== null);

/**
* Used to test Document components using the shallow command. This will only
* render the imediate component.
Expand All @@ -11,37 +14,74 @@ export default class DocFluxTest extends DocFlux {
return DocFlux.render(component, parser);
}

static shallow(component, parser, nest = true) {
if (!parser) return new Wrapper();
static renderComponentArray(components, renderFunc) {
const cleanedRender = components.filter(isValidSubValue);
const renderSubComponents = cleanedRender.map(comp =>
renderFunc(comp)).filter(isValidSubValue);
return renderSubComponents;
}

static renderComponent(component, renderFunc) {
const ComponentClass = component.node;
const props = DocFlux.parseProps(component.props, ComponentClass.defaultProps);
DocFlux.validateProps(props, ComponentClass.propTypes, ComponentClass.name);

let value;
if (ComponentClass.prototype.render) {
const initializedComponent = new ComponentClass(props);
value = renderFunc(initializedComponent.render());
if (ComponentClass.transform) {
return {
ref: initializedComponent,
value,
elementName: component.stringNodeName,
props,
};
}
} else {
value = renderFunc(ComponentClass(props));
}

return value;
}

/**
* Shallow renders only the imediate component tree
* @static
* @function
* @param {Component} JSXComponent A component to shallow render
* @return {Wrapper} A test wrapper representing the tree
*/
static shallow(component, nest = true) {
const parent = DocFlux;
const self = DocFluxTest;
const shallowFunc = (c, p) => self.shallow(c, p, nest);
const shallowEndNestFunc = (c, p) => self.shallow(c, p, false);
const shallowFunc = c => self.shallow(c, nest);
const shallowEndNestFunc = c => self.shallow(c, false);
if (component === undefined || component === null) return new Wrapper();

if (parent.isDOMNode(component) && parser) {
const tree = shallowFunc(component.props.children, parser);
if (parent.isDOMNode(component)) {
const tree = shallowFunc(component.props.children);
return new Wrapper(component, tree);
}

// Clean arrays and render items.
if (parent.isArray(component)) {
const tree = parent.renderComponentArray(component, parser, shallowFunc);
const tree = self.renderComponentArray(component, shallowFunc);
return tree;
}

// Render components
if (parent.isComponent(component)) {
if (component.stringNodeName) {
let tree = parent.renderComponent(component, parser, shallowFunc);
let tree = self.renderComponent(component, shallowFunc);
if (tree.ref) {
tree = new Wrapper(tree);
}
return new Wrapper(component, tree);
}

if (nest) {
const tree = parent.renderComponent(component, parser, shallowEndNestFunc);
const tree = self.renderComponent(component, shallowEndNestFunc);
return new Wrapper(component, tree);
}
return new Wrapper(component);
Expand Down
32 changes: 32 additions & 0 deletions src/TestArrayWrapper.js
@@ -1,5 +1,10 @@
import { findNodes, flattenText } from './TestHelpers';

/**
* For tests, this will be wrapped around each list of rendered components resulting from a
* call to `find`. This allows for exposing additional test helping code.
* @module Wrapper
*/
export default class ArrayWrapper {
constructor(results = []) {
this.component = { props: {} };
Expand All @@ -12,22 +17,49 @@ export default class ArrayWrapper {
this.length = this.tree.length;
}

/**
* Will search the rendered tree for a tag name or a component name.
* @function
* @param {Component|string} elementOrComponent A String DOM name or Component
* @return {ArrayWrapper} A wrapper containing the results of the search.
*/
find(elementOrComponent) {
return new ArrayWrapper(findNodes(elementOrComponent, this));
}

/**
* Will find all text in the tree and return a flattened version of it.
* @function
* @return {string} All of the text contained in the tree
*/
text() {
return flattenText(this);
}

/**
* Returns the first item in the tree
* @function
* @return {Wrapper|null} A component Test Wrapper, null if none
*/
first() {
return this.at(0);
}

/**
* Returns the last item in the tree
* @function
* @return {Wrapper|null} A component Test Wrapper, null if none
*/
last() {
return this.at(this.length - 1);
}

/**
* Returns the item at the specified index in the tree
* @function
* @param {number} index The index to pull from
* @return {Wrapper|null} A component Test Wrapper, null if not found
*/
at(index) {
if (index >= 0 && index <= this.length - 1) {
return this.tree[index];
Expand Down
10 changes: 7 additions & 3 deletions src/TestHelpers.js
Expand Up @@ -28,17 +28,21 @@ export function findNodes(elementOrComponent, currentNode) {
}

function mergeChildrenText(children) {
return children.reduce((final, child) => (typeof child === 'string' ? child + final : final), '');
return children.reduce((final, child) => {
if (typeof child === 'string') return child + final;
if (typeof child === 'number') return child.toString() + final;
return final;
}, '');
}

export function flattenText(currentNode) {
let result = '';
if (currentNode.component.props.children) {
if (currentNode.component && currentNode.component.props.children) {
const childResult = mergeChildrenText(currentNode.component.props.children);
if (childResult.length > 0) return childResult;
}

for (let i = 0; i < currentNode.tree.length; i += 1) {
for (let i = 0; i < (currentNode.tree || []).length; i += 1) {
const currentChild = currentNode.tree[i];
result += flattenText(currentChild);
}
Expand Down
16 changes: 16 additions & 0 deletions src/TestWrapper.js
@@ -1,6 +1,11 @@
import { findNodes, flattenText } from './TestHelpers';
import ArrayWrapper from './TestArrayWrapper';

/**
* For tests, this will be wrapped around each rendered component. This allows
* for exposing additional test helping code.
* @module Wrapper
*/
export default class Wrapper {
constructor(component = {}, tree = []) {
this.component = component;
Expand All @@ -12,10 +17,21 @@ export default class Wrapper {
}
}

/**
* Will search the rendered tree for a tag name or a component name.
* @function
* @param {Component|string} elementOrComponent A String DOM name or Component
* @return {ArrayWrapper} A wrapper containing the results of the search.
*/
find(elementOrComponent) {
return new ArrayWrapper(findNodes(elementOrComponent, this));
}

/**
* Will find all text in the tree and return a flattened version of it.
* @function
* @return {string} All of the text contained in the tree
*/
text() {
return flattenText(this);
}
Expand Down
6 changes: 6 additions & 0 deletions src/index.js
Expand Up @@ -13,3 +13,9 @@ export {
DocFlux,
DocFluxTest,
};

export function shallow(component) {
return DocFluxTest.shallow(component);
}

export default DocFlux;

0 comments on commit 1549b2f

Please sign in to comment.