From 76803bb767b3daea0233517f6c473f74d278161b Mon Sep 17 00:00:00 2001
From: Lea Rosema
Date: Mon, 23 Sep 2024 11:38:48 +0200
Subject: [PATCH] feat: support waterfall includes
---
src/css.js | 24 ++------
src/html.js | 37 +++++------
src/md.js | 24 ++------
src/resolver.js | 23 +++++--
src/sissi.js | 61 +++++--------------
src/transforms/bundle.js | 37 +++++++++++
src/transforms/template-data.js | 53 ++++++++++++++++
tests/css.test.js | 6 +-
tests/data.test.js | 2 +-
.../data}/_data/jsdata.js | 0
.../data}/_data/jsondata.json | 0
.../data}/_data/yamldata.yaml | 0
tests/fixtures/smallsite/_data/meta.js | 4 ++
.../fixtures/smallsite/_includes/footer.html | 4 ++
.../fixtures/smallsite/_includes/header.html | 4 ++
tests/fixtures/smallsite/_layouts/base.html | 16 +++++
tests/fixtures/smallsite/css/_globals.css | 43 +++++++++++++
tests/fixtures/smallsite/css/_vars.css | 13 ++++
tests/fixtures/smallsite/css/styles.css | 2 +
tests/fixtures/smallsite/imprint.html | 12 ++++
tests/fixtures/smallsite/index.html | 7 +++
tests/fixtures/smallsite/test.md | 19 ++++++
tests/html.test.js | 19 +++++-
tests/resolver.test.js | 4 +-
tests/sissi.test.js | 34 ++++++++++-
tests/transforms/template-data.test.js | 36 ++++++++++-
26 files changed, 365 insertions(+), 119 deletions(-)
create mode 100644 src/transforms/bundle.js
rename tests/{fixture => fixtures/data}/_data/jsdata.js (100%)
rename tests/{fixture => fixtures/data}/_data/jsondata.json (100%)
rename tests/{fixture => fixtures/data}/_data/yamldata.yaml (100%)
create mode 100644 tests/fixtures/smallsite/_data/meta.js
create mode 100644 tests/fixtures/smallsite/_includes/footer.html
create mode 100644 tests/fixtures/smallsite/_includes/header.html
create mode 100644 tests/fixtures/smallsite/_layouts/base.html
create mode 100644 tests/fixtures/smallsite/css/_globals.css
create mode 100644 tests/fixtures/smallsite/css/_vars.css
create mode 100644 tests/fixtures/smallsite/css/styles.css
create mode 100644 tests/fixtures/smallsite/imprint.html
create mode 100644 tests/fixtures/smallsite/index.html
create mode 100644 tests/fixtures/smallsite/test.md
diff --git a/src/css.js b/src/css.js
index 0402309..774db05 100644
--- a/src/css.js
+++ b/src/css.js
@@ -1,6 +1,6 @@
import path from 'path';
-import { resolve } from './resolver.js';
+import { handleTemplateFile } from './transforms/template-data.js';
// TODO: add a regex for layer syntax
const INCLUDE_REGEX = /@import [\"\']([\w:\/\\]+\.css)[\"\'];/g;
@@ -11,29 +11,15 @@ export default (config) => {
config.addExtension('css', {
outputFileExtension: 'css',
compile: async function (inputContent, inputPath) {
-
let parsed = path.parse(inputPath);
- if (parsed.name.startsWith('_')) {
- // Omit files prefixed with an underscore.
- return;
- }
-
- return async () => {
+
+ return async (data) => {
const includes = new Map();
const matches = inputContent.matchAll(INCLUDE_REGEX);
for (const [, file] of matches) {
-
- const fullPath = path.resolve(config.dir.input, parsed.dir, file);
- try {
- const content = await (config.resolve || resolve)(fullPath);
- includes.set(file, content);
- } catch (err) {
- console.error('error processing file:', fullPath, err);
- // silently fail if there is no include
- includes.set(file, `@import "${file}";`);
- }
+ const tpl = await handleTemplateFile(config, data, path.join(parsed.dir, file));
+ includes.set(file, tpl ? tpl.content : `@import url("${file}");`);
}
-
return inputContent.replace(INCLUDE_REGEX, (_, file) => includes.get(file))
};
},
diff --git a/src/html.js b/src/html.js
index d2cccff..6c0a273 100644
--- a/src/html.js
+++ b/src/html.js
@@ -1,6 +1,7 @@
import path from 'path';
-import { resolve } from './resolver.js';
-const INCLUDE_REGEX = //g;
+import { handleTemplateFile } from './transforms/template-data.js';
+
+const INCLUDE_REGEX = //g;
export default (config) => {
config.addTemplateFormats('html');
@@ -10,29 +11,21 @@ export default (config) => {
compile: async function (inputContent, inputPath) {
let parsed = path.parse(inputPath);
- if (parsed.name.startsWith('_')) {
- // Omit files prefixed with an underscore.
- return;
- }
- return async () => {
+ return async (data) => {
const includes = new Map();
- const matches = inputContent.matchAll(INCLUDE_REGEX);
- for (const [, file] of matches) {
-
- const fullPath = path.join(config.dir.input, config.dir.includes, file);
- try {
- const content = await (config.resolve || resolve)(fullPath);
- includes.set(file, content);
- } catch (err) {
- console.error('error processing file:', fullPath, err);
- // silently fail if there is no include
- includes.set(file, ``);
+ let content = inputContent, matches;
+
+ while ((matches = Array.from(content.matchAll(INCLUDE_REGEX))).length > 0) {
+ for (const [, file] of matches) {
+ const include = await handleTemplateFile(config, data, path.join(config.dir.includes, file));
+ includes.set(file, include ? include.content : ``);
}
- }
- return inputContent.replace(INCLUDE_REGEX, (_, file) => {
- return includes.get(file)
- });
+ content = content.replace(INCLUDE_REGEX, (_, file) => {
+ return includes.get(file)
+ });
+ }
+ return content;
};
},
});
diff --git a/src/md.js b/src/md.js
index 0d04b80..1766fb6 100644
--- a/src/md.js
+++ b/src/md.js
@@ -1,6 +1,8 @@
import path from 'path';
-import { resolve } from './resolver.js';
+
+import { handleTemplateFile } from './transforms/template-data.js';
import { markdown } from './transforms/markdown.js';
+
const INCLUDE_REGEX = //g;
export default (config) => {
@@ -10,27 +12,13 @@ export default (config) => {
outputFileExtension: 'html',
compile: async function (inputContent, inputPath) {
- let parsed = path.parse(inputPath);
- if (parsed.name.startsWith('_')) {
- // Omit files prefixed with an underscore.
- return;
- }
-
- return async () => {
+ return async (data) => {
const includes = new Map();
const content = markdown(inputContent);
const matches = content.matchAll(INCLUDE_REGEX);
for (const [, file] of matches) {
-
- const fullPath = path.join(config.dir.input, config.dir.includes, file);
- try {
- const content = await (config.resolve || resolve)(fullPath);
- includes.set(file, content);
- } catch (err) {
- console.error('error processing file:', fullPath, err);
- // silently fail if there is no include
- includes.set(file, ``);
- }
+ const include = await handleTemplateFile(config, data, path.join(config.dir.includes, file));
+ includes.set(file, include ? include.content : ``);
}
return content.replace(INCLUDE_REGEX, (_, file) => {
return includes.get(file)
diff --git a/src/resolver.js b/src/resolver.js
index 73d6318..cfd763b 100644
--- a/src/resolver.js
+++ b/src/resolver.js
@@ -1,9 +1,16 @@
-import { readFile } from 'node:fs/promises'
+import { readFile, stat } from 'node:fs/promises'
import path from 'node:path';
-export async function resolve(resource) {
- if (/^\w+:\/\//.test(resource)) {
- // seems to be an URI, fetch it
+/**
+ * Read a file from the input dir or from the internet.
+ * @param {string[]} paths
+ * @returns
+ */
+export async function resolve(...paths) {
+ const last = paths.slice(-1)[0];
+ if (/^\w+:\/\//.test(last)) {
+ // seems to be an URL, fetch it
+ const resource = last;
const response = await fetch(resource);
const contentType = response.headers.get('Content-Type');
if (!contentType || !contentType.startsWith('text')) {
@@ -11,6 +18,12 @@ export async function resolve(resource) {
}
return await response.text();
}
+
// otherwise, readFile it.
- return await readFile(path.resolve(resource), 'utf8');
+ const resource = path.normalize(path.join(...paths));
+ const absResource = path.resolve(resource);
+ if ((await stat(absResource)).isDirectory()) {
+ return null;
+ }
+ return await readFile(absResource, 'utf8');
}
diff --git a/src/sissi.js b/src/sissi.js
index 486e328..e6687ec 100644
--- a/src/sissi.js
+++ b/src/sissi.js
@@ -6,8 +6,7 @@ import { SissiConfig } from './sissi-config.js';
import { serve } from './httpd.js';
import EventEmitter from 'node:stream';
import { readDataDir } from './data.js';
-import { template } from './transforms/template-data.js'
-import { frontmatter } from './transforms/frontmatter.js';
+import { handleTemplateFile } from './transforms/template-data.js';
export class Sissi {
@@ -30,9 +29,11 @@ export class Sissi {
if (filter instanceof RegExp) return filter.test(file);
}
);
+ const writtenFiles = []
for (const file of files) {
- await this.processFile(file, eventEmitter);
+ writtenFiles.push(await this.processFile(file, eventEmitter));
}
+ return writtenFiles.filter(Boolean);
}
/**
@@ -96,61 +97,27 @@ export class Sissi {
if (! this.data) {
this.data = await readDataDir(this.config);
}
- const absInputFileName = path.resolve(this.config.dir.input, inputFileName);
- if (inputFileName.startsWith('_') || inputFileName.includes(path.sep + '_')) {
+ if (inputFileName.startsWith('_') || inputFileName.includes(path.sep + '_') || path.parse(inputFileName).name.startsWith('_')) {
return;
}
- const stats = await stat(absInputFileName);
- if (stats.isDirectory()) {
- return;
- }
- let content = await readFile(absInputFileName, 'utf8');
- const parsed = path.parse(inputFileName);
- const extension = parsed.ext?.slice(1);
-
- let ext = null;
- if (this.config.extensions.has(extension)) {
- ext = this.config.extensions.get(extension);
- const { data: matterData, body } = frontmatter(content);
- content = body;
- const fileData = Object.assign({}, structuredClone(this.data), matterData);
- const processor = await ext.compile(content, inputFileName);
- content = template(await processor(fileData))(fileData);
-
- if (fileData.layout) {
- fileData.content = content;
- const relLayoutDir = path.normalize(
- path.join(this.config.dir.input, this.config.dir.layouts || '_layouts')
- );
- const absLayoutFilePath = path.resolve(relLayoutDir, fileData.layout);
- const layoutExtKey = path.parse(absLayoutFilePath).ext?.slice(1);
- let layoutContent = await readFile(absLayoutFilePath, 'utf8');
-
- const layoutExt = layoutExtKey ? this.config.extensions.get(layoutExtKey) : null;
- if (layoutExt) {
- const processor = await layoutExt.compile(layoutContent, inputFileName);
- layoutContent = await processor(fileData);
- }
-
- content = template(layoutContent)(fileData);
- }
-
+ const tpl = await handleTemplateFile(this.config, this.data, inputFileName);
+ if (! tpl) {
+ return null;
}
-
- let outputFileName =this.config.naming(this.config.dir.output, inputFileName, ext?.outputFileExtension);
- console.log(`[write]\t${outputFileName}`);
+
+ console.log(`[write]\t${tpl.filename}`);
if (eventEmitter) {
eventEmitter.emit('watch-event', {
eventType: 'change',
filename: inputFileName
});
}
- if (this.dryMode) {
- return;
+ if (! this.dryMode) {
+ await mkdir(path.parse(tpl.filename).dir, {recursive: true});
+ await writeFile(tpl.filename, tpl.content, {});
}
- await mkdir(path.parse(outputFileName).dir, {recursive: true});
- await writeFile(outputFileName, content, {});
+ return tpl.filename;
}
/**
diff --git a/src/transforms/bundle.js b/src/transforms/bundle.js
new file mode 100644
index 0000000..989dd21
--- /dev/null
+++ b/src/transforms/bundle.js
@@ -0,0 +1,37 @@
+const SYNTAXES = {
+ html: //g,
+ css: /@import [\"\']([\w:\/\\]+\.css)[\"\'](?: layer\((\w+)\))?;/g,
+};
+
+/**
+ * Bundle assets into one file.
+ *
+ * @param {string} inputContent
+ * @param {(resource: string) => Promise} resolve
+ * @param {'html'|'css'} syntax
+ * @returns {Promise} return the bundled resource
+ */
+async function bundle(inputContent, resolve, syntax, processor) {
+ const includes = new Map();
+ let content = inputContent, matches;
+ const includePattern = SYNTAXES[syntax];
+
+ while ((matches = Array.from(content.matchAll(includePattern))).length > 0) {
+ for (const [, file] of matches) {
+
+ const fullPath = path.join(config.dir.input, config.dir.includes, file);
+ try {
+ const content = await resolve(fullPath);
+
+ includes.set(file, await (await processor(content, file)).compile(data));
+ } catch (err) {
+ console.error('error processing file:', fullPath, err);
+ // silently fail if there is no include
+ includes.set(file, ``);
+ }
+ }
+ content = content.replace(includePattern, (_, file) => {
+ return includes.get(file);
+ });
+ }
+}
diff --git a/src/transforms/template-data.js b/src/transforms/template-data.js
index bc7eb6e..03e37ea 100644
--- a/src/transforms/template-data.js
+++ b/src/transforms/template-data.js
@@ -1,3 +1,9 @@
+import path from 'node:path';
+
+import { frontmatter } from './frontmatter.js';
+import { resolve } from '../resolver.js';
+import { SissiConfig } from "../sissi-config.js";
+
const TEMPLATE_REGEX = /\{\{\s*([\w\.\[\]]+)\s*\}\}/g;
const JSON_PATH_REGEX = /^\w+((?:\.\w+)|(?:\[\d+\]))*$/
const JSON_PATH_TOKEN = /(^\w+)|(\.\w+)|(\[\d+\])/g
@@ -41,3 +47,50 @@ export function template(str) {
});
}
}
+
+/**
+ * Complete Template processing function
+ * @param {SissiConfig} config
+ * @param {any} data
+ * @param {string} inputFile
+ * @returns {Promise<{content: Buffer|string, filename}>} the content file name and the output file name
+ */
+export async function handleTemplateFile(config, data, inputFile) {
+ const content = await (config.resolve || resolve)(config.dir.input, inputFile);
+ if (content === null) {
+ return null;
+ }
+
+ const parsed = path.parse(inputFile);
+ const ext = parsed.ext?.slice(1);
+ if (! config.extensions.has(ext)) {
+ return {
+ content,
+ filename: config.naming(config.dir.output, inputFile)
+ };
+ }
+
+ const plugin = config.extensions.get(ext);
+
+ const { data: matterData, body } = frontmatter(content);
+ const fileData = Object.assign({}, structuredClone(data), matterData);
+
+ const outputFile = config.naming(config.dir.output, inputFile, plugin?.outputFileExtension);
+ Object.assign(fileData, {
+ inputFile,
+ outputFile,
+ })
+
+ const processor = await plugin.compile(body, inputFile);
+
+ let fileContent = template(await processor(fileData))(fileData);
+
+ if (fileData.layout) {
+ const layoutFilePath = path.normalize(path.join(config.dir.layouts, fileData.layout));
+ const l = await handleTemplateFile(config,
+ {...fileData, content: fileContent, layout: null}, layoutFilePath);
+ fileContent = l.content;
+ }
+
+ return {content: fileContent, filename: outputFile};
+}
diff --git a/tests/css.test.js b/tests/css.test.js
index 2a081ab..6c19730 100644
--- a/tests/css.test.js
+++ b/tests/css.test.js
@@ -1,5 +1,6 @@
import { describe, it, before } from 'node:test';
import assert from 'node:assert/strict';
+import path from 'node:path';
import { SissiConfig } from '../src/sissi-config.js';
import css from '../src/css.js'
@@ -18,7 +19,8 @@ describe('css plugin', () => {
virtualFileSystem.set('A.css', '.a {color: red; }');
virtualFileSystem.set('B.css', '.b {color: green }');
- function dummyResolver(resource) {
+ function dummyResolver(...paths) {
+ const resource = path.normalize(path.join(...paths));
const match = resource.match(/\\?\/?(\w+.css)$/);
if (!match || !virtualFileSystem.has(match[1]) ) {
throw new Error('Virtual File not found')
@@ -51,4 +53,4 @@ describe('css plugin', () => {
assert(result === expectedFile);
});
-});
\ No newline at end of file
+});
diff --git a/tests/data.test.js b/tests/data.test.js
index 3937ae4..10442df 100644
--- a/tests/data.test.js
+++ b/tests/data.test.js
@@ -9,7 +9,7 @@ describe('readDataDir', () => {
before(() => {
config = new SissiConfig({dir: {
- input: 'tests/fixture',
+ input: 'tests/fixtures/data',
data: '_data',
output: 'dist'
}});
diff --git a/tests/fixture/_data/jsdata.js b/tests/fixtures/data/_data/jsdata.js
similarity index 100%
rename from tests/fixture/_data/jsdata.js
rename to tests/fixtures/data/_data/jsdata.js
diff --git a/tests/fixture/_data/jsondata.json b/tests/fixtures/data/_data/jsondata.json
similarity index 100%
rename from tests/fixture/_data/jsondata.json
rename to tests/fixtures/data/_data/jsondata.json
diff --git a/tests/fixture/_data/yamldata.yaml b/tests/fixtures/data/_data/yamldata.yaml
similarity index 100%
rename from tests/fixture/_data/yamldata.yaml
rename to tests/fixtures/data/_data/yamldata.yaml
diff --git a/tests/fixtures/smallsite/_data/meta.js b/tests/fixtures/smallsite/_data/meta.js
new file mode 100644
index 0000000..ceca947
--- /dev/null
+++ b/tests/fixtures/smallsite/_data/meta.js
@@ -0,0 +1,4 @@
+export default {
+ author: 'Lea Rosema',
+ fediverse: 'https://lea.lgbt/@lea',
+}
diff --git a/tests/fixtures/smallsite/_includes/footer.html b/tests/fixtures/smallsite/_includes/footer.html
new file mode 100644
index 0000000..721ef22
--- /dev/null
+++ b/tests/fixtures/smallsite/_includes/footer.html
@@ -0,0 +1,4 @@
+
+ imprint
+
diff --git a/tests/fixtures/smallsite/_includes/header.html b/tests/fixtures/smallsite/_includes/header.html
new file mode 100644
index 0000000..aaabbf4
--- /dev/null
+++ b/tests/fixtures/smallsite/_includes/header.html
@@ -0,0 +1,4 @@
+
+
+ My Fancy Site
+
diff --git a/tests/fixtures/smallsite/_layouts/base.html b/tests/fixtures/smallsite/_layouts/base.html
new file mode 100644
index 0000000..22aedbb
--- /dev/null
+++ b/tests/fixtures/smallsite/_layouts/base.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+ {{ title }} – Sissi Demo Site
+
+
+
+
+
+ {{ content }}
+
+
+
+
diff --git a/tests/fixtures/smallsite/css/_globals.css b/tests/fixtures/smallsite/css/_globals.css
new file mode 100644
index 0000000..fce7821
--- /dev/null
+++ b/tests/fixtures/smallsite/css/_globals.css
@@ -0,0 +1,43 @@
+*, *::before, *::after {
+ box-sizing: border-box;
+}
+
+*:focus {
+ outline: 2px solid #fff;
+}
+
+body {
+ font-family: system-ui, sans-serif;
+ background: var(--bg);
+ color: var(--fg);
+ margin: 0;
+ display: flex;
+ flex-direction: column;
+ min-height: 100vh;
+}
+
+a {
+ color: var(--anchor, #f0f);
+}
+
+header {
+ padding: 2rem;
+ background: #163;
+ color: #fff;
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+}
+
+main {
+ flex: 1 auto;
+ width: min(100% - 3rem, var(--container-max, 60ch));
+ margin-inline: auto;
+}
+
+footer {
+ padding: 2rem;
+
+ background: #000;
+}
+
diff --git a/tests/fixtures/smallsite/css/_vars.css b/tests/fixtures/smallsite/css/_vars.css
new file mode 100644
index 0000000..c4da6bb
--- /dev/null
+++ b/tests/fixtures/smallsite/css/_vars.css
@@ -0,0 +1,13 @@
+:root {
+ --bg: #222;
+ --fg: white;
+ --anchor: #7df;
+}
+
+@media screen and (prefers-color-scheme: light) {
+ :root {
+ --bg: #fff;
+ --fg: #222;
+ --anchor: #027;
+ }
+}
diff --git a/tests/fixtures/smallsite/css/styles.css b/tests/fixtures/smallsite/css/styles.css
new file mode 100644
index 0000000..5d0bb2e
--- /dev/null
+++ b/tests/fixtures/smallsite/css/styles.css
@@ -0,0 +1,2 @@
+@import '_vars.css';
+@import '_globals.css';
diff --git a/tests/fixtures/smallsite/imprint.html b/tests/fixtures/smallsite/imprint.html
new file mode 100644
index 0000000..d42a62b
--- /dev/null
+++ b/tests/fixtures/smallsite/imprint.html
@@ -0,0 +1,12 @@
+---
+title: Imprint
+layout: base.html
+---
+Imprint
+This is an Imprint. That's the address of my employer:
+
+ Lea Rosema c/o adesso
+ Willy-Brandt-Straße 1, Etage 4 Mitte
+
+ 20457 Hamburg
+
diff --git a/tests/fixtures/smallsite/index.html b/tests/fixtures/smallsite/index.html
new file mode 100644
index 0000000..3fd446f
--- /dev/null
+++ b/tests/fixtures/smallsite/index.html
@@ -0,0 +1,7 @@
+---
+title: Hello World
+layout: base.html
+---
+Hello World!
+This is a Demo website built with Sissi, the Small Indieweb Static SIte generator.
+This is another page
diff --git a/tests/fixtures/smallsite/test.md b/tests/fixtures/smallsite/test.md
new file mode 100644
index 0000000..ec36dd2
--- /dev/null
+++ b/tests/fixtures/smallsite/test.md
@@ -0,0 +1,19 @@
+---
+layout: base.html
+title: This is a markdown test page
+---
+# Muh
+
+Lorem ipsum dolor sit amet.
+
+It has _some_ *basic* features:
+
+- Unordered Lists
+- Like this one
+ 1. but also ordered ones.
+ 2. nesting them also works
+
+Adding links with angle brackets:
+
+Addmin links with custom text: [Lea's Mastodon profile page](https://lea.lgbt/@lea)
+
diff --git a/tests/html.test.js b/tests/html.test.js
index 85b6b45..8681210 100644
--- a/tests/html.test.js
+++ b/tests/html.test.js
@@ -1,5 +1,6 @@
import { describe, it, before } from 'node:test';
import assert from 'node:assert/strict';
+import path from 'node:path';
import { SissiConfig } from '../src/sissi-config.js';
import html from '../src/html.js'
@@ -17,8 +18,14 @@ describe('html plugin', () => {
].join('\n'));
virtualFileSystem.set('_includes/header.html', '');
virtualFileSystem.set('_includes/main.html', '');
+ virtualFileSystem.set('_includes/nav.html', '');
- function dummyResolver(resource) {
+ virtualFileSystem.set('_includes/waterfall-header.html', '');
+ virtualFileSystem.set('waterfall.html', '');
+
+
+ function dummyResolver(...paths) {
+ const resource = path.normalize(path.join(...paths));
return virtualFileSystem.get(resource);
}
@@ -46,4 +53,14 @@ describe('html plugin', () => {
assert.equal(result, expectedFile);
});
+ it('should handle waterfall includes nicely', async () => {
+ const expectedFile = '';
+ const file = 'waterfall.html';
+
+ const transform = await config.extensions.get('html').compile(virtualFileSystem.get(file), file);
+ const result = await transform();
+
+ assert.equal(result, expectedFile);
+ });
+
});
diff --git a/tests/resolver.test.js b/tests/resolver.test.js
index 0bd48a0..f72faa9 100644
--- a/tests/resolver.test.js
+++ b/tests/resolver.test.js
@@ -20,7 +20,7 @@ describe('resolve', () => {
});
it('should resolve files from the local file system', async () => {
- const content = await resolve(path.join(config.dir.input, 'index.html'));
+ const content = await resolve(config.dir.input, 'index.html');
assert(content.startsWith('---\n'));
});
@@ -36,7 +36,7 @@ describe('resolve', () => {
})
});
- const content = await resolve('https://unpkg.com/open-props@1.7.6/open-props.min.css');
+ const content = await resolve(config.dir.input, 'https://unpkg.com/open-props@1.7.6/open-props.min.css');
assert.strictEqual(globalThis.fetch.mock.callCount(), 1);
assert(content.startsWith(':where'));
diff --git a/tests/sissi.test.js b/tests/sissi.test.js
index 593e277..daab8d8 100644
--- a/tests/sissi.test.js
+++ b/tests/sissi.test.js
@@ -1 +1,33 @@
-// none yet :D. Try and error mode for now. It's just a silly idea.
\ No newline at end of file
+import { describe, it } from 'node:test';
+import assert from 'node:assert/strict';
+import { SissiConfig } from '../src/sissi-config.js';
+import { Sissi } from '../src/sissi.js';
+
+import html from '../src/html.js';
+import css from '../src/css.js';
+
+describe('sissi', () => {
+
+ it('should successfully build a smallsite', async () => {
+
+
+ const config = new SissiConfig({
+ dir: {
+ input: 'tests/fixtures/smallsite',
+ output: ''
+ }
+ });
+ config.addPlugin(html);
+ config.addPlugin(css);
+ const sissi = new Sissi(config);
+ sissi.dryMode = true;
+
+ const writtenFiles = await sissi.build();
+
+ writtenFiles.sort();
+
+ assert.deepEqual(writtenFiles,
+ ['css/styles.css', 'imprint.html', 'index.html', 'test.html']
+ );
+ });
+});
diff --git a/tests/transforms/template-data.test.js b/tests/transforms/template-data.test.js
index 25a7814..0ecbab5 100644
--- a/tests/transforms/template-data.test.js
+++ b/tests/transforms/template-data.test.js
@@ -1,6 +1,10 @@
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
-import { dataPath, template } from '../../src/transforms/template-data.js';
+import path from 'node:path';
+
+import { dataPath, handleTemplateFile, template } from '../../src/transforms/template-data.js';
+import { SissiConfig } from '../../src/sissi-config.js';
+import md from '../../src/md.js';
const TEST_DATA = {
'title': 'This is a title',
@@ -11,6 +15,15 @@ const TEST_DATA = {
'theMatrix': [[1,2,3],[4,5,6],[7,8,9]],
}
+const TEST_MD = `---
+layout: base.html
+author: Lea Rosema
+---
+# {{ title }}
+
+An article by {{ author }}
+`
+
const TEST_TEMPLATE = `{{ title }}
Blog article by {{ meta.authors[1] }}
`
@@ -52,3 +65,24 @@ describe('template function', () => {
assert.equal(template(TEST_TEMPLATE)(TEST_DATA), TEST_TEMPLATE_EXPECTED);
});
})
+
+describe('handleTemplateFile function', () => {
+ it('should work with the default markdown plugin', async () => {
+ const config = new SissiConfig();
+ config.addExtension(md);
+
+ const vFS = new Map();
+ vFS.set('index.md', TEST_MD);
+ vFS.set('_layouts/base.html', '{{ content }}');
+
+ config.resolve = (...paths) => {
+ const resource = path.normalize(path.join(...paths));
+ return vFS.get(resource);
+ }
+
+ const result = await handleTemplateFile(config, {title: 'Lea was here'}, 'index.md');
+
+ assert.equal(result.filename, 'public/index.html');
+ assert.equal(result.content, 'Lea was here
\nAn article by Lea Rosema
')
+ });
+});