diff --git a/src/core/interfaces.d.ts b/src/core/interfaces.d.ts index 6c83ee993..d835edd23 100644 --- a/src/core/interfaces.d.ts +++ b/src/core/interfaces.d.ts @@ -455,6 +455,12 @@ export interface DefaultMiddlewareResult extends MiddlewareResult { (): MiddlewareResult; + withType: () => MiddlewareResultFactory< + CustomProps, + Children, + Middleware, + Api + >; } export interface DefaultChildrenWNodeFactory { diff --git a/src/core/middleware/icache.ts b/src/core/middleware/icache.ts index cf5e6e35e..35f8f3318 100644 --- a/src/core/middleware/icache.ts +++ b/src/core/middleware/icache.ts @@ -52,80 +52,79 @@ export interface ICacheResult { clear(invalidate?: boolean): void; } -export function createICacheMiddleware() { - const icache = factory( - ({ middleware: { invalidator, destroy } }): ICacheResult => { - const cacheMap = new Map(); - destroy(() => { - cacheMap.clear(); - }); - - const api: any = { - get: (key: any): any => { - const cachedValue = cacheMap.get(key); - if (!cachedValue || cachedValue.status === 'pending') { - return undefined; - } - return cachedValue.value; - } - }; +const icacheFactory = factory( + ({ middleware: { invalidator, destroy } }): ICacheResult => { + const cacheMap = new Map(); + destroy(() => { + cacheMap.clear(); + }); - api.set = (key: any, value: any, invalidate: boolean = true): any => { - const current = api.get(key); - if (typeof value === 'function') { - value = value(current); - if (value && typeof value.then === 'function') { - cacheMap.set(key, { - status: 'pending', - value - }); - value.then((result: any) => { - const cachedValue = cacheMap.get(key); - if (cachedValue && cachedValue.value === value) { - cacheMap.set(key, { - status: 'resolved', - value: result - }); - invalidate && invalidator(); - } - }); - return undefined; - } - } - cacheMap.set(key, { - status: 'resolved', - value - }); - invalidate && invalidator(); - return value; - }; - api.has = (key: any) => { - return cacheMap.has(key); - }; - api.delete = (key: any, invalidate: boolean = true) => { - cacheMap.delete(key); - invalidate && invalidator(); - }; - api.clear = (invalidate: boolean = true): void => { - cacheMap.clear(); - invalidate && invalidator(); - }; - api.getOrSet = (key: any, value: any, invalidate: boolean = true): any | undefined => { - let cachedValue = cacheMap.get(key); - if (!cachedValue) { - api.set(key, value, invalidate); - } - cachedValue = cacheMap.get(key); + const api: any = { + get: (key: any): any => { + const cachedValue = cacheMap.get(key); if (!cachedValue || cachedValue.status === 'pending') { return undefined; } return cachedValue.value; - }; - return api; - } - ); - return icache; -} + } + }; + + api.set = (key: any, value: any, invalidate: boolean = true): any => { + const current = api.get(key); + if (typeof value === 'function') { + value = value(current); + if (value && typeof value.then === 'function') { + cacheMap.set(key, { + status: 'pending', + value + }); + value.then((result: any) => { + const cachedValue = cacheMap.get(key); + if (cachedValue && cachedValue.value === value) { + cacheMap.set(key, { + status: 'resolved', + value: result + }); + invalidate && invalidator(); + } + }); + return undefined; + } + } + cacheMap.set(key, { + status: 'resolved', + value + }); + invalidate && invalidator(); + return value; + }; + api.has = (key: any) => { + return cacheMap.has(key); + }; + api.delete = (key: any, invalidate: boolean = true) => { + cacheMap.delete(key); + invalidate && invalidator(); + }; + api.clear = (invalidate: boolean = true): void => { + cacheMap.clear(); + invalidate && invalidator(); + }; + api.getOrSet = (key: any, value: any, invalidate: boolean = true): any | undefined => { + let cachedValue = cacheMap.get(key); + if (!cachedValue) { + api.set(key, value, invalidate); + } + cachedValue = cacheMap.get(key); + if (!cachedValue || cachedValue.status === 'pending') { + return undefined; + } + return cachedValue.value; + }; + return api; + } +); + +export const createICacheMiddleware = () => icacheFactory.withType>(); export const icache = createICacheMiddleware(); diff --git a/src/core/middleware/resources.ts b/src/core/middleware/resources.ts index 00127b733..a547cfcaf 100644 --- a/src/core/middleware/resources.ts +++ b/src/core/middleware/resources.ts @@ -941,170 +941,174 @@ function getResource( return resource; } -export function createResourceMiddleware() { - const factory = create({ diffProperty, invalidator, destroy }).properties< - MIDDLEWARE extends void ? {} : ResourceMiddlewareProperties - >(); - return factory( - ({ id: middlewareId, middleware: { diffProperty, invalidator, destroy } }): ResourceMiddleware => { - const optionsMap = new Map>(); - destroy(() => { - const resources = idToResourceMap.get(middlewareId); - if (resources) { - resources.forEach((resource) => { - if (resource.type === 'subscribed') { - resource.resource.unsubscribe(invalidator); - } else { - resource.resource.destroy(middlewareId); - } - }); - } - idToResourceMap.delete(middlewareId); - }); - diffProperty( - 'resource', - (): ResourceMiddlewareProperties => { - return {} as any; - }, - ({ resource: current }, { resource: next }) => { - if (current && next) { - const id = next.template.id || 'global'; - const { - template: { initOptions: currentInitOptions } - } = current; - const { - template: { initOptions: nextInitOptions } - } = next; - if (nextInitOptions) { - const changed = diffInitOptions(currentInitOptions || {}, nextInitOptions); - if (changed) { - const resourceMap = templateToResourceMap.get(next.template.template); - if (resourceMap) { - const resource = resourceMap.get(id); - if (resource) { - resource.init(nextInitOptions); - invalidator(); - } +const factory = create({ diffProperty, invalidator, destroy }); + +const resourceMiddlewareFactory = factory( + ({ id: middlewareId, middleware: { diffProperty, invalidator, destroy } }): ResourceMiddleware => { + const optionsMap = new Map>(); + destroy(() => { + const resources = idToResourceMap.get(middlewareId); + if (resources) { + resources.forEach((resource) => { + if (resource.type === 'subscribed') { + resource.resource.unsubscribe(invalidator); + } else { + resource.resource.destroy(middlewareId); + } + }); + } + idToResourceMap.delete(middlewareId); + }); + diffProperty( + 'resource', + (): ResourceMiddlewareProperties => { + return {} as any; + }, + ({ resource: current }, { resource: next }) => { + if (current && next) { + const id = next.template.id || 'global'; + const { + template: { initOptions: currentInitOptions } + } = current; + const { + template: { initOptions: nextInitOptions } + } = next; + if (nextInitOptions) { + const changed = diffInitOptions(currentInitOptions || {}, nextInitOptions); + if (changed) { + const resourceMap = templateToResourceMap.get(next.template.template); + if (resourceMap) { + const resource = resourceMap.get(id); + if (resource) { + resource.init(nextInitOptions); + invalidator(); } } } - const nextOptions = next.options; - const currOptions = current.options; - if (currOptions && currOptions !== nextOptions) { - const invalidatorSet = optionInvalidatorMap.get(currOptions.options); - if (invalidatorSet) { - invalidatorSet.delete(invalidator); - invalidator(); - } - } } - if (next) { - const nextOptions = next.options; - const currOptions = current && current.options; - if (nextOptions) { - const options: any = (options?: Partial>) => { - const invalidatorSet = optionInvalidatorMap.get(nextOptions.options) || new Set(); - invalidatorSet.add(invalidator); - optionInvalidatorMap.set(nextOptions.options, invalidatorSet); - return nextOptions.options(options); - }; - options.options = nextOptions.options; - if (!currOptions || currOptions.options !== nextOptions.options) { - invalidator(); - } - return { - options, - template: next.template - } as any; + const nextOptions = next.options; + const currOptions = current.options; + if (currOptions && currOptions !== nextOptions) { + const invalidatorSet = optionInvalidatorMap.get(currOptions.options); + if (invalidatorSet) { + invalidatorSet.delete(invalidator); + invalidator(); } } } - ); - - const middleware = function(resource: any) { - if (isTemplate(resource.template)) { - let { template, transform, initOptions, ...rest } = resource; - return { - template: { - template, - transform, - id: initOptions ? `${middlewareId}/${initOptions.id}` : 'global', - initOptions - }, - ...rest - }; - } - return resource; - }; - middleware.createOptions = (key: string): Options => { - const options = optionsMap.get(key); - if (options) { - return options; - } - const optionsWrapper = createOptionsWrapper(); - function setOptions(options?: Partial>) { - const invalidatorSet = optionInvalidatorMap.get(optionsWrapper.options) || new Set(); - invalidatorSet.add(invalidator); - optionInvalidatorMap.set(optionsWrapper.options, invalidatorSet); - return optionsWrapper(options); - } - setOptions.options = optionsWrapper.options; - optionsMap.set(key, setOptions); - return setOptions; - }; - middleware.getOrRead = (template: any, options: any, init?: any) => { - const resource = getResource(template, middlewareId, init); - const transform = !isTemplate(template) && template.transform; - const resourceOptions = transformOptions(options, transform); - resource.subscribeRead(invalidator, options); - const data = resource.getOrRead(resourceOptions); - if (data && transform) { - return data.map((items: any) => { - if (items) { - return items.map((item: any) => transformData(item, transform)); + if (next) { + const nextOptions = next.options; + const currOptions = current && current.options; + if (nextOptions) { + const options: any = (options?: Partial>) => { + const invalidatorSet = optionInvalidatorMap.get(nextOptions.options) || new Set(); + invalidatorSet.add(invalidator); + optionInvalidatorMap.set(nextOptions.options, invalidatorSet); + return nextOptions.options(options); + }; + options.options = nextOptions.options; + if (!currOptions || currOptions.options !== nextOptions.options) { + invalidator(); } - return items; - }); - } - return data; - }; - middleware.find = (template: any, options: any, init?: any) => { - const resource = getResource(template, middlewareId, init); - const transform = !isTemplate(template) && template.transform; - const findOptions = transformOptions(options, transform); - resource.subscribeFind(invalidator, findOptions); - const result = resource.find(findOptions); - if (result && result.item && transform) { - result.item = transformData(result.item, transform); + return { + options, + template: next.template + } as any; + } } - return result; - }; + } + ); + + const middleware = function(resource: any) { + if (isTemplate(resource.template)) { + let { template, transform, initOptions, ...rest } = resource; + return { + template: { + template, + transform, + id: initOptions ? `${middlewareId}/${initOptions.id}` : 'global', + initOptions + }, + ...rest + }; + } + return resource; + }; + middleware.createOptions = (key: string): Options => { + const options = optionsMap.get(key); + if (options) { + return options; + } + const optionsWrapper = createOptionsWrapper(); + function setOptions(options?: Partial>) { + const invalidatorSet = optionInvalidatorMap.get(optionsWrapper.options) || new Set(); + invalidatorSet.add(invalidator); + optionInvalidatorMap.set(optionsWrapper.options, invalidatorSet); + return optionsWrapper(options); + } + setOptions.options = optionsWrapper.options; + optionsMap.set(key, setOptions); + return setOptions; + }; + middleware.getOrRead = (template: any, options: any, init?: any) => { + const resource = getResource(template, middlewareId, init); + const transform = !isTemplate(template) && template.transform; + const resourceOptions = transformOptions(options, transform); + resource.subscribeRead(invalidator, options); + const data = resource.getOrRead(resourceOptions); + if (data && transform) { + return data.map((items: any) => { + if (items) { + return items.map((item: any) => transformData(item, transform)); + } + return items; + }); + } + return data; + }; + middleware.find = (template: any, options: any, init?: any) => { + const resource = getResource(template, middlewareId, init); + const transform = !isTemplate(template) && template.transform; + const findOptions = transformOptions(options, transform); + resource.subscribeFind(invalidator, findOptions); + const result = resource.find(findOptions); + if (result && result.item && transform) { + result.item = transformData(result.item, transform); + } + return result; + }; - middleware.meta = (template: any, options: any, request = false, init?: any) => { - const resource = getResource(template, middlewareId, init); - const transform = !isTemplate(template) && template.transform; - const resourceOptions = transformOptions(options, transform); - resource.subscribeMeta(invalidator, resourceOptions); - if (request) { - resource.subscribeRead(invalidator, resourceOptions); - } - return resource.meta(resourceOptions, request); - }; - middleware.isLoading = (template: any, options: any, init?: any) => { - const resource = getResource(template, middlewareId, init); - const transform = !isTemplate(template) && template.transform; - const resourceOptions = transformOptions(options, transform); - resource.subscribeLoading(invalidator, resourceOptions); - return resource.isLoading(resourceOptions); - }; - middleware.isFailed = (template: any, options: any, init?: any) => { - const resource = getResource(template, middlewareId, init); - const transform = !isTemplate(template) && template.transform; - const resourceOptions = transformOptions(options, transform); - resource.subscribeFailed(invalidator, resourceOptions); - return resource.isFailed(resourceOptions); - }; - return middleware; - } - ); + middleware.meta = (template: any, options: any, request = false, init?: any) => { + const resource = getResource(template, middlewareId, init); + const transform = !isTemplate(template) && template.transform; + const resourceOptions = transformOptions(options, transform); + resource.subscribeMeta(invalidator, resourceOptions); + if (request) { + resource.subscribeRead(invalidator, resourceOptions); + } + return resource.meta(resourceOptions, request); + }; + middleware.isLoading = (template: any, options: any, init?: any) => { + const resource = getResource(template, middlewareId, init); + const transform = !isTemplate(template) && template.transform; + const resourceOptions = transformOptions(options, transform); + resource.subscribeLoading(invalidator, resourceOptions); + return resource.isLoading(resourceOptions); + }; + middleware.isFailed = (template: any, options: any, init?: any) => { + const resource = getResource(template, middlewareId, init); + const transform = !isTemplate(template) && template.transform; + const resourceOptions = transformOptions(options, transform); + resource.subscribeFailed(invalidator, resourceOptions); + return resource.isFailed(resourceOptions); + }; + return middleware; + } +); + +export function createResourceMiddleware() { + return resourceMiddlewareFactory.withType< + ResourceMiddleware, + MIDDLEWARE extends void ? {} : ResourceMiddlewareProperties + >(); } diff --git a/src/core/vdom.ts b/src/core/vdom.ts index a4abd752d..af1f7d38b 100644 --- a/src/core/vdom.ts +++ b/src/core/vdom.ts @@ -705,6 +705,9 @@ function createFactory(callback: any, middlewares: any, key?: any): any { return keys; }, key ? [key] : []); + factory.withType = () => { + return factory; + }; callback.keys = keys; factory.keys = keys; factory.isFactory = true; diff --git a/src/testing/decorate.ts b/src/testing/decorate.ts index a600441a5..a61801686 100644 --- a/src/testing/decorate.ts +++ b/src/testing/decorate.ts @@ -66,7 +66,9 @@ export function decorate(actual: RenderResult, expected: RenderResult, instructi let node = nodes.shift(); while (node) { - const [actualNodes, expectedNodes] = node; + const [actualNodes, expectedNodes] = node.map((nodes) => + nodes.filter((node) => node != null && node !== true && node !== false) + ); let childNodes: DecorateTuple[] = []; while (expectedNodes.length > 0) { let actualNode: DNode | { [index: string]: any } = actualNodes.shift(); diff --git a/src/testing/harness/harness.ts b/src/testing/harness/harness.ts index 43a978689..96f6d97a8 100644 --- a/src/testing/harness/harness.ts +++ b/src/testing/harness/harness.ts @@ -7,7 +7,8 @@ import { VNode, Callback, RenderResult, - MiddlewareResultFactory + MiddlewareResultFactory, + DefaultMiddlewareResult } from '../../core/interfaces'; import { WidgetBase } from '../../core/WidgetBase'; import { isWidgetFunction } from '../../core/Registry'; @@ -56,7 +57,7 @@ export interface HarnessAPI { interface HarnessOptions { customComparator?: CustomComparator[]; - middleware?: [MiddlewareResultFactory, MiddlewareResultFactory][]; + middleware?: [MiddlewareResultFactory, () => DefaultMiddlewareResult][]; } const factory = create(); @@ -74,10 +75,7 @@ export function harness(renderFunc: () => WNode, options: HarnessOptions | Custo let customDiffs: [string, Function][] = []; let customDiffNames: string[] = []; let customComparator: CustomComparator[] = []; - let mockMiddleware: [ - MiddlewareResultFactory, - MiddlewareResultFactory - ][] = []; + let mockMiddleware: [MiddlewareResultFactory, () => DefaultMiddlewareResult][] = []; if (Array.isArray(options)) { customComparator = options; } else { diff --git a/src/testing/renderer.ts b/src/testing/renderer.ts index 5dc91bfa1..9296f2666 100644 --- a/src/testing/renderer.ts +++ b/src/testing/renderer.ts @@ -11,7 +11,8 @@ import { OptionalWNodeFactory, WidgetBaseInterface, DefaultChildrenWNodeFactory, - VNode + VNode, + DefaultMiddlewareResult } from '../core/interfaces'; import { WidgetBase } from '../core/WidgetBase'; import { isWidgetFunction } from '../core/Registry'; @@ -82,7 +83,7 @@ export interface Property { } interface RendererOptions { - middleware?: [MiddlewareResultFactory, MiddlewareResultFactory][]; + middleware?: [MiddlewareResultFactory, () => DefaultMiddlewareResult][]; } export type PropertiesComparatorFunction = (actualProperties: T) => T; @@ -100,7 +101,7 @@ function isWrappedNode(value: any): value is (WNode & { id: string }) | (WNode & function findNode>(renderResult: RenderResult, wrapped: T): VNode | WNode { renderResult = decorateNodes(renderResult).nodes; - let nodes = Array.isArray(renderResult) ? [...renderResult] : [renderResult]; + let nodes: any[] = Array.isArray(renderResult) ? [...renderResult] : [renderResult]; while (nodes.length) { let node = nodes.pop(); if (isWrappedNode(node)) { @@ -111,8 +112,24 @@ function findNode>(renderResult: RenderResult, wrapped: T if (isVNode(node) || isWNode(node)) { const children = node.children || []; nodes = [...children, ...nodes]; + } else if (node && typeof node === 'object') { + nodes = [ + ...Object.keys(node).reduce( + (newNodes, key) => { + if (typeof node[key] === 'function') { + const result = node[key](); + node[key] = result; + return Array.isArray(result) ? [...result, ...newNodes] : [result, ...newNodes]; + } + return newNodes; + }, + [] as any[] + ), + ...nodes + ]; } } + throw new Error('Unable to find node'); } @@ -263,6 +280,9 @@ export function assertion(renderFunc: () => DNode | DNode[]) { const node = findNode(render, wrapped); node.children = node.children || []; let childrenResult = children(); + if (!Array.isArray(childrenResult)) { + childrenResult = [childrenResult]; + } switch (type) { case 'prepend': node.children = [...childrenResult, ...node.children]; diff --git a/tests/testing/unit/assertRender.tsx b/tests/testing/unit/assertRender.tsx index 728e56303..5dcd62668 100644 --- a/tests/testing/unit/assertRender.tsx +++ b/tests/testing/unit/assertRender.tsx @@ -46,8 +46,8 @@ class WidgetWithMap extends WidgetBase { } function getExpectedError() { - const mockWidgetName = (MockWidget as any).name || 'Widget-4'; - const widgetWithChildrenName = (MockWidget as any).name ? 'WidgetWithNamedChildren' : 'Widget-5'; + const mockWidgetName = (MockWidget as any).name || 'Widget-5'; + const widgetWithChildrenName = (MockWidget as any).name ? 'WidgetWithNamedChildren' : 'Widget-6'; return `
{ assert.equal(Array.isArray(children) ? children[0] : children, 'hello'); }); + it('can get a child of a functional child widget', () => { + const AWidget = create().children<{ foo(): RenderResult }>()(({ children }) => children()[0].foo()); + const WrappedDiv = wrap('div'); + const testAssertion = assertion(() => ( +
+ + {{ + foo: () => [child] + }} + +
+ )); + assert.deepEqual(testAssertion.getChildren(WrappedDiv), ['child']); + }); + it('can assert a base assertion', () => { const r = renderer(() => w(MyWidget, {})); r.expect(baseAssertion); @@ -203,6 +218,35 @@ describe('new/assertion', () => { r.expect(baseAssertion.setChildren(WrappedHeader, () => ['replace'])); }); + it('can set a child of a functional child widget', () => { + const AWidget = create().children<{ bar: RenderResult; foo(): RenderResult }>()(({ children }) => ( +
+ {children()[0].foo()} + {children()[0].bar} +
+ )); + const ParentWidget = create()(() => ( +
+ {{ foo: () =>
bar
, bar:
foo
}}
+
+ )); + const WrappedWidget = wrap(AWidget); + const r = renderer(() => ); + r.child(WrappedWidget, { foo: [] }); + const WrappedDiv = wrap('div'); + const testAssertion = assertion(() => ( +
+ + {{ + foo: () => foo, + bar:
foo
+ }} +
+
+ )); + r.expect(testAssertion.setChildren(WrappedDiv, () => ['bar'])); + }); + it('children set should be immutable', () => { const factory = create(); const Widget = factory(function Widget() { diff --git a/tests/testing/unit/mocks/middleware/icache.tsx b/tests/testing/unit/mocks/middleware/icache.tsx index 316b3cf25..c0a4ff590 100644 --- a/tests/testing/unit/mocks/middleware/icache.tsx +++ b/tests/testing/unit/mocks/middleware/icache.tsx @@ -3,8 +3,9 @@ const { describe } = intern.getPlugin('jsdom'); import * as sinon from 'sinon'; import { tsx, create } from '../../../../../src/core/vdom'; import renderer, { assertion } from '../../../../../src/testing/renderer'; +import harness from '../../../../../src/testing/harness/harness'; import createICacheMock from '../../../../../src/testing/mocks/middleware/icache'; -import icache from '../../../../../src/core/middleware/icache'; +import icache, { createICacheMiddleware } from '../../../../../src/core/middleware/icache'; import global from '../../../../../src/shim/global'; describe('icache mock', () => { @@ -27,4 +28,45 @@ describe('icache mock', () => { await iCacheMock('users'); r.expect(assertion(() =>
api data
)); }); + + it('should provide access to async icache loads with harness', async () => { + const iCacheMock = createICacheMock(); + const factory = create({ icache }); + const App = factory(({ middleware: { icache } }) => { + const value = icache.getOrSet('users', async () => { + const response = await fetch('https://reqres.in/api/users'); + return await response.json(); + }); + + return value ?
{value}
:
Loading
; + }); + + global.fetch = sinon.stub().returns(Promise.resolve({ json: () => Promise.resolve('api data') })); + + const h = harness(() => , { middleware: [[icache, iCacheMock]] }); + h.expect(assertion(() =>
Loading
)); + await iCacheMock('users'); + h.expect(assertion(() =>
api data
)); + }); + + it('should provide access to async typed-icache loads', async () => { + const iCacheMock = createICacheMock(); + const icache = createICacheMiddleware<{ users: any }>(); + const factory = create({ icache }); + const App = factory(({ middleware: { icache } }) => { + const value = icache.getOrSet('users', async () => { + const response = await fetch('https://reqres.in/api/users'); + return await response.json(); + }); + + return value ?
{value}
:
Loading
; + }); + + global.fetch = sinon.stub().returns(Promise.resolve({ json: () => Promise.resolve('api data') })); + + const r = renderer(() => , { middleware: [[createICacheMiddleware(), iCacheMock]] }); + r.expect(assertion(() =>
Loading
)); + await iCacheMock('users'); + r.expect(assertion(() =>
api data
)); + }); }); diff --git a/tests/testing/unit/renderer.tsx b/tests/testing/unit/renderer.tsx index 14497a4e2..01fe2cca3 100644 --- a/tests/testing/unit/renderer.tsx +++ b/tests/testing/unit/renderer.tsx @@ -14,6 +14,23 @@ const noop: any = () => {}; class ChildWidget extends WidgetBase<{ id: string; func?: () => void }> {} +const ConditionalRender = create({ icache })(({ middleware: { icache } }) => { + return v('div', {}, [ + icache.get('render') + ? v('div', { + onclick: () => { + icache.set('render', false); + } + }) + : null, + v('div', { + onclick: () => { + icache.set('render', true); + } + }) + ]); +}); + class MyWidget extends WidgetBase { _count = 0; _result = 'result'; @@ -222,6 +239,19 @@ describe('test renderer', () => { ); }); + it('triggers property when there are undefined children in actual render', () => { + const WrappedRoot = wrap('div'); + const WrappedChild = wrap('div'); + const WrappedConditional = wrap('div'); + const baseTemplate = assertion(() => v(WrappedRoot.tag, {}, [v(WrappedChild.tag, { onclick: noop })])); + const r = renderer(() => w(ConditionalRender, {})); + r.expect(baseTemplate); + r.property(WrappedChild, 'onclick'); + r.expect(baseTemplate.prepend(WrappedRoot, () => [v(WrappedConditional.tag, { onclick: noop })])); + r.property(WrappedConditional, 'onclick'); + r.expect(baseTemplate); + }); + it('should call properties in the correct order', () => { const factory = create({ icache }); @@ -728,5 +758,19 @@ describe('test renderer', () => { )) ); }); + + it('Should wrap single children in an array when calling setChildren', () => { + const factory = create().children(); + const TestWidget = factory(() => 'foo'); + const App = create()(() => { + return bar; + }); + const WrappedTestWudget = wrap(TestWidget); + + const testAssertion = assertion(() => bar); + const r = renderer(() => ); + + r.expect(testAssertion.setChildren(WrappedTestWudget, () => 'bar')); + }); }); });