From 95fbda06ad6341187cbcb2e1a6b4f2d6e2379a4d Mon Sep 17 00:00:00 2001 From: Troy Alford Date: Sat, 5 Oct 2024 14:35:35 -0700 Subject: [PATCH] clean up a bunch of tests --- source/components/JsxParser.test.tsx | 878 +++++++++------------------ 1 file changed, 298 insertions(+), 580 deletions(-) diff --git a/source/components/JsxParser.test.tsx b/source/components/JsxParser.test.tsx index 1c72ab1..f28f5b8 100644 --- a/source/components/JsxParser.test.tsx +++ b/source/components/JsxParser.test.tsx @@ -14,7 +14,6 @@ function Custom({ children = [], className, text }) { } describe('JsxParser Component', () => { - let parent = null let originalConsoleError = null beforeAll(() => { @@ -28,128 +27,216 @@ describe('JsxParser Component', () => { beforeEach(() => { console.error.mockReset() - parent = document.createElement('div') }) - describe('using ternaries', () => { - test('should handle boolean test value ', () => { - const { instance, node } = render( - (display 1: {true ? 1 : 0}); (display 0: {false ? 1 : 0}) -

`} - />) + describe('conditional operators', () => { + const testCases = [ + // Equality (==) + ['1 == "1"', true], + ['1 == 1', true], + ['0 == false', true], + ['"" == false', true], + ['null == undefined', true], + ['NaN == NaN', false], - expect(node.querySelector('p').textContent?.trim()) - .toEqual('(display 1: 1); (display 0: 0)') + // Strict Equality (===) + ['1 === 1', true], + ['1 === "1"', false], + ['null === undefined', false], + ['NaN === NaN', false], - expect(instance.ParsedChildren[0].props.truthyProp).toBe(1) - expect(instance.ParsedChildren[0].props.falsyProp).toBe(0) - }) + // Inequality (!=) + ['1 != 2', true], + ['1 != "1"', false], + ['null != undefined', false], + ['NaN != NaN', true], - test('should handle evaluative ternaries', () => { - const { node } = render( - - {foo !== 1 ? 'isNotOne' : 'isOne'} - - `} - />, - ) + // Strict Inequality (!==) + ['1 !== "1"', true], + ['1 !== 1', false], + ['null !== undefined', true], + ['NaN !== NaN', true], - expect(node.childNodes[0].classList).toContain('isOne') - expect(node.childNodes[0].textContent.trim()).toEqual('isOne') - }) + // Greater Than (>) + ['2 > 1', true], + ['1 > 1', false], + ['Infinity > 1', true], + ['1 > -Infinity', true], + ['NaN > 1', false], + ['1 > NaN', false], - test('should handle test predicate returned value ', () => { - const { node } = render( - {true && true ? "a" : "b"}

-

{true && false ? "a" : "b"}

-

{true || false ? "a" : "b"}

-

{false || false ? "a" : "b"}

- `} - />, - ) - const [p1, p2, p3, p4] = Array.from(node.querySelectorAll('p')) - expect(p1.textContent).toEqual('a') - expect(p2.textContent).toEqual('b') - expect(p3.textContent).toEqual('a') - expect(p4.textContent).toEqual('b') + // Greater Than or Equal (>=) + ['2 >= 2', true], + ['2 >= 1', true], + ['1 >= 2', false], + ['Infinity >= Infinity', true], + ['NaN >= NaN', false], + + // Less Than (<) + ['1 < 2', true], + ['1 < 1', false], + ['-Infinity < 1', true], + ['1 < Infinity', true], + ['NaN < 1', false], + ['1 < NaN', false], + + // Less Than or Equal (<=) + ['2 <= 2', true], + ['1 <= 2', true], + ['2 <= 1', false], + ['-Infinity <= -Infinity', true], + ['NaN <= NaN', false], + ] + + test.each(testCases)('should evaluate %s = %p correctly', (expression, expected) => { + const { instance } = render(`} />) + expect(instance.ParsedChildren[0].props['data-foo']).toEqual(expected) }) }) - describe('conditional || rendering', () => { - test('should handle boolean test value ', () => { - const { instance, node } = render(' - + '(display "good": {"good" || "fallback"}); (display "fallback": {"" || "fallback"})' - + '

' - } - />) - expect(node.childNodes[0].textContent) - .toEqual('(display "good": good); (display "fallback": fallback)') + describe('mathematical operations', () => { + const testCases = [ + ['1 + 2', '3'], + ['2 - 1', '1'], + ['2 * 3', '6'], + ['6 / 2', '3'], + ['2 ** 4', '16'], + ['27 % 14', '13'], + ['Infinity + 1', 'Infinity'], + ['Infinity - Infinity', 'NaN'], + ['1 / 0', 'Infinity'], + ['0 / 0', 'NaN'], + ] - expect(instance.ParsedChildren[0].props.falsyProp).toBe('fallback') - expect(instance.ParsedChildren[0].props.truthyProp).toBe(true) + test.each(testCases)('should evaluate %s correctly', (operation, expected) => { + const { node } = render() + expect(node.innerHTML).toEqual(expected) }) + }) - test('should handle evaluative', () => { - const { instance, node } = render( - - {foo === 1 || 'trueFallback'}{foo !== 1 || 'falseFallback'} - - `} - />, - ) - expect(instance.ParsedChildren[0].props.truthyProp).toBe(true) - expect(instance.ParsedChildren[0].props.falseyProp).toBe('fallback') - expect(node.childNodes[0].textContent.trim()).toEqual('falseFallback') - }) + describe('unary operations', () => { + const testCases = [ + ['+60', 60], + ['-60', -60], + ['!true', false], + ['!false', true], + ['!0', true], + ['!1', false], + ['!null', true], + ['!undefined', true], + ['!NaN', true], + ['!""', true], + ['!{}', false], + ['![]', false], + ['+true', 1], + ['+false', 0], + ['+null', 0], + ['+undefined', NaN], + ['+""', 0], + ['+"123"', 123], + ['+"-123"', -123], + ] + + test.each(testCases)( + 'should evaluate unary %s correctly', + ({ operation, expected }) => { + const { instance } = render() + if (Number.isNaN(expected)) { + expect(Number.isNaN(instance.ParsedChildren[0])).toBe(true) + } else { + expect(instance.ParsedChildren[0]).toEqual(expected) + } + }, + ) }) - describe('conditional && rendering', () => { - test('should handle boolean test value ', () => { - const { instance, node } = render( - - (display "fallback": {"good" && "fallback"}); (display "": {"" && "fallback"}) -

- `} - />, - ) - expect(node.childNodes[0].textContent.trim()) - .toEqual('(display "fallback": fallback); (display "": )') + describe('ternary expressions', () => { + const testCases = [ + // [expression, expected, bindings (optional), context ('prop' or 'content')] + // Testing in props + ['false ? 1 : 0', 0, {}, 'prop'], + ['true ? 1 : 0', 1, {}, 'prop'], + ['foo === 1 ? "isOne" : "isNotOne"', 'isOne', { foo: 1 }, 'prop'], + // Testing in content + ['{true ? 1 : 0}', '1', {}, 'content'], + ['{false ? 1 : 0}', '0', {}, 'content'], + ['{foo !== 1 ? "isNotOne" : "isOne"}', 'isOne', { foo: 1 }, 'content'], + ['{true && true ? "a" : "b"}', 'a', {}, 'content'], + ['{true && false ? "a" : "b"}', 'b', {}, 'content'], + ['{true || false ? "a" : "b"}', 'a', {}, 'content'], + ['{false || false ? "a" : "b"}', 'b', {}, 'content'], + ] - expect(instance.ParsedChildren[0].props.falsyProp).toBe(false) - expect(instance.ParsedChildren[0].props.truthyProp).toBe('fallback') - }) + test.each(testCases)( + 'should evaluate %s correctly in %s', + (expression, expected, bindings, context) => { + if (context === 'prop') { + const { instance } = render(`} bindings={bindings} />) + expect(instance.ParsedChildren[0].props['data-test']).toEqual(expected) + } else { + const { node } = render(${expression}`} bindings={bindings} />) + expect(node.textContent.trim()).toEqual(expected) + } + }, + ) + }) - test('should handle evaluative', () => { - const { instance, node } = render( - - {foo === 1 && 'trueFallback'}{foo !== 1 && 'falseFallback'} - - `} - />, - ) - expect(instance.ParsedChildren[0].props.truthyProp).toBe('fallback') - expect(instance.ParsedChildren[0].props.falseyProp).toBe(false) - expect(node.childNodes[0].textContent.trim()).toEqual('trueFallback') - }) + describe('logical OR expressions', () => { + const testCases = [ + // [expression, expected, bindings (optional), context ('prop' or 'content')] + ['false || "fallback"', 'fallback', {}, 'prop'], + ['true || "fallback"', true, {}, 'prop'], + ['"good" || "fallback"', 'good', {}, 'content'], + ['"" || "fallback"', 'fallback', {}, 'content'], + // Adjusted expressions for content context to return strings + ['foo === 1 ? "trueResult" : "fallback"', 'trueResult', { foo: 1 }, 'content'], + ['foo !== 1 ? "trueResult" : "fallback"', 'fallback', { foo: 1 }, 'content'], + ] + + test.each(testCases)( + 'should evaluate %s correctly in %s', + (expression, expected, bindings, context) => { + if (context === 'prop') { + const { instance } = render(`} bindings={bindings} />) + expect(instance.ParsedChildren[0].props['data-test']).toEqual(expected) + } else { + const { node } = render({${expression}}`} bindings={bindings} />) + expect(node.textContent.trim()).toEqual(String(expected)) + } + }, + ) }) - describe('basic rendering', () => { - test('renders non-React components', () => { - const { instance, node } = render( + + describe('logical AND expressions', () => { + const testCases = [ + // [expression, expected, bindings (optional), context ('prop' or 'content')] + ['false && "result"', false, {}, 'prop'], + ['true && "result"', 'result', {}, 'prop'], + ['"good" && "result"', 'result', {}, 'content'], + ['"" && "result"', '', {}, 'content'], + // Adjusted expressions for content context to return strings + ['foo === 1 ? "result" : ""', 'result', { foo: 1 }, 'content'], + ['foo !== 1 ? "result" : ""', '', { foo: 1 }, 'content'], + ] + + test.each(testCases)( + 'should evaluate %s correctly in %s', + (expression, expected, bindings, context) => { + if (context === 'prop') { + const { instance } = render(`} bindings={bindings} />) + expect(instance.ParsedChildren[0].props['data-test']).toEqual(expected) + } else { + const { node } = render({${expression}}`} bindings={bindings} />) + expect(node.textContent.trim()).toEqual(String(expected)) + } + }, + ) + }) + + // Rewritten 'basic rendering' suite + describe('Basic Rendering', () => { + test('renders standard HTML elements', () => { + const { node } = render( Header' @@ -159,7 +246,6 @@ describe('JsxParser Component', () => { />, ) - expect(instance.ParsedChildren).toHaveLength(3) expect(node.childNodes).toHaveLength(3) expect(node.childNodes[0].nodeName).toEqual('H1') @@ -173,26 +259,27 @@ describe('JsxParser Component', () => { expect(node.childNodes[2].classList.contains('bar')).toBeTruthy() expect(node.childNodes[2].textContent).toEqual('Bar') }) - test('renders nested components', () => { - const { instance, node } = render( + + test('renders nested elements correctly', () => { + const { node } = render( , ) - expect(instance.ParsedChildren).toHaveLength(1) expect(node.childNodes).toHaveLength(1) const outer = node.childNodes[0] expect(outer.nodeName).toEqual('DIV') expect(outer.childNodes).toHaveLength(2) - const [text, div] = outer.childNodes - expect(text.nodeType).toEqual(Node.TEXT_NODE) // Text + const [text, innerDiv] = outer.childNodes + expect(text.nodeType).toEqual(Node.TEXT_NODE) expect(text.textContent).toEqual('Outer') - expect(div.nodeType).toEqual(Node.ELEMENT_NODE) // Element - expect(div.nodeName).toEqual('DIV') - expect(div.textContent).toEqual('Inner') + expect(innerDiv.nodeType).toEqual(Node.ELEMENT_NODE) + expect(innerDiv.nodeName).toEqual('DIV') + expect(innerDiv.textContent).toEqual('Inner') }) + test('renders custom components', () => { const { instance, node } = render( { ) expect(node.classList.contains('jsx-parser')).toBeTruthy() - - expect(instance.ParsedChildren).toHaveLength(2) expect(node.childNodes).toHaveLength(2) expect(node.childNodes[0].nodeName).toEqual('H1') expect(node.childNodes[0].textContent).toEqual('Header') - const custom = instance.ParsedChildren[1] - expect(custom instanceof Custom) - expect(custom.props.text).toEqual('Test Text') + // Verify that the Custom component is rendered correctly + const customElement = node.childNodes[1] + expect(customElement.nodeName).toEqual('DIV') + expect(customElement.className).toEqual('blah') + expect(customElement.textContent).toEqual('Test Text') - const customHTML = node.childNodes[1] - expect(customHTML.nodeName).toEqual('DIV') - expect(customHTML.textContent).toEqual('Test Text') + // Additionally, check the component props via instance + const customInstance = instance.ParsedChildren[1] + expect(customInstance.props.className).toEqual('blah') + expect(customInstance.props.text).toEqual('Test Text') }) - test('renders custom components with spread operator', () => { + + test('renders custom components with spread attributes', () => { const first = { className: 'blah', text: 'Will Be Overwritten', @@ -239,32 +328,29 @@ describe('JsxParser Component', () => { ) expect(node.classList.contains('jsx-parser')).toBeTruthy() - - expect(instance.ParsedChildren).toHaveLength(1) expect(node.childNodes).toHaveLength(1) - const custom = instance.ParsedChildren[0] - expect(custom instanceof Custom) - expect(custom.props.className).toEqual('blah') - expect(custom.props.text).toEqual('Test Text') - - const customNode = node.childNodes[0] - expect(customNode.nodeName).toEqual('DIV') - expect(customNode.textContent).toEqual('Test Text') - const customHTML = node.childNodes[0].innerHTML - expect(customHTML).not.toMatch(/Will Be Overwritten/) - expect(customHTML).not.toMatch(/Will Not Spread/) + const customElement = node.childNodes[0] + expect(customElement.nodeName).toEqual('DIV') + expect(customElement.className).toEqual('blah') + expect(customElement.textContent).toEqual('Test Text') + + // Check component props + const customInstance = instance.ParsedChildren[0] + expect(customInstance.props.className).toEqual('blah') + expect(customInstance.props.text).toEqual('Test Text') }) + test('renders custom components with nesting', () => { const { instance, node } = render( ' - + '' - + '
Non-Custom
' - + '
' - + '' + + '' + + '
Non-Custom
' + + '
' + + '' } />, ) @@ -291,54 +377,28 @@ describe('JsxParser Component', () => { expect(innerDiv.nodeName).toEqual('DIV') expect(innerDiv.textContent).toEqual('Non-Custom') }) - test('handles unrecognized components', () => { - const { node } = render( - - -
Non-Custom
-
- - `} - />, - ) - const unrecognized = node.querySelectorAll('unrecognized') - expect(unrecognized).toHaveLength(2) - - const [outer, inner] = Array.from(unrecognized) - expect(outer.classList).toContain('outer') - expect(outer.getAttribute('foo')).toEqual('Foo') - expect(inner.classList).toContain('inner') - expect(inner.getAttribute('bar')).toEqual('Bar') - - const div = inner.querySelector('div') - expect(div.textContent).toEqual('Non-Custom') - - expect(console.error).toHaveBeenLastCalledWith( - expect.stringMatching(/is unrecognized in this browser/), - 'unrecognized', - expect.stringMatching(/unrecognized/), - ) - }) + test('handles fragment shorthand syntax (<>)', () => { const { node } = render() expect(node.textContent).toBe('Test Test') }) + test('renders falsy expressions correctly', () => { const { node } = render() expect(node.innerHTML).toBe('0') }) - test('skips over DOCTYPE, html, head, and div if found', () => { + + test('skips over DOCTYPE, html, head, and body if found', () => { const { node } = render( , ) expect(node.childNodes).toHaveLength(2) + expect(node.querySelector('h1').textContent).toEqual('Test') + expect(node.querySelector('p').textContent).toEqual('Another Text') }) + test('renders custom elements without requiring closing tags', () => { - // eslint-disable-next-line react/prefer-stateless-function function CustomContent() { return

Custom Content

} @@ -356,22 +416,7 @@ describe('JsxParser Component', () => { expect(node.querySelectorAll('h1')).toHaveLength(1) expect(node.querySelector('h1').textContent).toEqual('Custom Content') }) - test('renders custom elements without closing tags', () => { - function CustomContent() { return

Ipsum

} - function CuStomContent() { return

Lorem

} - const { node } = render( - , - ) - - expect(node.childNodes).toHaveLength(2) - expect(node.querySelectorAll('h1,h2')).toHaveLength(2) - expect(node.querySelector('h1').textContent).toEqual('Ipsum') - expect(node.querySelector('h2').textContent).toEqual('Lorem') - }) test('renders custom elements with dot notation tags', () => { const Lib = { Custom } const { instance, node } = render( @@ -385,129 +430,37 @@ describe('JsxParser Component', () => { ) expect(node.classList.contains('jsx-parser')).toBeTruthy() - - expect(instance.ParsedChildren).toHaveLength(2) expect(node.childNodes).toHaveLength(2) expect(node.childNodes[0].nodeName).toEqual('H1') expect(node.childNodes[0].textContent).toEqual('Header') - const custom = instance.ParsedChildren[1] - expect(custom instanceof Custom) - expect(custom.props.text).toEqual('Test Text') + const customElement = node.childNodes[1] + expect(customElement.nodeName).toEqual('DIV') + expect(customElement.className).toEqual('blah') + expect(customElement.textContent).toEqual('Test Text') - const customHTML = node.childNodes[1] - expect(customHTML.nodeName).toEqual('DIV') - expect(customHTML.textContent).toEqual('Test Text') + // Check component props + const customInstance = instance.ParsedChildren[1] + expect(customInstance.props.className).toEqual('blah') + expect(customInstance.props.text).toEqual('Test Text') }) - test('renders custom elements with multiple dot notation tags', () => { - const SubLib = { Custom } - const Lib = { SubLib } - const { instance, node } = render( - Header' - + '' - } - />, - ) - expect(instance.ParsedChildren).toHaveLength(2) - expect(node.childNodes).toHaveLength(2) - - expect(node.childNodes[0].nodeName).toEqual('H1') - expect(node.childNodes[0].textContent).toEqual('Header') - - const custom = instance.ParsedChildren[1] - expect(custom instanceof Custom) - expect(custom.props.text).toEqual('Test Text') - - const customHTML = node.childNodes[1] - expect(customHTML.nodeName).toEqual('DIV') - expect(customHTML.textContent).toEqual('Test Text') - }) test('outputs no wrapper element when renderInWrapper prop is false', () => { const { root } = render() expect(root.innerHTML).toEqual('

Foo


') }) - test('omits unknown elements and errors if !allowUnknownElements', () => { - const onError = jest.fn() - const { node } = render( - , - ) - expect(onError).toHaveBeenCalledTimes(2) - expect(onError).toHaveBeenCalledWith( - expect.objectContaining({ message: expect.stringContaining(' is unrecognized') }), - ) - expect(onError).toHaveBeenCalledWith( - expect.objectContaining({ message: expect.stringContaining(' is unrecognized') }), - ) - expect(node.innerHTML).toMatch('
div
') - }) - test('renders errors with renderError prop, if supplied', () => { - const onError = jest.fn() - // eslint-disable-next-line - const renderError = ({ error }) =>
{error}
- const { node } = render( - , - ) - - expect(onError).toHaveBeenCalledTimes(1) - expect(node.querySelectorAll('h2')).toHaveLength(0) - expect(node.querySelectorAll('div')).toHaveLength(1) - expect(node.textContent).toMatch(/SyntaxError: Expected corresponding JSX closing tag for

/) - }) - test('re-rendering should update child elements rather than unmount and remount them', () => { - const updates = jest.fn() - const unmounts = jest.fn() - const props = { - components: { - Custom: class extends React.Component { - componentDidUpdate() { updates() } - componentWillUnmount() { unmounts() } - render() { return 'Custom element!' } - }, - }, - disableKeyGeneration: true, - jsx: '

Hello


', - } - const { update } = render() - update() - - expect(updates).toHaveBeenCalled() - expect(unmounts).not.toHaveBeenCalled() - }) }) - describe('blacklisting & whitelisting', () => { - test('strips ' - + '
After
' - } - />, - ) - expect(instance.ParsedChildren).toHaveLength(2) - expect(node.querySelector('script')).toBeNull() - expect(node.childNodes).toHaveLength(2) - }) - test('strips tags by default', () => { + // Rewritten 'blacklisting & whitelisting' suite + describe('Blacklisting & Whitelisting', () => { + test('strips ' - + '
After
' + + '' + + '
After
' } />, ) @@ -515,14 +468,14 @@ describe('JsxParser Component', () => { expect(instance.ParsedChildren).toHaveLength(2) expect(node.querySelector('script')).toBeNull() expect(node.childNodes).toHaveLength(2) - expect(parent.getElementsByTagName('script')).toHaveLength(0) }) - test('strips onEvent="..." attributes by default', () => { + + test('strips event handler attributes by default', () => { const { instance, node } = render( first' - + '
second
' + + '
second
' } />, ) @@ -534,6 +487,7 @@ describe('JsxParser Component', () => { expect(instance.ParsedChildren[1].props.onChange).toBeUndefined() expect(node.childNodes[1].attributes).toHaveLength(0) }) + test('strips custom blacklisted tags and attributes', () => { const { instance, node } = render( { blacklistedAttrs={['foo', 'prefixed[a-z]*']} jsx={ '
first
' - + 'second' + + 'second' } />, ) @@ -551,12 +505,9 @@ describe('JsxParser Component', () => { expect(instance.ParsedChildren[0].props.foo).toBeUndefined() expect(instance.ParsedChildren[0].props.prefixedFoo).toBeUndefined() expect(instance.ParsedChildren[0].props.prefixedBar).toBeUndefined() - expect(node.childNodes[0].attributes.foo).toBeUndefined() - expect(node.childNodes[0].attributes.prefixedFoo).toBeUndefined() - expect(node.childNodes[0].attributes.prefixedBar).toBeUndefined() }) - test('strips HTML tags if componentsOnly=true', () => { - // eslint-disable-next-line react/prop-types + + test('strips HTML tags if componentsOnly is true', () => { function Simple({ children, text }) { return
{text}{children}
} @@ -565,13 +516,13 @@ describe('JsxParser Component', () => { components={{ Simple }} componentsOnly jsx={` -

Ignored

- - -

Ignored

-
-
- `} +

Ignored

+ + +

Ignored

+
+
+ `} />, ) expect(node.querySelector('h1')).toBeNull() @@ -580,6 +531,7 @@ describe('JsxParser Component', () => { expect(node.textContent.replace(/\s/g, '')).toEqual('ParentChild') }) }) + describe('whitespace', () => { test('allows no-whitespace-element named custom components to take whitespace', () => { // eslint-disable-next-line react/prop-types @@ -832,53 +784,6 @@ describe('JsxParser Component', () => { expect(node.innerHTML) .toMatch('
Before
After
') }) - test('can execute binary mathematical operations', () => { - const { node } = render() - expect(node.childNodes[0].textContent).toEqual('1') - }) - test('can evaluate binary exponent operations', () => { - const { instance } = render() - expect(instance.ParsedChildren[0].props.testProp).toEqual(16) - }) - test('can evaluate binary modulo operations', () => { - const { instance } = render() - expect(instance.ParsedChildren[0].props.testProp).toEqual(13) - }) - test('can execute unary plus operations', () => { - const { node, instance } = render() - expect(node.childNodes[0].textContent).toEqual('75') - expect(instance.ParsedChildren[0].props.testProp).toEqual(60) - }) - test('can execute unary negation operations', () => { - const { node, instance } = render() - expect(node.childNodes[0].textContent).toEqual('-75') - expect(instance.ParsedChildren[0].props.testProp).toEqual(-60) - }) - test('can execute unary NOT operations', () => { - const { node, instance } = render() - expect(node.childNodes[0].textContent).toEqual('Yes') - expect(instance.ParsedChildren[0].props.testProp).toEqual(false) - }) - test('can evaluate > operator', () => { - const { node, instance } = render() - expect(node.childNodes[0].textContent).toEqual('Nope') - expect(instance.ParsedChildren[0].props.testProp).toEqual(false) - }) - test('can evaluate >= operator', () => { - const { node, instance } = render() - expect(node.childNodes[0].textContent).toEqual('Nope') - expect(instance.ParsedChildren[0].props.testProp).toEqual(false) - }) - test('can evaluate < operator', () => { - const { node, instance } = render() - expect(node.childNodes[0].textContent).toEqual('Nope') - expect(instance.ParsedChildren[0].props.testProp).toEqual(true) - }) - test('can evaluate <= operator', () => { - const { node, instance } = render() - expect(node.childNodes[0].textContent).toEqual('Nope') - expect(instance.ParsedChildren[0].props.testProp).toEqual(true) - }) test('will render options', () => { window.foo = jest.fn(() => true) const jsx = '' @@ -1017,46 +922,29 @@ describe('JsxParser Component', () => { }) }) describe('instance methods', () => { - test('literal value instance methods', () => { + test.each([ + ['String_startsWith', '"foobar".startsWith("fo")', true], + ['String_endsWith', '"foobar".endsWith("ar")', true], + ['String_includes', '"foobar".includes("ooba")', true], + ['String_substr', '"foobar".substr(1, 2)', 'oo'], + ['String_replace', '"foobar".replace("oo", "uu")', 'fuubar'], + ['String_search', '"foobar".search("bar")', 3], + ['String_toUpperCase', '"foobar".toUpperCase()', 'FOOBAR'], + ['String_toLowerCase', '"FOOBAR".toLowerCase()', 'foobar'], + ['String_trim', '" foobar ".trim()', 'foobar'], + ['Number_toFixed', '100.12345.toFixed(2)', '100.12'], + ['Number_toPrecision', '123.456.toPrecision(4)', '123.5'], + ['Array_includes', '[1, 2, 3].includes(2)', true], + ['Array_join', '[1, 2, 3].join("+")', '1+2+3'], + ['Array_sort', '[3, 1, 2].sort()', [1, 2, 3]], + ['Array_slice', '[1, 2, 3].slice(1, 2)', [2]], + ])('should evaluate %s correctly', (propName, expression, expected) => { const { instance } = render( - ' - } - />, + `} />, ) - expect(instance.ParsedChildren[0].props.String_startsWith).toEqual(true) - expect(instance.ParsedChildren[0].props.String_endsWith).toEqual(true) - expect(instance.ParsedChildren[0].props.String_includes).toEqual(true) - expect(instance.ParsedChildren[0].props.String_substr).toEqual('oo') - expect(instance.ParsedChildren[0].props.String_replace).toEqual('fuubar') - expect(instance.ParsedChildren[0].props.String_search).toEqual(3) - expect(instance.ParsedChildren[0].props.String_toUpperCase).toEqual('FOOBAR') - expect(instance.ParsedChildren[0].props.String_toLowerCase).toEqual('foobar') - expect(instance.ParsedChildren[0].props.String_trim).toEqual('foobar') - expect(instance.ParsedChildren[0].props.Number_toFixed).toEqual('100.12') - expect(instance.ParsedChildren[0].props.Number_toPrecision).toEqual('123.5') - expect(instance.ParsedChildren[0].props.Array_includes).toEqual(true) - expect(instance.ParsedChildren[0].props.Array_join).toEqual('1+2+3') - expect(instance.ParsedChildren[0].props.Array_sort).toEqual([1, 2, 3]) - expect(instance.ParsedChildren[0].props.Array_slice).toEqual([2]) + expect(instance.ParsedChildren[0].props[propName]).toEqual(expected) }) - test('bound property instance methods', () => { + test('bind properties', () => { const { node } = render( { expect(node.textContent).toEqual('QUUX') }) }) - test('props.renderUnrecognized()', () => { const { node } = render( { }) describe('Functions', () => { - it('supports nested jsx inside arrow functions', () => { - // see - // https://astexplorer.net/#/gist/fc48b12b8410a4ef779e0477a644bb06/cdbfc8b929b31e11e577dceb88e3a1ee9343f68e - // for acorn AST + it('supports nested JSX and expressions inside arrow functions', () => { const { node } = render( , - ) - expect(node.innerHTML).toMatch('

1

2

') - }) - - it('supports JSX expressions inside arrow functions', () => { - const { node } = render( - , - ) - expect(node.innerHTML).toMatch('
Fury

Megeara

') - }) - - it('passes attributes', () => { - function PropTest(props: { booleanAttribute: boolean }) { - return `val:${props.booleanAttribute}` - } - const { node, instance } = render( - , - ) - expect(node.innerHTML).toEqual('

val:true

val:false

') - expect(instance.ParsedChildren?.[0]).toHaveLength(2) - expect(instance.ParsedChildren[0][0].props.children.props.booleanAttribute).toEqual(true) - expect(instance.ParsedChildren[0][1].props.children.props.booleanAttribute).toEqual(false) - }) - - it('passes spread attributes', () => { - function PropTest(props: any) { - return <>{JSON.stringify(props)} - } - const { node } = render( - + {numbers.map(num =>

Number: {num}

)} + {items.map(item =>

{item.name}

)} + + `} />, ) - expect(node.innerHTML).toEqual('{"name":"Megeara","friend":true}') - }) - it('supports render props', () => { - const fakeData = { name: 'from-container' } - const RenderPropContainer = (props: any) => props.children(fakeData) - const { node } = render( - , + expect(node.innerHTML.replace(/[\n\t]+/g, '')).toMatch( + '
' + + '

Number: 1

' + + '

Number: 2

' + + '
Fury

Megeara

' + + '
Anger

Alecto

' + + '
', ) - expect(node.outerHTML).toEqual('

from-container

') }) it('supports math with scope', () => { @@ -1290,125 +1129,4 @@ describe('JsxParser Component', () => { expect(node.innerHTML).toEqual('1-13') }) }) - - describe('conditional operators', () => { - const testCases = [ - // Equality (==) - ['1 == "1"', true], - ['1 == 1', true], - ['0 == false', true], - ['"" == false', true], - ['null == undefined', true], - ['NaN == NaN', false], - - // Strict Equality (===) - ['1 === 1', true], - ['1 === "1"', false], - ['null === undefined', false], - ['NaN === NaN', false], - - // Inequality (!=) - ['1 != 2', true], - ['1 != "1"', false], - ['null != undefined', false], - ['NaN != NaN', true], - - // Strict Inequality (!==) - ['1 !== "1"', true], - ['1 !== 1', false], - ['null !== undefined', true], - ['NaN !== NaN', true], - - // Greater Than (>) - ['2 > 1', true], - ['1 > 1', false], - ['Infinity > 1', true], - ['1 > -Infinity', true], - ['NaN > 1', false], - ['1 > NaN', false], - - // Greater Than or Equal (>=) - ['2 >= 2', true], - ['2 >= 1', true], - ['1 >= 2', false], - ['Infinity >= Infinity', true], - ['NaN >= NaN', false], - - // Less Than (<) - ['1 < 2', true], - ['1 < 1', false], - ['-Infinity < 1', true], - ['1 < Infinity', true], - ['NaN < 1', false], - ['1 < NaN', false], - - // Less Than or Equal (<=) - ['2 <= 2', true], - ['1 <= 2', true], - ['2 <= 1', false], - ['-Infinity <= -Infinity', true], - ['NaN <= NaN', false], - ] - - test.each(testCases)('should evaluate %s = %p correctly', (expression, expected) => { - const { instance } = render(`} />) - expect(instance.ParsedChildren[0].props['data-foo']).toEqual(expected) - }) - }) - - describe('mathematical operations', () => { - const testCases = [ - ['1 + 2', '3'], - ['2 - 1', '1'], - ['2 * 3', '6'], - ['6 / 2', '3'], - ['2 ** 4', '16'], - ['27 % 14', '13'], - ['Infinity + 1', 'Infinity'], - ['Infinity - Infinity', 'NaN'], - ['1 / 0', 'Infinity'], - ['0 / 0', 'NaN'], - ] - - test.each(testCases)('should evaluate %s correctly', (operation, expected) => { - const { node } = render() - expect(node.innerHTML).toEqual(expected) - }) - }) - - describe('unary operations', () => { - const testCases = [ - ['+60', 60], - ['-60', -60], - ['!true', false], - ['!false', true], - ['!0', true], - ['!1', false], - ['!null', true], - ['!undefined', true], - ['!NaN', true], - ['!""', true], - ['!{}', false], - ['![]', false], - ['+true', 1], - ['+false', 0], - ['+null', 0], - ['+undefined', NaN], - ['+""', 0], - ['+"123"', 123], - ['+"-123"', -123], - ] - - test.each(testCases)( - 'should evaluate unary %s correctly', - ({ operation, expected }) => { - const { instance } = render() - if (Number.isNaN(expected)) { - expect(Number.isNaN(instance.ParsedChildren[0])).toBe(true) - } else { - expect(instance.ParsedChildren[0]).toEqual(expected) - } - }, - ) - }) })