Add a buncha tests.

master
Sir Robert Burbridge 2023-11-16 14:37:55 -05:00
parent d4a145b96b
commit cae8aba97f
13 changed files with 481 additions and 71 deletions

View File

@ -33,18 +33,14 @@ import {
transform, // transform an AST according to plugins
} from '@thefarce/loom'
console.log(
renderHtml(
interpolate(
transform(
parseHtml('<p>Hello, {name}!</p>'),
() => { ... },
[funcA, funcB, funcC],
),
const initial = htmlToAst('<p>Hello, {name}!</p>'),
const transformed = interpolate(
transform(initial, ...transformers),
{ name: 'Othello' },
)
)
);
console.log(astToHtml(transformed));
```
Running this script produces the following output:

View File

@ -1,9 +1,7 @@
# Transformation
In Loom, *transformation* refers to the process of modifying an abstract
syntax tree (AST), especially through the use of *transformers*.
Transformers are arrays of functions.
syntax tree (AST), especially through the use of *transformation functions*.
The mechanism provided by loom for these transformations is the
`transform()` function.
@ -12,14 +10,14 @@ The first argument to the `transform` function is an AST. Each node in the
AST will be considered for transformation.
After the AST, any number of additional arguments may be passed to the
`transform` function. Each of these should be a *transformer function* or
an array of such.
`transform` function. Each of these should be a *transformation function*
or an array of such.
## Transformation functions
Transformer functions take _a copy_ of an AST node. Because it is a copy,
the changes made to the node received by the transformer function will have
no effect on the tree. To change the tree, return a new AST node.
the changes made to the node received by the transformation function will
have no effect on the tree. To change the tree, return a new AST node.
### Return values

View File

@ -0,0 +1,6 @@
export function interpolate () {
}
export default interpolate;

View File

@ -0,0 +1,6 @@
export function transform () {
}
export default transform;

1
src/util.mjs 100644
View File

@ -0,0 +1 @@

View File

@ -0,0 +1,15 @@
import astToHtml from '../src/ast-to-html.mjs';
import htmlAstMapping from './fixtures/ast-html.fixture.mjs';
describe("ast-to-html", () => {
it("a UnifiedJS AST should be converted to html", () => {
expect(astToHtml).toBeDefined();
// Loop through all the mappings of html to AST and make sure they work.
for (const [html, ast] of Object.entries(htmlAstMapping)) {
expect(astToHtml(ast)).toEqual(html);
}
});
});

View File

@ -0,0 +1,5 @@
import div from "./html-ast.fixture.div.mjs";
export default Object.assign({},
div
);

View File

@ -0,0 +1,40 @@
export default {
'<div></div>': {
"type": "root",
"children": [
{
"type": "element",
"tagName": "div",
"properties": {},
"children": [],
"position": {
"start": {
"line": 1,
"column": 1,
"offset": 0,
},
"end": {
"line": 1,
"column": 12,
"offset": 11,
},
},
},
],
"data": {
"quirksMode": false,
},
"position": {
"start": {
"line": 1,
"column": 1,
"offset": 0,
},
"end": {
"line": 1,
"column": 12,
"offset": 11,
},
},
},
};

View File

@ -0,0 +1,5 @@
import div from "./html-ast.fixture.div.mjs";
export default Object.assign({},
div
);

View File

@ -1,53 +1,14 @@
import htmlToAst from '../src/html-to-ast.mjs';
import htmlAstMapping from './fixtures/html-ast.fixture.mjs';
describe("html-to-ast", () => {
it("should pass", () => {
it("html should be converted to a UnifiedJS AST", () => {
expect(htmlToAst).toBeDefined();
const validTransformations = {
'<div></div>': {
"type": "root",
"children": [
{
"type": "element",
"tagName": "div",
"properties": {},
"children": [],
"position": {
"start": {
"line": 1,
"column": 1,
"offset": 0,
},
"end": {
"line": 1,
"column": 12,
"offset": 11,
},
},
},
],
"data": {
"quirksMode": false,
},
"position": {
"start": {
"line": 1,
"column": 1,
"offset": 0,
},
"end": {
"line": 1,
"column": 12,
"offset": 11,
},
},
},
};
for (const [html, expected] of Object.entries(validTransformations)) {
expect(htmlToAst(html)).toEqual(expected);
// Loop through all the mappings of html to AST and make sure they work.
for (const [html, ast] of Object.entries(htmlAstMapping)) {
expect(htmlToAst(html)).toEqual(ast);
}
});
});

View File

@ -0,0 +1,24 @@
import interpolate from '../src/interpolate.mjs';
import htmlToAst from '../src/html-to-ast.mjs';
import astToHtml from '../src/ast-to-html.mjs';
describe("interpolate()", () => {
it("should import", () => {
expect(interpolate).toBeDefined();
})
it("should interpolate", () => {
const uninterpolated = '<div class="${classname}"></div>';
const expected = '<div class="foo"></div>';
const data = { classname: "foo" };
const ast = htmlToAst(uninterpolated);
const interpolated = interpolate(ast, data);
const rendered = astToHtml(interpolated);
expect(rendered).toEqual(expected);
})
});

View File

@ -0,0 +1,263 @@
import transform from '../src/transform.mjs';
import htmlToAst from '../src/html-to-ast.mjs';
describe("transform", () => {
it("should import transform()", () => {
expect(transform).toBeDefined();
});
it("should throw an error if there's no AST", () => {
expect(transform).toBeDefined();
expect(() => transform()).toThrow();
});
it("should act as an identity function if there are no tx fxns", () => {
const html = '<div></div>';
const ast = htmlToAst(html);
expect(transform(ast)).toEqual({
"type": "root",
"children": [
{
"type": "element",
"tagName": "div",
"properties": {},
"children": [],
"position": {
"start": {
"line": 1,
"column": 1,
"offset": 0,
},
"end": {
"line": 1,
"column": 12,
"offset": 11,
},
},
},
],
});
});
it("should leave the ast untouched for null", () => {
const html = '<div></div>';
const ast = htmlToAst(html);
expect(transform(ast, () => null)).toEqual({
"type": "root",
"children": [
{
"type": "element",
"tagName": "div",
"properties": {},
"children": [],
"position": {
"start": {
"line": 1,
"column": 1,
"offset": 0,
},
"end": {
"line": 1,
"column": 12,
"offset": 11,
},
},
},
],
});
});
it("should remove the node for undefined", () => {
const html = '<div></div>';
const ast = htmlToAst(html);
expect(transform(ast, () => undefined)).toEqual({
"type": "root",
"children": [],
});
});
it("should handle multiple tx fxns", () => {
const html = '<div></div>';
const ast = htmlToAst(html);
expect(transform(
ast,
() => ({
"type": "root",
"children": [
{
"type": "element",
"tagName": "span",
"properties": {},
"children": [],
"position": {
"start": {
"line": 1,
"column": 1,
"offset": 0,
},
"end": {
"line": 1,
"column": 12,
"offset": 11,
},
},
},
],
}),
() => null,
)).toEqual({
"type": "root",
"children": [
{
"type": "element",
"tagName": "div",
"properties": {},
"children": [],
"position": {
"start": {
"line": 1,
"column": 1,
"offset": 0,
},
"end": {
"line": 1,
"column": 12,
"offset": 11,
},
},
},
],
});
});
it("should handle tx fxns that return: null, node ", () => {
expect(transform).toBeDefined();
const html = '<div></div>';
const ast = htmlToAst(html);
expect(transform(ast, () => null)).toEqual({
"type": "root",
"children": [
{
"type": "element",
"tagName": "div",
"properties": {},
"children": [],
"position": {
"start": {
"line": 1,
"column": 1,
"offset": 0,
},
"end": {
"line": 1,
"column": 12,
"offset": 11,
},
},
},
],
});
});
it("should handle tx fxns that return: node, null ", () => {
expect(transform).toBeDefined();
const html = '<div></div>';
const ast = htmlToAst(html);
expect(transform(ast, () => null)).toEqual({
"type": "root",
"children": [
{
"type": "element",
"tagName": "div",
"properties": {},
"children": [],
"position": {
"start": {
"line": 1,
"column": 1,
"offset": 0,
},
"end": {
"line": 1,
"column": 12,
"offset": 11,
},
},
},
],
});
});
it("should handle tx fxns that return: undefined, node ", () => {
expect(transform).toBeDefined();
const html = '<div></div>';
const ast = htmlToAst(html);
expect(transform(ast, () => null)).toEqual({
"type": "root",
"children": [
{
"type": "element",
"tagName": "div",
"properties": {},
"children": [],
"position": {
"start": {
"line": 1,
"column": 1,
"offset": 0,
},
"end": {
"line": 1,
"column": 12,
"offset": 11,
},
},
},
],
});
});
it("should handle tx fxns that return: node, undefined", () => {
expect(transform).toBeDefined();
const html = '<div></div>';
const ast = htmlToAst(html);
expect(transform(ast, () => null)).toEqual({
"type": "root",
"children": [
{
"type": "element",
"tagName": "div",
"properties": {},
"children": [],
"position": {
"start": {
"line": 1,
"column": 1,
"offset": 0,
},
"end": {
"line": 1,
"column": 12,
"offset": 11,
},
},
},
],
});
});
});

90
test/util.test.mjs 100644
View File

@ -0,0 +1,90 @@
import {
cloneNode,
createAstNode,
isAstNode,
} from '../src/util.mjs';
describe("cloneNode()", () => {
it("should import cloneNode()", () => {
expect(cloneNode).toBeDefined();
});
it("should clone a node", () => {
const node = {
type: "root",
children: [
{
type: "element",
tagName: "div",
properties: {},
children: [],
position: {
start: {
line: 1,
column: 1,
offset: 0,
},
end: {
line: 1,
column: 12,
offset: 11,
},
},
},
],
};
expect(cloneNode(node)).toEqual(node);
expect(cloneNode(node) === node).toBe(false);
});
});
describe("createAstNode()", () => {
it("should import createAstNode()", () => {
expect(createAstNode).toBeDefined();
})
it("should create a generic AST node with no arguments", () => {
expect(createAstNode()).toEqual({
type: undefined,
value: undefined,
children: [],
})
})
});
describe("isAstNode()", () => {
it("should import isAstNode()", () => {
expect(isAstNode).toBeDefined();
});
it("should return true if the node is an AST node", () => {
[
createAstNode(),
].forEach(value => {
expect(isAstNode(value)).toBe(true);
});
});
it("should return true if the node is an AST node", () => {
[
"string",
"",
null,
undefined,
{},
{a:1},
{children:[]},
].forEach(value => {
expect(isAstNode(value)).toBe(false);
});
});
});