Skip to content

Commit

Permalink
Assertion template enhancements (#412)
Browse files Browse the repository at this point in the history
* add mimic functionality to mimic nodes in a template. add setProperties and getProperties

* fix decorator, add better test

* return expected node if no actual node exists

* rename to isNode

* rename replace to replaceChildren, add replace to replace a single node

* test end of mimic

* add remove

* add a remove node test

* rename Mimic to Ignore, drop fill as it doesn't work on IE

* simplify, fix silly replace logic

* fix replace again, silly variable shadowing :)

* Guard has been removed

* Fix test for IE

* Update readme
  • Loading branch information
agubler authored Jun 25, 2019
1 parent e9b561d commit b2cc67a
Show file tree
Hide file tree
Showing 4 changed files with 168 additions and 10 deletions.
6 changes: 5 additions & 1 deletion src/testing/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -325,9 +325,13 @@ insertAfter(selector: string, children: DNode[]): AssertionTemplateResult;
insertSiblings(selector: string, children: DNode[], type?: 'before' | 'after'): AssertionTemplateResult;
append(selector: string, children: DNode[]): AssertionTemplateResult;
prepend(selector: string, children: DNode[]): AssertionTemplateResult;
replace(selector: string, children: DNode[]): AssertionTemplateResult;
replaceChildren(selector: string, children: DNode[]): AssertionTemplateResult;
setChildren(selector: string, children: DNode[], type?: 'prepend' | 'replace' | 'append'): AssertionTemplateResult;
setProperty(selector: string, property: string, value: any): AssertionTemplateResult;
setProperties(selector: string, value: any | PropertiesComparatorFunction): AssertionTemplateResult;
getChildren(selector: string): DNode[];
getProperty(selector: string, property: string): any;
getProperties(selector: string): any;
replace(selector: string, node: DNode): AssertionTemplateResult;
remove(selector: string): AssertionTemplateResult;
```
48 changes: 46 additions & 2 deletions src/testing/assertionTemplate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,26 @@ import select from './support/selector';
import { VNode, WNode, DNode } from '../core/interfaces';
import { isWNode, isVNode } from '../core/vdom';
import { decorate } from '../core/util';
import WidgetBase from '../core/WidgetBase';

export type PropertiesComparatorFunction = (actualProperties: any) => any;

export interface AssertionTemplateResult {
(): DNode | DNode[];
append(selector: string, children: DNode[]): AssertionTemplateResult;
prepend(selector: string, children: DNode[]): AssertionTemplateResult;
replace(selector: string, children: DNode[]): AssertionTemplateResult;
replaceChildren(selector: string, children: DNode[]): AssertionTemplateResult;
insertBefore(selector: string, children: DNode[]): AssertionTemplateResult;
insertAfter(selector: string, children: DNode[]): AssertionTemplateResult;
insertSiblings(selector: string, children: DNode[], type?: 'before' | 'after'): AssertionTemplateResult;
setChildren(selector: string, children: DNode[], type?: 'prepend' | 'replace' | 'append'): AssertionTemplateResult;
setProperty(selector: string, property: string, value: any): AssertionTemplateResult;
setProperties(selector: string, value: any | PropertiesComparatorFunction): AssertionTemplateResult;
getChildren(selector: string): DNode[];
getProperty(selector: string, property: string): any;
getProperties(selector: string): any;
replace(selector: string, node: DNode): AssertionTemplateResult;
remove(selector: string): AssertionTemplateResult;
}

type NodeWithProperties = (VNode | WNode) & { properties: { [index: string]: any } };
Expand All @@ -38,6 +45,8 @@ const findOne = (nodes: DNode | DNode[], selector: string): NodeWithProperties =
return node;
};

export class Ignore extends WidgetBase {}

export function assertionTemplate(renderFunc: () => DNode | DNode[]) {
const assertionTemplateResult: any = () => {
const render = renderFunc();
Expand All @@ -57,13 +66,21 @@ export function assertionTemplate(renderFunc: () => DNode | DNode[]) {
return render;
});
};
assertionTemplateResult.setProperties = (selector: string, value: any | PropertiesComparatorFunction) => {
return assertionTemplate(() => {
const render = renderFunc();
const node = findOne(render, selector);
node.properties = value;
return render;
});
};
assertionTemplateResult.append = (selector: string, children: DNode[]) => {
return assertionTemplateResult.setChildren(selector, children, 'append');
};
assertionTemplateResult.prepend = (selector: string, children: DNode[]) => {
return assertionTemplateResult.setChildren(selector, children, 'prepend');
};
assertionTemplateResult.replace = (selector: string, children: DNode[]) => {
assertionTemplateResult.replaceChildren = (selector: string, children: DNode[]) => {
return assertionTemplateResult.setChildren(selector, children, 'replace');
};
assertionTemplateResult.setChildren = (
Expand Down Expand Up @@ -124,11 +141,38 @@ export function assertionTemplate(renderFunc: () => DNode | DNode[]) {
const node = findOne(render, selector);
return node.properties[property];
};
assertionTemplateResult.getProperties = (selector: string, property: string) => {
const render = renderFunc();
const node = findOne(render, selector);
return node.properties;
};
assertionTemplateResult.getChildren = (selector: string) => {
const render = renderFunc();
const node = findOne(render, selector);
return node.children || [];
};
assertionTemplateResult.replace = (selector: string, newNode: DNode) => {
return assertionTemplate(() => {
const render = renderFunc();
const node = findOne(render, selector);
const parent = (node as any).parent;
const children = [...parent.children];
children.splice(children.indexOf(node), 1, newNode);
parent.children = children;
return render;
});
};
assertionTemplateResult.remove = (selector: string) => {
return assertionTemplate(() => {
const render = renderFunc();
const node = findOne(render, selector);
const parent = (node as any).parent;
const children = [...parent.children];
children.splice(children.indexOf(node), 1);
parent.children = [...children];
return render;
});
};
return assertionTemplateResult as AssertionTemplateResult;
}

Expand Down
46 changes: 44 additions & 2 deletions src/testing/support/assertRender.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Set from '../../shim/Set';
import Map from '../../shim/Map';
import { from as arrayFrom } from '../../shim/array';
import { isVNode, isWNode } from '../../core/vdom';
import { Ignore } from '../assertionTemplate';

let widgetClassCounter = 0;
const widgetMap = new WeakMap<Constructor<DefaultWidgetBaseInterface>, number>();
Expand Down Expand Up @@ -98,9 +99,50 @@ function formatNode(node: WNode | VNode, tabs: any): string {
return `v("${node.tag}", ${properties}`;
}

function isNode(node: any): node is VNode | WNode {
return isVNode(node) || isWNode(node);
}

function decorate(actual: DNode | DNode[], expected: DNode | DNode[]): [DNode[], DNode[]] {
actual = Array.isArray(actual) ? actual : [actual];
expected = Array.isArray(expected) ? expected : [expected];
let actualDecoratedNodes = [];
let expectedDecoratedNodes = [];
const length = actual.length > expected.length ? actual.length : expected.length;
for (let i = 0; i < length; i++) {
let actualNode = actual[i];
let expectedNode = expected[i];

if (expectedNode && (expectedNode as any).widgetConstructor === Ignore) {
expectedNode = actualNode || expectedNode;
}

if (isNode(expectedNode)) {
if (typeof expectedNode.properties === 'function') {
const actualProperties = isNode(actualNode) ? actualNode.properties : {};
expectedNode.properties = (expectedNode as any).properties(actualProperties);
}
}
const childrenA = isNode(actualNode) ? actualNode.children : [];
const childrenB = isNode(expectedNode) ? expectedNode.children : [];

const [actualChildren, expectedChildren] = decorate(childrenA, childrenB);
if (isNode(actualNode)) {
actualNode.children = actualChildren;
}
if (isNode(expectedNode)) {
expectedNode.children = expectedChildren;
}
actualDecoratedNodes.push(actualNode);
expectedDecoratedNodes.push(expectedNode);
}
return [actualDecoratedNodes, expectedDecoratedNodes];
}

export function assertRender(actual: DNode | DNode[], expected: DNode | DNode[], message?: string): void {
const parsedActual = formatDNodes(actual);
const parsedExpected = formatDNodes(expected);
const [decoratedActual, decoratedExpected] = decorate(actual, expected);
const parsedActual = formatDNodes(Array.isArray(actual) ? decoratedActual : decoratedActual[0]);
const parsedExpected = formatDNodes(Array.isArray(expected) ? decoratedExpected : decoratedExpected[0]);
const diffResult = diff.diffLines(parsedActual, parsedExpected);
let diffFound = false;
const parsedDiff = diffResult.reduce((result: string, part, index) => {
Expand Down
78 changes: 73 additions & 5 deletions tests/testing/unit/assertionTemplate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,27 @@ const { assert } = intern.getPlugin('chai');
import { harness } from '../../../src/testing/harness';
import { WidgetBase } from '../../../src/core/WidgetBase';
import { v, w, tsx } from '../../../src/core/vdom';
import assertionTemplate from '../../../src/testing/assertionTemplate';
import assertionTemplate, { Ignore } from '../../../src/testing/assertionTemplate';

class MyWidget extends WidgetBase<{
toggleProperty?: boolean;
prependChild?: boolean;
appendChild?: boolean;
replaceChild?: boolean;
removeHeader?: boolean;
before?: boolean;
after?: boolean;
}> {
render() {
const { toggleProperty, prependChild, appendChild, replaceChild, before, after } = this.properties;
const {
toggleProperty,
prependChild,
appendChild,
replaceChild,
removeHeader,
before,
after
} = this.properties;
let children = ['hello'];
if (prependChild) {
children = ['prepend', ...children];
Expand All @@ -27,7 +36,7 @@ class MyWidget extends WidgetBase<{
children = ['replace'];
}
return v('div', { classes: ['root'] }, [
v('h2', children),
removeHeader ? undefined : v('h2', children),
before ? v('span', ['before']) : undefined,
v('ul', [v('li', { foo: toggleProperty ? 'b' : 'a' }, ['one']), v('li', ['two']), v('li', ['three'])]),
after ? v('span', ['after']) : undefined
Expand All @@ -38,10 +47,24 @@ class MyWidget extends WidgetBase<{
const baseAssertion = assertionTemplate(() =>
v('div', { '~key': 'root', classes: ['root'] }, [
v('h2', { '~key': 'header' }, ['hello']),
v('ul', [v('li', { '~key': 'li-one', foo: 'a' }, ['one']), v('li', ['two']), v('li', ['three'])])
undefined,
v('ul', [v('li', { '~key': 'li-one', foo: 'a' }, ['one']), v('li', ['two']), v('li', ['three'])]),
undefined
])
);

class ListWidget extends WidgetBase {
render() {
let children = [];
for (let i = 0; i < 30; i++) {
children.push(v('li', [`item: ${i}`]));
}
return v('div', { classes: ['root'] }, [v('ul', children)]);
}
}

const baseListAssertion = assertionTemplate(() => v('div', { classes: ['root'] }, [v('ul', [])]));

const tsxAssertion = assertionTemplate(() => (
<div classes={['root']}>
<h2>hello</h2>
Expand All @@ -61,6 +84,11 @@ describe('assertionTemplate', () => {
assert.deepEqual(classes, ['root']);
});

it('can get properties', () => {
const properties = baseAssertion.getProperties('~root');
assert.deepEqual(properties, { '~key': 'root', classes: ['root'] });
});

it('can get a child', () => {
const children = baseAssertion.getChildren('~header');
assert.equal(children[0], 'hello');
Expand All @@ -77,6 +105,32 @@ describe('assertionTemplate', () => {
h.expect(propertyAssertion);
});

it('can set properties', () => {
const h = harness(() => w(MyWidget, { toggleProperty: true }));
const propertyAssertion = baseAssertion.setProperties('~li-one', { foo: 'b' });
h.expect(propertyAssertion);
});

it('can set properties and use the actual properties', () => {
const h = harness(() => w(MyWidget, { toggleProperty: true }));
const propertyAssertion = baseAssertion.setProperties('~li-one', (actualProps: any) => {
return actualProps;
});
h.expect(propertyAssertion);
});

it('can replace a node', () => {
const h = harness(() => w(MyWidget, {}));
const childAssertion = baseAssertion.replace('~header', v('h2', { '~key': 'header' }, ['hello']));
h.expect(childAssertion);
});

it('can remove a node', () => {
const h = harness(() => w(MyWidget, { removeHeader: true }));
const childAssertion = baseAssertion.remove('~header');
h.expect(childAssertion);
});

it('can set a child', () => {
const h = harness(() => w(MyWidget, { replaceChild: true }));
const childAssertion = baseAssertion.setChildren('~header', ['replace']);
Expand All @@ -85,7 +139,7 @@ describe('assertionTemplate', () => {

it('can set a child with replace', () => {
const h = harness(() => w(MyWidget, { replaceChild: true }));
const childAssertion = baseAssertion.replace('~header', ['replace']);
const childAssertion = baseAssertion.replaceChildren('~header', ['replace']);
h.expect(childAssertion);
});

Expand Down Expand Up @@ -127,6 +181,20 @@ describe('assertionTemplate', () => {
);
});

it('can use ignore', () => {
const h = harness(() => w(ListWidget, {}));
const nodes = [];
for (let i = 0; i < 28; i++) {
nodes.push(w(Ignore, {}));
}
const childListAssertion = baseListAssertion.replaceChildren('ul', [
v('li', ['item: 0']),
...nodes,
v('li', ['item: 29'])
]);
h.expect(childListAssertion);
});

it('should be immutable', () => {
const fooAssertion = baseAssertion
.setChildren(':root', ['foo'])
Expand Down

0 comments on commit b2cc67a

Please sign in to comment.