Skip to content
Closed
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
71 changes: 71 additions & 0 deletions packages/builder/spec/builderBehaviors.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { assert } from 'chai'
import { VirtualProvider } from '@tko/provider.virtual'
import { bindings as ifBindings } from '@tko/binding.if'
import { options } from '@tko/utils'
Expand All @@ -22,3 +23,73 @@ describe('Builder', () => {
const builder = new Builder({ filters: {}, provider: new VirtualProvider(), bindings: [ifBindings], options: {} })
})
})

describe('Builder.create()', () => {
let builder: Builder

beforeEach(() => {
// @ts-ignore — global helper from mocha-test-helpers.js
restoreAfter(options, 'filters')
// @ts-ignore — global helper from mocha-test-helpers.js
restoreAfter(options, 'bindingProviderInstance')
builder = new Builder({ filters: {}, provider: new VirtualProvider(), bindings: [ifBindings], options: {} })
})

it('merges additional properties onto the knockout instance', function () {
const extensions = { version: '1.0.0', myProp: 42 }
const ko = builder.create(extensions)
assert.equal(ko.version, '1.0.0')
assert.equal(ko.myProp, 42)
})

it('preserves core KnockoutInstance properties', function () {
const ko = builder.create({})
assert.isFunction(ko.observable)
assert.isFunction(ko.computed)
assert.isFunction(ko.applyBindings)
assert.isFunction(ko.isObservable)
})

it('sets options.knockoutInstance to the returned instance', function () {
const ko = builder.create({})
assert.strictEqual(ko.options.knockoutInstance, ko)
})

it('handles an empty object as additional properties', function () {
const ko = builder.create({})
assert.isFunction(ko.observable)
})

it('handles nested object additional properties', function () {
const jsx = { createElement: () => {}, Fragment: Symbol('Fragment') }
const ko = builder.create({ jsx })
assert.deepEqual(ko.jsx, jsx)
})

it('additional properties do not override core knockout properties', function () {
// observable is a core KO property — if overridden, it should come from additionalProperties
// but the standard behavior is: knockout core < providedProperties < additionalProperties
const customObservable = () => 'custom'
const ko = builder.create({ customField: customObservable })
// Existing core props still present
assert.isFunction(ko.observable)
// Custom field also present
assert.isFunction(ko.customField)
})

it('returns an instance with the getBindingHandler getter', function () {
const ko = builder.create({})
// getBindingHandler should be accessible as a property (delegating to options)
assert.isDefined(ko.getBindingHandler)
})

it('additionalProperties object is treated as single merge (not spread)', function () {
// Previously create() used rest params, now it takes a single typed object.
// Verify a plain object with multiple keys all appear on the result.
const extensions = { alpha: 1, beta: 'two', gamma: true }
const ko = builder.create(extensions)
assert.equal(ko.alpha, 1)
assert.equal(ko.beta, 'two')
assert.equal(ko.gamma, true)
})
})
86 changes: 86 additions & 0 deletions packages/utils.jsx/spec/jsxRenderBehaviors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { assert } from 'chai'

import { render, createElement, Fragment } from '../src'

describe('render()', function () {
it('returns an object with node and dispose properties', function () {
const jsx = createElement('div', null)
const result = render(jsx)

Check failure on line 8 in packages/utils.jsx/spec/jsxRenderBehaviors.ts

View workflow job for this annotation

GitHub Actions / cli-happy-dom

[cli-happy-dom] packages/utils.jsx/spec/jsxRenderBehaviors.ts > render() > returns an object with node and dispose properties

TypeError: render is not a function ❯ packages/utils.jsx/spec/jsxRenderBehaviors.ts:8:20
assert.property(result, 'node')
assert.property(result, 'dispose')
assert.isFunction(result.dispose)
result.dispose()
})

it('returns the first child node when JSX produces exactly one node', function () {
const jsx = createElement('span', { id: 'test-single' })
const result = render(jsx)

Check failure on line 17 in packages/utils.jsx/spec/jsxRenderBehaviors.ts

View workflow job for this annotation

GitHub Actions / cli-happy-dom

[cli-happy-dom] packages/utils.jsx/spec/jsxRenderBehaviors.ts > render() > returns the first child node when JSX produces exactly one node

TypeError: render is not a function ❯ packages/utils.jsx/spec/jsxRenderBehaviors.ts:17:20
assert.isNotNull(result.node)
// A single element renders as a plain child node, not a fragment
assert.notInstanceOf(result.node, DocumentFragment)
assert.equal((result.node as HTMLElement).tagName.toLowerCase(), 'span')
result.dispose()
})

it('returns a DocumentFragment when JSX produces multiple nodes', function () {
// Fragment children expand to multiple nodes
const jsx = createElement(Fragment, null, createElement('div', null), createElement('span', null))
const result = render(jsx)

Check failure on line 28 in packages/utils.jsx/spec/jsxRenderBehaviors.ts

View workflow job for this annotation

GitHub Actions / cli-happy-dom

[cli-happy-dom] packages/utils.jsx/spec/jsxRenderBehaviors.ts > render() > returns a DocumentFragment when JSX produces multiple nodes

TypeError: render is not a function ❯ packages/utils.jsx/spec/jsxRenderBehaviors.ts:28:20
assert.instanceOf(result.node, DocumentFragment)
result.dispose()
})

it('dispose() can be called without throwing', function () {
const jsx = createElement('p', null, 'Hello')
const result = render(jsx)

Check failure on line 35 in packages/utils.jsx/spec/jsxRenderBehaviors.ts

View workflow job for this annotation

GitHub Actions / cli-happy-dom

[cli-happy-dom] packages/utils.jsx/spec/jsxRenderBehaviors.ts > render() > dispose() can be called without throwing

TypeError: render is not a function ❯ packages/utils.jsx/spec/jsxRenderBehaviors.ts:35:20
assert.doesNotThrow(() => result.dispose())
})

it('returns node=null-or-fragment when jsx is null/undefined (empty output)', function () {
// An empty JSX Fragment produces no child nodes → node is the DocumentFragment itself
const jsx = createElement(Fragment, null)
const result = render(jsx)

Check failure on line 42 in packages/utils.jsx/spec/jsxRenderBehaviors.ts

View workflow job for this annotation

GitHub Actions / cli-happy-dom

[cli-happy-dom] packages/utils.jsx/spec/jsxRenderBehaviors.ts > render() > returns node=null-or-fragment when jsx is null/undefined (empty output)

TypeError: render is not a function ❯ packages/utils.jsx/spec/jsxRenderBehaviors.ts:42:20
// Empty fragment: childNodes.length === 0, so node should be the fragment (not firstChild)
assert.instanceOf(result.node, DocumentFragment)
result.dispose()
})

it('renders a text node for a string child', function () {
const jsx = createElement('div', null, 'hello world')
const result = render(jsx)

Check failure on line 50 in packages/utils.jsx/spec/jsxRenderBehaviors.ts

View workflow job for this annotation

GitHub Actions / cli-happy-dom

[cli-happy-dom] packages/utils.jsx/spec/jsxRenderBehaviors.ts > render() > renders a text node for a string child

TypeError: render is not a function ❯ packages/utils.jsx/spec/jsxRenderBehaviors.ts:50:20
// The outer div is a single node
assert.notInstanceOf(result.node, DocumentFragment)
const el = result.node as HTMLElement
assert.equal(el.textContent, 'hello world')
result.dispose()
})

it('renders nested elements correctly', function () {
const jsx = createElement('section', null, createElement('h1', null, 'Title'))
const result = render(jsx)

Check failure on line 60 in packages/utils.jsx/spec/jsxRenderBehaviors.ts

View workflow job for this annotation

GitHub Actions / cli-happy-dom

[cli-happy-dom] packages/utils.jsx/spec/jsxRenderBehaviors.ts > render() > renders nested elements correctly

TypeError: render is not a function ❯ packages/utils.jsx/spec/jsxRenderBehaviors.ts:60:20
assert.notInstanceOf(result.node, DocumentFragment)
const el = result.node as HTMLElement
assert.equal(el.tagName.toLowerCase(), 'section')
assert.equal(el.querySelector('h1')?.textContent, 'Title')
result.dispose()
})

it('multiple render() calls each return independent instances', function () {
const jsx1 = createElement('div', { class: 'first' })
const jsx2 = createElement('div', { class: 'second' })
const result1 = render(jsx1)

Check failure on line 71 in packages/utils.jsx/spec/jsxRenderBehaviors.ts

View workflow job for this annotation

GitHub Actions / cli-happy-dom

[cli-happy-dom] packages/utils.jsx/spec/jsxRenderBehaviors.ts > render() > multiple render() calls each return independent instances

TypeError: render is not a function ❯ packages/utils.jsx/spec/jsxRenderBehaviors.ts:71:21
const result2 = render(jsx2)
assert.notStrictEqual(result1.node, result2.node)
result1.dispose()
result2.dispose()
})

it('dispose() can be called multiple times without throwing', function () {
const jsx = createElement('div', null)
const result = render(jsx)

Check failure on line 80 in packages/utils.jsx/spec/jsxRenderBehaviors.ts

View workflow job for this annotation

GitHub Actions / cli-happy-dom

[cli-happy-dom] packages/utils.jsx/spec/jsxRenderBehaviors.ts > render() > dispose() can be called multiple times without throwing

TypeError: render is not a function ❯ packages/utils.jsx/spec/jsxRenderBehaviors.ts:80:20
assert.doesNotThrow(() => {
result.dispose()
result.dispose()
})
})
})
Loading