115 lines
3.5 KiB
TypeScript
115 lines
3.5 KiB
TypeScript
import { expect, describe, test, beforeEach } from 'bun:test'
|
|
|
|
const buffer: string[] = []
|
|
|
|
const ribbitGlobals = {
|
|
ribbit: async (cb: Function) => {
|
|
await cb()
|
|
return buffer.join("\n")
|
|
},
|
|
tag: async (tagFn: Function, atDefaults = {}) => {
|
|
return (atNamed = {}, ...args: any[]) => tagFn(Object.assign({}, atDefaults, atNamed), ...args)
|
|
},
|
|
head: (atNamed: {}, ...args: any[]) => tag('head', atNamed, ...args),
|
|
title: (atNamed: {}, ...args: any[]) => tag('title', atNamed, ...args),
|
|
meta: (atNamed: {}, ...args: any[]) => tag('meta', atNamed, ...args),
|
|
p: (atNamed: {}, ...args: any[]) => tag('p', atNamed, ...args),
|
|
h1: (atNamed: {}, ...args: any[]) => tag('h1', atNamed, ...args),
|
|
h2: (atNamed: {}, ...args: any[]) => tag('h2', atNamed, ...args),
|
|
b: (atNamed: {}, ...args: any[]) => tag('b', atNamed, ...args),
|
|
ul: (atNamed: {}, ...args: any[]) => tag('ul', atNamed, ...args),
|
|
li: (atNamed: {}, ...args: any[]) => tag('li', atNamed, ...args),
|
|
nospace: () => NOSPACE_TOKEN,
|
|
echo: (...args: any[]) => console.log(...args)
|
|
}
|
|
|
|
function raw(fn: Function) { (fn as any).raw = true }
|
|
|
|
const tagBlock = async (tagName: string, props = {}, fn: Function) => {
|
|
const attrs = Object.entries(props).map(([key, value]) => `${key}="${value}"`)
|
|
const space = attrs.length ? ' ' : ''
|
|
|
|
buffer.push(`<${tagName}${space}${attrs.join(' ')}>`)
|
|
await fn()
|
|
buffer.push(`</${tagName}>`)
|
|
}
|
|
|
|
const tagCall = (tagName: string, atNamed = {}, ...args: any[]) => {
|
|
const attrs = Object.entries(atNamed).map(([key, value]) => `${key}="${value}"`)
|
|
const space = attrs.length ? ' ' : ''
|
|
const children = args
|
|
.reverse()
|
|
.map(a => a === TAG_TOKEN ? buffer.pop() : a)
|
|
.reverse().join(' ')
|
|
.replaceAll(` ${NOSPACE_TOKEN} `, '')
|
|
|
|
if (SELF_CLOSING.includes(tagName))
|
|
buffer.push(`<${tagName}${space}${attrs.join(' ')} />`)
|
|
else
|
|
buffer.push(`<${tagName}${space}${attrs.join(' ')}>${children}</${tagName}>`)
|
|
}
|
|
|
|
const tag = async (tagName: string, atNamed = {}, ...args: any[]) => {
|
|
if (typeof args[0] === 'function') {
|
|
await tagBlock(tagName, atNamed, args[0])
|
|
} else {
|
|
tagCall(tagName, atNamed, ...args)
|
|
return TAG_TOKEN
|
|
}
|
|
}
|
|
|
|
const NOSPACE_TOKEN = '!!ribbit-nospace!!'
|
|
const TAG_TOKEN = '!!ribbit-tag!!'
|
|
const SELF_CLOSING = ["area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr"]
|
|
|
|
describe('ribbit', () => {
|
|
beforeEach(() => buffer.length = 0)
|
|
|
|
test('head tag', () => {
|
|
expect(`
|
|
ribbit:
|
|
head:
|
|
title What up
|
|
meta charset=UTF-8
|
|
meta name=viewport content='width=device-width, initial-scale=1, viewport-fit=cover'
|
|
end
|
|
end
|
|
`).toEvaluateTo(`<head>
|
|
<title>What up</title>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
|
</head>`, ribbitGlobals)
|
|
})
|
|
|
|
test('custom tags', () => {
|
|
expect(`
|
|
list = tag ul class='list'
|
|
ribbit:
|
|
list:
|
|
li border-bottom='1px solid black' one
|
|
li two
|
|
li three
|
|
end
|
|
end`).toEvaluateTo(`<ul class="list">
|
|
<li border-bottom="1px solid black">one</li>
|
|
<li>two</li>
|
|
<li>three</li>
|
|
</ul>`, ribbitGlobals)
|
|
})
|
|
|
|
test('inline expressions', () => {
|
|
expect(`
|
|
ribbit:
|
|
p class=container:
|
|
h1 class=bright style='font-family: helvetica' Heya
|
|
h2 man that is (b wild) (nospace) !
|
|
p Double the fun.
|
|
end
|
|
end`).toEvaluateTo(
|
|
`<p class="container">
|
|
<h1 class="bright" style="font-family: helvetica">Heya</h1>
|
|
<h2>man that is <b>wild</b>!</h2>
|
|
<p>Double the fun.</p>
|
|
</p>`, ribbitGlobals)
|
|
})
|
|
}) |