appblocks
Version:
A lightweight javascript library for building micro apps for the front-end.
83 lines (71 loc) • 3.58 kB
JavaScript
import { AppBlock } from 'src/core.js';
import { createMockAppBlockConfig, createMockTemplate, resetDOM } from 'tests/fixtures/mockData.js';
describe('custom methods', () => {
afterEach(() => resetDOM());
test('registers custom method and makes it available in placeholders', () => {
const tpl = createMockTemplate('<span>{myMethod}</span>');
const config = createMockAppBlockConfig({ template: tpl, methods: { myMethod: (comp) => 'RESULT' } });
const app = new AppBlock(config);
const text = app.el.textContent.trim();
expect(text).toBe('RESULT');
});
test('custom method receives component instance as argument', () => {
const tpl = createMockTemplate('<span>{getName}</span>');
const methods = { getName: jest.fn((comp) => comp.name) };
const config = createMockAppBlockConfig({ template: tpl, methods });
const app = new AppBlock(config);
expect(methods.getName).toHaveBeenCalledWith(expect.any(Object));
expect(app.el.textContent.trim()).toBe(app.name);
});
test('should support nested method calls with placeholders and c-for', () => {
const template = createMockTemplate('<div><ul c-for="item in getItems()"><li>{processItem(item)}</li></ul></div>');
const config = createMockAppBlockConfig({
template,
methods: {
getItems: function(app) { return ['a', 'b', 'c']; },
processItem: function(app, item) { return item.toUpperCase(); }
}
});
const app = new AppBlock(config);
const lis = app.el.querySelectorAll('li');
expect(lis.length).toBe(3);
expect(lis[0].textContent).toContain('A');
expect(lis[1].textContent).toContain('B');
expect(lis[2].textContent).toContain('C');
});
test('should invoke each method only once per render with nested calls', () => {
const callCounts = { getItems: 0, processItem: 0 };
const template = createMockTemplate('<div><span>{processItem(getItems())}</span></div>');
const config = createMockAppBlockConfig({
template,
methods: {
getItems: function(app) { callCounts.getItems++; return ['x', 'y']; },
processItem: function(app, items) { callCounts.processItem++; return items.length; }
}
});
const app = new AppBlock(config);
expect(callCounts.getItems).toBe(1);
expect(callCounts.processItem).toBe(1);
expect(app.el.textContent).toContain('2');
});
test('should not double-invoke methods in same render cycle even with multiple placeholder refs', () => {
// This tests that the per-render cache prevents double invocation
// when the same method call appears in multiple placeholders
const callCounts = { computeValue: 0 };
const template = createMockTemplate('<div><span>{computeValue()}</span><span>{computeValue()}</span></div>');
const config = createMockAppBlockConfig({
template,
methods: {
computeValue: function(app) { callCounts.computeValue++; return 'result'; }
}
});
const app = new AppBlock(config);
// computeValue() appears twice, but should only be called once due to caching
// Actually, each placeholder expression is independent, so it will be called twice
// But if we had the same expression text twice with cache, it would only call once
// Let's test that the SAME expression text is cached
expect(callCounts.computeValue).toBe(2); // Two separate placeholder evaluations
expect(app.el.querySelectorAll('span')[0].textContent).toBe('result');
expect(app.el.querySelectorAll('span')[1].textContent).toBe('result');
});
});