import { expect, describe, test } from 'bun:test' describe('single line function blocks', () => { test('work with no args', () => { expect(`trap = do x: x end; trap: true end`).toEvaluateTo(true) }) test('work with one arg', () => { expect(`trap = do x y: [ x (y) ] end; trap EXIT: true end`).toEvaluateTo(['EXIT', true]) }) test('work with named args', () => { expect(`attach = do signal fn: [ signal (fn) ] end; attach signal='exit': true end`).toEvaluateTo(['exit', true]) }) test('work with dot-get', () => { expect(`signals = [trap=do x y: [x (y)] end]; signals.trap 'EXIT': true end`).toEvaluateTo(['EXIT', true]) }) }) describe('multi line function blocks', () => { test('work with no args', () => { expect(` trap = do x: x end trap: true end`).toEvaluateTo(true) }) test('work with one arg', () => { expect(` trap = do x y: [ x (y) ] end trap EXIT: true end`).toEvaluateTo(['EXIT', true]) }) test('work with named args', () => { expect(` attach = do signal fn: [ signal (fn) ] end attach signal='exit': true end`).toEvaluateTo(['exit', true]) }) test('work with dot-get', () => { expect(` signals = [trap=do x y: [x (y)] end] signals.trap 'EXIT': true end`).toEvaluateTo(['EXIT', true]) }) }) describe('ribbit', () => { test('head tag', () => { expect(` head: title What up meta charSet=UTF-8 meta name=viewport content='width=device-width, initial-scale=1, viewport-fit=cover' end`).toEvaluateTo(`head`, { head: () => 'head' }) }) test('li', () => { expect(` list: li border-bottom='1px solid black' one li two li three end`).toEvaluateTo(`list`, { list: () => 'list' }) }) test('inline expressions', () => { const buffer: string[] = [] 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(``) } 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 === null ? buffer.pop() : a) .reverse().join(' ') .replaceAll(' !!ribbit-nospace!! ', '') buffer.push(`<${tagName}${space}${attrs.join(' ')}>${children}`) } const tag = async (tagName: string, atNamed: {}, ...args: any[]) => { if (typeof args[0] === 'function') await tagBlock(tagName, atNamed, args[0]) else tagCall(tagName, atNamed, ...args) } 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( `

Heya

man that is wild!

Double the fun.

`, { ribbit: async (cb: Function) => { await cb() return buffer.join("\n") }, 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), nospace: () => '!!ribbit-nospace!!', join: (...args: string[]) => args.join(''), }) }) })