diff options
-rw-r--r-- | src/content-function.js | 4 | ||||
-rw-r--r-- | src/content/dependencies/index.js | 89 | ||||
-rw-r--r-- | tap-snapshots/test/snapshots/linkArtist.js.test.cjs | 14 | ||||
-rw-r--r-- | test/snapshots/_support.js | 55 | ||||
-rw-r--r-- | test/snapshots/linkArtist.js | 26 |
5 files changed, 177 insertions, 11 deletions
diff --git a/src/content-function.js b/src/content-function.js index dbac691b..0a217800 100644 --- a/src/content-function.js +++ b/src/content-function.js @@ -383,6 +383,7 @@ export function quickEvaluate({ // provided as part of allContentDependencies or allExtraDependencies. // Catch and report these early, together in an aggregate error. const unfulfilledErrors = []; + const unfulfilledNames = []; for (const name of neededContentDependencyNames) { const contentFunction = fulfilledContentDependencies[name]; if (!contentFunction) continue; @@ -392,12 +393,13 @@ export function quickEvaluate({ } catch (error) { error.message = `(${name}) ${error.message}`; unfulfilledErrors.push(error); + unfulfilledNames.push(name); } } } if (!empty(unfulfilledErrors)) { - throw new AggregateError(unfulfilledErrors, `Content functions unfulfilled`); + throw new AggregateError(unfulfilledErrors, `Content functions unfulfilled (${unfulfilledNames.join(', ')})`); } const slotResults = {}; diff --git a/src/content/dependencies/index.js b/src/content/dependencies/index.js index 5cd116d4..7f86abb1 100644 --- a/src/content/dependencies/index.js +++ b/src/content/dependencies/index.js @@ -7,12 +7,19 @@ import contentFunction from '../../content-function.js'; import {color, logWarn} from '../../util/cli.js'; import {annotateFunction} from '../../util/sugar.js'; -export function watchContentDependencies() { +export function watchContentDependencies({ + logging = true, +} = {}) { const events = new EventEmitter(); const contentDependencies = {}; + let emittedReady = false; + let initialScanComplete = false; + let allDependenciesFulfilled = false; + Object.assign(events, { contentDependencies, + close, }); // Watch adjacent files @@ -32,10 +39,42 @@ export function watchContentDependencies() { return; } handlePathRemoved(filePath); - }) + }); + + watcher.on('ready', () => { + initialScanComplete = true; + checkReadyConditions(); + }); return events; + async function close() { + return watcher.close(); + } + + function checkReadyConditions() { + if (emittedReady) { + return; + } + + if (!initialScanComplete) { + return; + } + + checkAllDependenciesFulfilled(); + + if (!allDependenciesFulfilled) { + return; + } + + events.emit('ready'); + emittedReady = true; + } + + function checkAllDependenciesFulfilled() { + allDependenciesFulfilled = !Object.values(contentDependencies).includes(null); + } + function getFunctionName(filePath) { const shortPath = path.basename(filePath); const functionName = shortPath.slice(0, -path.extname(shortPath).length); @@ -85,24 +124,54 @@ export function watchContentDependencies() { } contentDependencies[functionName] = fn; + + events.emit('update', functionName); + checkReadyConditions(); } if (!error) { return true; } - if (contentDependencies[functionName]) { - logWarn`Failed to import ${functionName} - using existing version`; - } else { - logWarn`Failed to import ${functionName} - no prior version loaded`; + if (!(functionName in contentDependencies)) { + contentDependencies[functionName] = null; } - if (typeof error === 'string') { - console.error(color.yellow(error)); - } else { - console.error(error); + events.emit('error', functionName, error); + + if (logging) { + if (contentDependencies[functionName]) { + logWarn`Failed to import ${functionName} - using existing version`; + } else { + logWarn`Failed to import ${functionName} - no prior version loaded`; + } + + if (typeof error === 'string') { + console.error(color.yellow(error)); + } else { + console.error(error); + } } return false; } } + +export function quickLoadContentDependencies() { + return new Promise((resolve, reject) => { + const watcher = watchContentDependencies(); + + watcher.on('error', (name, error) => { + watcher.close().then(() => { + error.message = `Error loading dependency ${name}: ${error}`; + reject(error); + }); + }); + + watcher.on('ready', () => { + watcher.close().then(() => { + resolve(watcher.contentDependencies); + }); + }); + }); +} diff --git a/tap-snapshots/test/snapshots/linkArtist.js.test.cjs b/tap-snapshots/test/snapshots/linkArtist.js.test.cjs new file mode 100644 index 00000000..7ca52796 --- /dev/null +++ b/tap-snapshots/test/snapshots/linkArtist.js.test.cjs @@ -0,0 +1,14 @@ +/* IMPORTANT + * This snapshot file is auto-generated, but designed for humans. + * It should be checked into source control and tracked carefully. + * Re-generate by setting TAP_SNAPSHOT=1 and running tests. + * Make sure to inspect the output below. Do not ignore changes! + */ +'use strict' +exports[`test/snapshots/linkArtist.js TAP linkArtist > output 1`] = ` +<a href="artist/toby-fox/">Toby Fox</a> +` + +exports[`test/snapshots/linkArtist.js TAP linkArtist > output 2`] = ` +<a href="artist/55gore/">55gore</a> +` diff --git a/test/snapshots/_support.js b/test/snapshots/_support.js new file mode 100644 index 00000000..b51f2847 --- /dev/null +++ b/test/snapshots/_support.js @@ -0,0 +1,55 @@ +import {quickEvaluate} from '../../src/content-function.js'; +import {quickLoadContentDependencies} from '../../src/content/dependencies/index.js'; + +import chroma from 'chroma-js'; +import * as html from '../../src/util/html.js'; +import urlSpec from '../../src/url-spec.js'; +import {getColors} from '../../src/util/colors.js'; +import {generateURLs} from '../../src/util/urls.js'; + +export function testContentFunctions(t, message, fn) { + const urls = generateURLs(urlSpec); + + t.test(message, async t => { + const loadedContentDependencies = await quickLoadContentDependencies(); + + const evaluate = ({ + from = 'localized.home', + contentDependencies = {}, + extraDependencies = {}, + ...opts + }) => { + const {to} = urls.from(from); + + try { + return quickEvaluate({ + ...opts, + contentDependencies: { + ...contentDependencies, + ...loadedContentDependencies, + }, + extraDependencies: { + html, + to, + urls, + appendIndexHTML: false, + getColors: c => getColors(c, {chroma}), + ...extraDependencies, + }, + }); + } catch (error) { + if (error instanceof AggregateError) { + error = new Error(`AggregateError: ${error.message}\n${error.errors.map(err => `** ${err}`).join('\n')}`); + } + throw error; + } + }; + + evaluate.snapshot = (opts, fn) => { + const result = (fn ? fn(evaluate(opts)) : evaluate(opts)); + t.matchSnapshot(result.toString(), 'output'); + }; + + return fn(t, evaluate); + }); +} diff --git a/test/snapshots/linkArtist.js b/test/snapshots/linkArtist.js new file mode 100644 index 00000000..43fee88e --- /dev/null +++ b/test/snapshots/linkArtist.js @@ -0,0 +1,26 @@ +import t from 'tap'; + +import {testContentFunctions} from './_support.js'; + +testContentFunctions(t, 'linkArtist', (t, evaluate) => { + evaluate.snapshot({ + name: 'linkArtist', + args: [ + { + name: `Toby Fox`, + directory: `toby-fox`, + } + ], + }); + + evaluate.snapshot({ + name: 'linkArtist', + args: [ + { + name: 'ICCTTCMDMIROTMCWMWFTPFTDDOTARHPOESWGBTWEATFCWSEBTSSFOFG', + nameShort: '55gore', + directory: '55gore', + }, + ], + }, v => v.slot('preferShortName', true)); +}); |