UNPKG

lupine.web

Version:

lupine.web is a extremely fast, small size and lightweight frontend framework, using React TSX syntax.

159 lines (133 loc) 6.74 kB
import { describe, it, mock, beforeEach, afterEach } from 'node:test'; import assert from 'node:assert/strict'; import { renderComponentAsync } from './render-component'; import { domUniqueId } from './render-attribute'; describe('renderComponentAsync', () => { beforeEach(() => { mock.method(Date, 'now', () => 1775190809080); domUniqueId(true); // reset global module state for each test }); afterEach(() => { mock.restoreAll(); }); // Helper to easily extract the rendered HTML string const getHtml = (props: any) => props._html.join(''); describe('Basic HTML Elements Rendering', () => { it('should render standard HTML tags without attributes', async () => { const props = { _html: [] }; await renderComponentAsync('div', props); assert.equal(getHtml(props), '<div></div>'); }); it('should render self-closing tags correctly', async () => { const props = { _html: [], src: 'image.png', alt: 'logo' }; await renderComponentAsync('img', props); assert.equal(getHtml(props), '<img src="image.png" alt="logo" />'); }); }); describe('Attributes Processing', () => { it('should correctly render basic attributes', async () => { const props = { _html: [], id: 'main', 'data-test': 'value', role: 'button' }; await renderComponentAsync('section', props); assert.equal(getHtml(props), '<section id="main" data-test="value" role="button"></section>'); }); it('should convert object style property to inline CSS string', async () => { const props = { _html: [], style: { backgroundColor: 'red', fontSize: '12px' } }; await renderComponentAsync('div', props); assert.equal(getHtml(props), '<div style="background-color:red;font-size:12px;"></div>'); }); it('should process default boolean attributes (disabled, readonly, selected, checked)', async () => { const props = { _html: [], disabled: true, readonly: 'true', checked: false, selected: 'selected' }; await renderComponentAsync('input', props); // 'checked' is false, so it should be omitted entirely. assert.equal(getHtml(props), '<input disabled="true" readonly="true" selected="selected" />'); }); it('should handle defaultChecked converting it to checked="true"', async () => { const props = { _html: [], defaultChecked: true }; await renderComponentAsync('input', props); assert.equal(getHtml(props), '<input checked="true" />'); }); }); describe('Children Content Rendering', () => { it('should successfully render string and number children', async () => { const props = { _html: [], children: ['Balance: ', 100] }; await renderComponentAsync('div', props); assert.equal(getHtml(props), '<div>Balance: 100</div>'); }); it('should gracefully ignore false, null, and undefined in children array', async () => { const props = { _html: [], children: [false, null, undefined, 'visible text'] }; await renderComponentAsync('span', props); assert.equal(getHtml(props), '<span>visible text</span>'); }); it('should recursively render nested component children tree', async () => { const nestedChild = { type: 'strong', props: { _html: [], children: ['bold content'] } }; const props = { _html: [], children: ['A ', nestedChild, ' end.'] }; await renderComponentAsync('p', props); assert.equal(getHtml(props), '<p>A <strong>bold content</strong> end.</p>'); }); }); describe('DangerouslySetInnerHTML Logic', () => { it('should render raw HTML correctly', async () => { const props = { _html: [], dangerouslySetInnerHTML: '<span>raw injected string</span>' }; await renderComponentAsync('div', props); assert.equal(getHtml(props), '<div><span>raw injected string</span></div>'); }); }); describe('Functional Components Resolution', () => { it('should execute synchronous functional components', async () => { const MockComponent = (p: any) => { return { type: 'h1', props: { children: [p.title] } }; }; const props = { _html: [], title: 'Lupine Test' }; await renderComponentAsync(MockComponent as any, props); assert.equal(getHtml(props), '<h1>Lupine Test</h1>'); }); it('should await and execute asynchronous functional components', async () => { const AsyncMockComponent = async (p: any) => { // mock some async logic await new Promise((r) => setTimeout(r, 5)); return { type: 'span', props: { children: ['Data Loaded'] } }; }; const props = { _html: [] }; await renderComponentAsync(AsyncMockComponent as any, props); assert.equal(getHtml(props), '<span>Data Loaded</span>'); }); }); describe('Fragment and Virtual Wrapper Handling', () => { it('should omit wrappers when using string type Fragment', async () => { const props = { _html: [], children: ['fragment inner text'] }; await renderComponentAsync('Fragment', props); assert.equal(getHtml(props), 'fragment inner text'); }); it('should omit wrapper when using named Fragment function', async () => { const FragmentFunc = () => { }; Object.defineProperty(FragmentFunc, 'name', { value: 'Fragment' }); const props = { _html: [], children: ['named function wrapper'] }; await renderComponentAsync(FragmentFunc as any, props); assert.equal(getHtml(props), 'named function wrapper'); }); }); describe('CSS Mapping and Unique ID Implementations', () => { it('should inject global and unique IDs into the class attribute matching ampersand symbol', async () => { const props = { _html: [], class: 'base-cls &-item' }; // Passing defined uniqueClassName and referToCssId await renderComponentAsync('div', props, 'u-id', 'g-id'); // Based on renderAttribute logic, it replaces "&" with both global and unique IDs assert.equal(getHtml(props), '<div class="base-cls g-id-item u-id-item"></div>'); }); it('should automatically prepend unique generated ID to class list when local CSS is present', async () => { // css must be an object, not a plain string, for processStyle to parse it correctly const props = { _html: [], css: { '.&-item': { color: 'block' } } }; await renderComponentAsync('div', props); const output = getHtml(props); // Because Date.now() is mocked and baseTime is lazily initialized, // the generated ID will always predictably be "l00" assert.equal(output, '<div class="l00" l00><style id="sty-l00">.l00 .l00-item{color:block;}</style></div>'); }); }); });