Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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