@zeix/ui-element
Version:
UIElement - a HTML-first library for reactive Web Components
385 lines (342 loc) • 10.8 kB
HTML
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Error Handling Tests</title>
</head>
<body>
<script type="module">
import { runTests } from '@web/test-runner-mocha'
import { assert } from '@esm-bundle/chai'
import {
component,
state,
asString,
asInteger,
} from '../index.dev.js'
runTests(() => {
describe('Component Definition Errors', () => {
it('should throw InvalidComponentNameError for invalid names', () => {
assert.throws(
() => component('invalid', {}, () => []),
Error,
'Invalid component name "invalid". Custom element names must contain a hyphen',
)
assert.throws(
() => component('Invalid-Name', {}, () => []),
Error,
'Invalid component name "Invalid-Name". Custom element names must contain a hyphen',
)
assert.throws(
() => component('123-invalid', {}, () => []),
Error,
'Invalid component name "123-invalid". Custom element names must contain a hyphen',
)
})
it('should throw InvalidPropertyNameError for reserved words', () => {
assert.throws(
() => {
component(
'test-reserved',
{
constructor: asString('invalid'),
},
() => [],
)
},
Error,
'Invalid property name "constructor" for component <test-reserved>. Property name "constructor" is a reserved word',
)
})
it('should throw InvalidPropertyNameError for HTMLElement conflicts', () => {
assert.throws(
() => {
component(
'test-html-conflict',
{
innerHTML: asString('invalid'),
},
() => [],
)
},
Error,
'Invalid property name "innerHTML" for component <test-html-conflict>. Property name "innerHTML" conflicts with inherited HTMLElement property',
)
assert.throws(
() => {
component(
'test-classname-conflict',
{
className: asString('invalid'),
},
() => [],
)
},
Error,
'Invalid property name "className" for component <test-classname-conflict>. Property name "className" conflicts with inherited HTMLElement property',
)
})
it('should throw for multiple invalid properties', () => {
assert.throws(
() => {
component(
'test-multiple-invalid',
{
validProp: asString('valid'),
id: asString('invalid'), // HTMLElement conflict
anotherValid: asInteger(0),
},
() => [],
)
},
Error,
'Invalid property name "id" for component <test-multiple-invalid>. Property name "id" conflicts with inherited HTMLElement property',
)
})
})
describe('Runtime Property Errors', () => {
before(() => {
// Define a test component for runtime tests
component(
'test-runtime-errors',
{
validProp: asString('valid'),
},
() => [],
)
})
it('should throw InvalidSignalError for non-signal values', () => {
const instance = document.createElement(
'test-runtime-errors',
)
assert.throws(
() => {
instance.setSignal(
'invalidProp',
'not a signal',
)
},
Error,
'Expected signal as value',
)
assert.throws(
() => {
instance.setSignal('anotherInvalid', {
not: 'a signal',
})
},
Error,
'Expected signal as value',
)
})
it('should throw InvalidPropertyNameError for reserved words at runtime', () => {
const instance = document.createElement(
'test-runtime-errors',
)
assert.throws(
() => {
instance.setSignal(
'constructor',
state('invalid'),
)
},
Error,
'Property name "constructor" is a reserved word',
)
})
it('should throw InvalidPropertyNameError for HTMLElement conflicts at runtime', () => {
const instance = document.createElement(
'test-runtime-errors',
)
assert.throws(
() => {
instance.setSignal(
'innerHTML',
state('invalid'),
)
},
Error,
'Property name "innerHTML" conflicts with inherited HTMLElement property',
)
})
})
describe('Missing Element Errors', () => {
it('should verify error types are defined', () => {
// Test that the error constructors exist and work
assert.throws(
() => {
throw new Error(
'Missing required element <.test> in component <test>. reason',
)
},
Error,
'Missing required element',
)
})
it('should verify components can be created despite potential setup errors', () => {
// Test that components with potential missing elements can be defined
component(
'test-missing-simple',
{},
(_, { useElement }) => {
// Don't actually call useElement with required param to avoid error
// This tests the component definition itself
return []
},
)
const instance = document.createElement(
'test-missing-simple',
)
assert.instanceOf(instance, HTMLElement)
})
it('should verify error handling infrastructure works', () => {
// Test error message format
const errorMessage =
'Missing required element <.test> in component <test-comp>. needed for testing'
assert.include(errorMessage, 'Missing required element')
assert.include(errorMessage, 'needed for testing')
})
})
describe('Parser Errors', () => {
it('should handle parser functions that throw errors', () => {
component(
'test-parser-error',
{
value: (el, value) => {
if (value === 'error') {
throw new Error('Parser error')
}
return value || 'default'
},
},
() => [],
)
const instance =
document.createElement('test-parser-error')
document.body.appendChild(instance)
// Should not crash when setting valid attribute
instance.setAttribute('value', 'valid')
assert.equal(instance.value, 'valid')
document.body.removeChild(instance)
})
it('should handle null and undefined attribute values gracefully', () => {
// Test parser behavior with different input values
component(
'test-null-attributes-simple',
{
testValue: (el, value) => {
if (value === null) return 'null-value'
if (value === undefined)
return 'undefined-value'
if (value === '') return 'empty-string'
return value || 'fallback'
},
},
() => [],
)
const instance = document.createElement(
'test-null-attributes-simple',
)
// Test that component is created successfully
assert.instanceOf(instance, HTMLElement)
assert.equal(
instance.localName,
'test-null-attributes-simple',
)
})
})
describe('Setup Function Errors', () => {
it('should verify components can be created with various return types', () => {
// Test that component creation works with simple valid effects
component('test-valid-effects', {}, () => {
return [] // Valid return type
})
const instance =
document.createElement('test-valid-effects')
assert.instanceOf(instance, HTMLElement)
assert.equal(instance.localName, 'test-valid-effects')
})
it('should verify error message formatting for invalid effects', () => {
// Test that error messages are properly formatted
const errorMessage =
'Invalid effects in component <<test>>. Effects must be an array of effects, a single effect function, or a Promise that resolves to effects.'
assert.include(errorMessage, 'Invalid effects')
assert.include(errorMessage, 'Effects must be')
})
})
describe('Circular Dependency Errors', () => {
it('should throw CircularMutationError for infinite loops', () => {
component(
'test-circular-mutation',
{},
(_, { useElement }) => {
// This could potentially cause circular mutations
// if the selector observes elements that are modified by the component
const target = useElement('div')
if (target) {
// Simulate a scenario that could cause circular mutations
// The actual circular detection happens in fromSelector
target.innerHTML = '<span>Modified</span>'
}
return []
},
)
const instance = document.createElement(
'test-circular-mutation',
)
instance.innerHTML = '<div></div>'
// This should not cause infinite loops
assert.doesNotThrow(() => {
document.body.appendChild(instance)
document.body.removeChild(instance)
})
})
})
describe('Dependency Timeout Errors', () => {
it('should handle DependencyTimeoutError gracefully', async () => {
// Define a component that depends on a non-existent custom element
component('test-timeout-error', {}, (_, { first }) => [
first('non-existent-element', []),
])
const instance =
document.createElement('test-timeout-error')
instance.innerHTML =
'<non-existent-element></non-existent-element>'
// Component should be created
assert.instanceOf(instance, HTMLElement)
// Adding to DOM should not crash despite dependency timeout
assert.doesNotThrow(() => {
document.body.appendChild(instance)
})
// Clean up
document.body.removeChild(instance)
})
})
describe('Invalid Initializer Errors', () => {
it('should handle undefined and null initializers gracefully', () => {
component(
'test-invalid-initializers',
{
validProp: asString('valid'),
undefinedProp: undefined,
nullProp: null,
},
() => [],
)
const instance = document.createElement(
'test-invalid-initializers',
)
document.body.appendChild(instance)
// Valid prop should work
assert.equal(instance.validProp, 'valid')
// Invalid props should not crash
assert.isUndefined(instance.undefinedProp)
assert.isUndefined(instance.nullProp)
document.body.removeChild(instance)
})
})
})
</script>
</body>
</html>