als-component
Version:
lightweight JavaScript library for creating reactive, server-rendered, and browser-based components.
259 lines (210 loc) • 9.46 kB
JavaScript
describe("Component Class Tests", () => {
// Тест на создание экземпляра
it("should create an instance of Component", () => {
class TestComponent extends Component {
render() {
return `<div>Hello World</div>`;
}
}
const instance = new TestComponent();
assert(instance instanceof Component, "Instance is not of type Component");
});
// Тест на асинхронность метода render
it("should correctly detect if render method is async", () => {
class SyncComponent extends Component {
render() {
return `<div>Sync Render</div>`;
}
}
class AsyncComponent extends Component {
async render() {
return `<div>Async Render</div>`;
}
}
assert.strictEqual(SyncComponent.isAsync, false, "SyncComponent should not be async");
assert.strictEqual(AsyncComponent.isAsync, true, "AsyncComponent should be async");
});
// Тест на метод call()
it("should call the render method and return HTML", () => {
class SimpleComponent extends Component {
constructor() {super()}
render() {
return `<div>Simple Render</div>`;
}
}
const instance = new SimpleComponent();
const html = instance.call();
assert.strictEqual(html, `<div component="SimpleComponent">Simple Render</div>`, "call() did not return expected HTML");
});
// Тест на метод action()
it("should add an action and return an ID", () => {
class ButtonComponent extends Component {
render() {
return `<button click="${this.action('click', () => this.update())}">Click Me</button>`;
}
}
const instance = new ButtonComponent();
const actionId = instance.action('click', () => { });
assert(actionId.startsWith("ButtonComponent"), "Action ID does not start with component name");
});
// Тест на обновление компонента
it("should update the component with new props", () => {
class CounterComponent extends Component {
render({ count = 0 }) {
return `<div>${count}</div>`;
}
}
document.body.insertAdjacentHTML('beforeend',/*html*/`<div component="CounterComponent"></div>`)
const instance = new CounterComponent({ count: 1 });
instance.update({ count: 2 });
const element = document.querySelector(instance.selector);
assert.strictEqual(element.textContent, "2", "Component did not update with new props");
element.remove()
});
// Тест на выполнение хуков mount и unmount
it("should trigger mount and unmount hooks", () => {
let mountTriggered = false;
let unmountTriggered = false;
document.body.insertAdjacentHTML('beforeend',/*html*/`<div component="HookComponent"></div>`)
class HookComponent extends Component {
render() {
return `<div>Hook Component</div>`;
}
}
const instance = new HookComponent();
instance.on("mount", () => (mountTriggered = true));
instance.on("unmount", () => (unmountTriggered = true));
instance.update();
assert(mountTriggered, "Mount hook was not triggered");
// Удаляем элемент из DOM для проверки unmount
document.querySelector(instance.selector).remove();
instance.runActions();
assert(unmountTriggered, "Unmount hook was not triggered");
});
// Тест на работу события load
it("should handle load event and focus input", async () => {
document.body.insertAdjacentHTML('beforeend',/*html*/`<input type="text" component="InputComponent" tabindex="0">`)
class InputComponent extends Component {
constructor() { super() }
render() {
return `<input type="text" load="${this.action('load', (element) => element.focus())}">`;
}
}
const instance = new InputComponent();
await instance.update();
const input = document.querySelector("input");
assert(document.activeElement === input, "Input element did not receive focus on load");
input.remove()
});
// Тест на удаление старых обработчиков событий
it("should remove old event listeners after update", () => {
let clickCount = 0;
document.body.insertAdjacentHTML('beforeend',/*html*/`<button component="ClickComponent"></button>`)
class ClickComponent extends Component {
constructor(props) { super(props) }
render({ count = 0 }) {
return `<button click="${this.action('click', () => { clickCount++ })}">Click Me ${count}</button>`;
}
}
const instance = new ClickComponent({ count: 1 });
instance.update({ count: 2 });
const button = document.querySelector("button");
button.click();
button.click();
// console.log(clickCount)
assert.strictEqual(clickCount, 2, "Old event listeners were not removed");
button.remove()
});
});
describe('Additional tests', () => {
it("should use the inner content correctly", () => {
class InnerComponent extends Component {
render(props, inner) {
return `<div>${inner}</div>`;
}
}
const instance = new InnerComponent({}, "Inner Content");
const html = instance.call();
assert.strictEqual(html, `<div component="InnerComponent">Inner Content</div>`, "Inner content was not rendered correctly");
});
it("should update the component multiple times with the same props", () => {
let updateCount = 0;
document.body.insertAdjacentHTML('beforeend',/*html*/`<div component="RepeatUpdateComponent"></div>`)
class RepeatUpdateComponent extends Component {
render({ count = 0 }) {
updateCount++;
return `<div>${count}</div>`;
}
}
const instance = new RepeatUpdateComponent({ count: 1 });
instance.update();
instance.update();
assert.strictEqual(updateCount, 2, "Component was not updated twice");
instance.element.remove()
});
it("should handle async render method in update", async () => {
document.body.insertAdjacentHTML('beforeend',/*html*/`<div component="AsyncRenderComponent"></div>`)
class AsyncRenderComponent extends Component {
constructor(props) {super(props)}
async render() {
return new Promise((resolve) => setTimeout(() => resolve(`<div>Async Render</div>`), 100));
}
}
const instance = new AsyncRenderComponent();
await instance.update();
const element = document.querySelector(instance.selector);
assert.strictEqual(element.textContent, "Async Render", "Async render method did not work correctly");
element.remove();
});
it("should not throw error if element is not found", () => {
class MissingElementComponent extends Component {
render() {
return `<div>Missing Element</div>`;
}
}
const instance = new MissingElementComponent();
try {
instance.update()
assert(true);
} catch (error) {
assert(false, "Update method threw an error when element was not found");
}
});
it("should handle multiple uses of the same action without duplicating handlers", () => {
let clickCount = 0;
document.body.insertAdjacentHTML('beforeend',/*html*/`<div component="MultiActionComponent"></div>`)
class MultiActionComponent extends Component {
render() {
const clickHandler = this.action('click', () => clickCount++);
return `<div>
<button click="${clickHandler}">Click 1</button>
<button click="${clickHandler}">Click 2</button>
</div>`;
}
}
const instance = new MultiActionComponent();
instance.update();
const buttons = document.querySelectorAll("button");
buttons[0].click();
buttons[1].click();
assert.strictEqual(clickCount, 2, "Click handler was not called correctly for multiple uses");
instance.element.remove()
});
it("should remove component from WeakMap after unmount", () => {
document.body.insertAdjacentHTML('beforeend',/*html*/`<div component="CleanupComponent"></div>`)
class CleanupComponent extends Component {
render() {
return `<div>Cleanup Component</div>`;
}
}
const instance = new CleanupComponent();
instance.update();
const element = document.querySelector(instance.selector);
element.remove();
instance.runActions();
// const isInWeakMap = Component.components.has(instance.name);
// assert.strictEqual(isInWeakMap, false, "Component was not removed from WeakMap after unmount");
const isInWeakMap = Component.components.has(instance);
assert.strictEqual(isInWeakMap, false, "Component was not removed from WeakMap after unmount");
});
})