Skip to content
Open
35 changes: 35 additions & 0 deletions packages/binding.core/spec/submitBehaviors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,39 @@ describe('Binding: Submit', function () {
expect(model.wasCalled).to.equal(true)
expect(firstParamStored).to.equal(formNode)
})

it('Should throw when the bound value is not a function', function () {
testNode.innerHTML = '<form data-bind=\'submit: "not a function"\' />'
expect(() => applyBindings({}, testNode)).to.throw(/value for a submit binding must be a function/)
})

it('Should not prevent the default form submission when the handler returns true', function () {
testNode.innerHTML = "<form data-bind='submit: doCall' />"
const handler = function () {
return true
}
applyBindings({ doCall: handler }, testNode)
const formNode = testNode.children[0] as HTMLFormElement
let defaultPrevented: boolean | undefined
formNode.addEventListener('submit', function (event) {
defaultPrevented = event.defaultPrevented
// stop the actual form navigation in case preventDefault was skipped
event.preventDefault()
})
triggerEvent(formNode, 'submit')
expect(defaultPrevented).to.equal(false)
})

it('Should prevent the default form submission when the handler returns a non-true value', function () {
testNode.innerHTML = "<form data-bind='submit: doCall' />"
applyBindings({ doCall: function () {} }, testNode)
const formNode = testNode.children[0] as HTMLFormElement
let defaultPrevented: boolean | undefined
formNode.addEventListener('submit', function (event) {
defaultPrevented = event.defaultPrevented
event.preventDefault()
})
triggerEvent(formNode, 'submit')
expect(defaultPrevented).to.equal(true)
})
})
21 changes: 21 additions & 0 deletions packages/observable/spec/subscribableBehaviors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,27 @@ describe('Subscribable', function () {
expect(notifiedValue).to.equal(undefined)
})

it('Should expose a TC39 Observable-style `unsubscribe()` alias for dispose', function () {
const instance = new subscribable()
let notified = 0
const subscription = instance.subscribe(function () {
notified++
})
expect(subscription.closed).to.equal(false)
subscription.unsubscribe()
expect(subscription.closed).to.equal(true)
instance.notifySubscribers('value')
expect(notified).to.equal(0)
})

it('Should report `closed` as true once disposed', function () {
const instance = new subscribable()
const subscription = instance.subscribe(function () {})
expect(subscription.closed).to.equal(false)
subscription.dispose()
expect(subscription.closed).to.equal(true)
})

it("Should be able to specify a 'this' pointer for the callback", function () {
const model = {
someProperty: 123,
Expand Down
72 changes: 72 additions & 0 deletions packages/provider/spec/BindingHandlerObjectBehaviors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { expect } from 'chai'
import sinon from 'sinon'

import { options } from '@tko/utils'

import { BindingHandlerObject } from '../src'

describe('BindingHandlerObject', function () {
let originalOnError: typeof options.onError

beforeEach(function () {
originalOnError = options.onError
})

afterEach(function () {
options.onError = originalOnError
})

it('registers a single handler when set() is called with (name, value)', function () {
const handlers = new BindingHandlerObject()
const handler = { init: () => {} }
handlers.set('myHandler', handler)
expect((handlers as any).myHandler).to.equal(handler)
expect(handlers.get('myHandler')).to.equal(handler)
})

it('registers multiple handlers when set() is called with an object', function () {
const handlers = new BindingHandlerObject()
const a = { init: () => {} }
const b = { update: () => {} }
handlers.set({ a, b })
expect(handlers.get('a')).to.equal(a)
expect(handlers.get('b')).to.equal(b)
})

it('reports onError when set() is called with both an object and a value', function () {
const handlers = new BindingHandlerObject()
const onError = sinon.stub()
options.onError = onError
handlers.set({ a: () => {} }, 'extraneous')
expect(onError.calledOnce).to.equal(true)
expect(onError.firstCall.args[0]).to.be.instanceOf(Error)
expect(onError.firstCall.args[0].message).to.match(/extraneous `value` parameter/)
})

it('reports onError when set() is called with a non-string, non-object key', function () {
const handlers = new BindingHandlerObject()
const onError = sinon.stub()
options.onError = onError
handlers.set(undefined as unknown as string)
expect(onError.calledOnce).to.equal(true)
expect(onError.firstCall.args[0].message).to.match(/bad binding handler type/)

onError.resetHistory()
handlers.set(42 as unknown as string)
expect(onError.calledOnce).to.equal(true)
expect(onError.firstCall.args[0].message).to.match(/bad binding handler type/)
})

it('get() resolves dotted handler names to the root segment', function () {
const handlers = new BindingHandlerObject()
const attr = { init: () => {} }
handlers.set('attr', attr)
expect(handlers.get('attr.title')).to.equal(attr)
expect(handlers.get('attr.style.color')).to.equal(attr)
})

it('get() returns undefined for an unknown handler', function () {
const handlers = new BindingHandlerObject()
expect(handlers.get('nope')).to.equal(undefined)
})
})
48 changes: 48 additions & 0 deletions packages/utils/spec/stringBehaviors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { expect } from 'chai'

import { parseJson } from '../dist'

describe('parseJson', function () {
it('parses a valid JSON string into an object', function () {
expect(parseJson('{"a":1,"b":"two"}')).to.deep.equal({ a: 1, b: 'two' })
})

it('parses primitives and arrays', function () {
expect(parseJson('42')).to.equal(42)
expect(parseJson('"hello"')).to.equal('hello')
expect(parseJson('true')).to.equal(true)
expect(parseJson('null')).to.equal(null)
expect(parseJson('[1,2,3]')).to.deep.equal([1, 2, 3])
})

it('trims surrounding whitespace before parsing', function () {
expect(parseJson(' {"x":10} ')).to.deep.equal({ x: 10 })
})

it('returns null for an empty string', function () {
expect(parseJson('')).to.equal(null)
})

it('returns null for a whitespace-only string', function () {
expect(parseJson(' \n\t ')).to.equal(null)
})

it('returns null when the input is not a string', function () {
// Inputs whose runtime type is not 'string' must short-circuit to null,
// not be coerced or thrown on. The signature is typed `string` but the
// function is callable from data-bind expressions where the runtime
// value can be anything.
expect(parseJson(undefined as unknown as string)).to.equal(null)
expect(parseJson(null as unknown as string)).to.equal(null)
expect(parseJson(42 as unknown as string)).to.equal(null)
expect(parseJson({} as unknown as string)).to.equal(null)
})

it('throws when the input is a non-empty malformed JSON string', function () {
// parseJson does not swallow JSON.parse errors — callers that want to
// tolerate bad input must wrap the call themselves. Pinning this
// behavior so a future "be helpful and return null" change is caught.
expect(() => parseJson('{not json')).to.throw()
expect(() => parseJson('undefined')).to.throw()
})
})
Loading
Loading