From 7b4f2249d6cdd6223780db048ca95f383ba13575 Mon Sep 17 00:00:00 2001 From: Adam Koch Date: Fri, 9 Aug 2019 23:13:10 -0500 Subject: [PATCH] pull request (#1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Removes glob-watcher middle-man and use upstream dependency instead (chokidar) * Test for #161 * Fixes #118 Fixes #325 (new stuff for 11ty.js templates) * Adds a test returning markdown inside of the JavaScript template. * Only remove JS templates from require cache once * Markdown in 11ty.js template test. * Adds benchmark link * Tests born from writing documentation šŸ¤” * Permalink async function * Actual test for disabling dynamic permalinks (and use the correct boolean value šŸ¤”) * Another test for JS templates * Adds a test for #352 * Fixes #229 * Documentation queue link * Documentation queue should use needs-votes * Fix: Tests invoking illegal constructor TemplateData() * Fixes #194 * Fixes #164 * Fixes #123 * Join paths together correctly with TemplatePath utility * Separate normalize and join methods * Remove superfluous slash in call to TemplatePath.join * Skipped failing Test for #253 * Removes config options for template aliases #207. * EMOJI * Version bump to 0.7.0. RELEASE IS NIGH * Oops broken tests #207 * Upgrade dependencies, unskip one liquid test * Eligible major version updates (purposefully not upgrading mustache and dependency-tree) * Fixes #372 * 0.7.0 coverage * Remove unused imports * Fixes #375 * Adds collection iteration test * Giant refactor of Eleventy.js to use new class syntax. * More travis operating systems. #353 * Walk back Windows test boxes on travis as there are a few broken tests in there (needs revisiting #353) * Version 0.7.1 quick maintenance release. * Fix broken tests on Windows (yes, it's newlines) * Add tests for normalizeNewLines * TemplatePath: _getModuleDir * TemplatePath: getWorkingDir * TemplatePath: getDir * TemplatePath: getDirFromFilePath * TemplatePath: getLastPathSegment * TemplatePath: getAllDirs * TemplatePath: hasTrailingSlash * TemplatePath: normalizeUrlPath * TemplatePath: absolutePath + relativePath * TemplatePath: addLeadingDotSlash + addLeadingDotSlashArray * TemplatePath: stripLeadingDotSlash * TemplatePath: startsWithSubPath * TemplatePath: stripLeadingSubPath * TemplatePath: isDirectorySync * TemplatePath: convertToRecursiveGlob * TemplatePath: getExtension * TemplatePath: removeExtension * Remove redundant tests * Refactor normalizeUrlPath, remove hasTrailingSlash * New nunjucks fixes #311 šŸŽ‰ * Fixes #384 * note * Class * Clearer errors when JSON parsing fails. * Start of refactor for #253 * Back to working tests with refactored code. * Fixes #253 but needs opt-in * Adds opt-in for #253. * Fix for string tags #253 * Unskip a test. * Better for #253, normalizes "string" tags to array in more places. Fixes `templateContent` missing in a few places. * Bump to 0.7.2 * Better template maps with user collections, uses two-pass to work around circular graphs * Simplified duplicate template map methods * More cleanup * Go deep on template local data dirs * Undo test result re-ordering * TemplatePath.getAllDirs: Reverse result array to match previous implementation * Fix issue with new paginated templates in collections including layouts when they shouldnā€™t. #253 * eleventyExcludeFromCollections data option will exclude template from any collections (including collections.all) * PERFORMANCE BOOST: Reuses template content without layouts from template map instead of separate renders for collections and full template files. (Will attempt to re-render if encounters a templateContent that is not yet available) Will now throw an error when encountering circular templateContent references (instead of just failing silently). * More tests for premature template content errors. * Fixes #424 and another npm audit warning for `nyc` too. One low vuln exists for browser-sync but we are on the latest version * Fixes #403 * Adds working tests for #436 * Test confirms #337 is fixed. * issue description * Dir needs to exist for #337 * Updating handlebars * Regression in config updates, config path from chokidar needed leading dot slash. Might fix #340? Not sure yet. * Start of #137 * Updates ava * issue #389 fixed, check for .gitignore empty and " " to take default Added tests: - test no .eleventyignore, .gitignore exists but empty - test .eleventyignore, .gitignore exists but empty - .gitignore with spaces takes default * Change TemplateLayoutPathResolver so that you pass in inputDir and it figures out the layouts directory internally. * All of these getInputDir calls were incorrectly named. * Adds tests for when a layouts folder exists. * Add tests for empty string layouts and empty string includes folders. * Adds test for #460 * Adds another test for #460 * Test cases for #398 and #413 * Removes sample license that was part of a test case šŸ˜… * Updates a few deps minor versions * Major release update javascript-stringify 1.6 -> 2.0 (rewrite in TypeScript, changes API signature) * semver major version update (should have no affect) https://github.com/npm/node-semver/blob/master/CHANGELOG.md#60 * Updates liquidjs (minor version) * v0.8.0-beta.1 * v0.8.0 * Release procedure notes. Would be better if this lived on 11ty.io but itā€™s going here for now. * Fixes #476, fixes #477 * v0.8.1 * Fix jenkins builds (just ordering problems in the tests) * Try again #353 * Walk back #353, still a few failures. * Fixes #475 * v0.8.2 * A few more linter/transform tests. * forces uses of posix path separator in url normalization * Updates license year * Try #353 again after #486 was merged. * The only remaining test failure for Windows per https://travis-ci.org/11ty/eleventy/jobs/518833095 #353 * Ugh, okay one more time for #353 * Fixes #484 * Fixes #479 * Fix getAllDirs not handling segments of equal name * Just another test for #484, why not * Fixes #466 * Add test for async addCollection * Fixes #498 * Update version to 0.8.3 * Make glob matching case insensitive * Coverage stats for 0.8.3 * Make tests work with out of order results. * Remove build status from the README (Windows tests on Jenkins are a little bit unreliable, but even so Iā€™d rather keep those around) * Adding test for case insensitivity in globbing * Fixing a typo * Create FUNDING.yml * New test, related to #544 * Check for empty tags Fixes #556 * refactor null check * Fixes #322 * Use less verbose test output for pre-push * Fixes #562 * Improvements to #562. One error at a time, better output, tests around EleventyErrorHandler. * Make update more readable * Test added for #557 #556 * docs(README): add reference to plugins section of official docs. Signed-off-by: AndrĆ© Jaenisch * Add Layouts Directory to debug output * Skip unimplemented tests (they failed on Windows in https://github.com/11ty/eleventy/pull/570) * Fixes #428 * Major version upgrades to ava and nyc (devDependencies only) * Add support for passthrough with directory remapping * Resolve issues with outpur paths * Add glob method and a whole lot of tests! * Fix async write issue * Sort when comparing arrays because some versions of node... I don't really know why but order is different and it matters * Update copy function to catch when destination is outside site output directory * Functions that return promises should not throw but reject with error. Pearent should catch * update test for dest outside output dir * chore: remove year old dead code * fix windows paths for test * update error type * test removal of windows path fix * fix tests after rebase * Normalise all paths in test * Rename getOutputPathForGlobFile * use TemplatePath.normalize rather than path.normalize * Attempted fix for #522 * Tests for #524. * Test cases for #536. * Support customizing front matter parsing options passed to gray-matter plugin. See https://www.npmjs.com/package/gray-matter#options Fixes #410. Needs docs. * A bit more readable for #410 * #410 work without a newline after excerpt separator * Custom aliasing for #410 * TOML test for #410 * Fix for Windows tests #410 * Tests for #567 * Fixes #190 * Tests for imports, extends, macros #190 * Support for way more relative includes. Now tested to be working in Liquid, Nunjucks, Pug, and EJS. #190 * Windows tests :( * Windows Tests :( * Skip TOML parsing test (fails on Windows), failure in dependency https://github.com/jonschlinkert/gray-matter/issues/92 * More links * Update issue templates * Remove rogue issue templates * Update issue templates * Throw when localConfig is a function that returns a promise * Fixes error output in tests * Another attempt at fixing test error console output * Update local node version to 10 * Remove test files from root * Still has one failing test * status * Fixes #619 * AVA uses only js extensions to find tests so use .js was superfluous. This also fixes some infinite looping in test watching where tests were writing to _site folders which would trigger another test run. * Trying to prevent watch loops * Fixes broken test * Next version will be 0.9.0 * Fixes #600 * Fixes #588 * Adds two more tests for #600. * Test for #603 Nunjucks * More tests for #603 and docs. * Tests for #611 * Unskip the test it works in Nunjucks #611. * This needs more tests but itā€™s a good start at adding support for public class fields, one class instance for data and render, as well as multiple exports as described here https://github.com/11ty/eleventy/issues/622#issuecomment-514681826 Also adds travis tests for Node 12 (required for public class fields) #622 * Adds simplification code suggested by this awesome feedback from @jakearchibald https://github.com/11ty/eleventy/issues/622#issuecomment-514955817 * Fixes https://github.com/11ty/eleventy/commit/752b39ace127ab0a258785ca885621aaa6fada3d#r34445791 * More tests for #622. Proceed gracefully if a render method is missing from class or object definition * Updates to how excerpts work (docs already updated) #410 * Fixes #626 * Normalize newlines on Windows * Newlines, what * A ha, thereā€™s the newline problem with excerpts * A few more tests for the newlines * Adds page.filePathStem, fixes #244. * Fix issue with #452 that threw an error when doing passthrough copy by unmatched file extension * start of a test for issue #446 (commented out) * v0.9.0-beta.1 * New code coverage for 0.9.0-beta.1 * Update dependencies (minor and patch versions) * Fixes #639 --- .editorconfig | 5 +- .eslintrc.js | 17 + .github/FUNDING.yml | 3 + .../i-have-a-question-about-eleventy.md | 10 + .../i-m-having-a-problem-with-eleventy.md | 31 + .../i-want-eleventy-do-to-this-new-thing.md | 20 + .gitignore | 6 +- .nvmrc | 1 + .travis.yml | 8 +- CODE_OF_CONDUCT.md | 46 + LICENSE | 4 +- README.md | 183 +- cmd.js | 86 +- config.js | 88 +- docs-src/.eleventy.docs.js | 18 + docs-src/_data/coverage.json | 57 + docs-src/coverage.njk | 10 + docs/collections.md | 3 + docs/copy.md | 3 + docs/coverage.md | 60 + docs/data.md | 46 +- docs/engines.md | 3 + docs/engines/ejs.md | 3 + docs/engines/haml.md | 3 + docs/engines/handlebars.md | 3 + docs/engines/html.md | 3 + docs/engines/jstl.md | 3 + docs/engines/liquid.md | 3 + docs/engines/markdown.md | 3 + docs/engines/mustache.md | 3 + docs/engines/nunjucks.md | 3 + docs/engines/pug.md | 3 + docs/filters.md | 3 + docs/install-local.md | 3 + docs/layouts.md | 3 + docs/logo-github.png | Bin 0 -> 6590 bytes docs/meta-release.md | 35 + docs/pagination.md | 137 +- docs/permalinks.md | 54 +- docs/pitfalls.md | 3 + docs/plugins.md | 3 + package.json | 126 +- src/Benchmark.js | 35 + src/BenchmarkGroup.js | 73 + src/BenchmarkManager.js | 47 + src/Config.js | 7 + src/Eleventy.js | 537 ++++-- src/EleventyBaseError.js | 10 + src/EleventyCommandCheck.js | 82 + src/EleventyConfig.js | 3 + src/EleventyError.js | 35 - src/EleventyErrorHandler.js | 93 + src/EleventyErrorUtil.js | 17 + src/EleventyExtensionMap.js | 164 ++ src/EleventyFiles.js | 349 ++++ src/EleventyServe.js | 176 ++ src/EleventyWatchTargets.js | 125 ++ src/Engines/Ejs.js | 51 +- src/Engines/Haml.js | 14 +- src/Engines/Handlebars.js | 53 +- src/Engines/Html.js | 6 +- src/Engines/JavaScript.js | 130 +- src/Engines/JavaScriptTemplateLiteral.js | 63 + src/Engines/Liquid.js | 193 +- src/Engines/Markdown.js | 84 +- src/Engines/Mustache.js | 18 +- src/Engines/Nunjucks.js | 155 +- src/Engines/Pug.js | 44 +- src/Engines/TemplateEngine.js | 131 +- .../TemplateContentPrematureUseError.js | 4 + ...ngCircularTemplateContentReferenceError.js | 5 + src/Filters/Slug.js | 8 + src/Filters/Url.js | 36 + src/Plugins/Pagination.js | 329 ++-- src/Template.js | 877 ++++++--- src/TemplateCache.js | 35 + src/TemplateCollection.js | 69 + src/TemplateConfig.js | 156 +- src/TemplateContent.js | 206 ++ src/TemplateData.js | 402 +++- src/TemplateFileSlug.js | 49 + src/TemplateGlob.js | 37 + src/TemplateLayout.js | 172 +- src/TemplateLayoutPathResolver.js | 128 ++ src/TemplateMap.js | 550 ++++++ src/TemplatePassthrough.js | 110 ++ src/TemplatePassthroughManager.js | 140 ++ src/TemplatePath.js | 274 ++- src/TemplatePermalink.js | 20 +- src/TemplatePermalinkNoWrite.js | 11 + src/TemplateRender.js | 261 ++- src/TemplateWriter.js | 266 +-- src/UserConfig.js | 549 ++++++ src/Util/Capitalize.js | 20 + src/Util/Merge.js | 51 + src/Util/Pluralize.js | 3 + src/Util/Sortable.js | 144 ++ src/VersionCheck.js | 22 - test/BenchmarkTest.js | 53 + test/CapitalizeTest.js | 12 + test/EleventyCommandCheckTest.js | 51 + test/EleventyConfigTest.js | 88 + test/EleventyErrorHandlerTest.js | 56 + test/EleventyExtensionMapTest.js | 143 ++ test/EleventyFilesTest.js | 597 ++++++ test/EleventyServeTest.js | 80 + test/EleventyTest.js | 99 +- test/EleventyWatchTargetsTest.js | 98 + test/MergeTest.js | 147 ++ test/PaginationTest.js | 430 ++++- test/PluralizeTest.js | 10 + test/SortableTest.js | 151 ++ test/TemplateCacheTest.js | 72 + test/TemplateCollectionTest.js | 207 ++ test/TemplateConfigTest.js | 310 ++- test/TemplateDataTest.js | 296 ++- test/TemplateEngineTest.js | 9 + test/TemplateFileSlugTest.js | 169 ++ test/TemplateGlobTest.js | 132 ++ test/TemplateLayoutPathResolverTest.js | 189 ++ test/TemplateLayoutTest.js | 132 +- test/TemplateMapTest.js | 1109 +++++++++++ test/TemplatePassthroughManagerTest.js | 128 ++ test/TemplatePassthroughTest.js | 221 +++ test/TemplatePathTest.js | 273 ++- test/TemplatePermalinkNoWriteTest.js | 8 + test/TemplatePermalinkTest.js | 59 +- test/TemplateRenderEJSTest.js | 110 ++ test/TemplateRenderHTMLTest.js | 51 + test/TemplateRenderHamlTest.js | 22 + test/TemplateRenderHandlebarsTest.js | 252 +++ test/TemplateRenderJSTLTest.js | 41 + test/TemplateRenderJavaScriptTest.js | 221 +++ test/TemplateRenderLiquidTest.js | 736 ++++++++ test/TemplateRenderMarkdownTest.js | 333 ++++ test/TemplateRenderMustacheTest.js | 80 + test/TemplateRenderNunjucksTest.js | 433 +++++ test/TemplateRenderPugTest.js | 148 ++ test/TemplateRenderTest.js | 434 +---- test/TemplateTest-JavaScript.js | 302 +++ test/TemplateTest.js | 1665 ++++++++++++++++- test/TemplateWriterTest.js | 673 ++++++- test/TestUtilityTest.js | 10 + test/UrlTest.js | 253 +++ test/Util/normalizeNewLines.js | 5 + test/stubs-337/data/xyz.json | 3 + .../multiple.ejs => stubs-337/src/empty.md} | 0 test/stubs-403/.eleventyignore | 1 + .../_includes/include.liquid} | 0 test/stubs-403/template.liquid | 0 test/stubs-413/date-frontmatter.md | 6 + test/stubs-475/_includes/layout.njk | 1 + .../transform-layout/transform-layout.njk | 4 + test/stubs/.eleventyignore | 3 +- test/stubs/2016-02-01-permalinkdate.liquid | 5 + test/stubs/_data/globalData2.js | 3 + test/stubs/_data/globalDataFn.js | 7 + test/stubs/_data/testDataEjs.json | 4 + test/stubs/_includes/default.ejs | 0 test/stubs/_includes/defaultLayout.ejs | 4 +- .../_includes/defaultLayoutLayoutContent.ejs | 10 + .../_includes/defaultLayout_layoutContent.ejs | 10 + test/stubs/_includes/included-data.html | 1 + test/stubs/_includes/included-relative.njk | 1 + test/stubs/_includes/included.nunj | 1 + test/stubs/_includes/layout-a.ejs | 4 +- test/stubs/_includes/layout-b.ejs | 4 +- test/stubs/_includes/layoutLiquid.liquid | 7 + .../_includes/layouts/div-wrapper-layout.njk | 1 + .../_includes/layouts/engineOverrides.njk | 4 + .../_includes/layouts/engineOverridesMd.njk | 6 + test/stubs/_includes/layouts/inasubdir.njk | 0 test/stubs/_includes/layouts/issue-115.liquid | 6 + .../_includes/layouts/layout-contentdump.njk | 5 + .../_includes/layouts/layout-inherit-a.njk | 5 + .../_includes/layouts/layout-inherit-b.njk | 6 + .../_includes/layouts/layout-inherit-c.njk | 6 + test/stubs/_includes/layouts/post.ejs | 0 .../layouts/templateMapCollection.njk | 5 + .../_includes/layouts/usescollection.ejs | 0 test/stubs/_includes/multiple.ejs | 0 test/stubs/_includes/multiple.md | 0 test/stubs/_includes/mylocallayout.njk | 1 + .../stubs/_includes/permalink-data-layout.njk | 4 + .../permalink-in-layout/layout-fileslug.ejs | 4 + .../_includes/permalink-in-layout/layout.ejs | 4 + test/stubs/_includes/scopeleak.liquid | 1 + test/stubs/_includes/subfolder/included.hbs | 1 + test/stubs/_includes/subfolder/included.html | 1 + .../stubs/_includes/subfolder/included.liquid | 1 + .../_includes/subfolder/included.mustache | 1 + test/stubs/_includes/subfolder/included.nunj | 1 + test/stubs/_includes/test.js | 1 + test/stubs/_layouts/layoutsdefault.ejs | 0 test/stubs/broken-config.js | 9 + test/stubs/buffer.11ty.js | 1 + test/stubs/class-async-data-fn.11ty.js | 17 + test/stubs/class-async-filter.11ty.js | 17 + test/stubs/class-async.11ty.js | 7 + test/stubs/class-buffer.11ty.js | 17 + test/stubs/class-data-filter.11ty.js | 13 + test/stubs/class-data-fn-filter.11ty.js | 13 + test/stubs/class-data-fn-shorthand.11ty.js | 30 + test/stubs/class-data-fn.11ty.js | 13 + .../class-data-permalink-async-fn.11ty.js | 20 + .../stubs/class-data-permalink-buffer.11ty.js | 13 + .../class-data-permalink-fn-buffer.11ty.js | 14 + .../class-data-permalink-fn-filter.11ty.js | 16 + test/stubs/class-data-permalink-fn.11ty.js | 14 + test/stubs/class-data-permalink.11ty.js | 13 + test/stubs/class-data-renderdata.11ty.js | 24 + test/stubs/class-data.11ty.js | 13 + test/stubs/class-filter.11ty.js | 17 + test/stubs/class-norender.11ty.js | 9 + test/stubs/class-with-dep-upstream.js | 1 + test/stubs/class-with-dep.11ty.js | 17 + test/stubs/class.11ty.js | 15 + test/stubs/classfields-data.11ty.js | 11 + test/stubs/collection-layout-wrap.njk | 5 + .../collection-layout/_includes/layout.ejs | 6 + test/stubs/collection-layout/dog1.ejs | 4 + test/stubs/collection-layout/template.ejs | 4 + test/stubs/collection-renderdata/dog.njk | 7 + test/stubs/collection-renderdata/template.njk | 1 + test/stubs/collection-slug/dog1.njk | 4 + test/stubs/collection-slug/template.njk | 1 + .../collection-template/_includes/layout.ejs | 3 + test/stubs/collection-template/dog1.ejs | 4 + test/stubs/collection-template/template.ejs | 7 + test/stubs/collection/test1.md | 8 + test/stubs/collection/test2.md | 5 + test/stubs/collection/test3.md | 7 + test/stubs/collection/test4.md | 5 + test/stubs/collection/test5.md | 5 + test/stubs/collection/test6.html | 1 + test/stubs/collection/test7.njk | 1 + test/stubs/collection2/test1.md | 9 + test/stubs/collection2/test2.md | 8 + .../component-async/component.11tydata.js | 9 + test/stubs/component-async/component.njk | 1 + test/stubs/component/component.11tydata.js | 5 + test/stubs/component/component.11tydata.json | 4 + test/stubs/component/component.json | 3 +- test/stubs/config-deps-upstream.js | 1 + test/stubs/config-deps.js | 6 + test/stubs/config-promise.js | 5 + test/stubs/config.js | 49 +- .../template-excerpt-comment.njk | 6 + .../custom-frontmatter/template-newline1.njk | 5 + .../custom-frontmatter/template-newline2.njk | 4 + .../custom-frontmatter/template-newline3.njk | 5 + .../custom-frontmatter/template-nonewline.njk | 5 + .../custom-frontmatter/template-toml.njk | 4 + test/stubs/custom-frontmatter/template.njk | 6 + test/stubs/data-cascade/template.11tydata.js | 8 + test/stubs/data-cascade/template.njk | 9 + test/stubs/datafiledoesnotexist/template.njk | 0 test/stubs/dates/2018-01-01-file5.md | 3 + test/stubs/dates/file1.md | 5 + test/stubs/dates/file2.md | 4 + test/stubs/dates/file2b.md | 4 + test/stubs/dates/file3.md | 4 + test/stubs/dates/file4.md | 4 + test/stubs/dependencies/dep1.js | 1 + test/stubs/dependencies/dep2.js | 1 + test/stubs/dependencies/two-deps.11ty.js | 2 + test/stubs/deps/dep1.js | 1 + test/stubs/deps/dep2.js | 1 + test/stubs/dynamic-permalink/test.njk | 4 + test/stubs/eleventyExcludeFromCollections.njk | 8 + test/stubs/exitCode/failure.njk | 1 + test/stubs/exports-flatdata.11ty.js | 6 + test/stubs/fileslug.11ty.js | 3 + test/stubs/firstdir/seconddir/component.njk | 0 test/stubs/frontmatter-date/test.liquid | 4 + test/stubs/frontmatter-date/test.njk | 4 + test/stubs/function-arrow.11ty.js | 1 + test/stubs/function-async.11ty.js | 7 + test/stubs/function-buffer.11ty.js | 3 + test/stubs/function-filter.11ty.js | 9 + test/stubs/function-markdown.11ty.js | 3 + test/stubs/function-prototype.11ty.js | 17 + test/stubs/function.11ty.js | 3 + test/stubs/glob-pages/about.md | 5 + test/stubs/glob-pages/contact.md | 5 + test/stubs/glob-pages/home.md | 5 + test/stubs/global-dash-variable.liquid | 4 + test/stubs/globby/_includes/include.html | 0 test/stubs/globby/test.html | 0 test/stubs/ignore1/ignoredFolder/ignored.md | 1 + test/stubs/ignore2/.gitignore | 1 + test/stubs/ignore2/ignoredFolder/ignored.md | 1 + test/stubs/ignore3/.eleventyignore | 3 + test/stubs/ignore3/ignoredFolder/ignored.md | 1 + test/stubs/ignore4/.eleventyignore | 3 + test/stubs/ignore4/.gitignore | 1 + test/stubs/ignore4/ignoredFolder/ignored.md | 1 + test/stubs/ignore5/.gitignore | 0 test/stubs/ignore5/ignoredFolder/ignored.md | 1 + test/stubs/ignore6/.eleventyignore | 3 + test/stubs/ignore6/.gitignore | 0 test/stubs/ignore6/ignoredFolder/ignored.md | 1 + test/stubs/ignore7/.gitignore | 1 + test/stubs/ignore7/ignoredFolder/ignored.md | 1 + test/stubs/ignore8/.eleventyignore | 3 + test/stubs/ignore8/.gitignore | 1 + test/stubs/ignore8/ignoredFolder/ignored.md | 1 + test/stubs/ignorelocalroot/.eleventyignore | 1 + test/stubs/img/stub.md | 1 + test/stubs/included.hbs | 1 + test/stubs/included.liquid | 1 + test/stubs/included.pug | 1 + test/stubs/includedrelative.mustache | 1 + test/stubs/includesemptystring.ejs | 0 test/stubs/index.ejs | 0 test/stubs/issue-115/index-with-layout.liquid | 7 + test/stubs/issue-115/index.liquid | 12 + test/stubs/issue-115/template-bars.liquid | 6 + test/stubs/issue-115/template-foos.liquid | 6 + test/stubs/issue-135/template.json | 16 + test/stubs/issue-135/template.njk | 8 + test/stubs/issue-522/excluded.md | 7 + test/stubs/issue-522/template.md | 1 + test/stubs/issue-95/cat.md | 6 + test/stubs/issue-95/notacat.md | 5 + .../_includes/test.njk | 4 + test/stubs/layout-permalink-difflang/test.md | 6 + test/stubs/layout-relative.pug | 3 + test/stubs/layoutsemptystring.ejs | 0 .../local-data-tags/component.11tydata.js | 3 + test/stubs/local-data-tags/component.njk | 6 + test/stubs/multiple-ignores/.eleventyignore | 3 + .../multiple-ignores/ignoredFolder/ignored.md | 0 .../subfolder/.eleventyignore | 3 + .../subfolder/ignoredFolder2/ignored2.md | 0 test/stubs/multipleexports-promises.11ty.js | 15 + test/stubs/multipleexports.11ty.js | 7 + test/stubs/njk-relative/dir/base.njk | 1 + test/stubs/njk-relative/dir/imports.njk | 1 + test/stubs/njk-relative/dir/included.njk | 1 + .../njk-relative/dir/unique-include-123.njk | 1 + test/stubs/object-norender.11ty.js | 5 + test/stubs/object.11ty.js | 8 + test/stubs/oneinstance.11ty.js | 18 + test/stubs/overrides/layout.njk | 6 + test/stubs/overrides/layoutfalse.njk | 6 + test/stubs/overrides/test-bypass.md | 6 + test/stubs/overrides/test-ejs.liquid | 5 + test/stubs/overrides/test-empty.html | 5 + test/stubs/overrides/test-empty.md | 6 + test/stubs/overrides/test-error.njk | 5 + test/stubs/overrides/test-md.liquid | 4 + test/stubs/overrides/test-multiple.md | 6 + test/stubs/overrides/test-multiple2.njk | 5 + test/stubs/overrides/test.ejs | 5 + test/stubs/overrides/test.html | 5 + test/stubs/overrides/test.md | 6 + test/stubs/overrides/test2.md | 7 + .../page-target-collections/paginateall.njk | 10 + .../page-target-collections/tagpages.njk | 10 + .../page-target-collections/tagpagesall.njk | 11 + .../consumer.njk | 1 + .../paged-downstream.njk | 6 + .../paged-main.njk | 9 + .../test1.njk | 6 + .../test2.njk | 6 + .../test3.njk | 6 + .../collection-apply-to-all/consumer.njk | 1 + .../paged/collection-apply-to-all/main.njk | 9 + .../paged/collection-apply-to-all/test1.njk | 6 + .../paged/collection-apply-to-all/test2.njk | 6 + .../paged/collection-apply-to-all/test3.njk | 6 + test/stubs/paged/collection/consumer.njk | 1 + test/stubs/paged/collection/main.njk | 8 + test/stubs/paged/collection/test1.njk | 6 + test/stubs/paged/collection/test2.njk | 6 + test/stubs/paged/collection/test3.njk | 6 + test/stubs/paged/pagedinlinedata-reverse.njk | 16 + test/stubs/paged/pagedobject.njk | 16 + test/stubs/paged/pagedobjectfilterarray.njk | 18 + test/stubs/paged/pagedobjectfilterstring.njk | 17 + test/stubs/paged/pagedobjectvalues.njk | 17 + test/stubs/paged/pagedpermalinkif.liquid | 12 + test/stubs/paged/pagedpermalinkif.njk | 12 + test/stubs/pagedate.liquid | 1 + test/stubs/pagedate.njk | 1 + test/stubs/pagedateutc.njk | 1 + .../pagination-templatecontent/index.njk | 9 + .../pagination-templatecontent/post-1.md | 6 + .../pagination-templatecontent/post-2.md | 6 + test/stubs/permalink-conflicts-false/test1.md | 9 + test/stubs/permalink-conflicts-false/test2.md | 9 + test/stubs/permalink-conflicts/test1.md | 9 + test/stubs/permalink-conflicts/test2.md | 9 + test/stubs/permalink-conflicts/test3.md | 9 + test/stubs/permalink-data-layout/test.json | 3 + test/stubs/permalink-data-layout/test.njk | 1 + test/stubs/permalink-false/test.md | 5 + test/stubs/permalink-in-layout-fileslug.ejs | 4 + test/stubs/permalink-in-layout.ejs | 4 + test/stubs/permalink-markdown-override.md | 7 + test/stubs/permalink-markdown-var.md | 6 + test/stubs/permalink-markdown.md | 6 + test/stubs/permalinkdate.liquid | 6 + test/stubs/posts/post1.njk | 1 + test/stubs/posts/posts.json | 3 + test/stubs/posts/posts.njk | 1 + .../prematureTemplateContent/test.11ty.js | 3 + test/stubs/prematureTemplateContent/test.ejs | 1 + test/stubs/prematureTemplateContent/test.haml | 1 + test/stubs/prematureTemplateContent/test.hbs | 1 + .../prematureTemplateContent/test.liquid | 1 + test/stubs/prematureTemplateContent/test.md | 1 + .../prematureTemplateContent/test.mustache | 1 + test/stubs/prematureTemplateContent/test.njk | 1 + test/stubs/prematureTemplateContent/test.pug | 1 + test/stubs/promise.11ty.js | 5 + test/stubs/relative-ejs/dir/included.ejs | 1 + .../stubs/relative-liquid/dir/included.liquid | 1 + test/stubs/renderData/renderData.md | 7 + test/stubs/renderData/renderData.njk | 7 + .../reuse-permalink/reuse-permalink.json | 3 + test/stubs/reuse-permalink/test1.liquid | 6 + test/stubs/string.11ty.js | 1 + .../test.njk | 14 + .../tagged-pagination-multiples/test.njk | 12 + test/stubs/template-passthrough/.htaccess | 0 test/stubs/template-passthrough/img.jpg | 0 .../template-passthrough/src/views/avatar.png | 0 .../static/nested/test-nested.css | 0 .../template-passthrough/static/test.css | 0 .../stubs/template-passthrough/static/test.js | 0 test/stubs/template-passthrough2/.htaccess | 0 test/stubs/template-passthrough2/img.jpg | 0 .../src/views/avatar.png | 0 .../static/nested/test-nested.css | 0 .../template-passthrough2/static/test.css | 0 .../template-passthrough2/static/test.js | 0 test/stubs/templateFrontMatter.ejs | 2 +- test/stubs/templateFrontMatterJs.ejs | 10 + test/stubs/templateFrontMatterJson.ejs | 8 + .../_includes/layout.njk | 1 + .../_includes/layout.njk | 1 + .../paged-cfg-permalink.md | 10 + .../paged-cfg-tagged-apply-to-all.md | 12 + ...paged-cfg-tagged-permalink-apply-to-all.md | 13 + .../paged-cfg-tagged-permalink.md | 12 + .../templateMapCollection/paged-cfg-tagged.md | 11 + test/stubs/templateMapCollection/paged-cfg.md | 8 + .../paged-tag-dogs-templateContent-alias.md | 13 + .../paged-tag-dogs-templateContent.md | 12 + test/stubs/templateMapCollection/paged-tag.md | 8 + .../templateMapCollection/templateContent.md | 7 + test/stubs/templateMapCollection/test1.md | 8 + test/stubs/templateMapCollection/test2.md | 5 + test/stubs/templateMapCollection/test3.md | 5 + test/stubs/templateMapCollection/test4.md | 8 + test/stubs/templateMapCollection/test5.md | 5 + .../templateMapCollection/testWithLayout.md | 5 + test/stubs/templateWithLayout.liquid | 8 + test/stubs/templateWithLayoutBackCompat.ejs | 8 + test/stubs/templateWithLayoutContent.ejs | 7 + .../templatetest-frontmatter/multiple.njk | 6 + .../stubs/templatetest-frontmatter/single.njk | 4 + test/stubs/test-override-js-markdown.11ty.js | 14 + test/stubs/transform-pages/template.njk | 15 + test/stubs/use-collection.11ty.js | 5 + test/stubs/viperhtml.11ty.js | 9 + test/stubs/vue-layout.11ty.js | 20 + test/stubs/vue.11ty.js | 16 + test/stubs/writeTestJS/sample.js | 1 + test/stubs/writeTestJS/test.11ty.js | 1 + test/stubs/writeTestMarkdown/sample.md | 1 + test/stubs/writeTestMarkdown/sample2.markdown | 1 + 474 files changed, 19561 insertions(+), 2058 deletions(-) create mode 100644 .eslintrc.js create mode 100644 .github/FUNDING.yml create mode 100644 .github/ISSUE_TEMPLATE/i-have-a-question-about-eleventy.md create mode 100644 .github/ISSUE_TEMPLATE/i-m-having-a-problem-with-eleventy.md create mode 100644 .github/ISSUE_TEMPLATE/i-want-eleventy-do-to-this-new-thing.md create mode 100644 .nvmrc create mode 100644 CODE_OF_CONDUCT.md create mode 100644 docs-src/.eleventy.docs.js create mode 100644 docs-src/_data/coverage.json create mode 100644 docs-src/coverage.njk create mode 100644 docs/collections.md create mode 100644 docs/copy.md create mode 100644 docs/coverage.md create mode 100644 docs/engines.md create mode 100644 docs/engines/ejs.md create mode 100644 docs/engines/haml.md create mode 100644 docs/engines/handlebars.md create mode 100644 docs/engines/html.md create mode 100644 docs/engines/jstl.md create mode 100644 docs/engines/liquid.md create mode 100644 docs/engines/markdown.md create mode 100644 docs/engines/mustache.md create mode 100644 docs/engines/nunjucks.md create mode 100644 docs/engines/pug.md create mode 100644 docs/filters.md create mode 100644 docs/install-local.md create mode 100644 docs/layouts.md create mode 100644 docs/logo-github.png create mode 100644 docs/meta-release.md create mode 100644 docs/pitfalls.md create mode 100644 docs/plugins.md mode change 100644 => 100755 package.json create mode 100644 src/Benchmark.js create mode 100644 src/BenchmarkGroup.js create mode 100644 src/BenchmarkManager.js create mode 100644 src/Config.js create mode 100644 src/EleventyBaseError.js create mode 100644 src/EleventyCommandCheck.js create mode 100644 src/EleventyConfig.js delete mode 100644 src/EleventyError.js create mode 100644 src/EleventyErrorHandler.js create mode 100644 src/EleventyErrorUtil.js create mode 100644 src/EleventyExtensionMap.js create mode 100644 src/EleventyFiles.js create mode 100644 src/EleventyServe.js create mode 100644 src/EleventyWatchTargets.js create mode 100644 src/Engines/JavaScriptTemplateLiteral.js create mode 100644 src/Errors/TemplateContentPrematureUseError.js create mode 100644 src/Errors/UsingCircularTemplateContentReferenceError.js create mode 100644 src/Filters/Slug.js create mode 100644 src/Filters/Url.js create mode 100644 src/TemplateCache.js create mode 100644 src/TemplateCollection.js create mode 100644 src/TemplateContent.js create mode 100644 src/TemplateFileSlug.js create mode 100644 src/TemplateGlob.js create mode 100644 src/TemplateLayoutPathResolver.js create mode 100644 src/TemplateMap.js create mode 100644 src/TemplatePassthrough.js create mode 100644 src/TemplatePassthroughManager.js create mode 100644 src/TemplatePermalinkNoWrite.js create mode 100644 src/UserConfig.js create mode 100644 src/Util/Capitalize.js create mode 100644 src/Util/Merge.js create mode 100644 src/Util/Pluralize.js create mode 100644 src/Util/Sortable.js delete mode 100644 src/VersionCheck.js create mode 100644 test/BenchmarkTest.js create mode 100644 test/CapitalizeTest.js create mode 100644 test/EleventyCommandCheckTest.js create mode 100644 test/EleventyConfigTest.js create mode 100644 test/EleventyErrorHandlerTest.js create mode 100644 test/EleventyExtensionMapTest.js create mode 100644 test/EleventyFilesTest.js create mode 100644 test/EleventyServeTest.js create mode 100644 test/EleventyWatchTargetsTest.js create mode 100644 test/MergeTest.js create mode 100644 test/PluralizeTest.js create mode 100644 test/SortableTest.js create mode 100644 test/TemplateCacheTest.js create mode 100644 test/TemplateCollectionTest.js create mode 100644 test/TemplateFileSlugTest.js create mode 100644 test/TemplateGlobTest.js create mode 100644 test/TemplateLayoutPathResolverTest.js create mode 100644 test/TemplateMapTest.js create mode 100644 test/TemplatePassthroughManagerTest.js create mode 100644 test/TemplatePassthroughTest.js create mode 100644 test/TemplatePermalinkNoWriteTest.js create mode 100644 test/TemplateRenderEJSTest.js create mode 100644 test/TemplateRenderHTMLTest.js create mode 100644 test/TemplateRenderHamlTest.js create mode 100644 test/TemplateRenderHandlebarsTest.js create mode 100644 test/TemplateRenderJSTLTest.js create mode 100644 test/TemplateRenderJavaScriptTest.js create mode 100644 test/TemplateRenderLiquidTest.js create mode 100644 test/TemplateRenderMarkdownTest.js create mode 100644 test/TemplateRenderMustacheTest.js create mode 100644 test/TemplateRenderNunjucksTest.js create mode 100644 test/TemplateRenderPugTest.js create mode 100644 test/TemplateTest-JavaScript.js create mode 100644 test/TestUtilityTest.js create mode 100644 test/UrlTest.js create mode 100644 test/Util/normalizeNewLines.js create mode 100644 test/stubs-337/data/xyz.json rename test/{stubs/multiple.ejs => stubs-337/src/empty.md} (100%) create mode 100644 test/stubs-403/.eleventyignore rename test/{stubs/multiple.md => stubs-403/_includes/include.liquid} (100%) create mode 100644 test/stubs-403/template.liquid create mode 100644 test/stubs-413/date-frontmatter.md create mode 100644 test/stubs-475/_includes/layout.njk create mode 100644 test/stubs-475/transform-layout/transform-layout.njk create mode 100644 test/stubs/2016-02-01-permalinkdate.liquid create mode 100644 test/stubs/_data/globalData2.js create mode 100644 test/stubs/_data/globalDataFn.js create mode 100644 test/stubs/_data/testDataEjs.json create mode 100644 test/stubs/_includes/default.ejs create mode 100644 test/stubs/_includes/defaultLayoutLayoutContent.ejs create mode 100644 test/stubs/_includes/defaultLayout_layoutContent.ejs create mode 100644 test/stubs/_includes/included-data.html create mode 100644 test/stubs/_includes/included-relative.njk create mode 100644 test/stubs/_includes/included.nunj create mode 100644 test/stubs/_includes/layoutLiquid.liquid create mode 100644 test/stubs/_includes/layouts/div-wrapper-layout.njk create mode 100644 test/stubs/_includes/layouts/engineOverrides.njk create mode 100644 test/stubs/_includes/layouts/engineOverridesMd.njk create mode 100644 test/stubs/_includes/layouts/inasubdir.njk create mode 100644 test/stubs/_includes/layouts/issue-115.liquid create mode 100644 test/stubs/_includes/layouts/layout-contentdump.njk create mode 100644 test/stubs/_includes/layouts/layout-inherit-a.njk create mode 100644 test/stubs/_includes/layouts/layout-inherit-b.njk create mode 100644 test/stubs/_includes/layouts/layout-inherit-c.njk create mode 100644 test/stubs/_includes/layouts/post.ejs create mode 100644 test/stubs/_includes/layouts/templateMapCollection.njk create mode 100644 test/stubs/_includes/layouts/usescollection.ejs create mode 100644 test/stubs/_includes/multiple.ejs create mode 100644 test/stubs/_includes/multiple.md create mode 100644 test/stubs/_includes/mylocallayout.njk create mode 100644 test/stubs/_includes/permalink-data-layout.njk create mode 100644 test/stubs/_includes/permalink-in-layout/layout-fileslug.ejs create mode 100644 test/stubs/_includes/permalink-in-layout/layout.ejs create mode 100644 test/stubs/_includes/scopeleak.liquid create mode 100644 test/stubs/_includes/subfolder/included.hbs create mode 100644 test/stubs/_includes/subfolder/included.html create mode 100644 test/stubs/_includes/subfolder/included.liquid create mode 100644 test/stubs/_includes/subfolder/included.mustache create mode 100644 test/stubs/_includes/subfolder/included.nunj create mode 100644 test/stubs/_includes/test.js create mode 100644 test/stubs/_layouts/layoutsdefault.ejs create mode 100644 test/stubs/broken-config.js create mode 100644 test/stubs/buffer.11ty.js create mode 100644 test/stubs/class-async-data-fn.11ty.js create mode 100644 test/stubs/class-async-filter.11ty.js create mode 100644 test/stubs/class-async.11ty.js create mode 100644 test/stubs/class-buffer.11ty.js create mode 100644 test/stubs/class-data-filter.11ty.js create mode 100644 test/stubs/class-data-fn-filter.11ty.js create mode 100644 test/stubs/class-data-fn-shorthand.11ty.js create mode 100644 test/stubs/class-data-fn.11ty.js create mode 100644 test/stubs/class-data-permalink-async-fn.11ty.js create mode 100644 test/stubs/class-data-permalink-buffer.11ty.js create mode 100644 test/stubs/class-data-permalink-fn-buffer.11ty.js create mode 100644 test/stubs/class-data-permalink-fn-filter.11ty.js create mode 100644 test/stubs/class-data-permalink-fn.11ty.js create mode 100644 test/stubs/class-data-permalink.11ty.js create mode 100644 test/stubs/class-data-renderdata.11ty.js create mode 100644 test/stubs/class-data.11ty.js create mode 100644 test/stubs/class-filter.11ty.js create mode 100644 test/stubs/class-norender.11ty.js create mode 100644 test/stubs/class-with-dep-upstream.js create mode 100644 test/stubs/class-with-dep.11ty.js create mode 100644 test/stubs/class.11ty.js create mode 100644 test/stubs/classfields-data.11ty.js create mode 100644 test/stubs/collection-layout-wrap.njk create mode 100644 test/stubs/collection-layout/_includes/layout.ejs create mode 100644 test/stubs/collection-layout/dog1.ejs create mode 100644 test/stubs/collection-layout/template.ejs create mode 100644 test/stubs/collection-renderdata/dog.njk create mode 100644 test/stubs/collection-renderdata/template.njk create mode 100644 test/stubs/collection-slug/dog1.njk create mode 100644 test/stubs/collection-slug/template.njk create mode 100644 test/stubs/collection-template/_includes/layout.ejs create mode 100644 test/stubs/collection-template/dog1.ejs create mode 100644 test/stubs/collection-template/template.ejs create mode 100644 test/stubs/collection/test1.md create mode 100644 test/stubs/collection/test2.md create mode 100644 test/stubs/collection/test3.md create mode 100644 test/stubs/collection/test4.md create mode 100644 test/stubs/collection/test5.md create mode 100644 test/stubs/collection/test6.html create mode 100644 test/stubs/collection/test7.njk create mode 100644 test/stubs/collection2/test1.md create mode 100644 test/stubs/collection2/test2.md create mode 100644 test/stubs/component-async/component.11tydata.js create mode 100644 test/stubs/component-async/component.njk create mode 100644 test/stubs/component/component.11tydata.js create mode 100644 test/stubs/component/component.11tydata.json create mode 100644 test/stubs/config-deps-upstream.js create mode 100644 test/stubs/config-deps.js create mode 100644 test/stubs/config-promise.js create mode 100644 test/stubs/custom-frontmatter/template-excerpt-comment.njk create mode 100644 test/stubs/custom-frontmatter/template-newline1.njk create mode 100644 test/stubs/custom-frontmatter/template-newline2.njk create mode 100644 test/stubs/custom-frontmatter/template-newline3.njk create mode 100644 test/stubs/custom-frontmatter/template-nonewline.njk create mode 100644 test/stubs/custom-frontmatter/template-toml.njk create mode 100644 test/stubs/custom-frontmatter/template.njk create mode 100644 test/stubs/data-cascade/template.11tydata.js create mode 100644 test/stubs/data-cascade/template.njk create mode 100644 test/stubs/datafiledoesnotexist/template.njk create mode 100644 test/stubs/dates/2018-01-01-file5.md create mode 100644 test/stubs/dates/file1.md create mode 100644 test/stubs/dates/file2.md create mode 100644 test/stubs/dates/file2b.md create mode 100644 test/stubs/dates/file3.md create mode 100644 test/stubs/dates/file4.md create mode 100644 test/stubs/dependencies/dep1.js create mode 100644 test/stubs/dependencies/dep2.js create mode 100644 test/stubs/dependencies/two-deps.11ty.js create mode 100644 test/stubs/deps/dep1.js create mode 100644 test/stubs/deps/dep2.js create mode 100644 test/stubs/dynamic-permalink/test.njk create mode 100644 test/stubs/eleventyExcludeFromCollections.njk create mode 100644 test/stubs/exitCode/failure.njk create mode 100644 test/stubs/exports-flatdata.11ty.js create mode 100644 test/stubs/fileslug.11ty.js create mode 100644 test/stubs/firstdir/seconddir/component.njk create mode 100644 test/stubs/frontmatter-date/test.liquid create mode 100644 test/stubs/frontmatter-date/test.njk create mode 100644 test/stubs/function-arrow.11ty.js create mode 100644 test/stubs/function-async.11ty.js create mode 100644 test/stubs/function-buffer.11ty.js create mode 100644 test/stubs/function-filter.11ty.js create mode 100644 test/stubs/function-markdown.11ty.js create mode 100644 test/stubs/function-prototype.11ty.js create mode 100644 test/stubs/function.11ty.js create mode 100644 test/stubs/glob-pages/about.md create mode 100644 test/stubs/glob-pages/contact.md create mode 100644 test/stubs/glob-pages/home.md create mode 100644 test/stubs/global-dash-variable.liquid create mode 100644 test/stubs/globby/_includes/include.html create mode 100644 test/stubs/globby/test.html create mode 100644 test/stubs/ignore1/ignoredFolder/ignored.md create mode 100644 test/stubs/ignore2/.gitignore create mode 100644 test/stubs/ignore2/ignoredFolder/ignored.md create mode 100644 test/stubs/ignore3/.eleventyignore create mode 100644 test/stubs/ignore3/ignoredFolder/ignored.md create mode 100644 test/stubs/ignore4/.eleventyignore create mode 100644 test/stubs/ignore4/.gitignore create mode 100644 test/stubs/ignore4/ignoredFolder/ignored.md create mode 100644 test/stubs/ignore5/.gitignore create mode 100644 test/stubs/ignore5/ignoredFolder/ignored.md create mode 100644 test/stubs/ignore6/.eleventyignore create mode 100644 test/stubs/ignore6/.gitignore create mode 100644 test/stubs/ignore6/ignoredFolder/ignored.md create mode 100644 test/stubs/ignore7/.gitignore create mode 100644 test/stubs/ignore7/ignoredFolder/ignored.md create mode 100644 test/stubs/ignore8/.eleventyignore create mode 100644 test/stubs/ignore8/.gitignore create mode 100644 test/stubs/ignore8/ignoredFolder/ignored.md create mode 100644 test/stubs/ignorelocalroot/.eleventyignore create mode 100644 test/stubs/img/stub.md create mode 100644 test/stubs/included.hbs create mode 100644 test/stubs/included.liquid create mode 100644 test/stubs/included.pug create mode 100644 test/stubs/includedrelative.mustache create mode 100644 test/stubs/includesemptystring.ejs create mode 100644 test/stubs/index.ejs create mode 100644 test/stubs/issue-115/index-with-layout.liquid create mode 100644 test/stubs/issue-115/index.liquid create mode 100644 test/stubs/issue-115/template-bars.liquid create mode 100644 test/stubs/issue-115/template-foos.liquid create mode 100644 test/stubs/issue-135/template.json create mode 100644 test/stubs/issue-135/template.njk create mode 100644 test/stubs/issue-522/excluded.md create mode 100644 test/stubs/issue-522/template.md create mode 100644 test/stubs/issue-95/cat.md create mode 100644 test/stubs/issue-95/notacat.md create mode 100644 test/stubs/layout-permalink-difflang/_includes/test.njk create mode 100644 test/stubs/layout-permalink-difflang/test.md create mode 100644 test/stubs/layout-relative.pug create mode 100644 test/stubs/layoutsemptystring.ejs create mode 100644 test/stubs/local-data-tags/component.11tydata.js create mode 100644 test/stubs/local-data-tags/component.njk create mode 100644 test/stubs/multiple-ignores/.eleventyignore create mode 100644 test/stubs/multiple-ignores/ignoredFolder/ignored.md create mode 100644 test/stubs/multiple-ignores/subfolder/.eleventyignore create mode 100644 test/stubs/multiple-ignores/subfolder/ignoredFolder2/ignored2.md create mode 100644 test/stubs/multipleexports-promises.11ty.js create mode 100644 test/stubs/multipleexports.11ty.js create mode 100644 test/stubs/njk-relative/dir/base.njk create mode 100644 test/stubs/njk-relative/dir/imports.njk create mode 100644 test/stubs/njk-relative/dir/included.njk create mode 100644 test/stubs/njk-relative/dir/unique-include-123.njk create mode 100644 test/stubs/object-norender.11ty.js create mode 100644 test/stubs/object.11ty.js create mode 100644 test/stubs/oneinstance.11ty.js create mode 100644 test/stubs/overrides/layout.njk create mode 100644 test/stubs/overrides/layoutfalse.njk create mode 100644 test/stubs/overrides/test-bypass.md create mode 100644 test/stubs/overrides/test-ejs.liquid create mode 100644 test/stubs/overrides/test-empty.html create mode 100644 test/stubs/overrides/test-empty.md create mode 100644 test/stubs/overrides/test-error.njk create mode 100644 test/stubs/overrides/test-md.liquid create mode 100644 test/stubs/overrides/test-multiple.md create mode 100644 test/stubs/overrides/test-multiple2.njk create mode 100644 test/stubs/overrides/test.ejs create mode 100644 test/stubs/overrides/test.html create mode 100644 test/stubs/overrides/test.md create mode 100644 test/stubs/overrides/test2.md create mode 100644 test/stubs/page-target-collections/paginateall.njk create mode 100644 test/stubs/page-target-collections/tagpages.njk create mode 100644 test/stubs/page-target-collections/tagpagesall.njk create mode 100644 test/stubs/paged/cfg-collection-tag-cfg-collection/consumer.njk create mode 100644 test/stubs/paged/cfg-collection-tag-cfg-collection/paged-downstream.njk create mode 100644 test/stubs/paged/cfg-collection-tag-cfg-collection/paged-main.njk create mode 100644 test/stubs/paged/cfg-collection-tag-cfg-collection/test1.njk create mode 100644 test/stubs/paged/cfg-collection-tag-cfg-collection/test2.njk create mode 100644 test/stubs/paged/cfg-collection-tag-cfg-collection/test3.njk create mode 100644 test/stubs/paged/collection-apply-to-all/consumer.njk create mode 100644 test/stubs/paged/collection-apply-to-all/main.njk create mode 100644 test/stubs/paged/collection-apply-to-all/test1.njk create mode 100644 test/stubs/paged/collection-apply-to-all/test2.njk create mode 100644 test/stubs/paged/collection-apply-to-all/test3.njk create mode 100644 test/stubs/paged/collection/consumer.njk create mode 100644 test/stubs/paged/collection/main.njk create mode 100644 test/stubs/paged/collection/test1.njk create mode 100644 test/stubs/paged/collection/test2.njk create mode 100644 test/stubs/paged/collection/test3.njk create mode 100644 test/stubs/paged/pagedinlinedata-reverse.njk create mode 100644 test/stubs/paged/pagedobject.njk create mode 100644 test/stubs/paged/pagedobjectfilterarray.njk create mode 100644 test/stubs/paged/pagedobjectfilterstring.njk create mode 100644 test/stubs/paged/pagedobjectvalues.njk create mode 100644 test/stubs/paged/pagedpermalinkif.liquid create mode 100644 test/stubs/paged/pagedpermalinkif.njk create mode 100644 test/stubs/pagedate.liquid create mode 100644 test/stubs/pagedate.njk create mode 100644 test/stubs/pagedateutc.njk create mode 100644 test/stubs/pagination-templatecontent/index.njk create mode 100644 test/stubs/pagination-templatecontent/post-1.md create mode 100644 test/stubs/pagination-templatecontent/post-2.md create mode 100644 test/stubs/permalink-conflicts-false/test1.md create mode 100644 test/stubs/permalink-conflicts-false/test2.md create mode 100644 test/stubs/permalink-conflicts/test1.md create mode 100644 test/stubs/permalink-conflicts/test2.md create mode 100644 test/stubs/permalink-conflicts/test3.md create mode 100644 test/stubs/permalink-data-layout/test.json create mode 100644 test/stubs/permalink-data-layout/test.njk create mode 100644 test/stubs/permalink-false/test.md create mode 100644 test/stubs/permalink-in-layout-fileslug.ejs create mode 100644 test/stubs/permalink-in-layout.ejs create mode 100644 test/stubs/permalink-markdown-override.md create mode 100644 test/stubs/permalink-markdown-var.md create mode 100644 test/stubs/permalink-markdown.md create mode 100644 test/stubs/permalinkdate.liquid create mode 100644 test/stubs/posts/post1.njk create mode 100644 test/stubs/posts/posts.json create mode 100644 test/stubs/posts/posts.njk create mode 100644 test/stubs/prematureTemplateContent/test.11ty.js create mode 100644 test/stubs/prematureTemplateContent/test.ejs create mode 100644 test/stubs/prematureTemplateContent/test.haml create mode 100644 test/stubs/prematureTemplateContent/test.hbs create mode 100644 test/stubs/prematureTemplateContent/test.liquid create mode 100644 test/stubs/prematureTemplateContent/test.md create mode 100644 test/stubs/prematureTemplateContent/test.mustache create mode 100644 test/stubs/prematureTemplateContent/test.njk create mode 100644 test/stubs/prematureTemplateContent/test.pug create mode 100644 test/stubs/promise.11ty.js create mode 100644 test/stubs/relative-ejs/dir/included.ejs create mode 100644 test/stubs/relative-liquid/dir/included.liquid create mode 100644 test/stubs/renderData/renderData.md create mode 100644 test/stubs/renderData/renderData.njk create mode 100644 test/stubs/reuse-permalink/reuse-permalink.json create mode 100644 test/stubs/reuse-permalink/test1.liquid create mode 100644 test/stubs/string.11ty.js create mode 100644 test/stubs/tagged-pagination-multiples-layout/test.njk create mode 100644 test/stubs/tagged-pagination-multiples/test.njk create mode 100644 test/stubs/template-passthrough/.htaccess create mode 100755 test/stubs/template-passthrough/img.jpg create mode 100644 test/stubs/template-passthrough/src/views/avatar.png create mode 100755 test/stubs/template-passthrough/static/nested/test-nested.css create mode 100755 test/stubs/template-passthrough/static/test.css create mode 100755 test/stubs/template-passthrough/static/test.js create mode 100644 test/stubs/template-passthrough2/.htaccess create mode 100755 test/stubs/template-passthrough2/img.jpg create mode 100644 test/stubs/template-passthrough2/src/views/avatar.png create mode 100755 test/stubs/template-passthrough2/static/nested/test-nested.css create mode 100755 test/stubs/template-passthrough2/static/test.css create mode 100755 test/stubs/template-passthrough2/static/test.js create mode 100644 test/stubs/templateFrontMatterJs.ejs create mode 100644 test/stubs/templateFrontMatterJson.ejs create mode 100644 test/stubs/templateLayoutCacheDuplicates-b/_includes/layout.njk create mode 100644 test/stubs/templateLayoutCacheDuplicates/_includes/layout.njk create mode 100644 test/stubs/templateMapCollection/paged-cfg-permalink.md create mode 100644 test/stubs/templateMapCollection/paged-cfg-tagged-apply-to-all.md create mode 100644 test/stubs/templateMapCollection/paged-cfg-tagged-permalink-apply-to-all.md create mode 100644 test/stubs/templateMapCollection/paged-cfg-tagged-permalink.md create mode 100644 test/stubs/templateMapCollection/paged-cfg-tagged.md create mode 100644 test/stubs/templateMapCollection/paged-cfg.md create mode 100644 test/stubs/templateMapCollection/paged-tag-dogs-templateContent-alias.md create mode 100644 test/stubs/templateMapCollection/paged-tag-dogs-templateContent.md create mode 100644 test/stubs/templateMapCollection/paged-tag.md create mode 100644 test/stubs/templateMapCollection/templateContent.md create mode 100644 test/stubs/templateMapCollection/test1.md create mode 100644 test/stubs/templateMapCollection/test2.md create mode 100644 test/stubs/templateMapCollection/test3.md create mode 100644 test/stubs/templateMapCollection/test4.md create mode 100644 test/stubs/templateMapCollection/test5.md create mode 100644 test/stubs/templateMapCollection/testWithLayout.md create mode 100644 test/stubs/templateWithLayout.liquid create mode 100644 test/stubs/templateWithLayoutBackCompat.ejs create mode 100644 test/stubs/templateWithLayoutContent.ejs create mode 100644 test/stubs/templatetest-frontmatter/multiple.njk create mode 100644 test/stubs/templatetest-frontmatter/single.njk create mode 100644 test/stubs/test-override-js-markdown.11ty.js create mode 100644 test/stubs/transform-pages/template.njk create mode 100644 test/stubs/use-collection.11ty.js create mode 100644 test/stubs/viperhtml.11ty.js create mode 100644 test/stubs/vue-layout.11ty.js create mode 100644 test/stubs/vue.11ty.js create mode 100644 test/stubs/writeTestJS/sample.js create mode 100644 test/stubs/writeTestJS/test.11ty.js create mode 100644 test/stubs/writeTestMarkdown/sample.md create mode 100644 test/stubs/writeTestMarkdown/sample2.markdown diff --git a/.editorconfig b/.editorconfig index d41540467..cd3721ddd 100644 --- a/.editorconfig +++ b/.editorconfig @@ -4,6 +4,9 @@ root = true indent_style = space indent_size = 2 end_of_line = lf -insert_final_newline = true +insert_final_newline = false trim_trailing_whitespace = true charset = utf-8 + +[*.js] +insert_final_newline = true \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 000000000..62e8c1607 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,17 @@ +module.exports = { + env: { + es6: true, + node: true + }, + extends: "eslint:recommended", + parserOptions: { + sourceType: "module", + ecmaVersion: 2017 + }, + rules: { + indent: ["error", 2], + "linebreak-style": ["error", "unix"], + quotes: ["error", "double"], + semi: ["error", "always"] + } +}; diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..d1a85d512 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +# These are supported funding model platforms + +open_collective: 11ty diff --git a/.github/ISSUE_TEMPLATE/i-have-a-question-about-eleventy.md b/.github/ISSUE_TEMPLATE/i-have-a-question-about-eleventy.md new file mode 100644 index 000000000..d53991f24 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/i-have-a-question-about-eleventy.md @@ -0,0 +1,10 @@ +--- +name: I have a question about Eleventy +about: e.g. ā€œHow do I do this in Eleventy?ā€ or ā€œCan Eleventy do this?ā€ +title: '' +labels: education +assignees: '' + +--- + + diff --git a/.github/ISSUE_TEMPLATE/i-m-having-a-problem-with-eleventy.md b/.github/ISSUE_TEMPLATE/i-m-having-a-problem-with-eleventy.md new file mode 100644 index 000000000..29b3946be --- /dev/null +++ b/.github/ISSUE_TEMPLATE/i-m-having-a-problem-with-eleventy.md @@ -0,0 +1,31 @@ +--- +name: Iā€™m having a problem with Eleventy +about: Create a report to help us improve +title: '' +labels: needs-triage +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Environment:** + - OS and Version: [e.g. Windows/Mac/Linux] + - Eleventy Version [via `eleventy --version` or `npx @11ty/eleventy --version`] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/i-want-eleventy-do-to-this-new-thing.md b/.github/ISSUE_TEMPLATE/i-want-eleventy-do-to-this-new-thing.md new file mode 100644 index 000000000..22a1154ac --- /dev/null +++ b/.github/ISSUE_TEMPLATE/i-want-eleventy-do-to-this-new-thing.md @@ -0,0 +1,20 @@ +--- +name: I want Eleventy do to this new thing +about: Suggest an idea for this project +title: '' +labels: enhancement +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.gitignore b/.gitignore index f2eb169e6..7ac8ecb61 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ -dist/ _site/ node_modules/ -playground/ -package-lock.json +.nyc_output/ +coverage/ +package-lock.json \ No newline at end of file diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 000000000..9a037142a --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +10 \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index baa035c49..4468c9a8a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,16 @@ language: node_js node_js: - 8 + - 10 + - 12 before_script: - npm install script: npm run test branches: except: - gh-pages -sudo: false \ No newline at end of file +sudo: false +os: + - linux + - osx + - windows \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..b3824062e --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,46 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +- The use of sexualized language or imagery and unwelcome sexual attention or advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at eleventy@zachleat.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/LICENSE b/LICENSE index 11a338f15..b1726d081 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2017 Zach Leatherman @zachleat +Copyright (c) 2019 Zach Leatherman @zachleat Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index a73e85770..a85c9c439 100644 --- a/README.md +++ b/README.md @@ -1,173 +1,40 @@ -# eleventy šŸ•š +

eleventy Logo

-A static site generator. An alternative to Jekyll. Written in JavaScript. Transforms a directory of templates (of varying types) into HTML. +# eleventy šŸ•šāš”ļø -Works with: +A simpler static site generator. An alternative to Jekyll. Written in JavaScript. Transforms a directory of templates (of varying types) into HTML. -* HTML (`.html`) -* Markdown (`.md`) (using [`markdown-it`](https://github.com/markdown-it/markdown-it)) -* [Liquid](https://www.npmjs.com/package/liquidjs) (`.liquid`) (used by Jekyll) -* [Nunjucks](https://mozilla.github.io/nunjucks/) (`.njk`) -* [Handlebars](https://github.com/wycats/handlebars.js) (`.hbs`) -* [Mustache](https://github.com/janl/mustache.js/) (`.mustache`) -* [EJS](https://www.npmjs.com/package/ejs) (`.ejs`) -* [Haml](https://github.com/tj/haml.js) (`.haml`) -* [Pug](https://github.com/pugjs/pug) (formerly Jade, `.pug`) -* JavaScript Template Literals (`.jstl`) (\`strings with backticks\`) +Works with HTML, Markdown, Liquid, Nunjucks, Handlebars, Mustache, EJS, Haml, Pug, and JavaScript Template Literals. -## Getting Started +## āž” [Documentation](https://www.11ty.io/docs/) -### Installation +- Please star [this repo on GitHub](https://github.com/11ty/eleventy/)! +- Follow us on Twitter [@eleven_ty](https://twitter.com/eleven_ty) +- Support [11ty on Open Collective](https://opencollective.com/11ty) +- [11ty on npm](https://www.npmjs.com/org/11ty) +- [11ty on GitHub](https://github.com/11ty) +- [11ty/eleventy on Travis CI](https://travis-ci.org/11ty/eleventy) -Available [on npm](https://www.npmjs.com/package/eleventy-cli). +[![npm Version](https://img.shields.io/npm/v/@11ty/eleventy.svg?style=for-the-badge)](https://www.npmjs.com/package/@11ty/eleventy) [![GitHub issues](https://img.shields.io/github/issues/11ty/eleventy.svg?style=for-the-badge)](https://github.com/11ty/eleventy/issues) [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=for-the-badge)](https://github.com/prettier/prettier) [![npm Downloads](https://img.shields.io/npm/dt/@11ty/eleventy.svg?style=for-the-badge)](https://www.npmjs.com/package/@11ty/eleventy) -``` -npm install -g eleventy-cli -``` - -### Sample project - -Have a look at @Heydonā€™s lovely [Inclusive Web Design Checklist, converted to use `eleventy`](https://github.com/zachleat/eleventy-inclusive-design-checklist). The [original](https://github.com/Heydon/inclusive-design-checklist) project took a JSON file and converted it HTML with some one-off JavaScript. This uses eleventy to transform the data using a nunjucks template, resulting in a cleaner, templated setup. - -## Usage - -``` -# Searches the current directory, outputs to ./_site -eleventy - -# Equivalent to -eleventy --input=. --output=_site - -# Automatically run when template files change. -eleventy --watch - -# Use only a subset of template types -eleventy --formats=md,html,ejs - -# Find out the most up-to-date list of commands (there are more) -eleventy --help -``` - -### Examples - -#### Example: Default options - -``` -eleventy --input=. --output=_site -``` - -A `template.md` in the current directory will be rendered to `_site/template/index.html`. [Read more about Permalinks](docs/permalinks.md) - -#### Example: Same Input and Output - -Yes, you can use the same `input` and `output` directories, like so: - -``` -# Watch a directory for any changes to markdown files, then -# automatically parse and output as HTML files, respecting -# directory structure. - -eleventy --input=. --output=. --watch --formats=md -``` - -##### Exception: index.html Templates - -When the input and output directories are the same _and_ the source template is named `index.html`, it will output as `index-o.html` to avoid overwriting itself. This is a special case that only applies to `index.html` filenames. You can customize the `-o` suffix with the `htmlOutputSuffix` configuration option. - -``` -# Adds `-o` to index.html file names to avoid overwriting matching files. - -eleventy --input=. --output=. --formats=html -``` - -### Data (optional) - -#### Front Matter on any Template - -You may use front matter on any template file to add local data. Front matter looks like this: - -``` ---- -title: My page title ---- - - -ā€¦ -``` - -This allows you to assign data values right in the template itself. Here are a few front matter keys that we use for special things: - -* `permalink`: Add in front matter to change the output target of the current template. You can use template syntax for variables here. [Read more about Permalinks](docs/permalinks.md). -* `layout`: Wrap current template with a layout template found in the `_includes` folder. -* `pagination`: Enable to iterate over data. Output multiple HTML files from a single template. [Read more about Pagination](docs/pagination.md). - -#### Special Variables - -* `pkg`: The local projectā€™s `package.json` values. -* `pagination`: (When enabled in front matter) [Read more about Pagination](docs/pagination.md). - -#### Data Files - -Optionally add data files to add global static data available to all templates. Uses the `dir.data` configuration option. [Read more about Template Data Files](docs/data.md). - -### Ignore files (optional) - -Add an `.eleventyignore` file to the _root of your input directory_ for a new line-separated list of files that will not be processed. Paths listed in your projectā€™s `.gitignore` file are automatically ignored. - -### Configuration (optional) - -Add an `.eleventy.js` file to root directory of your project to override these configuration options with your own preferences. Example: +## Tests ``` -module.exports = { - dir: { - input: "views" - } -}; +npm run test ``` -| Configuration Option Key | Default Option | Valid Options | Command Line Override | Description | -| ------------------------ | ------------------------------------------------- | -------------------------------------------- | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `dir.input` | `.` | _Any valid directory._ | `--input` | Controls the top level directory inside which the templates should be found. | -| `dir.includes` | `_includes` | _Any valid directory inside of `dir.input`._ | N/A | Controls the directory inside which the template includes/extends/partials/etc can be found. | -| `dir.data` | `_data` | _Any valid directory inside of `dir.input`._ | N/A | Controls the directory inside which the global data template files, available to all templates, can be found. | -| `dir.output` | `_site` | _Any valid directory._ | `--output` | Controls the directory inside which the transformed finished templates can be found. | -| `dataTemplateEngine` | `liquid` | _A valid template engine_ or `false` | N/A | Run the `data.dir` global data files through this template engine before transforming it to JSON. | -| `markdownTemplateEngine` | `liquid` | _A valid template engine_ or `false` | N/A | Run markdown through this template engine before transforming it to HTML. | -| `htmlTemplateEngine` | `liquid` | _A valid template engine_ or `false` | N/A | Run HTML templates through this template engine before transforming it to (better) HTML. | -| `templateFormats` | `liquid,ejs, md,hbs, mustache,haml, pug,njk,html` | _Any combination of these_ | `--formats` | Specify which type of templates should be transformed. | -| `htmlOutputSuffix` | `-o` | `String` | N/A | If the input and output directory match, `index.html` files will have this suffix added to their output filename to prevent overwriting the template. | -| `filters` | `{}` | `Object` | N/A | Filters can transform output on a template. Take the format `function(str, outputPath) { return str; }`. For example, use a filter to format an HTML file with proper whitespace. | -| `handlebarsHelpers` | `{}` | `Object` | N/A | The helper functions passed to `Handlebars.registerHelper`. Helper names are keys, functions are the values. | -| `nunjucksFilters` | `{}` | `Object` | N/A | The helper functions passed to `nunjucksEnv.addFilter`. Helper names are keys, functions are the values. | +- We use the [ava JavaScript test runner](https://github.com/avajs/ava) ([Assertions documentation](https://github.com/avajs/ava/blob/master/docs/03-assertions.md)) +- ā„¹ļø To keep tests fast, thou shalt try to avoid writing files in tests. +- [Code Coverage Statistics](https://github.com/11ty/eleventy/blob/master/docs/coverage.md) +- [Benchmark for Performance Regressions](https://github.com/11ty/eleventy-benchmark) -### Template Engine Features +## Major Roadmapped Features -Here are the features tested with each template engine that use external files and thus are subject to setup and scaffolding. +- [Top Feature Requests](https://github.com/11ty/eleventy/issues?q=label%3Aneeds-votes+sort%3Areactions-%2B1-desc) (Add your own votes using the šŸ‘ reaction) +- [Documentation Requests](https://github.com/11ty/eleventy/issues?utf8=%E2%9C%93&q=is%3Aissue+sort%3Areactions-%2B1-desc+is%3Aclosed+label%3Adocumentation+label%3Aneeds-votes) (Add your own votes using the šŸ‘ reaction) +- [Top Bugs šŸ˜±](https://github.com/11ty/eleventy/issues?q=is%3Aissue+is%3Aopen+label%3Abug+sort%3Areactions-%2B1-desc) (Add your own votes using the šŸ‘ reaction) +- [Newest Bugs šŸ™€](https://github.com/11ty/eleventy/issues?q=is%3Aopen+is%3Aissue+label%3Abug) -| Engine | Feature | Syntax | -| ---------- | ----------------------------------- | --------------------------------------------------------------------------------- | -| ejs | āœ… Include (Preprocessor Directive) | `<% include /user/show %>` looks for `_includes/show/user.ejs` | -| ejs | āœ… Include (pass in Data) | `<%- include('/user/show', {user: 'Ava'}) %>` looks for `_includes/user/show.ejs` | -| Liquid | āœ… Include | `{% include 'show/user' %}` looks for `_includes/show/user.liquid` | -| Liquid | āœ… Include (pass in Data) | `{% include 'user' with 'Ava' %}` | -| Liquid | āœ… Include (pass in Data) | `{% include 'user', user1: 'Ava', user2: 'Bill' %}` | -| Mustache | āœ… Partials | `{{> user}}` looks for `_includes/user.mustache` | -| Handlebars | āœ… Partials | `{{> user}}` looks for `_includes/user.hbs` | -| Handlebars | āœ… Helpers | See `handlebarsHelpers` configuration option. | -| HAML | āŒ but šŸ”œ Filters | | -| Pug | āœ… Includes | `include /includedvar.pug` looks in `_includes/includedvar.pug` | -| Pug | āœ… Excludes | `extends /layout.pug` looks in `_includes/layout.pug` | -| Nunjucks | āœ… Includes | `{% include 'included.njk' %}` looks in `_includes/included.njk` | -| Nunjucks | āœ… Extends | `{% extends 'base.njk' %}` looks in `_includes/base.njk` | -| Nunjucks | āœ… Imports | `{% import 'macros.njk' %}` looks in `_includes/macros.njk` | -| Nunjucks | āœ… Filters | See `nunjucksFilters` configuration option. | +## Plugins -## Tests - -Build Status: [![Build Status](https://travis-ci.org/zachleat/eleventy.svg?branch=master)](https://travis-ci.org/zachleat/eleventy) - -``` -npm run test -npm run watch:test -``` +See the [official docs on plugins](https://www.11ty.io/docs/plugins/). diff --git a/cmd.js b/cmd.js index d7fc63052..c7bc2f964 100755 --- a/cmd.js +++ b/cmd.js @@ -1,25 +1,71 @@ #!/usr/bin/env node -const argv = require("minimist")(process.argv.slice(2)); -const EleventyNodeVersionCheck = require("./src/VersionCheck"); +const pkg = require("./package.json"); +const chalk = require("chalk"); // node 4+ +require("please-upgrade-node")(pkg, { + message: function(requiredVersion) { + return chalk.red( + `Eleventy requires Node ${requiredVersion}. Youā€™ll need to upgrade to use it!` + ); + } +}); + +if (process.env.DEBUG) { + require("time-require"); +} + +const EleventyErrorHandler = require("./src/EleventyErrorHandler"); -EleventyNodeVersionCheck().then(function() { +try { + const argv = require("minimist")(process.argv.slice(2)); const Eleventy = require("./src/Eleventy"); + const EleventyCommandCheck = require("./src/EleventyCommandCheck"); - let eleven = new Eleventy(argv.input, argv.output); - eleven.setFormats(argv.formats); - eleven.setIsVerbose(!argv.quiet); - - eleven.init().then(function() { - if (argv.version) { - console.log(eleven.getVersion()); - } else if (argv.help) { - console.log(eleven.getHelp()); - } else if (argv.watch) { - eleven.watch(); - } else { - eleven.write().then(function() { - console.log(eleven.getFinishedLog()); - }); - } + process.on("unhandledRejection", (error, promise) => { + EleventyErrorHandler.error(promise, "Unhandled rejection in promise"); }); -}); + process.on("uncaughtException", e => { + EleventyErrorHandler.fatal(e, "Uncaught exception"); + }); + process.on("rejectionHandled", promise => { + EleventyErrorHandler.warn( + promise, + "A promise rejection was handled asynchronously" + ); + }); + + let cmdCheck = new EleventyCommandCheck(argv); + cmdCheck.hasUnknownArguments(); + + let elev = new Eleventy(argv.input, argv.output); + elev.setConfigPathOverride(argv.config); + elev.setPathPrefix(argv.pathprefix); + elev.setDryRun(argv.dryrun); + elev.setPassthroughAll(argv.passthroughall); + elev.setFormats(argv.formats); + + let isVerbose = process.env.DEBUG ? false : !argv.quiet; + elev.setIsVerbose(isVerbose); + + // careful, we canā€™t use async/await here to error properly + // with old node versions in `please-upgrade-node` above. + elev + .init() + .then(function() { + if (argv.version) { + console.log(elev.getVersion()); + } else if (argv.help) { + console.log(elev.getHelp()); + } else if (argv.serve) { + elev.watch().then(function() { + elev.serve(argv.port); + }); + } else if (argv.watch) { + elev.watch(); + } else { + elev.write(); + } + }) + .catch(EleventyErrorHandler.fatal); +} catch (e) { + EleventyErrorHandler.fatal(e, "Eleventy fatal error"); +} diff --git a/config.js b/config.js index b5ecdd9c7..8f8374b4e 100644 --- a/config.js +++ b/config.js @@ -1,42 +1,50 @@ -const slugify = require("slugify"); +const urlFilter = require("./src/Filters/Url"); +const slugFilter = require("./src/Filters/Slug"); -module.exports = { - templateFormats: [ - "liquid", - "ejs", - "md", - "hbs", - "mustache", - "haml", - "pug", - "njk", - "html", - "jstl" - ], - markdownTemplateEngine: "liquid", - htmlTemplateEngine: "liquid", - dataTemplateEngine: "liquid", - htmlOutputSuffix: "-o", - keys: { - package: "pkg", - layout: "layout", - permalink: "permalink", - permalinkRoot: "permalinkBypassOutputDir" - }, - dir: { - input: ".", - includes: "_includes", - data: "_data", - output: "_site" - }, - filters: {}, - handlebarsHelpers: {}, - nunjucksFilters: { - slug: str => { - return slugify(str, { - replacement: "-", - lower: true - }); - } - } +module.exports = function(config) { + config.addFilter("slug", slugFilter); + config.addFilter("url", urlFilter); + + return { + templateFormats: [ + "liquid", + "ejs", + "md", + "hbs", + "mustache", + "haml", + "pug", + "njk", + "html", + "jstl", + "11ty.js" + ], + // if your site lives in a subdirectory, change this + pathPrefix: "/", + markdownTemplateEngine: "liquid", + htmlTemplateEngine: "liquid", + dataTemplateEngine: "liquid", + passthroughFileCopy: true, + htmlOutputSuffix: "-o", + jsDataFileSuffix: ".11tydata", + keys: { + package: "pkg", + layout: "layout", + permalink: "permalink", + permalinkRoot: "permalinkBypassOutputDir", + engineOverride: "templateEngineOverride" + }, + dir: { + input: ".", + includes: "_includes", + data: "_data", + output: "_site" + }, + // deprecated, use config.addTransform + filters: {}, + // deprecated, use config.addHandlebarsHelper + handlebarsHelpers: {}, + // deprecated, use config.addNunjucksFilter + nunjucksFilters: {} + }; }; diff --git a/docs-src/.eleventy.docs.js b/docs-src/.eleventy.docs.js new file mode 100644 index 000000000..fd4809884 --- /dev/null +++ b/docs-src/.eleventy.docs.js @@ -0,0 +1,18 @@ +const TemplatePath = require("../src/TemplatePath"); + +module.exports = { + templateFormats: ["njk"], + dir: { + input: "docs-src", + data: "_data", + output: "docs" + }, + nunjucksFilters: { + removeDir: function(str) { + return TemplatePath.stripLeadingSubPath( + str, + TemplatePath.join(__dirname, "..") + ); + } + } +}; diff --git a/docs-src/_data/coverage.json b/docs-src/_data/coverage.json new file mode 100644 index 000000000..0ea49f749 --- /dev/null +++ b/docs-src/_data/coverage.json @@ -0,0 +1,57 @@ +{"total": {"lines":{"total":2757,"covered":2443,"skipped":0,"pct":88.61},"statements":{"total":2772,"covered":2458,"skipped":0,"pct":88.67},"functions":{"total":657,"covered":574,"skipped":0,"pct":87.37},"branches":{"total":1102,"covered":894,"skipped":0,"pct":81.13}} +,"/Users/zachleat/Code/eleventy/config.js": {"lines":{"total":6,"covered":6,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":6,"covered":6,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} +,"/Users/zachleat/Code/eleventy/src/Benchmark.js": {"lines":{"total":12,"covered":11,"skipped":0,"pct":91.67},"functions":{"total":6,"covered":5,"skipped":0,"pct":83.33},"statements":{"total":12,"covered":11,"skipped":0,"pct":91.67},"branches":{"total":6,"covered":4,"skipped":0,"pct":66.67}} +,"/Users/zachleat/Code/eleventy/src/BenchmarkGroup.js": {"lines":{"total":34,"covered":23,"skipped":0,"pct":67.65},"functions":{"total":7,"covered":5,"skipped":0,"pct":71.43},"statements":{"total":34,"covered":23,"skipped":0,"pct":67.65},"branches":{"total":10,"covered":3,"skipped":0,"pct":30}} +,"/Users/zachleat/Code/eleventy/src/BenchmarkManager.js": {"lines":{"total":17,"covered":13,"skipped":0,"pct":76.47},"functions":{"total":7,"covered":5,"skipped":0,"pct":71.43},"statements":{"total":17,"covered":13,"skipped":0,"pct":76.47},"branches":{"total":4,"covered":3,"skipped":0,"pct":75}} +,"/Users/zachleat/Code/eleventy/src/Config.js": {"lines":{"total":5,"covered":5,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":5,"covered":5,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} +,"/Users/zachleat/Code/eleventy/src/Eleventy.js": {"lines":{"total":175,"covered":95,"skipped":0,"pct":54.29},"functions":{"total":33,"covered":19,"skipped":0,"pct":57.58},"statements":{"total":175,"covered":95,"skipped":0,"pct":54.29},"branches":{"total":52,"covered":24,"skipped":0,"pct":46.15}} +,"/Users/zachleat/Code/eleventy/src/EleventyBaseError.js": {"lines":{"total":5,"covered":5,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":5,"covered":5,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} +,"/Users/zachleat/Code/eleventy/src/EleventyCommandCheck.js": {"lines":{"total":28,"covered":28,"skipped":0,"pct":100},"functions":{"total":5,"covered":5,"skipped":0,"pct":100},"statements":{"total":28,"covered":28,"skipped":0,"pct":100},"branches":{"total":8,"covered":7,"skipped":0,"pct":87.5}} +,"/Users/zachleat/Code/eleventy/src/EleventyConfig.js": {"lines":{"total":2,"covered":2,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":2,"covered":2,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} +,"/Users/zachleat/Code/eleventy/src/EleventyErrorHandler.js": {"lines":{"total":36,"covered":33,"skipped":0,"pct":91.67},"functions":{"total":8,"covered":8,"skipped":0,"pct":100},"statements":{"total":36,"covered":33,"skipped":0,"pct":91.67},"branches":{"total":37,"covered":25,"skipped":0,"pct":67.57}} +,"/Users/zachleat/Code/eleventy/src/EleventyErrorUtil.js": {"lines":{"total":3,"covered":3,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":3,"covered":3,"skipped":0,"pct":100},"branches":{"total":5,"covered":5,"skipped":0,"pct":100}} +,"/Users/zachleat/Code/eleventy/src/EleventyExtensionMap.js": {"lines":{"total":60,"covered":58,"skipped":0,"pct":96.67},"functions":{"total":25,"covered":23,"skipped":0,"pct":92},"statements":{"total":60,"covered":58,"skipped":0,"pct":96.67},"branches":{"total":27,"covered":27,"skipped":0,"pct":100}} +,"/Users/zachleat/Code/eleventy/src/EleventyFiles.js": {"lines":{"total":126,"covered":119,"skipped":0,"pct":94.44},"functions":{"total":37,"covered":33,"skipped":0,"pct":89.19},"statements":{"total":126,"covered":119,"skipped":0,"pct":94.44},"branches":{"total":46,"covered":42,"skipped":0,"pct":91.3}} +,"/Users/zachleat/Code/eleventy/src/EleventyServe.js": {"lines":{"total":61,"covered":22,"skipped":0,"pct":36.07},"functions":{"total":16,"covered":9,"skipped":0,"pct":56.25},"statements":{"total":61,"covered":22,"skipped":0,"pct":36.07},"branches":{"total":41,"covered":10,"skipped":0,"pct":24.39}} +,"/Users/zachleat/Code/eleventy/src/EleventyWatchTargets.js": {"lines":{"total":46,"covered":43,"skipped":0,"pct":93.48},"functions":{"total":20,"covered":18,"skipped":0,"pct":90},"statements":{"total":46,"covered":43,"skipped":0,"pct":93.48},"branches":{"total":15,"covered":14,"skipped":0,"pct":93.33}} +,"/Users/zachleat/Code/eleventy/src/Template.js": {"lines":{"total":282,"covered":267,"skipped":0,"pct":94.68},"functions":{"total":47,"covered":46,"skipped":0,"pct":97.87},"statements":{"total":284,"covered":269,"skipped":0,"pct":94.72},"branches":{"total":101,"covered":87,"skipped":0,"pct":86.14}} +,"/Users/zachleat/Code/eleventy/src/TemplateCache.js": {"lines":{"total":9,"covered":9,"skipped":0,"pct":100},"functions":{"total":6,"covered":6,"skipped":0,"pct":100},"statements":{"total":9,"covered":9,"skipped":0,"pct":100},"branches":{"total":2,"covered":2,"skipped":0,"pct":100}} +,"/Users/zachleat/Code/eleventy/src/TemplateCollection.js": {"lines":{"total":29,"covered":27,"skipped":0,"pct":93.1},"functions":{"total":13,"covered":12,"skipped":0,"pct":92.31},"statements":{"total":31,"covered":29,"skipped":0,"pct":93.55},"branches":{"total":10,"covered":8,"skipped":0,"pct":80}} +,"/Users/zachleat/Code/eleventy/src/TemplateConfig.js": {"lines":{"total":57,"covered":52,"skipped":0,"pct":91.23},"functions":{"total":9,"covered":6,"skipped":0,"pct":66.67},"statements":{"total":57,"covered":52,"skipped":0,"pct":91.23},"branches":{"total":24,"covered":22,"skipped":0,"pct":91.67}} +,"/Users/zachleat/Code/eleventy/src/TemplateContent.js": {"lines":{"total":81,"covered":76,"skipped":0,"pct":93.83},"functions":{"total":17,"covered":16,"skipped":0,"pct":94.12},"statements":{"total":81,"covered":76,"skipped":0,"pct":93.83},"branches":{"total":34,"covered":31,"skipped":0,"pct":91.18}} +,"/Users/zachleat/Code/eleventy/src/TemplateData.js": {"lines":{"total":161,"covered":152,"skipped":0,"pct":94.41},"functions":{"total":27,"covered":27,"skipped":0,"pct":100},"statements":{"total":163,"covered":154,"skipped":0,"pct":94.48},"branches":{"total":50,"covered":41,"skipped":0,"pct":82}} +,"/Users/zachleat/Code/eleventy/src/TemplateFileSlug.js": {"lines":{"total":23,"covered":23,"skipped":0,"pct":100},"functions":{"total":4,"covered":4,"skipped":0,"pct":100},"statements":{"total":23,"covered":23,"skipped":0,"pct":100},"branches":{"total":8,"covered":8,"skipped":0,"pct":100}} +,"/Users/zachleat/Code/eleventy/src/TemplateGlob.js": {"lines":{"total":15,"covered":14,"skipped":0,"pct":93.33},"functions":{"total":4,"covered":4,"skipped":0,"pct":100},"statements":{"total":15,"covered":14,"skipped":0,"pct":93.33},"branches":{"total":8,"covered":7,"skipped":0,"pct":87.5}} +,"/Users/zachleat/Code/eleventy/src/TemplateLayout.js": {"lines":{"total":68,"covered":68,"skipped":0,"pct":100},"functions":{"total":9,"covered":9,"skipped":0,"pct":100},"statements":{"total":69,"covered":69,"skipped":0,"pct":100},"branches":{"total":14,"covered":14,"skipped":0,"pct":100}} +,"/Users/zachleat/Code/eleventy/src/TemplateLayoutPathResolver.js": {"lines":{"total":47,"covered":45,"skipped":0,"pct":95.74},"functions":{"total":11,"covered":11,"skipped":0,"pct":100},"statements":{"total":47,"covered":45,"skipped":0,"pct":95.74},"branches":{"total":18,"covered":16,"skipped":0,"pct":88.89}} +,"/Users/zachleat/Code/eleventy/src/TemplateMap.js": {"lines":{"total":238,"covered":232,"skipped":0,"pct":97.48},"functions":{"total":33,"covered":31,"skipped":0,"pct":93.94},"statements":{"total":238,"covered":232,"skipped":0,"pct":97.48},"branches":{"total":123,"covered":111,"skipped":0,"pct":90.24}} +,"/Users/zachleat/Code/eleventy/src/TemplatePassthrough.js": {"lines":{"total":36,"covered":34,"skipped":0,"pct":94.44},"functions":{"total":9,"covered":8,"skipped":0,"pct":88.89},"statements":{"total":36,"covered":34,"skipped":0,"pct":94.44},"branches":{"total":10,"covered":8,"skipped":0,"pct":80}} +,"/Users/zachleat/Code/eleventy/src/TemplatePassthroughManager.js": {"lines":{"total":58,"covered":53,"skipped":0,"pct":91.38},"functions":{"total":17,"covered":17,"skipped":0,"pct":100},"statements":{"total":58,"covered":53,"skipped":0,"pct":91.38},"branches":{"total":14,"covered":10,"skipped":0,"pct":71.43}} +,"/Users/zachleat/Code/eleventy/src/TemplatePath.js": {"lines":{"total":74,"covered":74,"skipped":0,"pct":100},"functions":{"total":23,"covered":22,"skipped":0,"pct":95.65},"statements":{"total":75,"covered":75,"skipped":0,"pct":100},"branches":{"total":38,"covered":38,"skipped":0,"pct":100}} +,"/Users/zachleat/Code/eleventy/src/TemplatePermalink.js": {"lines":{"total":30,"covered":30,"skipped":0,"pct":100},"functions":{"total":7,"covered":7,"skipped":0,"pct":100},"statements":{"total":30,"covered":30,"skipped":0,"pct":100},"branches":{"total":20,"covered":20,"skipped":0,"pct":100}} +,"/Users/zachleat/Code/eleventy/src/TemplatePermalinkNoWrite.js": {"lines":{"total":3,"covered":3,"skipped":0,"pct":100},"functions":{"total":2,"covered":2,"skipped":0,"pct":100},"statements":{"total":3,"covered":3,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} +,"/Users/zachleat/Code/eleventy/src/TemplateRender.js": {"lines":{"total":77,"covered":76,"skipped":0,"pct":98.7},"functions":{"total":22,"covered":22,"skipped":0,"pct":100},"statements":{"total":77,"covered":76,"skipped":0,"pct":98.7},"branches":{"total":42,"covered":40,"skipped":0,"pct":95.24}} +,"/Users/zachleat/Code/eleventy/src/TemplateWriter.js": {"lines":{"total":96,"covered":78,"skipped":0,"pct":81.25},"functions":{"total":21,"covered":15,"skipped":0,"pct":71.43},"statements":{"total":96,"covered":78,"skipped":0,"pct":81.25},"branches":{"total":10,"covered":3,"skipped":0,"pct":30}} +,"/Users/zachleat/Code/eleventy/src/UserConfig.js": {"lines":{"total":166,"covered":107,"skipped":0,"pct":64.46},"functions":{"total":45,"covered":23,"skipped":0,"pct":51.11},"statements":{"total":167,"covered":108,"skipped":0,"pct":64.67},"branches":{"total":66,"covered":36,"skipped":0,"pct":54.55}} +,"/Users/zachleat/Code/eleventy/src/Engines/Ejs.js": {"lines":{"total":20,"covered":19,"skipped":0,"pct":95},"functions":{"total":7,"covered":6,"skipped":0,"pct":85.71},"statements":{"total":20,"covered":19,"skipped":0,"pct":95},"branches":{"total":9,"covered":8,"skipped":0,"pct":88.89}} +,"/Users/zachleat/Code/eleventy/src/Engines/Haml.js": {"lines":{"total":9,"covered":9,"skipped":0,"pct":100},"functions":{"total":3,"covered":3,"skipped":0,"pct":100},"statements":{"total":9,"covered":9,"skipped":0,"pct":100},"branches":{"total":2,"covered":2,"skipped":0,"pct":100}} +,"/Users/zachleat/Code/eleventy/src/Engines/Handlebars.js": {"lines":{"total":30,"covered":30,"skipped":0,"pct":100},"functions":{"total":9,"covered":9,"skipped":0,"pct":100},"statements":{"total":30,"covered":30,"skipped":0,"pct":100},"branches":{"total":6,"covered":5,"skipped":0,"pct":83.33}} +,"/Users/zachleat/Code/eleventy/src/Engines/Html.js": {"lines":{"total":9,"covered":9,"skipped":0,"pct":100},"functions":{"total":3,"covered":3,"skipped":0,"pct":100},"statements":{"total":9,"covered":9,"skipped":0,"pct":100},"branches":{"total":2,"covered":2,"skipped":0,"pct":100}} +,"/Users/zachleat/Code/eleventy/src/Engines/JavaScript.js": {"lines":{"total":50,"covered":48,"skipped":0,"pct":96},"functions":{"total":12,"covered":12,"skipped":0,"pct":100},"statements":{"total":51,"covered":49,"skipped":0,"pct":96.08},"branches":{"total":42,"covered":37,"skipped":0,"pct":88.1}} +,"/Users/zachleat/Code/eleventy/src/Engines/JavaScriptTemplateLiteral.js": {"lines":{"total":18,"covered":17,"skipped":0,"pct":94.44},"functions":{"total":3,"covered":3,"skipped":0,"pct":100},"statements":{"total":18,"covered":17,"skipped":0,"pct":94.44},"branches":{"total":6,"covered":6,"skipped":0,"pct":100}} +,"/Users/zachleat/Code/eleventy/src/Engines/Liquid.js": {"lines":{"total":73,"covered":70,"skipped":0,"pct":95.89},"functions":{"total":26,"covered":25,"skipped":0,"pct":96.15},"statements":{"total":73,"covered":70,"skipped":0,"pct":95.89},"branches":{"total":15,"covered":13,"skipped":0,"pct":86.67}} +,"/Users/zachleat/Code/eleventy/src/Engines/Markdown.js": {"lines":{"total":33,"covered":30,"skipped":0,"pct":90.91},"functions":{"total":9,"covered":8,"skipped":0,"pct":88.89},"statements":{"total":33,"covered":30,"skipped":0,"pct":90.91},"branches":{"total":16,"covered":13,"skipped":0,"pct":81.25}} +,"/Users/zachleat/Code/eleventy/src/Engines/Mustache.js": {"lines":{"total":10,"covered":10,"skipped":0,"pct":100},"functions":{"total":4,"covered":4,"skipped":0,"pct":100},"statements":{"total":10,"covered":10,"skipped":0,"pct":100},"branches":{"total":2,"covered":2,"skipped":0,"pct":100}} +,"/Users/zachleat/Code/eleventy/src/Engines/Nunjucks.js": {"lines":{"total":61,"covered":54,"skipped":0,"pct":88.52},"functions":{"total":19,"covered":18,"skipped":0,"pct":94.74},"statements":{"total":61,"covered":54,"skipped":0,"pct":88.52},"branches":{"total":13,"covered":11,"skipped":0,"pct":84.62}} +,"/Users/zachleat/Code/eleventy/src/Engines/Pug.js": {"lines":{"total":17,"covered":17,"skipped":0,"pct":100},"functions":{"total":5,"covered":5,"skipped":0,"pct":100},"statements":{"total":17,"covered":17,"skipped":0,"pct":100},"branches":{"total":9,"covered":8,"skipped":0,"pct":88.89}} +,"/Users/zachleat/Code/eleventy/src/Engines/TemplateEngine.js": {"lines":{"total":51,"covered":51,"skipped":0,"pct":100},"functions":{"total":18,"covered":18,"skipped":0,"pct":100},"statements":{"total":53,"covered":53,"skipped":0,"pct":100},"branches":{"total":8,"covered":8,"skipped":0,"pct":100}} +,"/Users/zachleat/Code/eleventy/src/Errors/TemplateContentPrematureUseError.js": {"lines":{"total":2,"covered":2,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":2,"covered":2,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} +,"/Users/zachleat/Code/eleventy/src/Errors/UsingCircularTemplateContentReferenceError.js": {"lines":{"total":2,"covered":2,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":2,"covered":2,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} +,"/Users/zachleat/Code/eleventy/src/Filters/Slug.js": {"lines":{"total":3,"covered":3,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":3,"covered":3,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} +,"/Users/zachleat/Code/eleventy/src/Filters/Url.js": {"lines":{"total":18,"covered":18,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":18,"covered":18,"skipped":0,"pct":100},"branches":{"total":21,"covered":21,"skipped":0,"pct":100}} +,"/Users/zachleat/Code/eleventy/src/Plugins/Pagination.js": {"lines":{"total":105,"covered":97,"skipped":0,"pct":92.38},"functions":{"total":16,"covered":15,"skipped":0,"pct":93.75},"statements":{"total":107,"covered":99,"skipped":0,"pct":92.52},"branches":{"total":64,"covered":52,"skipped":0,"pct":81.25}} +,"/Users/zachleat/Code/eleventy/src/Util/Capitalize.js": {"lines":{"total":4,"covered":4,"skipped":0,"pct":100},"functions":{"total":2,"covered":2,"skipped":0,"pct":100},"statements":{"total":4,"covered":4,"skipped":0,"pct":100},"branches":{"total":2,"covered":2,"skipped":0,"pct":100}} +,"/Users/zachleat/Code/eleventy/src/Util/Merge.js": {"lines":{"total":28,"covered":26,"skipped":0,"pct":92.86},"functions":{"total":2,"covered":2,"skipped":0,"pct":100},"statements":{"total":28,"covered":26,"skipped":0,"pct":92.86},"branches":{"total":22,"covered":19,"skipped":0,"pct":86.36}} +,"/Users/zachleat/Code/eleventy/src/Util/Pluralize.js": {"lines":{"total":2,"covered":2,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":2,"covered":2,"skipped":0,"pct":100},"branches":{"total":2,"covered":2,"skipped":0,"pct":100}} +,"/Users/zachleat/Code/eleventy/src/Util/Sortable.js": {"lines":{"total":46,"covered":39,"skipped":0,"pct":84.78},"functions":{"total":23,"covered":17,"skipped":0,"pct":73.91},"statements":{"total":47,"covered":40,"skipped":0,"pct":85.11},"branches":{"total":18,"covered":17,"skipped":0,"pct":94.44}} +} diff --git a/docs-src/coverage.njk b/docs-src/coverage.njk new file mode 100644 index 000000000..f2aa72313 --- /dev/null +++ b/docs-src/coverage.njk @@ -0,0 +1,10 @@ +--- +permalink: coverage.md +--- +# Code Coverage for Eleventy v{{ pkg.version }} + +| Filename | % Lines | % Statements | % Functions | % Branches | +| --- | --- | --- | --- | --- | +{% for file, line in coverage -%} +| `{{ file | removeDir }}` | {{ line.lines.pct }}% | {{ line.statements.pct }}% | {{ line.functions.pct }}% | {{ line.branches.pct }}% | +{% endfor -%} diff --git a/docs/collections.md b/docs/collections.md new file mode 100644 index 000000000..586fb702e --- /dev/null +++ b/docs/collections.md @@ -0,0 +1,3 @@ +# Collections (and Tags) + +Moved to [https://www.11ty.io/docs/collections/](https://www.11ty.io/docs/collections/). diff --git a/docs/copy.md b/docs/copy.md new file mode 100644 index 000000000..15d456c37 --- /dev/null +++ b/docs/copy.md @@ -0,0 +1,3 @@ +# Pass-through File Copy + +Moved to [https://www.11ty.io/docs/copy/](https://www.11ty.io/docs/copy/). diff --git a/docs/coverage.md b/docs/coverage.md new file mode 100644 index 000000000..265548fe3 --- /dev/null +++ b/docs/coverage.md @@ -0,0 +1,60 @@ +# Code Coverage for Eleventy v0.9.0-beta.1 + +| Filename | % Lines | % Statements | % Functions | % Branches | +| ---------------------------------------------------------- | ------- | ------------ | ----------- | ---------- | +| `total` | 88.61% | 88.67% | 87.37% | 81.13% | +| `config.js` | 100% | 100% | 100% | 100% | +| `src/Benchmark.js` | 91.67% | 91.67% | 83.33% | 66.67% | +| `src/BenchmarkGroup.js` | 67.65% | 67.65% | 71.43% | 30% | +| `src/BenchmarkManager.js` | 76.47% | 76.47% | 71.43% | 75% | +| `src/Config.js` | 100% | 100% | 100% | 100% | +| `src/Eleventy.js` | 54.29% | 54.29% | 57.58% | 46.15% | +| `src/EleventyBaseError.js` | 100% | 100% | 100% | 100% | +| `src/EleventyCommandCheck.js` | 100% | 100% | 100% | 87.5% | +| `src/EleventyConfig.js` | 100% | 100% | 100% | 100% | +| `src/EleventyErrorHandler.js` | 91.67% | 91.67% | 100% | 67.57% | +| `src/EleventyErrorUtil.js` | 100% | 100% | 100% | 100% | +| `src/EleventyExtensionMap.js` | 96.67% | 96.67% | 92% | 100% | +| `src/EleventyFiles.js` | 94.44% | 94.44% | 89.19% | 91.3% | +| `src/EleventyServe.js` | 36.07% | 36.07% | 56.25% | 24.39% | +| `src/EleventyWatchTargets.js` | 93.48% | 93.48% | 90% | 93.33% | +| `src/Template.js` | 94.68% | 94.72% | 97.87% | 86.14% | +| `src/TemplateCache.js` | 100% | 100% | 100% | 100% | +| `src/TemplateCollection.js` | 93.1% | 93.55% | 92.31% | 80% | +| `src/TemplateConfig.js` | 91.23% | 91.23% | 66.67% | 91.67% | +| `src/TemplateContent.js` | 93.83% | 93.83% | 94.12% | 91.18% | +| `src/TemplateData.js` | 94.41% | 94.48% | 100% | 82% | +| `src/TemplateFileSlug.js` | 100% | 100% | 100% | 100% | +| `src/TemplateGlob.js` | 93.33% | 93.33% | 100% | 87.5% | +| `src/TemplateLayout.js` | 100% | 100% | 100% | 100% | +| `src/TemplateLayoutPathResolver.js` | 95.74% | 95.74% | 100% | 88.89% | +| `src/TemplateMap.js` | 97.48% | 97.48% | 93.94% | 90.24% | +| `src/TemplatePassthrough.js` | 94.44% | 94.44% | 88.89% | 80% | +| `src/TemplatePassthroughManager.js` | 91.38% | 91.38% | 100% | 71.43% | +| `src/TemplatePath.js` | 100% | 100% | 95.65% | 100% | +| `src/TemplatePermalink.js` | 100% | 100% | 100% | 100% | +| `src/TemplatePermalinkNoWrite.js` | 100% | 100% | 100% | 100% | +| `src/TemplateRender.js` | 98.7% | 98.7% | 100% | 95.24% | +| `src/TemplateWriter.js` | 81.25% | 81.25% | 71.43% | 30% | +| `src/UserConfig.js` | 64.46% | 64.67% | 51.11% | 54.55% | +| `src/Engines/Ejs.js` | 95% | 95% | 85.71% | 88.89% | +| `src/Engines/Haml.js` | 100% | 100% | 100% | 100% | +| `src/Engines/Handlebars.js` | 100% | 100% | 100% | 83.33% | +| `src/Engines/Html.js` | 100% | 100% | 100% | 100% | +| `src/Engines/JavaScript.js` | 96% | 96.08% | 100% | 88.1% | +| `src/Engines/JavaScriptTemplateLiteral.js` | 94.44% | 94.44% | 100% | 100% | +| `src/Engines/Liquid.js` | 95.89% | 95.89% | 96.15% | 86.67% | +| `src/Engines/Markdown.js` | 90.91% | 90.91% | 88.89% | 81.25% | +| `src/Engines/Mustache.js` | 100% | 100% | 100% | 100% | +| `src/Engines/Nunjucks.js` | 88.52% | 88.52% | 94.74% | 84.62% | +| `src/Engines/Pug.js` | 100% | 100% | 100% | 88.89% | +| `src/Engines/TemplateEngine.js` | 100% | 100% | 100% | 100% | +| `src/Errors/TemplateContentPrematureUseError.js` | 100% | 100% | 100% | 100% | +| `src/Errors/UsingCircularTemplateContentReferenceError.js` | 100% | 100% | 100% | 100% | +| `src/Filters/Slug.js` | 100% | 100% | 100% | 100% | +| `src/Filters/Url.js` | 100% | 100% | 100% | 100% | +| `src/Plugins/Pagination.js` | 92.38% | 92.52% | 93.75% | 81.25% | +| `src/Util/Capitalize.js` | 100% | 100% | 100% | 100% | +| `src/Util/Merge.js` | 92.86% | 92.86% | 100% | 86.36% | +| `src/Util/Pluralize.js` | 100% | 100% | 100% | 100% | +| `src/Util/Sortable.js` | 84.78% | 85.11% | 73.91% | 94.44% | diff --git a/docs/data.md b/docs/data.md index 88a0e8b0a..3c55b97a6 100644 --- a/docs/data.md +++ b/docs/data.md @@ -1,45 +1,3 @@ -# Template Data +# Using Data -All data files will be pre-processed with the template engine specified under the `dataTemplateEngine` configuration option. Note that `package.json` data is available here under the `pkg` variable. - -For example, if your `dataTemplateEngine` is using the default, `liquid`, you can do this: - -``` -{ - "version": "{{ pkg.version }}" -} -``` - -## Global Template Data - -Your global data folder is controlled by the `dir.data` configuration option. All `json` files in this directory will be parsed into a global data object available to all templates. - -If a data file is in a subdirectory, the subdirectory structure will inform your global data object structure. For example, consider `_data/users/userList.json` with the following data: - -``` -[ - "user1", - "user2", - "user3", - ā€¦ -] -``` - -This data will be available to your templates like so: - -``` -{ - users: { - userList: [ - "user1", - "user2", - "user3", - ā€¦ - ] - } -} -``` - -## Local Template Data - -_TO DO_ +Moved to [https://www.11ty.io/docs/data/](https://www.11ty.io/docs/data/). diff --git a/docs/engines.md b/docs/engines.md new file mode 100644 index 000000000..19f1e98bd --- /dev/null +++ b/docs/engines.md @@ -0,0 +1,3 @@ +# Changing a Templateā€™s Rendering Engine + +Moved to [https://www.11ty.io/docs/languages/](https://www.11ty.io/docs/languages/). diff --git a/docs/engines/ejs.md b/docs/engines/ejs.md new file mode 100644 index 000000000..af42d4ec4 --- /dev/null +++ b/docs/engines/ejs.md @@ -0,0 +1,3 @@ +# EJS + +Moved to [https://www.11ty.io/docs/languages/ejs/](https://www.11ty.io/docs/languages/ejs/). diff --git a/docs/engines/haml.md b/docs/engines/haml.md new file mode 100644 index 000000000..4a0a189dc --- /dev/null +++ b/docs/engines/haml.md @@ -0,0 +1,3 @@ +# HAML + +Moved to [https://www.11ty.io/docs/languages/haml/](https://www.11ty.io/docs/languages/haml/). diff --git a/docs/engines/handlebars.md b/docs/engines/handlebars.md new file mode 100644 index 000000000..4b3c0e1d8 --- /dev/null +++ b/docs/engines/handlebars.md @@ -0,0 +1,3 @@ +# Handlebars + +Moved to [https://www.11ty.io/docs/languages/handlebars/](https://www.11ty.io/docs/languages/handlebars/). diff --git a/docs/engines/html.md b/docs/engines/html.md new file mode 100644 index 000000000..e33147164 --- /dev/null +++ b/docs/engines/html.md @@ -0,0 +1,3 @@ +# HTML + +Moved to [https://www.11ty.io/docs/languages/html/](https://www.11ty.io/docs/languages/html/). diff --git a/docs/engines/jstl.md b/docs/engines/jstl.md new file mode 100644 index 000000000..99bc93232 --- /dev/null +++ b/docs/engines/jstl.md @@ -0,0 +1,3 @@ +# JavaScript Template Literals + +Moved to [https://www.11ty.io/docs/languages/jstl/](https://www.11ty.io/docs/languages/jstl/). diff --git a/docs/engines/liquid.md b/docs/engines/liquid.md new file mode 100644 index 000000000..6f58d5c7f --- /dev/null +++ b/docs/engines/liquid.md @@ -0,0 +1,3 @@ +# Liquid + +Moved to [https://www.11ty.io/docs/languages/liquid/](https://www.11ty.io/docs/languages/liquid/). diff --git a/docs/engines/markdown.md b/docs/engines/markdown.md new file mode 100644 index 000000000..cd5ef9677 --- /dev/null +++ b/docs/engines/markdown.md @@ -0,0 +1,3 @@ +# Markdown + +Moved to [https://www.11ty.io/docs/languages/markdown/](https://www.11ty.io/docs/languages/markdown/). diff --git a/docs/engines/mustache.md b/docs/engines/mustache.md new file mode 100644 index 000000000..798bb31b9 --- /dev/null +++ b/docs/engines/mustache.md @@ -0,0 +1,3 @@ +# Mustache + +Moved to [https://www.11ty.io/docs/languages/mustache/](https://www.11ty.io/docs/languages/mustache/). diff --git a/docs/engines/nunjucks.md b/docs/engines/nunjucks.md new file mode 100644 index 000000000..13d0d91cc --- /dev/null +++ b/docs/engines/nunjucks.md @@ -0,0 +1,3 @@ +# Nunjucks + +Moved to [https://www.11ty.io/docs/languages/nunjucks/](https://www.11ty.io/docs/languages/nunjucks/). diff --git a/docs/engines/pug.md b/docs/engines/pug.md new file mode 100644 index 000000000..2b7f95546 --- /dev/null +++ b/docs/engines/pug.md @@ -0,0 +1,3 @@ +# Pug (formerly Jade) + +Moved to [https://www.11ty.io/docs/languages/pug/](https://www.11ty.io/docs/languages/pug/). diff --git a/docs/filters.md b/docs/filters.md new file mode 100644 index 000000000..cff7d91dc --- /dev/null +++ b/docs/filters.md @@ -0,0 +1,3 @@ +# Filters, Tags, etc. + +Moved to [https://www.11ty.io/docs/filters/](https://www.11ty.io/docs/filters/). diff --git a/docs/install-local.md b/docs/install-local.md new file mode 100644 index 000000000..dbfa1457b --- /dev/null +++ b/docs/install-local.md @@ -0,0 +1,3 @@ +# Install locally + +Moved to [https://www.11ty.io/docs/local-installation/](https://www.11ty.io/docs/local-installation/). diff --git a/docs/layouts.md b/docs/layouts.md new file mode 100644 index 000000000..8c9fdf58e --- /dev/null +++ b/docs/layouts.md @@ -0,0 +1,3 @@ +# Layouts + +Moved to [https://www.11ty.io/docs/layouts/](https://www.11ty.io/docs/layouts/). diff --git a/docs/logo-github.png b/docs/logo-github.png new file mode 100644 index 0000000000000000000000000000000000000000..2e29d0b7ca6ea71929a33efcf04f8893a68436ed GIT binary patch literal 6590 zcmX9?1ymI8+XX2Bfgdd`AiZ?Uf+AfaOD!o&Bi-F4CB1Z)zyeE2O6;<9BOoOb5+dFG zt^aS%nP+D1bMNz>nfE<2^W2#C@05rLXbCVdFo;x?`3Gmx_w&>C>kK1O)i__=JRn!otGnQ5YE+@$m4BjEquKQ(IbE zhKGj-1_r9Cs`T{qI5{~#eE5)_p5EHp+TY(_QBk3(sTmX$ghHWKR#rAPHrChIXJ==* zxVY5S)jxguw6wHzdV2c!_;_-1l9!hU0)gV=*wa?j*gD*?(RZDLJA5BwzjrtX=&Zu+{((z z#>U2WcXw}ZZ`an=C@3g=eSIe;nwpw^{P@w^+uPC6K}t$WMn;yFmS$^f3x~t!=jTU9N1dFU8XFsrkB@tLdX$xw zOG`_~$H%+6x^8Z61_uWX4GpWSt0yKVMn*==&CPRjbIZ%i9UUDnFE4-o{7Ftu9uN@V z>gpOB8=I1n0);|LN=mY_vI+|eD=RCrv$MOqyZie37#J96XlRy~m!Cg>Zf|cdB_*}D zx2K?>prN7hjwu1x3#tX`SZum&(F%r>iqn?wzl^5>(?+C3FN3E)vMy-VlOW*d3pJdA3wf( z_s+t?A}J{;G&J=4_wUKc$v!?l#>U2RadB_nym|Zft+TT;78aJYw6v0vl1^Hm1$wdK zc!1t%_dVV`-ePVzbzopn*{aCP>iA&({Sl3iA@B@K!2ABu?ZZ=6*dWJKsWf(brb-+t zm>5hAb8eL8BhU#@wMmgW11VtQ9z7-*81j9Plx1}U}I{9 zRd?3+d$lbOxHIL>pJMx$;2SjaXo$|Sa|k!b>8m7+D$Q4MfXZk*Q__AbSj2t~RwKsK zOQjNu@-+1vR>1h-mvi2-@-AvI2oJ2tBW0aFLW}XYb!J?+-SW!<9n#qeIt!0WInQq6 z0S2BR*K=uTT7K>0nq5k7jpp&nRleck@(wEzub&a}8V_O$k-p4sxs*YAH3w>a+~Pp= z27z!rL2i#L$<(Q|RMdzs?fJnNA;Y0~3<1iH&(m~P+#YDFbtN4uvT8FvU=+ISoFR!x z6c-snS*wBhx{ONagW0fDg3Y%+sSAw}rcEOkEEs#8k2@Ov)Fc6WZ))g6Vf&a$lU_kj zRqrP7#3ah4?i>UVPRI2}>yTYZdg`Dm)6nJa2w(vWF45?2fFbb^L9Y4AAgYSag|lqa z`iS=LGkzNkiQnI6-WB*N#;*{=4PYXKHp6e`c(4OrbPf@y=>%Zd4#MiQW5w6(-6qh@HAyR1}Lvg<}rtLUKXI+ljG7jXPwFXUQFz3)n zMxNNOo$!2aZHQdKgP;&(d@>$aoM7e}5~LWrl}ucYeNFQN&ADtWt@TL~b9~*jag2(hCy@y?UrU_fs9zOIeDzf)Cz6Eyu~L;fLL?U(ZfwkxuAXEgeY6^0 zb}gZV%K&!l9X9lVYk>1I(`(j?zW0EtpH^q~HmTt4J!($dWeU2OS;Yqq0)?5ykjZ$B{D4GyMSIl=0Zu6@#AV?=R+x3SUEJ^Y zd(Cx$%SuZLn9#^Xru;&T2_O89!R#uv-s(aIa8%-{(hn{~ttjjK`SP9oHEAWT z3G0q@rv`W#p>M-S|NS8d5Qt=^)YMnS*x<_ zHAj3PN)|dm`k)ESH5va^f`$`Fpa~rI_~9s@GGk6k)mZ?4^PxeqNfG^m-IfnzpYA20 z9w&3_fAP&x3QGyp(C7>+A)&GeLu9e2DVUh?sR0|JksT9u{|deseZEEvyMTO~4xQzL zk$FL7@$@=0k%?s14B%3S&c)DKb^vSQ2$~qF+pCA348(MUgg7yDDpa>c(>zMc5=@0P zGDa(B9+oKbTQRTIR+_taCy@i2>XKo?GcR5=HNq3e^wik@P3?AO@i>Sl-C>?F&0Z>h z&isyr-0)f8@;3S=L`lM5MMZe>D%#4%kTAKx1yrGBjqKAVSm)d{Koz*p*VvVbpunGO zr}>fP zDj@xhs7ZsKGfiHkFQqT**WpKy0in+Q;rSpnRL}P

sSRHC{;pi^or{c+e(Szhe!rF7P<5qu z+&}^-`K-29G^Vc*a0EI3l}-wVyo{*U&!|&j946SLZ^bo`>`Id8KDL6ZnwtfFtEL3_ z74Oo7vU1L31D~VJH*5#FM+Fd8vD&nAHV;^8w(PYf($(Dos?Dw`Euuvmub@N7!%K}}y`exac{#bJ;?IsTS6ys>OD!3EJV}2tO2OKu z4iVpWG6gVseDH7rJuo>h{xYxMS`Y5Vs7yuF6{XXsrsYP5eIhUrFVLu2OV14tv;FZ` zhqR8J%7}sh{Hg^^9;8&9h-~e);7@6WlYB!?*PK`p+Rd^88};M{wEAw&txER3A0j zc!nqKvA))%Wx(y!Wp$CF5vmGQsY66?VYFT&oIdQv_&Q%H%rm9k%DvDgmGieCa^Fp# zyM~L*nSY>syMk+4Y$lj|3}bVysmJb^fp5v)?bKk_fbHAtYQQ2HYP%eG?H6UT$5`QZ;=t zuS^8c>i6&~^OU|z9)eA1=j1u}u2qJj9O+f@YJQGJQkp)t6&TMxZ+Q2>rOF}-N3b?U zC&}(@#;wh|9=XtxuHKFeay*C9vck+sGm_z(E9bk|V*_tlx`LTdESiZnG-AXlv9U+n z^U)>dRMDizfq`khOL%!HBys*%3%aGR^9g0{hJ=>bNf-?W zI-E7>^cfX`s?4l-Hc7M_E}qj1-fcZp%LfFnZwo9HL=BM~tz8^Zd(!XpuNQ~h##qkj zeX#{72=b^mGj?QJ2CxgE)&{?_9Sbbe5ZiENN5$)VSD&R=3-j0$Yc#M72(fgn=7@od z=G9rp-UWT+yk`lx`z4$Jul2)p8aCU-p;zK4Iiw{p^rPv1DJB*DNWiX^Wz%+b&TBcj zi0zdcwU#c5=4F3)YMI(t^*!0&`e2v!i;)vF~>gF@&dmzRDpc zHPCBOiT*4B1E7X>3Zg&F3y0^~#iUj=i~DA9<2ro)SknAQ`oO;F(f$;$NOF%`tIPm6 zRnn<@Q(5eGZA-stZ9@}8KQ?`~+4$DZ7pM%%Z{GaMoj5H~N1l@xoism9-6)^s@SJwA zA~-JPT??}wh%q8enHj*%t^f?8o{iHZE)fgMI@7h$~aU z(q}`c*CW1{M7+i)^O9Yy0L=w24jrh4k&x~E6ER`+fHpzPO7UNx&V2Pa>2i|FjSxar zJSVr}h{c16!P6$h4Ixd_)}xQ_TtAVfc>kq=xol*a8|_Jek5DAo&NGm}8q=FBCvOkQ zpQ1Xkp3c^q7RJ^c38I-NgXf3Zmy%Nn8|TVV9R`s{0?Soq-?(!40P<#%@bQ*7E5BG( z`oFXu4EZ6p2;7B#mw+s64yC=hkiM03xX)4qe-k?amdzy11QMoo{*g{{_Nom}R^b7# z6Z}x%o_)R+jJZDcIb}P7nWqMR_QY3+%^14w%97R0NWjtzZ&je@h)*GW-M5SjURz*P z(%haANYg@PaDh$e)-(IBf(Gs3(|ZQ-QkimWjGLyfTgyo2nBGr;V<8Ja!k-z|bu0B< z4wMAAznV8FQtw<)8bekGPYrT^>_JV_HG&n9N}S-vTIY!7D`Fp(G5BChICJJcZANKe zutItT4OL(p-90Z~WU>_(@saw~m87RoB*6_oLKH+4XH&g7L=pQl;Rsv>iImFEc}rGf z;Mt9r(gj$P#hiz=hc z3*EgWwnN#N#x#E_Z`W({N<&1IV`zi26hO}a5+sMqYqKU(5VaDTUSmEYX11a_a2dhN z5Tq~wb6<=^0hfO?;IyX9m zm5C(fY=q5ABNL1G7R7+&z9F2Mu47 zxD6_-uFmywX9BZKEC_l|j%r!Nqm^D%*Na(gM6?$w>HRao%%5^-0BaEAE0coq%gtIXCOzxV zq4ITR(FMZzS&cTeG4$pTaYCOAl(Ug1()t@3WXDpoIK0K~c}N463g+aFl7i?!f3pZ! z>?i({hjAI9PYH(=q(iWmeo)S}O(lx8L3u7`&A^B;omtC?gdmGmrZkBdXyHz0Y( z-f88yfc9V}t!38}$}88%0V>~MZEnio;Jl%VCjh8u%nGxsXn4wrYbGvhS;k&=7Wr6! z5N!?_{EVKoiF4L+-^Nvq-wCp}AUGJ_eo&?o&E{a-Xp(boD>a~ZZ@;i3!BA7O_GuGY z?EL#vv?q3x!{5&~5aZ2`KOAuK0|Sqev@N)GEyG1tQJ=KAKX@J#r$Gva#m;|bRv*go zd?#pOb}b>Q-Z%5m>b2F48K;2_K=$Wen4M?5hM;&7_}u*5)KpcFOgB3^dhj zbg|A|$r?lV3=bFB5Van+2U4s!O5w8!ebt|MIT}eelJYDGgW_ax#@NQC29(qr(|%%5 zl^2kQ3JTdkKlz0p;~V7I)$x(XGnHV6teTgreB!~B6T-M#!;9R$P#2eq_ztsvN^&f< zw37a_v-R4ALdQ-ryuDmK6_XXDx4*5S^81*?y&FL5@9w8W?{8QEAplO&4f6u5enc_43~< z>}3e>tcO)@!5Oa+n65^4PBm7eFOAF{q8c(^fMM;$s!#^pwXzB?APkrv81{AVmhKJ; z2h#++nd_Tn`mZ;(xe-F<`*0Z;AEPF?1?Z(+4}A^W8KB$}%=f*^!w`5qzcy0qJ0kq!W>B-Bh)ZaJX{3Zfy-2bySTgSu0+SjO{TP_mkklw{t@DSy?@qlqx&zllt6u zb@=rqC{`&}x+;ky?}WYFCmkojTdsE(6fo-K`F;`}*>z-bu%v~aDD%~u!5f@~BU=j} z01n(xX)xvMdPwI6rDvWEvCkgqaD9FkCZA0;Vg%4P^LhGK25Zze2~WHWx&Sy&1l z(T01_<@7+xZ|Xr5!c;6z&6h_quY51EnLhh7o1N7$c$dweKC*gu_`8(WoHohGk!xgH z^NUZ*m2>k<#muW=*DU++)f@<@xtkn{fnSkGtRHN$jlvxkt=??{3=8n&NRA`Zo?mmA!NJ>0+Q4GF=j z!*pvdbD;`NU2Gqn?tQhIc87FCOhtkgPo3IT7tka_mebh8aRB{jU> zk80*r?P%6WiT)Fax#a+P(38x%p}W0LJX6g#cReyIeI8mp_qgu=rjoYdkB{EB2rWaR ztNFv%ou^hAi!Q&QVanMq`(7AI66!-s_;RyQAU_h z4?_*^aAf_@RK*^#dpaT%fujaDlO%!|M8UED=e3&QL+OA3@C5)IhBJce{Lhs2hi3gV zc+tSWIQ!4koyVjCpbsJuXtVa;b-oqa4@MV7pR@goCrM(B*#D}d73k_{Q+LFU^IyV^ c7BP+S@GeZ}I<{OLZ9-$HD7=%eeQOr>e=&93^Z)<= literal 0 HcmV?d00001 diff --git a/docs/meta-release.md b/docs/meta-release.md new file mode 100644 index 000000000..1a410bdd1 --- /dev/null +++ b/docs/meta-release.md @@ -0,0 +1,35 @@ +# Beta Release Procedure + +0. npmclean +1. Update version in `package.json`, include `-beta.1` suffix +1. Run `npm run coverage` +1. Check it all in and commit +1. Tag new version +1. `npm publish --access=public --tag=beta` + +# Release Procedure + +0. npmclean first +1. Update version in `package.json` +1. Run `npm run coverage` +1. Check it all in and commit +1. Tag new version +1. `npm publish --access=public` + +## If branch docs do not exist + +1. Check in a new `11ty.io` site with updated `package.json` version. +2. Add version to 11ty.io `versions.json` +3. Create a new branch for branched version +4. Go to https://app.netlify.com/sites/11ty/settings/domain and set up a subdomain for it. + +### Always: + +1. Check out the previous version git branch and add `outdated: true` to `_data/config.json` and commit/push. +2. Update `eleventy-base-blog`? + +## If Branch docs already exist, 11ty.io (unlikely, I donā€™t do this any more) + +1. Check to make sure `"prerelease": false` in `_data/config.json` +2. Check to make sure `"prerelease": true` does not exist in current version in `_data/versions.json` +3. Merge branch to master. diff --git a/docs/pagination.md b/docs/pagination.md index bf1130e37..9be309a55 100644 --- a/docs/pagination.md +++ b/docs/pagination.md @@ -1,138 +1,3 @@ # Pagination -To iterate over a data set and create pages for individual chunks of data, use pagination. Enable in your templateā€™s front matter by adding the `pagination` key. Consider this Nunjucks template: - -``` ---- -pagination: - data: testdata - size: 2 -testdata: - - item1 - - item2 - - item3 - - item4 ---- -

    {% for item in pagination.items %}
  1. {{ item }}
  2. {% endfor %}
-``` - -We enable pagination and then give it a dataset with the `data` key. We control the number of items in each chunk with `size`. The pagination data variable will be populated with what you need to create each template. Hereā€™s whatā€™s in `pagination`: - -``` -{ - items: [], // current pageā€™s chunk of data - pageNumber: 0, // current page number, 0 indexed - nextPageLink: "", // put inside Next Page - previousPageLink: "", // put inside Previous Page - pageLinks: [], // all page links - data: "", // pointer to dataset - size: 1, // chunk sizes -} -``` - -If the above file were named `paged.njk`, it would create two pages: `_site/paged/0/index.html` and `_site/paged/1/index.html`. These output paths are configurable with `permalink` (see below). - -## Paginate a global or local data file - -[Read more about Template Data Files](data.md). The only change here is that you point your `data` pagination key to the global or local data instead of data in the front matter. For example, consider the following `globalDataSet.json` file in your global data directory. - -``` -{ - myData: [ - "item1", - "item2", - "item3", - "item4" - ] -} -``` - -Your front matter would look like this: - -``` ---- -pagination: - data: globalDataSet.myData - size: 1 ---- -
    {% for item in pagination.items %}
  1. {{ item }}
  2. {% endfor %}
-``` - -## Remapping with permalinks - -Pagination variables also work here. Hereā€™s an example of a permalink using the pagination page number: - -``` ---- -permalink: different/page-{{ pagination.pageNumber }}/index.html ---- -``` - -Writes to `_site/different/page-0/index.html`, `_site/different/page-1/index.html`, et cetera. - -That means Nunjucks will also let you start your page numbers with 1 instead of 0, by just adding 1 here: - -``` ---- -permalink: different/page-{{ pagination.pageNumber + 1 }}/index.html ---- -``` - -Writes to `_site/different/page-1/index.html`, `_site/different/page-2/index.html`, et cetera. - -### Use page item data in the permalink - -You can do more advanced things like this: - -``` ---- -pagination: - data: testdata - size: 1 -testdata: - - My Item -permalink: different/{{ pagination.items[0] | slug }}/index.html ---- -``` - -Using a Nunjucks `slug` filter (transforms `My Item` to `my-item`), this outputs: `_site/different/my-item/index.html`. - -#### Aliasing pagination items to a different variable - -Ok, so `pagination.items[0]` is ugly. We provide an option to alias this to something different. - -``` ---- -pagination: - data: testdata - size: 1 - alias: wonder -testdata: - - Item1 - - Item2 -permalink: different/{{ wonder | slug }}/index.html ---- -You can use the alias in your content too {{ wonder }}. -``` - -This writes to `_site/different/item1/index.html`. - -If your chunk `size` is greater than 1, the alias will be an array instead of a single value. - -``` ---- -pagination: - data: testdata - size: 2 - alias: wonder -testdata: - - Item1 - - Item2 - - Item3 - - Item4 -permalink: different/{{ wonder[0] | slug }}/index.html ---- -You can use the alias in your content too {{ wonder[0] }}. -``` - -This writes to `_site/different/item1/index.html`. +Moved to [https://www.11ty.io/docs/pagination/](https://www.11ty.io/docs/pagination/). diff --git a/docs/permalinks.md b/docs/permalinks.md index ae365cf7c..c55076e16 100644 --- a/docs/permalinks.md +++ b/docs/permalinks.md @@ -1,55 +1,3 @@ # Permalinks -## Cool URIs donā€™t change - -Eleventy automatically helps you make sure that [Cool URIs donā€™t change](https://www.w3.org/Provider/Style/URI.html). - -> What to leave outā€¦ -> File name extension. This is a very common one. "cgi", even ".html" is something which will change. You may not be using HTML for that page in 20 years time, but you might want today's links to it to still be valid. The canonical way of making links to the W3C site doesn't use the extension. - -Assuming your `--output` directory is the default, `_site`: - -* `template.njk` outputs to `_site/template/index.html`, pairs nicely with `` -* `subdir/template.njk` outputs to `_site/subdir/template/index.html`, pairs nicely with `` -* _Notably_, if your template file name and parent directory match, itā€™ll be simplified to a single folder. So, `subdir/template/template.njk` outputs to `_site/subdir/template/index.html` (note only one template folder). - -## Remapping Output (Permalink) - -To remap your templateā€™s output to a different path than the default, use the `permalink` key in the templateā€™s front matter. If a subdirectory does not exist, it will be created. - -``` ---- -permalink: this-is-a-new-path/subdirectory/testing/index.html ---- -``` - -The above will write to `_site/this-is-a-new-path/subdirectory/testing/index.html`. - -You may use data variables available here. These will be parsed with the current templateā€™s rendering engine. - -For example, in a Nunjucks template: - -``` ---- -mySlug: this-is-a-new-path -permalink: subdir/{{ mySlug }}/index.html ---- -``` - -Writes to `_site/subdir/this-is-a-new-path/index.html`. - -### Permalink, ignore output directory - -To remap your templateā€™s output to a directory independent of the output directory (`--output`), use `permalinkBypassOutputDir: true` in your front matter. - -``` ---- -permalink: _includes/index.html ---- -``` - -Writes to `_includes/index.html` even though the output directory is `_site`. This is useful for writing child templates to the `_includes` directory for re-use in your other templates. - -### Pagination - -Pagination variables also work here. [Read more about Pagination](pagination.md) +Moved to [https://www.11ty.io/docs/permalinks/](https://www.11ty.io/docs/permalinks/). diff --git a/docs/pitfalls.md b/docs/pitfalls.md new file mode 100644 index 000000000..b39aca287 --- /dev/null +++ b/docs/pitfalls.md @@ -0,0 +1,3 @@ +# Common Eleventy Pitfalls + +Moved to [https://www.11ty.io/docs/pitfalls/](https://www.11ty.io/docs/pitfalls/). diff --git a/docs/plugins.md b/docs/plugins.md new file mode 100644 index 000000000..299c6b4ac --- /dev/null +++ b/docs/plugins.md @@ -0,0 +1,3 @@ +# Plugins + +Moved to [https://www.11ty.io/docs/plugins/](https://www.11ty.io/docs/plugins/). diff --git a/package.json b/package.json old mode 100644 new mode 100755 index bfd2319a8..53a81d067 --- a/package.json +++ b/package.json @@ -1,21 +1,43 @@ { - "name": "eleventy-cli", - "version": "0.1.9", + "name": "@11ty/eleventy", + "version": "0.9.0-beta.1", "description": "Transform a directory of templates into HTML.", "main": "src/Eleventy.js", "license": "MIT", "engines": { - "node": ">=8.0.0" + "node": ">=8" }, + "keywords": [ + "static-site-generator", + "static-site", + "ssg", + "documentation", + "website", + "jekyll", + "blog", + "templates", + "generator", + "framework", + "eleventy", + "11ty", + "html", + "markdown", + "liquid", + "nunjucks", + "pug", + "handlebars", + "mustache", + "ejs", + "haml" + ], "bin": { "eleventy": "./cmd.js" }, "scripts": { - "default": "node cmd.js --input=playground --output=_site", - "test": "npx ava", - "watch:test": "ava --watch --verbose", - "precommit": "lint-staged", - "prepush": "npx ava" + "default": "npm run test", + "test": "npx ava --verbose", + "lint-staged": "lint-staged", + "coverage": "npx nyc ava && npx nyc report --reporter=json-summary && cp coverage/coverage-summary.json docs-src/_data/coverage.json && node cmd.js --config=docs-src/.eleventy.docs.js" }, "author": { "name": "Zach Leatherman", @@ -24,46 +46,74 @@ }, "repository": { "type": "git", - "url": "git://github.com/zachleat/eleventy.git" + "url": "git://github.com/11ty/eleventy.git" }, "ava": { - "files": ["test/*.js"], - "source": ["**/.eleventyignore", "src/**/*.js"] + "files": [ + "./test/*.js" + ], + "sources": [ + "./**/.eleventyignore", + "./src/**/*.js", + "./test/stubs/**", + "!./test/stubs/**/_site/**" + ] }, "lint-staged": { - "*.{js,json,css,md}": ["prettier --write", "git add"] + "*.{js,css,md}": [ + "prettier --write", + "git add" + ] }, "devDependencies": { - "ava": "^0.24.0", - "husky": "^0.14.3", - "lint-staged": "^6.0.0", - "prettier": "1.9.1" + "@11ty/eleventy-plugin-syntaxhighlight": "^2.0.3", + "ava": "^2.2.0", + "lint-staged": "^8.2.1", + "markdown-it-emoji": "^1.4.0", + "nyc": "^14.1.1", + "pre-commit": "^1.2.2", + "pre-push": "^0.1.1", + "prettier": "^1.18.2", + "rimraf": "^2.6.3", + "toml": "^3.0.0", + "viperhtml": "^2.17.0", + "vue": "^2.6.10", + "vue-server-renderer": "^2.6.10" }, "dependencies": { - "chalk": "^2.3.0", - "check-node-version": "^3.1.1", - "ejs": "^2.5.7", - "fs-extra": "^4.0.2", - "glob-watcher": "^4.0.0", - "globby": "^7.1.1", - "gray-matter": "^3.1.1", + "browser-sync": "^2.26.7", + "chalk": "^2.4.2", + "chokidar": "^2.1.5", + "debug": "^4.1.1", + "dependency-graph": "^0.8.0", + "dependency-tree": "^6.3.0", + "ejs": "^2.6.2", + "fast-glob": "^3.0.4", + "fs-extra": "^7.0.1", + "gray-matter": "^4.0.2", "hamljs": "^0.6.2", - "handlebars": "^4.0.11", - "liquidjs": "^2.2.0", - "lodash.chunk": "^4.2.0", - "lodash.get": "^4.4.2", - "lodash.isobject": "^3.0.2", - "lodash.merge": "^4.6.0", - "lodash.set": "^4.3.2", - "markdown-it": "^8.4.0", + "handlebars": "^4.1.2", + "javascript-stringify": "^2.0.0", + "liquidjs": "^6.4.3", + "lodash": "^4.17.15", + "luxon": "^1.17.2", + "markdown-it": "^8.4.2", "minimist": "^1.2.0", + "moo": "^0.5.0", + "multimatch": "^3.0.0", "mustache": "^2.3.0", - "normalize-path": "^2.1.1", - "nunjucks": "^3.0.1", - "parse-filepath": "^1.0.1", - "pify": "^3.0.0", + "normalize-path": "^3.0.0", + "nunjucks": "^3.2.0", + "parse-filepath": "^1.0.2", + "please-upgrade-node": "^3.1.1", "pretty": "^2.0.0", - "pug": "^2.0.0-rc.4", - "slugify": "^1.2.7" - } + "pug": "^2.0.4", + "recursive-copy": "^2.0.10", + "semver": "^6.3.0", + "slugify": "^1.3.4", + "time-require": "^0.1.2", + "valid-url": "^1.0.9" + }, + "pre-commit": "lint-staged", + "pre-push": "test" } diff --git a/src/Benchmark.js b/src/Benchmark.js new file mode 100644 index 000000000..8f03679a1 --- /dev/null +++ b/src/Benchmark.js @@ -0,0 +1,35 @@ +class Benchmark { + constructor() { + this.reset(); + } + + reset() { + this.timeSpent = 0; + this.beforeDates = []; + } + + before() { + this.beforeDates.push(new Date()); + } + + after() { + if (!this.beforeDates.length) { + throw new Error("You called Benchmark after() without a before()."); + } + + let before = this.beforeDates.pop(); + if (!this.beforeDates.length) { + this.timeSpent += new Date().getTime() - before.getTime(); + } + } + + getTotal() { + return this.timeSpent; + } + + getTotalString() { + return this.timeSpent > 0 ? ` (${this.timeSpent}ms)` : ""; + } +} + +module.exports = Benchmark; diff --git a/src/BenchmarkGroup.js b/src/BenchmarkGroup.js new file mode 100644 index 000000000..bc1e786d7 --- /dev/null +++ b/src/BenchmarkGroup.js @@ -0,0 +1,73 @@ +const chalk = require("chalk"); + +const Benchmark = require("./Benchmark"); +const debugWarn = require("debug")("Eleventy:Warnings"); + +class BenchmarkGroup { + constructor() { + this.benchmarks = {}; + this.start = new Date(); + this.isVerbose = true; + this.minimumThresholdMs = 0; + } + + reset() { + this.start = new Date(); + + for (var type in this.benchmarks) { + this.benchmarks[type].reset(); + } + } + + // TODO make this async + add(type, callback) { + let benchmark = (this.benchmarks[type] = new Benchmark()); + + return function(...args) { + benchmark.before(); + let ret = callback.call(this, ...args); + benchmark.after(); + return ret; + }; + } + + setMinimumThresholdMs(minimumThresholdMs) { + let val = parseInt(minimumThresholdMs, 10); + if (isNaN(val)) { + throw new Error("`setMinimumThresholdMs` expects a number argument."); + } + this.minimumThresholdMs = val; + } + + get(type) { + this.benchmarks[type] = new Benchmark(); + return this.benchmarks[type]; + } + + finish(label, thresholdPercent, isVerbose) { + let totalTimeSpent = new Date().getTime() - this.start.getTime(); + thresholdPercent = thresholdPercent !== undefined ? thresholdPercent : 10; + for (var type in this.benchmarks) { + let bench = this.benchmarks[type]; + let totalForBenchmark = bench.getTotal(); + let percent = (totalForBenchmark * 100) / totalTimeSpent; + if ( + percent > thresholdPercent && + totalForBenchmark >= this.minimumThresholdMs + ) { + let str = chalk.yellow( + `Benchmark (${label}): ${type} took ${bench.getTotal()}ms (${percent.toFixed( + 1 + )}%)` + ); + if (isVerbose) { + console.log(str); + } + + debugWarn(str); + } + } + } +} + +module.exports = BenchmarkGroup; diff --git a/src/BenchmarkManager.js b/src/BenchmarkManager.js new file mode 100644 index 000000000..8ea166a52 --- /dev/null +++ b/src/BenchmarkManager.js @@ -0,0 +1,47 @@ +const BenchmarkGroup = require("./BenchmarkGroup"); + +class BenchmarkManager { + constructor() { + this.benches = {}; + this.isVerbose = true; + } + + reset() { + for (var j in this.benches) { + this.benches[j].reset(); + } + } + + setVerboseOutput(isVerbose) { + this.isVerbose = !!isVerbose; + } + + getBenchmarkGroup(name) { + if (!this.benches[name]) { + this.benches[name] = new BenchmarkGroup(); + } + + return this.benches[name]; + } + + getAll() { + return this.benches; + } + + get(name) { + if (name) { + return this.getBenchmarkGroup(name); + } + + return this.getAll(); + } + + finish(thresholdPercent) { + for (var j in this.benches) { + this.benches[j].finish(j, thresholdPercent, this.isVerbose); + } + } +} + +let manager = new BenchmarkManager(); +module.exports = manager; diff --git a/src/Config.js b/src/Config.js new file mode 100644 index 000000000..51425e068 --- /dev/null +++ b/src/Config.js @@ -0,0 +1,7 @@ +const TemplateConfig = require("./TemplateConfig"); +const debug = require("debug")("Eleventy:Config"); + +debug("Setting up global TemplateConfig."); +let config = new TemplateConfig(); + +module.exports = config; diff --git a/src/Eleventy.js b/src/Eleventy.js index 1a76caaf5..ad3e92a78 100644 --- a/src/Eleventy.js +++ b/src/Eleventy.js @@ -1,145 +1,446 @@ -const watch = require("glob-watcher"); -const chalk = require("chalk"); -const TemplateConfig = require("./TemplateConfig"); +const TemplatePath = require("./TemplatePath"); const TemplateData = require("./TemplateData"); const TemplateWriter = require("./TemplateWriter"); -const EleventyError = require("./EleventyError"); -const pkg = require("../package.json"); +const EleventyErrorHandler = require("./EleventyErrorHandler"); +const EleventyServe = require("./EleventyServe"); +const EleventyWatchTargets = require("./EleventyWatchTargets"); +const EleventyFiles = require("./EleventyFiles"); +const templateCache = require("./TemplateCache"); +const simplePlural = require("./Util/Pluralize"); +const config = require("./Config"); +const bench = require("./BenchmarkManager"); +const debug = require("debug")("Eleventy"); -let cfg = TemplateConfig.getDefaultConfig(); +class Eleventy { + constructor(input, output) { + this.config = config.getConfig(); + this.configPath = null; + this.isVerbose = true; + this.isDebug = false; + this.isDryRun = false; -function Eleventy(input, output) { - this.input = input || cfg.dir.input; - this.output = output || cfg.dir.output; - this.formats = cfg.templateFormats; - this.data = null; - this.start = new Date(); -} + this.start = new Date(); + this.formatsOverride = null; + this.eleventyServe = new EleventyServe(); -Eleventy.prototype.restart = function() { - this.start = new Date(); -}; + this.rawInput = input; + this.rawOutput = output; -Eleventy.prototype._simplePlural = function(count, singleWord, pluralWord) { - return count === 1 ? singleWord : pluralWord; -}; + this.watchTargets = new EleventyWatchTargets(); + this.watchTargets.watchJavaScriptDependencies = this.config.watchJavaScriptDependencies; + } -Eleventy.prototype.getFinishedLog = function() { - if (!this.writer) { - throw new Error( - "Did you call Eleventy.init to create the TemplateWriter instance? Hint: you probably didnā€™t." - ); + get input() { + return this.rawInput || this.config.dir.input; + } + + get inputDir() { + return TemplatePath.getDir(this.input); + } + + get outputDir() { + let dir = this.rawOutput || this.config.dir.output; + if (dir !== this._savedOutputDir) { + this.eleventyServe.setOutputDir(dir); + } + this._savedOutputDir = dir; + + return dir; } - let ret = []; + setDryRun(isDryRun) { + this.isDryRun = !!isDryRun; + } + + setPassthroughAll(isPassthroughAll) { + this.isPassthroughAll = !!isPassthroughAll; + } + + setPathPrefix(pathPrefix) { + if (pathPrefix || pathPrefix === "") { + config.setPathPrefix(pathPrefix); + this.config = config.getConfig(); + } + } + + setWatchTargets(watchTargets) { + this.watchTargets = watchTargets; + } + + setConfigPathOverride(configPath) { + if (configPath) { + this.configPath = configPath; + + config.setProjectConfigPath(configPath); + this.config = config.getConfig(); + } + } + + async restart() { + debug("Restarting"); + this.start = new Date(); + templateCache.clear(); + bench.reset(); + this.eleventyFiles.restart(); + + // reload package.json values (if applicable) + // TODO only reset this if it changed + delete require.cache[TemplatePath.absolutePath("package.json")]; + + await this.init(); + } + + finish() { + bench.finish(); + + (this.logger || console).log(this.logFinished()); + debug("Finished writing templates."); + } + + logFinished() { + if (!this.writer) { + throw new Error( + "Did you call Eleventy.init to create the TemplateWriter instance? Hint: you probably didnā€™t." + ); + } + + let ret = []; + + let writeCount = this.writer.getWriteCount(); + let copyCount = this.writer.getCopyCount(); + if (this.isDryRun) { + ret.push("Pretended to"); + } + if (copyCount) { + ret.push( + `${this.isDryRun ? "Copy" : "Copied"} ${copyCount} ${simplePlural( + copyCount, + "item", + "items" + )} and` + ); + } + ret.push( + `${this.isDryRun ? "Process" : "Processed"} ${writeCount} ${simplePlural( + writeCount, + "file", + "files" + )}` + ); - let writeCount = this.writer.getWriteCount(); - ret.push( - `Wrote ${writeCount} ${this._simplePlural(writeCount, "file", "files")}` - ); + let time = ((new Date() - this.start) / 1000).toFixed(2); + ret.push(`in ${time} ${simplePlural(time, "second", "seconds")}`); - let time = ((new Date() - this.start) / 1000).toFixed(2); - ret.push(`in ${time} ${this._simplePlural(time, "second", "seconds")}`); + if (writeCount >= 10) { + ret.push(`(${((time * 1000) / writeCount).toFixed(1)}ms each)`); + } - return ret.join(" "); -}; + return ret.join(" "); + } -Eleventy.prototype.init = async function() { - this.data = new TemplateData(this.input); + async init() { + let formats = this.formatsOverride || this.config.templateFormats; + this.eleventyFiles = new EleventyFiles( + this.input, + this.outputDir, + formats, + this.isPassthroughAll + ); + this.eleventyFiles.init(); - this.writer = new TemplateWriter( - this.input, - this.output, - this.formats, - this.data - ); + this.templateData = new TemplateData(this.inputDir); + this.eleventyFiles.setTemplateData(this.templateData); - this.writer.setVerboseOutput(this.isVerbose); + this.writer = new TemplateWriter( + this.input, + this.outputDir, + formats, + this.templateData, + this.isPassthroughAll + ); - return this.data.cacheData(); -}; + this.writer.setEleventyFiles(this.eleventyFiles); -Eleventy.prototype.setIsVerbose = function(isVerbose) { - this.isVerbose = !!isVerbose; + // TODO maybe isVerbose -> console.log? + debug(`Directories: +Input: ${this.inputDir} +Data: ${this.templateData.getDataDir()} +Includes: ${this.eleventyFiles.getIncludesDir()} +Layouts: ${this.eleventyFiles.getLayoutsDir()} +Output: ${this.outputDir} +Template Formats: ${formats.join(",")}`); - if (this.writer) { this.writer.setVerboseOutput(this.isVerbose); + this.writer.setDryRun(this.isDryRun); + + return this.templateData.cacheData(); } -}; - -Eleventy.prototype.setFormats = function(formats) { - if (formats && formats !== "*") { - this.formats = formats.split(","); - } -}; - -Eleventy.prototype.getVersion = function() { - return pkg.version; -}; - -Eleventy.prototype.getHelp = function() { - let out = []; - out.push("usage: eleventy"); - out.push(" eleventy --watch"); - out.push(" eleventy --input=./templates --output=./dist"); - out.push(""); - out.push("Arguments: "); - out.push(" --version"); - out.push(" --watch"); - out.push(" Wait for files to change and automatically rewrite."); - out.push(" --input"); - out.push(" Input template files (default: `templates`)"); - out.push(" --output"); - out.push(" Write HTML output to this folder (default: `dist`)"); - out.push(" --formats=liquid,md"); - out.push(" Whitelist only certain template types (default: `*`)"); - out.push(" --quiet"); - out.push(" Donā€™t print all written files (default: `false`)"); - // out.push( " --config" ); - // out.push( " Set your own local configuration file (default: `.eleventy.js`)" ); - out.push(" --help"); - return out.join("\n"); -}; - -Eleventy.prototype.watch = async function() { - await this.write(); - - console.log("Watchingā€¦"); - var watcher = watch(this.writer.getRawFiles(), { - ignored: this.writer.getWatchedIgnores() - }); - - watcher.on( - "change", - async function(path, stat) { - console.log("File changed:", path); - await this.write(); + + setIsDebug(isDebug) { + this.isDebug = !!isDebug; + } + + setIsVerbose(isVerbose) { + this.isVerbose = !!isVerbose; + + if (this.writer) { + this.writer.setVerboseOutput(this.isVerbose); + } + if (bench) { + bench.setVerboseOutput(this.isVerbose); + } + } + + setFormats(formats) { + if (formats && formats !== "*") { + this.formatsOverride = formats.split(","); + } + } + + getVersion() { + return require("../package.json").version; + } + + getHelp() { + return `usage: eleventy + eleventy --input=. --output=./_site + eleventy --serve + +Arguments: + --version + --input=. + Input template files (default: \`.\`) + --output=_site + Write HTML output to this folder (default: \`_site\`) + --serve + Run web server on --port (default 8080) and watch them too + --watch + Wait for files to change and automatically rewrite (no web server) + --formats=liquid,md + Whitelist only certain template types (default: \`*\`) + --quiet + Donā€™t print all written files (off by default) + --config=filename.js + Override the eleventy config file path (default: \`.eleventy.js\`) + --pathprefix='/' + Change all url template filters to use this subdirectory. + --dryrun + Donā€™t write any files. Useful with \`DEBUG=Eleventy* npx eleventy\` + --help`; + } + + resetConfig() { + config.reset(); + + this.config = config.getConfig(); + this.eleventyServe.config = this.config; + } + + async _watch(path) { + if (path) { + path = TemplatePath.addLeadingDotSlash(path); + } + + if (this.active) { + this.queuedToRun = path; + return; + } + + this.active = true; + + let localProjectConfigPath = config.getLocalProjectConfigFile(); + // reset and reload global configuration :O + if (path === localProjectConfigPath) { + this.resetConfig(); + } + config.resetOnWatch(); + + await this.restart(); + this.watchTargets.clearDependencyRequireCache(); + + await this.write(); + + this.watchTargets.reset(); + await this._initWatchDependencies(); + + // Add new deps to chokidar + this.watcher.add(this.watchTargets.getNewTargetsSinceLastReset()); + + let isInclude = + path && + TemplatePath.startsWithSubPath(path, this.eleventyFiles.getIncludesDir()); + this.eleventyServe.reload(path, isInclude); + + this.active = false; + + if (this.queuedToRun) { + console.log("You saved while Eleventy was running, letā€™s run again."); + this.queuedToRun = false; + await this._watch(this.queuedToRun); + } else { console.log("Watchingā€¦"); - }.bind(this) - ); + } + } + + get watcherBench() { + return bench.get("Watcher"); + } + + async initWatch() { + this.watchTargets.add(this.eleventyFiles.getGlobWatcherFiles()); + + // Watch the local project config file + this.watchTargets.add(config.getLocalProjectConfigFile()); + + // Template and Directory Data Files + this.watchTargets.add( + await this.eleventyFiles.getGlobWatcherTemplateDataFiles() + ); + + let benchmark = this.watcherBench.get( + "Watching JavaScript Dependencies (disable with `eleventyConfig.setWatchJavaScriptDependencies(false)`)" + ); + benchmark.before(); + await this._initWatchDependencies(); + benchmark.after(); + } + + async _initWatchDependencies() { + if (!this.watchTargets.watchJavaScriptDependencies) { + return; + } + + let dataDir = this.templateData.getDataDir(); + function filterOutGlobalDataFiles(path) { + return !dataDir || path.indexOf(dataDir) === -1; + } + + // Template files .11ty.js + this.watchTargets.addDependencies(this.eleventyFiles.getWatchPathCache()); + + // Config file dependencies + this.watchTargets.addDependencies( + config.getLocalProjectConfigFile(), + filterOutGlobalDataFiles.bind(this) + ); + + // Deps from Global Data (that arenā€™t in the global data directory, everything is watched there) + this.watchTargets.addDependencies( + this.templateData.getWatchPathCache(), + filterOutGlobalDataFiles.bind(this) + ); + + this.watchTargets.addDependencies( + await this.eleventyFiles.getWatcherTemplateJavaScriptDataFiles() + ); + } + + async getWatchedFiles() { + return this.watchTargets.getTargets(); + } - watcher.on( - "add", - async function(path, stat) { + async watch() { + this.watcherBench.setMinimumThresholdMs(500); + this.watcherBench.reset(); + + const chokidar = require("chokidar"); + + this.active = false; + this.queuedToRun = false; + + // Note that watching indirectly depends on this for fetching dependencies from JS files + // See: TemplateWriter:pathCache and EleventyWatchTargets + await this.write(); + + await this.initWatch(); + + // TODO improve unwatching if JS dependencies are removed (or files are deleted) + let rawFiles = await this.getWatchedFiles(); + debug("Watching for changes to: %o", rawFiles); + + let ignores = this.eleventyFiles.getGlobWatcherIgnores(); + debug("Watching but ignoring changes to: %o", ignores); + + let watcher = chokidar.watch(rawFiles, { + ignored: ignores, + ignoreInitial: true + }); + + this.watcherBench.finish("Initialize --watch", 10, this.isVerbose); + + console.log("Watchingā€¦"); + + this.watcher = watcher; + + async function watchRun(path) { + try { + await this._watch(path); + } catch (e) { + EleventyErrorHandler.fatal(e, "Eleventy fatal watch error"); + watcher.close(); + } + } + + watcher.on("change", async path => { + console.log("File changed:", path); + await watchRun.call(this, path); + }); + + watcher.on("add", async path => { console.log("File added:", path); - await this.write(); - console.log("Watchingā€¦"); - }.bind(this) - ); -}; - -Eleventy.prototype.write = async function() { - try { - return await this.writer.write(); - } catch (e) { - console.log("\n" + chalk.red("Problem writing eleventy templates: ")); - if (e instanceof EleventyError) { - console.log(chalk.red(e.log())); - console.log("\n" + e.dump()); - } else { - console.log(e); + await watchRun.call(this, path); + }); + + process.on( + "SIGINT", + function() { + debug("Cleaning up chokidar and browsersync (if exists) instances."); + this.eleventyServe.close(); + this.watcher.close(); + process.exit(); + }.bind(this) + ); + } + + serve(port) { + this.eleventyServe.serve(port); + } + + /* For testing */ + setLogger(logger) { + this.logger = logger; + } + + async write() { + let ret; + if (this.logger) { + EleventyErrorHandler.logger = this.logger; } + + try { + let promise = this.writer.write(); + + ret = await promise; + } catch (e) { + EleventyErrorHandler.initialMessage( + "Problem writing Eleventy templates", + "error", + "red" + ); + EleventyErrorHandler.fatal(e); + } + + this.finish(); + + debug(` +Getting frustrated? Have a suggestion/feature request/feedback? +I want to hear it! Open an issue: https://github.com/11ty/eleventy/issues/new`); + + // unset the logger + EleventyErrorHandler.logger = undefined; + + return ret; } -}; +} module.exports = Eleventy; diff --git a/src/EleventyBaseError.js b/src/EleventyBaseError.js new file mode 100644 index 000000000..ef2b30e8a --- /dev/null +++ b/src/EleventyBaseError.js @@ -0,0 +1,10 @@ +class EleventyBaseError extends Error { + constructor(message, originalError) { + super(message); + Error.captureStackTrace(this, this.constructor); + this.name = this.constructor.name; + + this.originalError = originalError; + } +} +module.exports = EleventyBaseError; diff --git a/src/EleventyCommandCheck.js b/src/EleventyCommandCheck.js new file mode 100644 index 000000000..cd32cf160 --- /dev/null +++ b/src/EleventyCommandCheck.js @@ -0,0 +1,82 @@ +const EleventyBaseError = require("./EleventyBaseError"); +const debug = require("debug")("Eleventy:CommandCheck"); + +class EleventyCommandCheckError extends EleventyBaseError {} + +class EleventyCommandCheck { + constructor(argv) { + this.valueArgs = [ + "input", + "output", + "formats", + "config", + "pathprefix", + "port" + ]; + + this.booleanArgs = [ + "quiet", + "version", + "watch", + "dryrun", + "help", + "serve", + "passthroughall" + ]; + + this.args = argv; + this.argsMap = this.getArgumentLookupMap(); + + debug("command: eleventy ", this.toString()); + } + + toString() { + let cmd = []; + + for (let valueArgName of this.valueArgs) { + if (this.args[valueArgName]) { + cmd.push(`--${valueArgName}=${this.args[valueArgName]}`); + } + } + + for (let booleanArgName of this.booleanArgs) { + if (this.args[booleanArgName]) { + cmd.push(`--${booleanArgName}`); + } + } + + return cmd.join(" "); + } + + getArgumentLookupMap() { + let obj = {}; + for (let valueArgName of this.valueArgs) { + obj[valueArgName] = true; + } + for (let booleanArgName of this.booleanArgs) { + obj[booleanArgName] = true; + } + return obj; + } + + isKnownArgument(name) { + // _ is the default keyless parameter + if (name === "_") { + return true; + } + + return !!this.argsMap[name]; + } + + hasUnknownArguments() { + for (let argName in this.args) { + if (!this.isKnownArgument(argName)) { + throw new EleventyCommandCheckError( + `We donā€™t know what '${argName}' is. Use --help to see the list of supported commands.` + ); + } + } + } +} + +module.exports = EleventyCommandCheck; diff --git a/src/EleventyConfig.js b/src/EleventyConfig.js new file mode 100644 index 000000000..ef2e65f8a --- /dev/null +++ b/src/EleventyConfig.js @@ -0,0 +1,3 @@ +const UserConfig = require("./UserConfig"); + +module.exports = new UserConfig(); diff --git a/src/EleventyError.js b/src/EleventyError.js deleted file mode 100644 index ebaee0565..000000000 --- a/src/EleventyError.js +++ /dev/null @@ -1,35 +0,0 @@ -class EleventyError { - constructor(newErrorObj, oldErrorObj) { - this.errors = [newErrorObj, oldErrorObj]; - } - - // if already an existing EleventyError, will push on to stack instead of creating a new EleventyError obj - static make(newErrorObj, oldErrorObj) { - if (oldErrorObj instanceof EleventyError) { - oldErrorObj.add(newErrorObj); - return oldErrorObj; - } else { - return new EleventyError(newErrorObj, oldErrorObj); - } - } - - add(errorObj) { - this.errors.push(errorObj); - } - - dump() { - for (let err of this.errors) { - console.log(err); - } - } - - log() { - let str = []; - for (let err of this.errors) { - str.push(` * ${err}`); - } - return str.join("\n"); - } -} - -module.exports = EleventyError; diff --git a/src/EleventyErrorHandler.js b/src/EleventyErrorHandler.js new file mode 100644 index 000000000..b6d5caebb --- /dev/null +++ b/src/EleventyErrorHandler.js @@ -0,0 +1,93 @@ +const chalk = require("chalk"); +const debug = require("debug")("Eleventy:EleventyErrorHandler"); + +class EleventyErrorHandler { + static get isChalkEnabled() { + if (this._isChalkEnabled !== undefined) { + return this._isChalkEnabled; + } + return true; + } + + static set isChalkEnabled(enabled) { + this._isChalkEnabled = !!enabled; + } + + static warn(e, msg) { + if (msg) { + EleventyErrorHandler.initialMessage(msg, "warn", "yellow"); + } + EleventyErrorHandler.log(e, "warn"); + } + + static fatal(e, msg) { + EleventyErrorHandler.error(e, msg); + process.exitCode = 1; + } + + static error(e, msg) { + if (msg) { + EleventyErrorHandler.initialMessage(msg, "error", "red"); + } + EleventyErrorHandler.log(e, "error"); + } + + static log(e, type = "log", prefix = ">") { + let ref = e; + while (ref) { + let nextRef = ref.originalError; + EleventyErrorHandler.message( + (process.env.DEBUG ? "" : `${prefix} `) + + `${ref.message.trim()} + +\`${ref.name}\` was thrown${!nextRef && ref.stack ? ":" : ""}`, + type + ); + + if (process.env.DEBUG) { + debug(`(${type} stack): ${ref.stack}`); + } else if (!nextRef) { + // last error in the loop + let prefix = " "; + // remove duplicate error messages if the stack contains the original message output above + // + let stackStr = ref.stack || ""; + if (e.removeDuplicateErrorStringFromOutput) { + stackStr = stackStr.replace( + `${ref.name}: ${ref.message}`, + "(Repeated output has been truncatedā€¦)" + ); + } + EleventyErrorHandler.message( + prefix + stackStr.split("\n").join("\n" + prefix) + ); + } + ref = nextRef; + } + } + + static initialMessage(message, type = "log", chalkColor = "blue") { + if (message) { + EleventyErrorHandler.message( + message + ":" + (process.env.DEBUG ? "" : " (more in DEBUG output)"), + type, + chalkColor + ); + } + } + + static message(message, type = "log", chalkColor) { + if (process.env.DEBUG) { + debug(message); + } else { + let logger = EleventyErrorHandler.logger || console; + if (chalkColor && EleventyErrorHandler.isChalkEnabled) { + logger[type](chalk[chalkColor](message)); + } else { + logger[type](message); + } + } + } +} + +module.exports = EleventyErrorHandler; diff --git a/src/EleventyErrorUtil.js b/src/EleventyErrorUtil.js new file mode 100644 index 000000000..73291c563 --- /dev/null +++ b/src/EleventyErrorUtil.js @@ -0,0 +1,17 @@ +const TemplateContentPrematureUseError = require("./Errors/TemplateContentPrematureUseError"); + +class EleventyErrorUtil { + static isPrematureTemplateContentError(e) { + // TODO the rest of the template engines + return ( + e instanceof TemplateContentPrematureUseError || + (e.originalError && + e.originalError.name === "RenderError" && + e.originalError.originalError instanceof + TemplateContentPrematureUseError) || // Liquid + e.message.indexOf("TemplateContentPrematureUseError") > -1 + ); // Nunjucks + } +} + +module.exports = EleventyErrorUtil; diff --git a/src/EleventyExtensionMap.js b/src/EleventyExtensionMap.js new file mode 100644 index 000000000..5b2940c81 --- /dev/null +++ b/src/EleventyExtensionMap.js @@ -0,0 +1,164 @@ +const TemplatePath = require("./TemplatePath"); +const config = require("./Config"); + +class EleventyExtensionMap { + constructor(formatKeys = []) { + this.unfilteredFormatKeys = formatKeys.map(function(key) { + return key.trim().toLowerCase(); + }); + + this.formatKeys = this.unfilteredFormatKeys.filter(key => + this.hasExtension(key) + ); + + this.prunedFormatKeys = this.unfilteredFormatKeys.filter( + key => !this.hasExtension(key) + ); + } + + get config() { + return this.configOverride || config.getConfig(); + } + set config(cfg) { + this.configOverride = cfg; + } + + /* Used for layout path resolution */ + getFileList(path, dir) { + if (!path) { + return []; + } + + let files = []; + this.formatKeys.forEach( + function(key) { + this.getExtensionsFromKey(key).forEach(function(extension) { + files.push((dir ? dir + "/" : "") + path + "." + extension); + }); + }.bind(this) + ); + + return files; + } + + getPrunedGlobs(inputDir) { + return this._getGlobs(this.prunedFormatKeys, inputDir); + } + + getGlobs(inputDir) { + if (this.config.passthroughFileCopy) { + return this._getGlobs(this.unfilteredFormatKeys, inputDir); + } + + return this._getGlobs(this.formatKeys, inputDir); + } + + _getGlobs(formatKeys, inputDir) { + let dir = TemplatePath.convertToRecursiveGlob(inputDir); + let globs = []; + formatKeys.forEach( + function(key) { + if (this.hasExtension(key)) { + this.getExtensionsFromKey(key).forEach(function(extension) { + globs.push(dir + "/*." + extension); + }); + } else { + globs.push(dir + "/*." + key); + } + }.bind(this) + ); + return globs; + } + + hasExtension(key) { + for (var extension in EleventyExtensionMap.keyMap) { + if (EleventyExtensionMap.keyMap[extension] === key) { + return true; + } + } + return false; + } + + getExtensionsFromKey(key) { + let extensions = []; + for (var extension in this.keyMap) { + if (this.keyMap[extension] === key) { + extensions.push(extension); + } + } + return extensions; + } + + getKey(pathOrKey) { + return EleventyExtensionMap._getKey(pathOrKey, this.keyMap); + } + static getKey(pathOrKey) { + return EleventyExtensionMap._getKey(pathOrKey, EleventyExtensionMap.keyMap); + } + static _getKey(pathOrKey, map) { + pathOrKey = pathOrKey.toLowerCase(); + + for (var extension in map) { + let key = map[extension]; + if (pathOrKey === extension) { + return key; + } else if (pathOrKey.endsWith("." + extension)) { + return key; + } + } + } + + removeTemplateExtension(path) { + return EleventyExtensionMap._removeTemplateExtension(path, this.keyMap); + } + static removeTemplateExtension(path) { + return EleventyExtensionMap._removeTemplateExtension( + path, + EleventyExtensionMap.keyMap + ); + } + static _removeTemplateExtension(path, map) { + for (var extension in map) { + if (path === extension || path.endsWith("." + extension)) { + return path.substr(0, path.length - 1 - extension.length); + } + } + return path; + } + + get keyMap() { + return EleventyExtensionMap._getKeyMap( + this.config.templateExtensionAliases || {} + ); + } + static get keyMap() { + return EleventyExtensionMap._getKeyMap( + config.getConfig().templateExtensionAliases || {} + ); + } + + // file extension => key + static _getKeyMap(aliases) { + let fileExtensionToKeyMap = { + ejs: "ejs", + md: "md", + jstl: "jstl", + html: "html", + hbs: "hbs", + mustache: "mustache", + haml: "haml", + pug: "pug", + njk: "njk", + liquid: "liquid", + "11ty.js": "11ty.js" + }; + + for (let extension in aliases) { + fileExtensionToKeyMap[extension] = aliases[extension]; + } + + return fileExtensionToKeyMap; + } +} + +module.exports = EleventyExtensionMap; diff --git a/src/EleventyFiles.js b/src/EleventyFiles.js new file mode 100644 index 000000000..67e2dd94d --- /dev/null +++ b/src/EleventyFiles.js @@ -0,0 +1,349 @@ +const fs = require("fs-extra"); +const fastglob = require("fast-glob"); + +const EleventyExtensionMap = require("./EleventyExtensionMap"); +const TemplateData = require("./TemplateData"); +const TemplateGlob = require("./TemplateGlob"); +const TemplatePath = require("./TemplatePath"); +const TemplatePassthroughManager = require("./TemplatePassthroughManager"); + +const config = require("./Config"); +const debug = require("debug")("Eleventy:EleventyFiles"); +// const debugDev = require("debug")("Dev:Eleventy:EleventyFiles"); + +class EleventyFiles { + constructor(input, outputDir, formats, passthroughAll) { + this.config = config.getConfig(); + this.input = input; + this.inputDir = TemplatePath.getDir(this.input); + this.outputDir = outputDir; + + this.initConfig(); + + this.passthroughAll = !!passthroughAll; + + this.formats = formats; + this.extensionMap = new EleventyExtensionMap(formats); + } + + initConfig() { + this.includesDir = TemplatePath.join( + this.inputDir, + this.config.dir.includes + ); + + if ("layouts" in this.config.dir) { + this.layoutsDir = TemplatePath.join( + this.inputDir, + this.config.dir.layouts + ); + } + } + + init() { + this.initFormatsGlobs(); + this.setPassthroughManager(); + this.setupGlobs(); + } + + restart() { + this.passthroughManager.reset(); + this.setupGlobs(); + } + + /* For testing */ + _setConfig(config) { + this.config = config; + this.initConfig(); + } + /* For testing */ + _setExtensionMap(map) { + this.extensionMap = map; + } + /* Set command root for local project paths */ + _setLocalPathRoot(dir) { + this.localPathRoot = dir; + } + + setPassthroughAll(passthroughAll) { + this.passthroughAll = !!passthroughAll; + } + + initFormatsGlobs() { + // Input was a directory + if (this.input === this.inputDir) { + this.templateGlobs = TemplateGlob.map( + this.extensionMap.getGlobs(this.inputDir) + ); + } else { + this.templateGlobs = TemplateGlob.map([this.input]); + } + } + + getPassthroughManager() { + return this.passthroughManager; + } + + setPassthroughManager(mgr) { + if (!mgr) { + mgr = new TemplatePassthroughManager(); + mgr.setInputDir(this.inputDir); + mgr.setOutputDir(this.outputDir); + } + + this.passthroughManager = mgr; + } + + setTemplateData(templateData) { + this.templateData = templateData; + } + + // TODO make this a getter + getTemplateData() { + if (!this.templateData) { + this.templateData = new TemplateData(this.inputDir); + } + return this.templateData; + } + + getDataDir() { + let data = this.getTemplateData(); + + return data.getDataDir(); + } + + setupGlobs() { + this.ignores = this.getIgnores(); + + if (this.passthroughAll) { + this.watchedGlobs = TemplateGlob.map([ + TemplateGlob.normalizePath(this.input, "/**") + ]).concat(this.ignores); + } else { + this.watchedGlobs = this.templateGlobs.concat(this.ignores); + } + + this.templateGlobsWithIgnores = this.watchedGlobs.concat( + this.getTemplateIgnores() + ); + } + + static getFileIgnores(ignoreFiles, defaultIfFileDoesNotExist) { + if (!Array.isArray(ignoreFiles)) { + ignoreFiles = [ignoreFiles]; + } + + let ignores = []; + let fileFound = false; + let dirs = []; + for (let ignorePath of ignoreFiles) { + ignorePath = TemplatePath.normalize(ignorePath); + + let dir = TemplatePath.getDirFromFilePath(ignorePath); + dirs.push(dir); + + if (fs.existsSync(ignorePath) && fs.statSync(ignorePath).size > 0) { + fileFound = true; + let ignoreContent = fs.readFileSync(ignorePath, "utf-8"); + + // make sure that empty .gitignore with spaces takes default ignore. + if (ignoreContent.trim().length === 0) { + fileFound = false; + } else { + ignores = ignores.concat( + EleventyFiles.normalizeIgnoreContent(dir, ignoreContent) + ); + } + } + } + + if (!fileFound && defaultIfFileDoesNotExist) { + ignores.push("!" + TemplateGlob.normalizePath(defaultIfFileDoesNotExist)); + for (let dir of dirs) { + ignores.push( + "!" + TemplateGlob.normalizePath(dir, defaultIfFileDoesNotExist) + ); + } + } + + ignores.forEach(function(path) { + debug(`${ignoreFiles} ignoring: ${path}`); + }); + return ignores; + } + + static normalizeIgnoreContent(dir, ignoreContent) { + let ignores = []; + + if (ignoreContent) { + ignores = ignoreContent + .split("\n") + .map(line => { + return line.trim(); + }) + .filter(line => { + // empty lines or comments get filtered out + return line.length > 0 && line.charAt(0) !== "#"; + }) + .map(line => { + let path = TemplateGlob.normalizePath(dir, "/", line); + path = TemplatePath.addLeadingDotSlash( + TemplatePath.relativePath(path) + ); + + try { + // Note these folders must exist to get /** suffix + let stat = fs.statSync(path); + if (stat.isDirectory()) { + return "!" + path + "/**"; + } + return "!" + path; + } catch (e) { + return "!" + path; + } + }); + } + + return ignores; + } + + getIgnores() { + let files = []; + if (this.config.useGitIgnore) { + files = files.concat( + EleventyFiles.getFileIgnores( + [ + TemplatePath.join( + this.localPathRoot || TemplatePath.getWorkingDir(), + ".gitignore" + ), + TemplatePath.join(this.inputDir, ".gitignore") + ], + "node_modules/**" + ) + ); + } + + if (this.config.eleventyignoreOverride !== false) { + let eleventyIgnores = [ + TemplatePath.join( + this.localPathRoot || TemplatePath.getWorkingDir(), + ".eleventyignore" + ), + TemplatePath.join(this.inputDir, ".eleventyignore") + ]; + + files = files.concat( + this.config.eleventyignoreOverride || + EleventyFiles.getFileIgnores(eleventyIgnores) + ); + } + + files = files.concat(TemplateGlob.map("!" + this.outputDir + "/**")); + + return files; + } + + getIncludesDir() { + return this.includesDir; + } + + getLayoutsDir() { + return this.layoutsDir; + } + + getFileGlobs() { + return this.templateGlobsWithIgnores; + } + + getRawFiles() { + return this.templateGlobs; + } + + getWatchPathCache() { + return this.pathCache; + } + + async getFiles() { + let globs = this.getFileGlobs(); + + debug("Searching for: %o", globs); + let paths = TemplatePath.addLeadingDotSlashArray( + await fastglob(globs, { + caseSensitiveMatch: false, + dot: true + }) + ); + this.pathCache = paths; + return paths; + } + + getGlobWatcherFiles() { + // TODO is it better to tie the includes and data to specific file extensions or keep the **? + return this.templateGlobs + .concat(this.getIncludesAndDataDirs()) + .concat(this.getPassthroughManager().getConfigPathGlobs()); + } + + async getGlobWatcherTemplateDataFiles() { + let templateData = this.getTemplateData(); + return await templateData.getTemplateDataFileGlob(); + } + + // TODO this isnā€™t great but reduces complexity avoiding using TemplateData:getLocalDataPaths for each template in the cache + async getWatcherTemplateJavaScriptDataFiles() { + let globs = await this.getTemplateData().getTemplateJavaScriptDataFileGlob(); + return TemplatePath.addLeadingDotSlashArray( + await fastglob(globs, { + ignore: ["**/node_modules/**"], + caseSensitiveMatch: false, + dot: true + }) + ); + } + + getGlobWatcherIgnores() { + // convert to format without ! since they are passed in as a separate argument to glob watcher + return this.ignores.map(ignore => + TemplatePath.stripLeadingDotSlash(ignore.substr(1)) + ); + } + + getPassthroughPaths() { + let paths = []; + paths = paths.concat(this.passthroughManager.getConfigPaths()); + // These are already added in the root templateGlobs + // paths = paths.concat(this.extensionMap.getPrunedGlobs(this.inputDir)); + return paths; + } + + getIncludesAndDataDirs() { + let files = []; + // we want this to fail on "" because we donā€™t want to ignore the + // entire input directory when using "" + if (this.config.dir.includes) { + files = files.concat(TemplateGlob.map(this.includesDir + "/**")); + } + + // we want this to fail on "" because we donā€™t want to ignore the + // entire input directory when using "" + if (this.config.dir.layouts) { + files = files.concat(TemplateGlob.map(this.layoutsDir + "/**")); + } + + if (this.config.dir.data && this.config.dir.data !== ".") { + let dataDir = this.getDataDir(); + files = files.concat(TemplateGlob.map(dataDir + "/**")); + } + + return files; + } + + getTemplateIgnores() { + return this.getIncludesAndDataDirs().map(function(dir) { + return "!" + dir; + }); + } +} + +module.exports = EleventyFiles; diff --git a/src/EleventyServe.js b/src/EleventyServe.js new file mode 100644 index 000000000..c1d80b805 --- /dev/null +++ b/src/EleventyServe.js @@ -0,0 +1,176 @@ +const fs = require("fs-extra"); + +const TemplatePath = require("./TemplatePath"); +const config = require("./Config"); +const debug = require("debug")("EleventyServe"); + +class EleventyServe { + constructor() {} + + get config() { + return this.configOverride || config.getConfig(); + } + set config(config) { + this.configOverride = config; + } + + setOutputDir(outputDir) { + this.outputDir = outputDir; + } + + getPathPrefix() { + return this.config.pathPrefix || "/"; + } + + getRedirectDir(dirName) { + return TemplatePath.join(this.outputDir, dirName); + } + getRedirectDirOverride() { + // has a pathPrefix, add a /index.html template to redirect to /pathPrefix/ + if (this.getPathPrefix() !== "/") { + return "_eleventy_redirect"; + } + } + + getRedirectFilename(dirName) { + return TemplatePath.join(this.getRedirectDir(dirName), "index.html"); + } + + getOptions(port) { + let pathPrefix = this.getPathPrefix(); + + // TODO customize this in Configuration API? + let serverConfig = { + baseDir: this.outputDir + }; + + let redirectDirName = this.getRedirectDirOverride(); + // has a pathPrefix, add a /index.html template to redirect to /pathPrefix/ + if (redirectDirName) { + serverConfig.baseDir = this.getRedirectDir(redirectDirName); + serverConfig.routes = {}; + serverConfig.routes[pathPrefix] = this.outputDir; + + // if has a savedPathPrefix, use the /savedPathPrefix/index.html template to redirect to /pathPrefix/ + if (this.savedPathPrefix) { + serverConfig.routes[this.savedPathPrefix] = TemplatePath.join( + this.outputDir, + this.savedPathPrefix + ); + } + } + + return Object.assign( + { + server: serverConfig, + port: port || 8080, + ignore: ["node_modules"], + watch: false, + open: false, + notify: false, + index: "index.html" + }, + this.config.browserSyncConfig + ); + } + + cleanupRedirect(dirName) { + if (dirName && dirName !== "/") { + let savedPathFilename = this.getRedirectFilename(dirName); + + setTimeout(function() { + if (!fs.existsSync(savedPathFilename)) { + debug(`Cleanup redirect: Could not find ${savedPathFilename}`); + return; + } + + let savedPathContent = fs.readFileSync(savedPathFilename, "utf-8"); + if ( + savedPathContent.indexOf("Browsersync pathPrefix Redirect") === -1 + ) { + debug( + `Cleanup redirect: Found ${savedPathFilename} but it wasnā€™t an eleventy redirect.` + ); + return; + } + + fs.unlink(savedPathFilename, err => { + if (!err) { + debug(`Cleanup redirect: Deleted ${savedPathFilename}`); + } + }); + }, 2000); + } + } + + serveRedirect(dirName) { + fs.outputFile( + this.getRedirectFilename(dirName), + ` + + Browsersync pathPrefix Redirect + Go to ${this.config.pathPrefix}` + ); + } + + serve(port) { + // only load on serveā€”this is pretty expensive + const browserSync = require("browser-sync"); + this.server = browserSync.create(); + + let pathPrefix = this.getPathPrefix(); + + if (this.savedPathPrefix && pathPrefix !== this.savedPathPrefix) { + let redirectFilename = this.getRedirectFilename(this.savedPathPrefix); + if (!fs.existsSync(redirectFilename)) { + debug( + `Redirecting BrowserSync from ${ + this.savedPathPrefix + } to ${pathPrefix}` + ); + this.serveRedirect(this.savedPathPrefix); + } else { + debug( + `Config updated with a new pathPrefix. Tried to set up a transparent redirect but found a template already existing at ${redirectFilename}. Youā€™ll have to navigate manually.` + ); + } + } + + let redirectDirName = this.getRedirectDirOverride(); + // has a pathPrefix, add a /index.html template to redirect to /pathPrefix/ + if (redirectDirName) { + this.serveRedirect(redirectDirName); + } + + this.cleanupRedirect(this.savedPathPrefix); + this.server.init(this.getOptions(port)); + + // this needs to happen after `.getOptions` + this.savedPathPrefix = pathPrefix; + } + + close() { + if (this.server) { + this.server.exit(); + } + } + + reload(path, isInclude) { + if (this.server) { + if (this.getPathPrefix() !== this.savedPathPrefix) { + this.server.exit(); + this.serve(); + } else { + // Is a CSS input file and is not in the includes folder + // TODO check output path file extension of this template (not input path) + if (path && path.split(".").pop() === "css" && !isInclude) { + this.server.reload("*.css"); + } else { + this.server.reload(); + } + } + } + } +} + +module.exports = EleventyServe; diff --git a/src/EleventyWatchTargets.js b/src/EleventyWatchTargets.js new file mode 100644 index 000000000..6965732a9 --- /dev/null +++ b/src/EleventyWatchTargets.js @@ -0,0 +1,125 @@ +const dependencyTree = require("dependency-tree"); +const TemplatePath = require("./TemplatePath"); + +class EleventyWatchTargets { + constructor() { + this.targets = new Set(); + this.dependencies = new Set(); + this.newTargets = new Set(); + this._watchJavaScriptDependencies = true; + } + + set watchJavaScriptDependencies(watch) { + this._watchJavaScriptDependencies = !!watch; + } + + get watchJavaScriptDependencies() { + return this._watchJavaScriptDependencies; + } + + _normalizeTargets(targets) { + if (!targets) { + return []; + } else if (Array.isArray(targets)) { + return targets; + } + + return [targets]; + } + + reset() { + this.newTargets = new Set(); + } + + isWatched(target) { + return this.targets.has(target); + } + + addRaw(targets, isDependency) { + for (let target of targets) { + let path = TemplatePath.addLeadingDotSlash(target); + if (!this.isWatched(path)) { + this.newTargets.add(path); + } + + this.targets.add(path); + + if (isDependency) { + this.dependencies.add(path); + } + } + } + + // add only a target + add(targets) { + targets = this._normalizeTargets(targets); + this.addRaw(targets); + } + + // add only a targetā€™s dependencies + addDependencies(targets, filterCallback) { + if (!this.watchJavaScriptDependencies) { + return; + } + + targets = this._normalizeTargets(targets); + + let deps = this.getJavaScriptDependenciesFromList(targets); + if (filterCallback) { + deps = deps.filter(filterCallback); + } + + this.addRaw(deps, true); + } + + setWriter(templateWriter) { + this.writer = templateWriter; + } + + getJavaScriptDependenciesFromList(files = []) { + let depSet = new Set(); + files + .filter(file => file.endsWith(".js")) // TODO does this need to work with aliasing? what other JS extensions will have deps? + .forEach(file => { + dependencyTree + .toList({ + filename: file, + directory: TemplatePath.absolutePath(), + filter: function(path) { + return path.indexOf("node_modules") === -1; + } + }) + .map(dependency => { + return TemplatePath.addLeadingDotSlash( + TemplatePath.relativePath(dependency) + ); + }) + .filter(dependency => { + return ( + dependency !== file && dependency.indexOf("node_modules") === -1 + ); + }) + .forEach(dependency => { + depSet.add(dependency); + }); + }); + + return Array.from(depSet); + } + + clearDependencyRequireCache() { + for (let path of this.dependencies) { + delete require.cache[TemplatePath.absolutePath(path)]; + } + } + + getNewTargetsSinceLastReset() { + return Array.from(this.newTargets); + } + + getTargets() { + return Array.from(this.targets); + } +} + +module.exports = EleventyWatchTargets; diff --git a/src/Engines/Ejs.js b/src/Engines/Ejs.js index bdebf8901..98ca1ebbf 100644 --- a/src/Engines/Ejs.js +++ b/src/Engines/Ejs.js @@ -1,13 +1,52 @@ const ejsLib = require("ejs"); const TemplateEngine = require("./TemplateEngine"); +const config = require("../Config"); class Ejs extends TemplateEngine { - async compile(str) { - let fn = ejsLib.compile(str, { - root: "./" + super.getInputDir(), - compileDebug: true, - filename: "./" + super.getInputDir() - }); + constructor(name, includesDir) { + super(name, includesDir); + + this.ejsOptions = {}; + + this.setLibrary(this.config.libraryOverrides.ejs); + this.setEjsOptions(this.config.ejsOptions); + } + + setLibrary(lib) { + this.ejsLib = lib || ejsLib; + this.setEngineLib(this.ejsLib); + } + + getEngine() { + return this.ejsLib; + } + + setEjsOptions(options) { + this.ejsOptions = options; + } + + getEjsOptions() { + let includesDir = super.getIncludesDir(); + + return Object.assign( + { + root: "./" + includesDir, + compileDebug: true, + filename: "./" + includesDir + }, + this.ejsOptions || {} + ); + } + + async compile(str, inputPath) { + let options = this.getEjsOptions(); + if (!inputPath || inputPath === "ejs" || inputPath === "md") { + // do nothing + } else { + options.filename = inputPath; + } + + let fn = this.ejsLib.compile(str, options); return function(data) { return fn(data); diff --git a/src/Engines/Haml.js b/src/Engines/Haml.js index c56a0583e..693e8ad13 100644 --- a/src/Engines/Haml.js +++ b/src/Engines/Haml.js @@ -1,9 +1,21 @@ const HamlLib = require("hamljs"); const TemplateEngine = require("./TemplateEngine"); +const config = require("../Config"); class Haml extends TemplateEngine { + constructor(name, includesDir) { + super(name, includesDir); + + this.setLibrary(this.config.libraryOverrides.haml); + } + + setLibrary(lib) { + this.hamlLib = lib || HamlLib; + this.setEngineLib(lib); + } + async compile(str) { - return HamlLib.compile(str); + return this.hamlLib.compile(str); } } diff --git a/src/Engines/Handlebars.js b/src/Engines/Handlebars.js index 0ac0bc91f..54c07dfb4 100644 --- a/src/Engines/Handlebars.js +++ b/src/Engines/Handlebars.js @@ -1,29 +1,62 @@ const HandlebarsLib = require("handlebars"); const TemplateEngine = require("./TemplateEngine"); -const TemplateConfig = require("../TemplateConfig"); - -let cfg = TemplateConfig.getDefaultConfig(); +const config = require("../Config"); class Handlebars extends TemplateEngine { - constructor(name, inputDir) { - super(name, inputDir); + constructor(name, includesDir) { + super(name, includesDir); + + this.setLibrary(this.config.libraryOverrides.hbs); + } + + setLibrary(lib) { + this.handlebarsLib = lib || HandlebarsLib; + this.setEngineLib(this.handlebarsLib); let partials = super.getPartials(); for (let name in partials) { - HandlebarsLib.registerPartial(name, partials[name]); + this.handlebarsLib.registerPartial(name, partials[name]); } - this.addHelpers(cfg.handlebarsHelpers); + // TODO these all go to the same place (addHelper), add warnings for overwrites + this.addHelpers(this.config.handlebarsHelpers); + this.addShortcodes(this.config.handlebarsShortcodes); + this.addPairedShortcodes(this.config.handlebarsPairedShortcodes); + } + + addHelper(name, callback) { + this.handlebarsLib.registerHelper(name, callback); } addHelpers(helpers) { for (let name in helpers) { - HandlebarsLib.registerHelper(name, helpers[name]); + this.addHelper(name, helpers[name]); + } + } + + addShortcodes(shortcodes) { + for (let name in shortcodes) { + this.addHelper(name, shortcodes[name]); + } + } + + addPairedShortcodes(shortcodes) { + for (let name in shortcodes) { + let callback = shortcodes[name]; + this.addHelper(name, function(...args) { + let options = args[args.length - 1]; + let content = ""; + if (options && options.fn) { + content = options.fn(this); + } + + return callback.apply(this, [content, ...args]); + }); } } - async compile(str) { - let fn = HandlebarsLib.compile(str); + async compile(str, inputPath) { + let fn = this.handlebarsLib.compile(str); return function(data) { return fn(data); }; diff --git a/src/Engines/Html.js b/src/Engines/Html.js index 98a070e47..8815076df 100644 --- a/src/Engines/Html.js +++ b/src/Engines/Html.js @@ -1,13 +1,13 @@ const TemplateEngine = require("./TemplateEngine"); class Html extends TemplateEngine { - async compile(str, preTemplateEngine) { + async compile(str, inputPath, preTemplateEngine) { if (preTemplateEngine) { let engine = TemplateEngine.getEngine( preTemplateEngine, - super.getInputDir() + super.getIncludesDir() ); - let fn = await engine.compile(str); + let fn = await engine.compile(str, inputPath); return async function(data) { return fn(data); diff --git a/src/Engines/JavaScript.js b/src/Engines/JavaScript.js index b3e9439a5..1a9869b95 100644 --- a/src/Engines/JavaScript.js +++ b/src/Engines/JavaScript.js @@ -1,30 +1,120 @@ const TemplateEngine = require("./TemplateEngine"); -const EleventyError = require("../EleventyError"); +const TemplatePath = require("../TemplatePath"); +const EleventyBaseError = require("../EleventyBaseError"); + +class JavaScriptTemplateInvalidDataFormatError extends EleventyBaseError {} class JavaScript extends TemplateEngine { - async compile(str) { - return function(data) { - // avoid `with` - let dataStr = ""; - for (var j in data) { - dataStr += `let ${j} = ${JSON.stringify(data[j])};\n`; - } + constructor(name, includesDir) { + super(name, includesDir); + this.instances = {}; + } + + normalize(result) { + if (Buffer.isBuffer(result)) { + return result.toString(); + } - // add ` around template if it doesnā€™t exist. - let trimmedStr = str.trim(); - if (trimmedStr.charAt(trimmedStr.length - 1) !== "`") { - str = "`" + str + "`"; + return result; + } + + // String, Buffer, Promise + // Function, Class + // Object + _getInstance(mod) { + let noop = function() { + return ""; + }; + + if (typeof mod === "string" || mod instanceof Buffer || mod.then) { + return { render: () => mod }; + } else if (typeof mod === "function") { + if ( + mod.prototype && + ("data" in mod.prototype || "render" in mod.prototype) + ) { + if (!("render" in mod.prototype)) { + mod.prototype.render = noop; + } + return new mod(); + } else { + return { render: mod }; + } + } else if ("data" in mod || "render" in mod) { + if (!("render" in mod)) { + mod.render = noop; } + return mod; + } + } + + getInstanceFromInputPath(inputPath) { + if (this.instances[inputPath]) { + return this.instances[inputPath]; + } + + const mod = this._getRequire(inputPath); + let inst = this._getInstance(mod); + + this.instances[inputPath] = inst; + return inst; + } + + _getRequire(inputPath) { + let requirePath = TemplatePath.absolutePath(inputPath); + return require(requirePath); + } - let evalStr = `${dataStr}\n${str};`; - try { - // TODO switch to https://www.npmjs.com/package/es6-template-strings - let val = eval(evalStr); - return val; - } catch (e) { - EleventyError.make(`Broken ES6 template:\n${evalStr}`, e); + needsToReadFileContents() { + return false; + } + + // only remove from cache once on startup (if it already exists) + initRequireCache(inputPath) { + let requirePath = TemplatePath.absolutePath(inputPath); + if (requirePath in require.cache) { + delete require.cache[requirePath]; + } + + if (inputPath in this.instances) { + delete this.instances[inputPath]; + } + } + + async getExtraDataFromFile(inputPath) { + let inst = this.getInstanceFromInputPath(inputPath); + if (inst && "data" in inst) { + // get extra data from `data` method, + // either as a function or getter or object literal + let result = await (typeof inst.data === "function" + ? inst.data() + : inst.data); + if (typeof result !== "object") { + throw new JavaScriptTemplateInvalidDataFormatError( + `Invalid data format returned from ${inputPath}: typeof ${typeof result}` + ); } - }; + return result; + } + } + + async compile(str, inputPath) { + let inst; + if (str) { + // When str has a value, it's being used for permalinks in data + inst = this._getInstance(str); + } else { + // For normal templates, str will be falsy. + inst = this.getInstanceFromInputPath(inputPath); + } + + if (inst && "render" in inst) { + Object.assign(inst, this.config.javascriptFunctions); + + return function(data) { + return this.normalize(inst.render.call(inst, data)); + }.bind(this); + } } } diff --git a/src/Engines/JavaScriptTemplateLiteral.js b/src/Engines/JavaScriptTemplateLiteral.js new file mode 100644 index 000000000..4071593ee --- /dev/null +++ b/src/Engines/JavaScriptTemplateLiteral.js @@ -0,0 +1,63 @@ +const javascriptStringify = require("javascript-stringify"); +const TemplateEngine = require("./TemplateEngine"); +const EleventyBaseError = require("../EleventyBaseError"); + +class JavaScriptTemplateLiteralCompileError extends EleventyBaseError {} + +class JavaScriptTemplateLiteral extends TemplateEngine { + // add ` around template if it doesnā€™t exist. + static normalizeTicks(str) { + // TODO make this work with tagged templates html`` + let trimmedStr = str.trim(); + if ( + trimmedStr.charAt(0) === "`" && + trimmedStr.charAt(trimmedStr.length - 1) === "`" + ) { + str = + "`" + + trimmedStr.substr(1, trimmedStr.length - 2).replace(/`/g, "\\`") + + "`"; + } else { + str = + "`" + + str.replace(/`/g, "\\`") + + (trimmedStr.charAt(trimmedStr.length - 1) === "`" ? "\n" : "") + + "`"; + } + + return str; + } + + async compile(str, inputPath) { + return function(data) { + // avoid `with` + let dataStr = ""; + for (let j in data) { + dataStr += `let ${j} = ${javascriptStringify.stringify( + data[j], + null, + null, + { + references: true + } + )};\n`; + } + + let evalStr = `${dataStr}\n${JavaScriptTemplateLiteral.normalizeTicks( + str + )};`; + try { + // TODO switch to https://www.npmjs.com/package/es6-template-strings + let val = eval(evalStr); + return val; + } catch (e) { + throw new JavaScriptTemplateLiteralCompileError( + `Broken ES6 template:\n${evalStr}`, + e + ); + } + }; + } +} + +module.exports = JavaScriptTemplateLiteral; diff --git a/src/Engines/Liquid.js b/src/Engines/Liquid.js index e0043a08f..e2b6ca9cf 100644 --- a/src/Engines/Liquid.js +++ b/src/Engines/Liquid.js @@ -1,18 +1,197 @@ +const moo = require("moo"); const LiquidLib = require("liquidjs"); const TemplateEngine = require("./TemplateEngine"); +const TemplatePath = require("../TemplatePath"); +// const debug = require("debug")("Eleventy:Liquid"); class Liquid extends TemplateEngine { - async compile(str) { - // warning, the include syntax supported here does not match what jekyll uses. - let engine = LiquidLib({ - root: [super.getInputDir()], + constructor(name, includesDir) { + super(name, includesDir); + + this.liquidOptions = {}; + + this.setLibrary(this.config.libraryOverrides.liquid); + this.setLiquidOptions(this.config.liquidOptions); + + this.argLexer = moo.compile({ + number: /[0-9]+\.*[0-9]*/, + doubleQuoteString: /"(?:\\["\\]|[^\n"\\])*"/, + singleQuoteString: /'(?:\\['\\]|[^\n'\\])*'/, + keyword: /[a-zA-Z0-9\.\-\_]+/, + "ignore:whitespace": /[, \t]+/ // includes comma separator + }); + } + + setLibrary(lib) { + this.liquidLibOverride = lib; + + // warning, the include syntax supported here does not exactly match what Jekyll uses. + this.liquidLib = lib || LiquidLib(this.getLiquidOptions()); + this.setEngineLib(this.liquidLib); + + this.addFilters(this.config.liquidFilters); + + // TODO these all go to the same place (addTag), add warnings for overwrites + this.addCustomTags(this.config.liquidTags); + this.addAllShortcodes(this.config.liquidShortcodes); + this.addAllPairedShortcodes(this.config.liquidPairedShortcodes); + } + + setLiquidOptions(options) { + this.liquidOptions = options; + + this.setLibrary(this.liquidLibOverride); + } + + getLiquidOptions() { + let defaults = { + root: [super.getIncludesDir()], // overrides in compile with inputPath below extname: ".liquid", - dynamicPartials: false + dynamicPartials: false, + strict_filters: false + }; + + let options = Object.assign(defaults, this.liquidOptions || {}); + // debug("Liquid constructor options: %o", options); + + return options; + } + + addCustomTags(tags) { + for (let name in tags) { + this.addTag(name, tags[name]); + } + } + + addFilters(filters) { + for (let name in filters) { + this.addFilter(name, filters[name]); + } + } + + addFilter(name, filter) { + this.liquidLib.registerFilter(name, filter); + } + + addTag(name, tagFn) { + let tagObj; + if (typeof tagFn === "function") { + tagObj = tagFn(this.liquidLib); + } else { + throw new Error( + "Liquid.addTag expects a callback function to be passed in: addTag(name, function(liquidEngine) { return { parse: ā€¦, render: ā€¦ } })" + ); + } + this.liquidLib.registerTag(name, tagObj); + } + + addAllShortcodes(shortcodes) { + for (let name in shortcodes) { + this.addShortcode(name, shortcodes[name]); + } + } + + addAllPairedShortcodes(shortcodes) { + for (let name in shortcodes) { + this.addPairedShortcode(name, shortcodes[name]); + } + } + + static parseArguments(lexer, str, scope) { + let argArray = []; + + if (typeof str === "string") { + // TODO key=value key2=value + // TODO JSON? + lexer.reset(str); + let arg = lexer.next(); + while (arg) { + /*{ + type: 'doubleQuoteString', + value: '"test 2"', + text: '"test 2"', + toString: [Function: tokenToString], + offset: 0, + lineBreaks: 0, + line: 1, + col: 1 }*/ + if (arg.type.indexOf("ignore:") === -1) { + argArray.push(LiquidLib.evalExp(arg.value, scope)); // or evalValue + } + arg = lexer.next(); + } + } + + return argArray; + } + + addShortcode(shortcodeName, shortcodeFn) { + let _t = this; + this.addTag(shortcodeName, function(liquidEngine) { + return { + parse: function(tagToken, remainTokens) { + this.name = tagToken.name; + this.args = tagToken.args; + }, + render: function(scope, hash) { + let argArray = Liquid.parseArguments(_t.argLexer, this.args, scope); + + return Promise.resolve(shortcodeFn(...argArray)); + } + }; }); + } + + addPairedShortcode(shortcodeName, shortcodeFn) { + let _t = this; + this.addTag(shortcodeName, function(liquidEngine) { + return { + parse: function(tagToken, remainTokens) { + this.name = tagToken.name; + this.args = tagToken.args; + this.templates = []; + + var stream = liquidEngine.parser + .parseStream(remainTokens) + .on("template", tpl => this.templates.push(tpl)) + .on("tag:end" + shortcodeName, token => stream.stop()) + .on("end", x => { + throw new Error(`tag ${tagToken.raw} not closed`); + }); + + stream.start(); + }, + render: function(scope, hash) { + let argArray = Liquid.parseArguments(_t.argLexer, this.args, scope); + + return new Promise((resolve, reject) => { + liquidEngine.renderer + .renderTemplates(this.templates, scope) + .then(function(html) { + resolve(shortcodeFn(html, ...argArray)); + }); + }); + } + }; + }); + } + + async compile(str, inputPath) { + let engine = this.liquidLib; + let tmpl = await engine.parse(str, inputPath); - let tmpl = await engine.parse(str); + // Required for relative includes + let options = {}; + if (!inputPath || inputPath === "njk" || inputPath === "md") { + // do nothing + } else { + options.root = [ + super.getIncludesDir(), + TemplatePath.getDirFromFilePath(inputPath) + ]; + } return async function(data) { - return engine.render(tmpl, data); + return engine.render(tmpl, data, options); }; } } diff --git a/src/Engines/Markdown.js b/src/Engines/Markdown.js index bc6e7b108..f064c5f21 100644 --- a/src/Engines/Markdown.js +++ b/src/Engines/Markdown.js @@ -1,14 +1,66 @@ -const mdlib = require("markdown-it")(); +const markdownIt = require("markdown-it"); const TemplateEngine = require("./TemplateEngine"); +const config = require("../Config"); +// const debug = require("debug")("Eleventy:Markdown"); class Markdown extends TemplateEngine { - async compile(str, preTemplateEngine, bypassMarkdown) { + constructor(name, includesDir) { + super(name, includesDir); + + this.markdownOptions = {}; + + this.setLibrary(this.config.libraryOverrides.md); + } + + setLibrary(mdLib) { + this.mdLib = mdLib || markdownIt(this.getMarkdownOptions()); + + // Overrides a highlighter set in `markdownOptions` + // This is separate so devs can pass in a new mdLib and still use the official eleventy plugin for markdown highlighting + if (this.config.markdownHighlighter) { + this.mdLib.set({ + highlight: this.config.markdownHighlighter + }); + } + + this.setEngineLib(this.mdLib); + } + + setMarkdownOptions(options) { + this.markdownOptions = options; + } + + getMarkdownOptions() { + // work with "mode" presets https://github.com/markdown-it/markdown-it#init-with-presets-and-options + if (typeof this.markdownOptions === "string") { + return this.markdownOptions; + } + + return Object.assign( + { + html: true + }, + this.markdownOptions || {} + ); + } + + async compile(str, inputPath, preTemplateEngine, bypassMarkdown) { + let mdlib = this.mdLib; + if (preTemplateEngine) { - var engine = TemplateEngine.getEngine( - preTemplateEngine, - super.getInputDir() - ); - let fn = await engine.compile(str); + let fn; + + let engine; + if (typeof preTemplateEngine === "string") { + engine = TemplateEngine.getEngine( + preTemplateEngine, + super.getIncludesDir() + ); + } else { + engine = preTemplateEngine; + } + + fn = await engine.compile(str, inputPath); if (bypassMarkdown) { return async function(data) { @@ -16,14 +68,22 @@ class Markdown extends TemplateEngine { }; } else { return async function(data) { - return mdlib.render(await fn(data)); + let preTemplateEngineRender = await fn(data); + let finishedRender = mdlib.render(preTemplateEngineRender); + return finishedRender; }; } } else { - return function(data) { - // throw away data if preTemplateEngine is falsy - return mdlib.render(str); - }; + if (bypassMarkdown) { + return function() { + return str; + }; + } else { + return function() { + // throw away data if preTemplateEngine is falsy + return mdlib.render(str); + }; + } } } } diff --git a/src/Engines/Mustache.js b/src/Engines/Mustache.js index 0e9ca5dc7..078fb9295 100644 --- a/src/Engines/Mustache.js +++ b/src/Engines/Mustache.js @@ -2,11 +2,23 @@ const MustacheLib = require("mustache"); const TemplateEngine = require("./TemplateEngine"); class Mustache extends TemplateEngine { - async compile(str) { + constructor(name, includesDir) { + super(name, includesDir); + + this.setLibrary(this.config.libraryOverrides.mustache); + } + + setLibrary(lib) { + this.mustacheLib = lib || MustacheLib; + this.setEngineLib(this.mustacheLib); + } + + async compile(str, inputPath) { let partials = super.getPartials(); + return function(data) { - return MustacheLib.render(str, data, partials).trim(); - }; + return this.render(str, data, partials).trim(); + }.bind(this.mustacheLib); } } diff --git a/src/Engines/Nunjucks.js b/src/Engines/Nunjucks.js index 44a5f469b..3a6e81579 100644 --- a/src/Engines/Nunjucks.js +++ b/src/Engines/Nunjucks.js @@ -1,30 +1,155 @@ const NunjucksLib = require("nunjucks"); const TemplateEngine = require("./TemplateEngine"); -const TemplateConfig = require("../TemplateConfig"); - -let cfg = TemplateConfig.getDefaultConfig(); +const TemplatePath = require("../TemplatePath"); class Nunjucks extends TemplateEngine { - constructor(name, inputDir) { - super(name, inputDir); + constructor(name, includesDir) { + super(name, includesDir); + + this.setLibrary(this.config.libraryOverrides.njk); + } - this.njkEnv = new NunjucksLib.Environment( - new NunjucksLib.FileSystemLoader(super.getInputDir()) - ); + setLibrary(env) { + this.njkEnv = + env || + new NunjucksLib.Environment( + new NunjucksLib.FileSystemLoader([ + super.getIncludesDir(), + TemplatePath.getWorkingDir() + ]) + ); + this.setEngineLib(this.njkEnv); - this.addFilters(cfg.nunjucksFilters); + this.addFilters(this.config.nunjucksFilters); + this.addFilters(this.config.nunjucksAsyncFilters, true); + + // TODO these all go to the same place (addTag), add warnings for overwrites + this.addCustomTags(this.config.nunjucksTags); + this.addAllShortcodes(this.config.nunjucksShortcodes); + this.addAllPairedShortcodes(this.config.nunjucksPairedShortcodes); } - addFilters(helpers) { + addFilters(helpers, isAsync) { for (let name in helpers) { - this.njkEnv.addFilter(name, helpers[name]); + this.njkEnv.addFilter(name, helpers[name], isAsync); + } + } + + addCustomTags(tags) { + for (let name in tags) { + this.addTag(name, tags[name]); } } - async compile(str) { - let tmpl = NunjucksLib.compile(str, this.njkEnv); - return function(data) { - return tmpl.render(data); + addTag(name, tagFn) { + let tagObj; + if (typeof tagFn === "function") { + tagObj = tagFn(NunjucksLib, this.njkEnv); + } else { + throw new Error( + "Nunjucks.addTag expects a callback function to be passed in: addTag(name, function(nunjucksEngine) {})" + ); + } + + this.njkEnv.addExtension(name, tagObj); + } + + addAllShortcodes(shortcodes) { + for (let name in shortcodes) { + this.addShortcode(name, shortcodes[name]); + } + } + + addAllPairedShortcodes(shortcodes) { + for (let name in shortcodes) { + this.addPairedShortcode(name, shortcodes[name]); + } + } + + addShortcode(shortcodeName, shortcodeFn) { + function ShortcodeFunction() { + this.tags = [shortcodeName]; + + this.parse = function(parser, nodes, lexer) { + let args; + let tok = parser.nextToken(); + + args = parser.parseSignature(true, true); + + // Nunjucks bug with non-paired custom tags bug still exists even + // though this issue is closed. Works fine for paired. + // https://github.com/mozilla/nunjucks/issues/158 + if (args.children.length === 0) { + args.addChild(new nodes.Literal(0, 0, "")); + } + + parser.advanceAfterBlockEnd(tok.value); + + // return new nodes.CallExtensionAsync(this, "run", args); + return new nodes.CallExtension(this, "run", args); + }; + + this.run = function(...args) { + // let callback = args.pop(); + let [context, ...argArray] = args; + let ret = new NunjucksLib.runtime.SafeString(shortcodeFn(...argArray)); + // callback(null, ret); + return ret; + }; + } + + this.njkEnv.addExtension(shortcodeName, new ShortcodeFunction()); + } + + addPairedShortcode(shortcodeName, shortcodeFn) { + function PairedShortcodeFunction() { + this.tags = [shortcodeName]; + + this.parse = function(parser, nodes, lexer) { + var tok = parser.nextToken(); + + var args = parser.parseSignature(true, true); + parser.advanceAfterBlockEnd(tok.value); + + var body = parser.parseUntilBlocks("end" + shortcodeName); + parser.advanceAfterBlockEnd(); + + // return new nodes.CallExtensionAsync(this, "run", args, [body]); + return new nodes.CallExtension(this, "run", args, [body]); + }; + + this.run = function(...args) { + // let callback = args.pop(); + let body = args.pop(); + let [context, ...argArray] = args; + let ret = new NunjucksLib.runtime.SafeString( + shortcodeFn(body(), ...argArray) + ); + // callback(null, ret); + return ret; + }; + } + + this.njkEnv.addExtension(shortcodeName, new PairedShortcodeFunction()); + } + + async compile(str, inputPath) { + let tmpl; + if (!inputPath || inputPath === "njk" || inputPath === "md") { + tmpl = NunjucksLib.compile(str, this.njkEnv); + } else { + tmpl = NunjucksLib.compile(str, this.njkEnv, inputPath); + } + return async function(data) { + return new Promise(function(resolve, reject) { + tmpl.render(data, function(err, res) { + if (err) { + reject(err); + } else { + resolve(res); + } + }); + }); }; } } diff --git a/src/Engines/Pug.js b/src/Engines/Pug.js index cfe743c65..eb620a6d2 100644 --- a/src/Engines/Pug.js +++ b/src/Engines/Pug.js @@ -1,11 +1,47 @@ const PugLib = require("pug"); const TemplateEngine = require("./TemplateEngine"); +const TemplatePath = require("../TemplatePath"); class Pug extends TemplateEngine { - async compile(str) { - return PugLib.compile(str, { - basedir: super.getInputDir() - }); + constructor(name, includesDir) { + super(name, includesDir); + + this.pugOptions = {}; + + this.setLibrary(this.config.libraryOverrides.pug); + this.setPugOptions(this.config.pugOptions); + } + + setLibrary(lib) { + this.pugLib = lib || PugLib; + this.setEngineLib(this.pugLib); + } + + setPugOptions(options) { + this.pugOptions = options; + } + + getPugOptions() { + let includesDir = super.getIncludesDir(); + + return Object.assign( + { + basedir: includesDir, + filename: includesDir + }, + this.pugOptions || {} + ); + } + + async compile(str, inputPath) { + let options = this.getPugOptions(); + if (!inputPath || inputPath === "pug" || inputPath === "md") { + // do nothing + } else { + options.filename = inputPath; + } + + return this.pugLib.compile(str, options); } } diff --git a/src/Engines/TemplateEngine.js b/src/Engines/TemplateEngine.js index c20a37a96..780d5038e 100644 --- a/src/Engines/TemplateEngine.js +++ b/src/Engines/TemplateEngine.js @@ -1,65 +1,150 @@ -const globby = require("globby"); +const fastglob = require("fast-glob"); const fs = require("fs-extra"); -const parsePath = require("parse-filepath"); +const TemplatePath = require("../TemplatePath"); +const EleventyExtensionMap = require("../EleventyExtensionMap"); +const config = require("../Config"); +const debug = require("debug")("Eleventy:TemplateEngine"); class TemplateEngine { - constructor(name, inputDir) { + constructor(name, includesDir) { this.name = name; - this.extension = "." + name; - this.inputDir = inputDir; - this.partials = this.cachePartialFiles(this.extension); + + this.extensionMap = new EleventyExtensionMap(); + this.extensions = this.extensionMap.getExtensionsFromKey(name); + this.includesDir = includesDir; + this.partialsHaveBeenCached = false; + this.partials = []; + this.engineLib = null; + } + + get config() { + if (!this._config) { + this._config = config.getConfig(); + } + return this._config; + } + + set config(config) { + this._config = config; } getName() { return this.name; } - getInputDir() { - return this.inputDir; + + getIncludesDir() { + return this.includesDir; } + + // TODO make async getPartials() { + if (!this.partialsHaveBeenCached) { + this.partials = this.cachePartialFiles(); + } + return this.partials; } + // TODO make async cachePartialFiles() { + // This only runs if getPartials() is called, which is only for Mustache/Handlebars + this.partialsHaveBeenCached = true; let partials = {}; + let prefix = this.includesDir + "/**/*."; // TODO: reuse mustache partials in handlebars? - let partialFiles = this.inputDir - ? globby.sync(this.inputDir + "/*" + this.extension) - : []; - for (var j = 0, k = partialFiles.length; j < k; j++) { - let key = parsePath(partialFiles[j]).name; - partials[key] = fs.readFileSync(partialFiles[j], "utf-8"); + let partialFiles = []; + if (this.includesDir) { + this.extensions.forEach(function(extension) { + partialFiles = partialFiles.concat( + fastglob.sync(prefix + extension, { + caseSensitiveMatch: false, + dot: true + }) + ); + }); } + + partialFiles = TemplatePath.addLeadingDotSlashArray(partialFiles); + + for (let j = 0, k = partialFiles.length; j < k; j++) { + let partialPath = TemplatePath.stripLeadingSubPath( + partialFiles[j], + this.includesDir + ); + let partialPathNoExt = partialPath; + this.extensions.forEach(function(extension) { + partialPathNoExt = TemplatePath.removeExtension( + partialPathNoExt, + "." + extension + ); + }); + partials[partialPathNoExt] = fs.readFileSync(partialFiles[j], "utf-8"); + } + + debug( + `${this.includesDir}/*.{${this.extensions}} found partials for: %o`, + Object.keys(partials) + ); + return partials; } + setEngineLib(engineLib) { + this.engineLib = engineLib; + } + + getEngineLib() { + return this.engineLib; + } + async render(str, data) { - var fn = await this.compile(str); + /* TODO compile needs to pass in inputPath? */ + let fn = await this.compile(str); return fn(data); } - static getEngine(name, inputDir) { - let classMap = { + // JavaScript files defer to the module loader rather than read the files to strings + needsToReadFileContents() { + return true; + } + + getExtraDataFromFile(inputPath) { + return {}; + } + + initRequireCache(inputPath) { + // do nothing + } + + static get templateKeyMapToClassName() { + return { ejs: "Ejs", md: "Markdown", - jstl: "JavaScript", + jstl: "JavaScriptTemplateLiteral", html: "Html", hbs: "Handlebars", mustache: "Mustache", haml: "Haml", pug: "Pug", njk: "Nunjucks", - liquid: "Liquid" + liquid: "Liquid", + "11ty.js": "JavaScript" }; + } + + static hasEngine(name) { + return name in TemplateEngine.templateKeyMapToClassName; + } - if (!(name in classMap)) { + static getEngine(name, includesDir) { + if (!this.hasEngine(name)) { throw new Error( - "Template Engine " + name + " does not exist in getEngine" + `Template Engine ${name} does not exist in getEngine (includes dir: ${includesDir})` ); } - const cls = require("./" + classMap[name]); - return new cls(name, inputDir); + const cls = require("./" + TemplateEngine.templateKeyMapToClassName[name]); + return new cls(name, includesDir); } } diff --git a/src/Errors/TemplateContentPrematureUseError.js b/src/Errors/TemplateContentPrematureUseError.js new file mode 100644 index 000000000..36550613a --- /dev/null +++ b/src/Errors/TemplateContentPrematureUseError.js @@ -0,0 +1,4 @@ +const EleventyBaseError = require("../EleventyBaseError"); +class TemplateContentPrematureUseError extends EleventyBaseError {} + +module.exports = TemplateContentPrematureUseError; diff --git a/src/Errors/UsingCircularTemplateContentReferenceError.js b/src/Errors/UsingCircularTemplateContentReferenceError.js new file mode 100644 index 000000000..f5e9ea0ce --- /dev/null +++ b/src/Errors/UsingCircularTemplateContentReferenceError.js @@ -0,0 +1,5 @@ +const EleventyBaseError = require("../EleventyBaseError"); + +class UsingCircularTemplateContentReferenceError extends EleventyBaseError {} + +module.exports = UsingCircularTemplateContentReferenceError; diff --git a/src/Filters/Slug.js b/src/Filters/Slug.js new file mode 100644 index 000000000..739c3d2d6 --- /dev/null +++ b/src/Filters/Slug.js @@ -0,0 +1,8 @@ +const slugify = require("slugify"); + +module.exports = function(str) { + return slugify(str, { + replacement: "-", + lower: true + }); +}; diff --git a/src/Filters/Url.js b/src/Filters/Url.js new file mode 100644 index 000000000..e8824329d --- /dev/null +++ b/src/Filters/Url.js @@ -0,0 +1,36 @@ +const validUrl = require("valid-url"); +const TemplatePath = require("../TemplatePath"); + +module.exports = function(url, pathPrefix) { + // work with undefined + url = url || ""; + + if ( + validUrl.isUri(url) || + url.indexOf("http://") === 0 || + url.indexOf("https://") === 0 + ) { + return url; + } + + if (pathPrefix === undefined || typeof pathPrefix !== "string") { + let projectConfig = require("../Config").getConfig(); + pathPrefix = projectConfig.pathPrefix; + } + + let normUrl = TemplatePath.normalizeUrlPath(url); + let normRootDir = TemplatePath.normalizeUrlPath("/", pathPrefix); + let normFull = TemplatePath.normalizeUrlPath("/", pathPrefix, url); + let isRootDirTrailingSlash = + normRootDir.length && normRootDir.charAt(normRootDir.length - 1) === "/"; + + // minor difference with straight `normalize`, "" resolves to root dir and not "." + // minor difference with straight `normalize`, "/" resolves to root dir + if (normUrl === "/" || normUrl === normRootDir) { + return normRootDir + (!isRootDirTrailingSlash ? "/" : ""); + } else if (normUrl.indexOf("/") === 0) { + return normFull; + } + + return normUrl; +}; diff --git a/src/Plugins/Pagination.js b/src/Plugins/Pagination.js index b7d461462..f7944ad9f 100644 --- a/src/Plugins/Pagination.js +++ b/src/Plugins/Pagination.js @@ -1,144 +1,249 @@ -const lodashchunk = require("lodash.chunk"); -const lodashget = require("lodash.get"); -const lodashset = require("lodash.set"); -const TemplateConfig = require("../TemplateConfig"); +const lodashChunk = require("lodash/chunk"); +const lodashGet = require("lodash/get"); +const lodashSet = require("lodash/set"); +const EleventyBaseError = require("../EleventyBaseError"); +const config = require("../Config"); -let cfg = TemplateConfig.getDefaultConfig(); +class PaginationError extends EleventyBaseError {} -function Pagination(data) { - this.data = data || {}; - this.size = 1; - this.target = []; - this.writeCount = 0; +class Pagination { + constructor(data) { + this.config = config.getConfig(); - if (!this.hasPagination()) { - return; + this.setData(data); } - if (!data.pagination) { - throw new Error( - "Misconfigured pagination data in template front matter (did you use tabs and not spaces?)." - ); - } else if (!("size" in data.pagination)) { - throw new Error("Missing pagination size in front matter data."); + static hasPagination(data) { + return "pagination" in data; } - this.size = data.pagination.size; - this.alias = data.pagination.alias; + hasPagination() { + if (!this.data) { + throw new Error("Missing `setData` call for Pagination object."); + } + return Pagination.hasPagination(this.data); + } - this.target = this._resolveItems(data); - this.items = this.getPagedItems(); -} + circularReferenceCheck(data) { + let key = data.pagination.data; + let tags = data.tags || []; + for (let tag of tags) { + if (`collections.${tag}` === key) { + throw new PaginationError( + `Pagination circular reference${ + this.template ? ` on ${this.template.inputPath}` : "" + }, data:\`${key}\` iterates over both the \`${tag}\` tag and also supplies pages to that tag.` + ); + } + } + } -Pagination.prototype.hasPagination = function() { - return "pagination" in this.data; -}; + setData(data) { + this.data = data || {}; + this.target = []; -Pagination.prototype.setTemplate = function(tmpl) { - this.template = tmpl; -}; + if (!this.hasPagination()) { + return; + } -Pagination.prototype._getDataKey = function() { - return this.data.pagination.data; -}; + if (!data.pagination) { + throw new Error( + "Misconfigured pagination data in template front matter (YAML front matter precaution: did you use tabs and not spaces for indentation?)." + ); + } else if (!("size" in data.pagination)) { + throw new Error("Missing pagination size in front matter data."); + } + this.circularReferenceCheck(data); -Pagination.prototype._resolveItems = function() { - let notFoundValue = "__NOT_FOUND_ERROR__"; - let ret = lodashget(this.data, this._getDataKey(), notFoundValue); + this.size = data.pagination.size; + this.alias = data.pagination.alias; - if (ret === notFoundValue) { - throw new Error( - `Could not resolve pagination key in template data: ${this._getDataKey()}` - ); + this.target = this._resolveItems(); + this.items = this.getPagedItems(); } - return ret; -}; - -Pagination.prototype.getPagedItems = function() { - return lodashchunk(this.target, this.size); -}; + setTemplate(tmpl) { + this.template = tmpl; + } -// TODO this name is not good -// donā€™t write the original root template -Pagination.prototype.cancel = function() { - return this.hasPagination(); -}; + _getDataKey() { + return this.data.pagination.data; + } -Pagination.prototype.getTemplates = async function() { - if (!this.hasPagination()) { - return []; + resolveObjectToValues() { + if ("resolve" in this.data.pagination) { + return this.data.pagination.resolve === "values"; + } + return false; } - let data = this.data; - let pages = []; - let items = this.items; - let tmpl = this.template; - let templates = []; - let links = []; - let overrides = []; - - for (var pageNumber = 0, k = items.length; pageNumber < k; pageNumber++) { - let chunk = items[pageNumber]; - let cloned = tmpl.clone(); - // TODO maybe also move this permalink additions up into the pagination class - if (pageNumber > 0 && !this.data[cfg.keys.permalink]) { - cloned.setExtraOutputSubdirectory(pageNumber); + isFiltered(value) { + if ("filter" in this.data.pagination) { + let filtered = this.data.pagination.filter; + if (Array.isArray(filtered)) { + return filtered.indexOf(value) > -1; + } + + return filtered === value; } - cloned.removePlugin("pagination"); - templates.push(cloned); + return false; + } - let override = { - pagination: { - data: this.data.pagination.data, - size: this.data.pagination.size, - items: items[pageNumber], - pageNumber: pageNumber - } - }; - if (this.alias) { - lodashset( - override, - this.alias, - this.size === 1 ? items[pageNumber][0] : items[pageNumber] + _resolveItems() { + let notFoundValue = "__NOT_FOUND_ERROR__"; + let key = this._getDataKey(); + let ret = lodashGet(this.data, key, notFoundValue); + if (ret === notFoundValue) { + throw new Error( + `Could not resolve pagination key in template data: ${key}` ); } - overrides.push(override); - cloned.setDataOverrides(overrides[pageNumber]); + if (!Array.isArray(ret)) { + if (this.resolveObjectToValues()) { + ret = Object.values(ret); + } else { + ret = Object.keys(ret); + } + } - // TO DO subdirectory to links if the site doesnā€™t live at / - links.push("/" + (await cloned.getOutputLink())); + let result = ret.filter( + function(value) { + return !this.isFiltered(value); + }.bind(this) + ); + if (this.data.pagination.reverse === true) { + return result.reverse(); + } + return result; } - // we loop twice to pass in the appropriate prev/next links (already full generated now) - templates.forEach( - function(cloned, pageNumber) { - overrides[pageNumber].pagination.previousPageLink = - pageNumber > 0 ? links[pageNumber - 1] : null; - overrides[pageNumber].pagination.nextPageLink = - pageNumber < templates.length - 1 ? links[pageNumber + 1] : null; - overrides[pageNumber].pagination.pageLinks = links; - cloned.setDataOverrides(overrides[pageNumber]); - - pages.push(cloned); - }.bind(this) - ); - - return pages; -}; - -Pagination.prototype.write = async function() { - let pages = await this.getTemplates(); - for (let page of pages) { - await page.write(); - this.writeCount += page.getWriteCount(); + getPagedItems() { + // TODO switch to a getter + if (!this.data) { + throw new Error("Missing `setData` call for Pagination object."); + } + + return lodashChunk(this.target, this.size); } -}; -Pagination.prototype.getWriteCount = function() { - return this.writeCount; -}; + // TODO this name is not good + // ā€œTo cancelā€ means to not write the original root template + cancel() { + return this.hasPagination(); + } + + getPageCount() { + if (!this.hasPagination()) { + return 0; + } + + return this.items.length; + } + + async getPageTemplates() { + if (!this.data) { + throw new Error("Missing `setData` call for Pagination object."); + } + + if (!this.hasPagination()) { + return []; + } + + if (this.pagesCache) { + return this.pagesCache; + } + + let pages = []; + let items = this.items; + let tmpl = this.template; + let templates = []; + let links = []; + let hrefs = []; + let overrides = []; + + for (let pageNumber = 0, k = items.length; pageNumber < k; pageNumber++) { + let cloned = tmpl.clone(); + + // TODO maybe also move this permalink additions up into the pagination class + if (pageNumber > 0 && !this.data[this.config.keys.permalink]) { + cloned.setExtraOutputSubdirectory(pageNumber); + } + + templates.push(cloned); + + let override = { + pagination: { + data: this.data.pagination.data, + size: this.data.pagination.size, + items: items[pageNumber], + pageNumber: pageNumber + } + }; + + if (this.alias) { + lodashSet( + override, + this.alias, + this.size === 1 ? items[pageNumber][0] : items[pageNumber] + ); + } + + overrides.push(override); + cloned.setPaginationData(override); + + // TO DO subdirectory to links if the site doesnā€™t live at / + links.push("/" + (await cloned.getOutputLink())); + hrefs.push(await cloned.getOutputHref()); + } + + // we loop twice to pass in the appropriate prev/next links (already full generated now) + templates.forEach( + function(cloned, pageNumber) { + // links + overrides[pageNumber].pagination.previousPageLink = + pageNumber > 0 ? links[pageNumber - 1] : null; + overrides[pageNumber].pagination.previous = + overrides[pageNumber].pagination.previousPageLink; + + overrides[pageNumber].pagination.nextPageLink = + pageNumber < templates.length - 1 ? links[pageNumber + 1] : null; + overrides[pageNumber].pagination.next = + overrides[pageNumber].pagination.nextPageLink; + + overrides[pageNumber].pagination.firstPageLink = + links.length > 0 ? links[0] : null; + overrides[pageNumber].pagination.lastPageLink = + links.length > 0 ? links[links.length - 1] : null; + + overrides[pageNumber].pagination.links = links; + // todo deprecated, consistency with collections and use links instead + overrides[pageNumber].pagination.pageLinks = links; + + // hrefs + overrides[pageNumber].pagination.previousPageHref = + pageNumber > 0 ? hrefs[pageNumber - 1] : null; + overrides[pageNumber].pagination.nextPageHref = + pageNumber < templates.length - 1 ? hrefs[pageNumber + 1] : null; + + overrides[pageNumber].pagination.firstPageHref = + hrefs.length > 0 ? hrefs[0] : null; + overrides[pageNumber].pagination.lastPageHref = + hrefs.length > 0 ? hrefs[hrefs.length - 1] : null; + + overrides[pageNumber].pagination.hrefs = hrefs; + + cloned.setPaginationData(overrides[pageNumber]); + + pages.push(cloned); + }.bind(this) + ); + + this.pagesCache = pages; + + return pages; + } +} module.exports = Pagination; diff --git a/src/Template.js b/src/Template.js index b82be4b5b..16809dfc4 100644 --- a/src/Template.js +++ b/src/Template.js @@ -1,319 +1,698 @@ -const pify = require("pify"); const fs = require("fs-extra"); const parsePath = require("parse-filepath"); -const matter = require("gray-matter"); const normalize = require("normalize-path"); -const _isObject = require("lodash.isobject"); -const TemplateRender = require("./TemplateRender"); +const lodashIsObject = require("lodash/isObject"); +const { DateTime } = require("luxon"); + +const EleventyExtensionMap = require("./EleventyExtensionMap"); +const TemplateData = require("./TemplateData"); +const TemplateContent = require("./TemplateContent"); const TemplatePath = require("./TemplatePath"); const TemplatePermalink = require("./TemplatePermalink"); -const Layout = require("./TemplateLayout"); -const TemplateConfig = require("./TemplateConfig"); -const Eleventy = require("./Eleventy"); - -let cfg = TemplateConfig.getDefaultConfig(); +const TemplatePermalinkNoWrite = require("./TemplatePermalinkNoWrite"); +const TemplateLayout = require("./TemplateLayout"); +const TemplateFileSlug = require("./TemplateFileSlug"); +const Pagination = require("./Plugins/Pagination"); +const TemplateContentPrematureUseError = require("./Errors/TemplateContentPrematureUseError"); +const debug = require("debug")("Eleventy:Template"); +const debugDev = require("debug")("Dev:Eleventy:Template"); + +class Template extends TemplateContent { + constructor(path, inputDir, outputDir, templateData) { + debugDev("new Template(%o)", path); + super(path, inputDir); + + this.parsed = parsePath(path); + + // for pagination + this.extraOutputSubdirectory = ""; + + if (outputDir) { + this.outputDir = normalize(outputDir); + } else { + this.outputDir = false; + } -function Template(path, inputDir, outputDir, templateData) { - this.inputPath = path; - this.inputContent = fs.readFileSync(path, "utf-8"); - this.parsed = parsePath(path); + this.linters = []; + this.transforms = []; + this.plugins = {}; + this.templateData = templateData; + if (this.templateData) { + this.templateData.setInputDir(this.inputDir); + } + this.paginationData = {}; + + this.isVerbose = true; + this.isDryRun = false; + this.writeCount = 0; + this.wrapWithLayouts = true; + this.fileSlug = new TemplateFileSlug(this.inputPath, this.inputDir); + this.fileSlugStr = this.fileSlug.getSlug(); + this.filePathStem = this.fileSlug.getFullPathWithoutExtension(); + } - // for pagination - this.extraOutputSubdirectory = ""; + setIsVerbose(isVerbose) { + this.isVerbose = isVerbose; + } - if (inputDir) { - this.inputDir = normalize(inputDir); - this.layoutsDir = this.inputDir + "/" + cfg.dir.includes; - } else { - this.inputDir = false; + setDryRun(isDryRun) { + this.isDryRun = !!isDryRun; } - if (outputDir) { - this.outputDir = normalize(outputDir); - } else { - this.outputDir = false; + + setWrapWithLayouts(wrap) { + this.wrapWithLayouts = wrap; } - this.frontMatter = this.getMatter(); + setExtraOutputSubdirectory(dir) { + this.extraOutputSubdirectory = dir + "/"; + } - this.filters = []; - this.plugins = {}; - this.templateData = templateData; - this.dataOverrides = {}; + getTemplateSubfolder() { + return TemplatePath.stripLeadingSubPath(this.parsed.dir, this.inputDir); + } - this.templateRender = new TemplateRender(this.inputPath, this.inputDir); + get baseFile() { + return (this._extensionMap || EleventyExtensionMap).removeTemplateExtension( + this.parsed.base + ); + } - // HTML output canā€™t overwrite the HTML input file. - this.isHtmlIOException = - this.inputDir === this.outputDir && - this.templateRender.isEngine("html") && - this.parsed.name === "index"; + get htmlIOException() { + // HTML output canā€™t overwrite the HTML input file. + return ( + this.inputDir === this.outputDir && + this.templateRender.isEngine("html") && + this.baseFile === "index" + ); + } - this.isVerbose = true; - this.writeCount = 0; -} + async _getLink(data) { + if (!data) { + data = await this.getData(); + } -Template.prototype.setIsVerbose = function(isVerbose) { - this.isVerbose = isVerbose; -}; + let permalink = data[this.config.keys.permalink]; + if (permalink) { + // render variables inside permalink front matter, bypass markdown + let permalinkValue; + if (!this.config.dynamicPermalinks || data.dynamicPermalink === false) { + debugDev("Not using dynamicPermalinks, using %o", permalink); + permalinkValue = permalink; + } else { + permalinkValue = await super.render(permalink, data, true); + debug( + "Rendering permalink for %o: %s becomes %o", + this.inputPath, + permalink, + permalinkValue + ); + debugDev("Permalink rendered with data: %o", data); + } -Template.prototype.getTemplateSubfolder = function() { - return TemplatePath.stripPathFromDir(this.parsed.dir, this.inputDir); -}; + let perm = new TemplatePermalink( + permalinkValue, + this.extraOutputSubdirectory + ); -Template.prototype.setExtraOutputSubdirectory = function(dir) { - this.extraOutputSubdirectory = dir + "/"; -}; + return perm; + } else if (permalink === false) { + return new TemplatePermalinkNoWrite(); + } -// TODO instead of isHTMLIOException, do a global search to check if output path = input path and then add extra suffix -Template.prototype.getOutputLink = async function() { - let permalink = this.getFrontMatterData()[cfg.keys.permalink]; - if (permalink) { - let data = await this.getData(); - let perm = new TemplatePermalink( - // render variables inside permalink front matter - await this.renderContent(permalink, data, { - bypassMarkdown: true - }), - this.extraOutputSubdirectory - ); - return perm.toString(); - } - - return TemplatePermalink.generate( - this.getTemplateSubfolder(), - this.parsed.name, - this.extraOutputSubdirectory, - this.isHtmlIOException ? cfg.htmlOutputSuffix : "" - ).toString(); -}; - -// TODO check for conflicts, see if file already exists? -Template.prototype.getOutputPath = async function() { - let uri = await this.getOutputLink(); - if (this.getFrontMatterData()[cfg.keys.permalinkRoot]) { - return normalize(uri); - } else { - return normalize(this.outputDir + "/" + uri); - } -}; - -Template.prototype.setDataOverrides = function(overrides) { - this.dataOverrides = overrides; -}; - -Template.prototype.isIgnored = function() { - return this.outputDir === false; -}; - -Template.prototype.getMatter = function() { - return matter(this.inputContent); -}; - -Template.prototype.getPreRender = function() { - return this.frontMatter.content; -}; - -Template.prototype.getLayoutTemplate = function(name) { - let path = new Layout(name, this.layoutsDir).getFullPath(); - return new Template(path, this.inputDir, this.outputDir); -}; - -Template.prototype.getFrontMatterData = function() { - return this.frontMatter.data || {}; -}; - -Template.prototype.getAllLayoutFrontMatterData = async function( - tmpl, - data, - merged -) { - if (!merged) { - merged = data; - } - - if (data[cfg.keys.layout]) { - let layout = tmpl.getLayoutTemplate(data[cfg.keys.layout]); - let layoutData = layout.getFrontMatterData(); - - return this.getAllLayoutFrontMatterData( - tmpl, - layoutData, - Object.assign({}, layoutData, merged) + return TemplatePermalink.generate( + this.getTemplateSubfolder(), + this.baseFile, + this.extraOutputSubdirectory, + this.htmlIOException ? this.config.htmlOutputSuffix : "" ); } - return merged; -}; + // TODO instead of htmlIOException, do a global search to check if output path = input path and then add extra suffix + async getOutputLink(data) { + let link = await this._getLink(data); + return link.toString(); + } + + async getOutputHref(data) { + let link = await this._getLink(data); + return link.toHref(); + } -Template.prototype.getLocalDataPath = function() { - return this.parsed.dir + "/" + this.parsed.name + ".json"; -}; + // TODO check for conflicts, see if file already exists? + async getOutputPath(data) { + let uri = await this.getOutputLink(data); + + if (uri === false) { + return false; + } else if ( + (await this.getFrontMatterData())[this.config.keys.permalinkRoot] + ) { + // TODO this only works with immediate front matter and not data files + return normalize(uri); + } else { + return normalize(this.outputDir + "/" + uri); + } + } + + setPaginationData(paginationData) { + this.paginationData = paginationData; + } -Template.prototype.mapDataAsRenderedTemplates = async function( - data, - templateData -) { - if (Array.isArray(data)) { - let arr = []; - for (let j = 0, k = data.length; j < k; j++) { - arr.push(await this.mapDataAsRenderedTemplates(data[j], templateData)); + async mapDataAsRenderedTemplates(data, templateData) { + // function supported in JavaScript type + if (typeof data === "string" || typeof data === "function") { + debug("rendering data.renderData for %o", this.inputPath); + // bypassMarkdown + let str = await super.render(data, templateData, true); + return str; + } else if (Array.isArray(data)) { + let arr = []; + for (let j = 0, k = data.length; j < k; j++) { + arr.push(await this.mapDataAsRenderedTemplates(data[j], templateData)); + } + return arr; + } else if (lodashIsObject(data)) { + let obj = {}; + for (let value in data) { + obj[value] = await this.mapDataAsRenderedTemplates( + data[value], + templateData + ); + } + return obj; } - return arr; - } else if (_isObject(data)) { - let obj = {}; - for (let value in data) { - obj[value] = await this.mapDataAsRenderedTemplates( - data[value], - templateData + + return data; + } + + async _testGetAllLayoutFrontMatterData() { + let frontMatterData = await this.getFrontMatterData(); + if (frontMatterData[this.config.keys.layout]) { + let layout = TemplateLayout.getTemplate( + frontMatterData[this.config.keys.layout], + this.getInputDir(), + this.config ); + return await layout.getData(); } - return obj; - } else if (typeof data === "string") { - let str = await this.renderContent(data, templateData); - return str; + return {}; } - return data; -}; + async getData() { + if (!this.dataCache) { + debugDev("%o getData()", this.inputPath); + let localData = {}; + + if (this.templateData) { + localData = await this.templateData.getLocalData(this.inputPath); + debugDev("%o getData() getLocalData", this.inputPath); + } -Template.prototype.getData = async function(localData) { - let data = {}; + let frontMatterData = await this.getFrontMatterData(); + let foundLayout = + frontMatterData[this.config.keys.layout] || + localData[this.config.keys.layout]; + + let mergedLayoutData = {}; + if (foundLayout) { + let layout = TemplateLayout.getTemplate( + foundLayout, + this.getInputDir(), + this.config + ); + mergedLayoutData = await layout.getData(); + debugDev( + "%o getData() get merged layout chain front matter", + this.inputPath + ); + } - if (this.templateData) { - data = await this.templateData.getLocalData(this.getLocalDataPath()); + let mergedData = TemplateData.mergeDeep( + this.config, + {}, + mergedLayoutData, + localData, + frontMatterData + ); + mergedData = await this.addPageDate(mergedData); + mergedData = this.addPageData(mergedData); + debugDev("%o getData() mergedData", this.inputPath); + + this.dataCache = mergedData; + } + + // Donā€™t deep merge pagination data! See https://github.com/11ty/eleventy/issues/147#issuecomment-440802454 + return Object.assign( + TemplateData.mergeDeep(this.config, {}, this.dataCache), + this.paginationData + ); } - let mergedLocalData = Object.assign({}, localData, this.dataOverrides); + async addPageDate(data) { + if (!("page" in data)) { + data.page = {}; + } - let frontMatterData = this.getFrontMatterData(); + let newDate = await this.getMappedDate(data); - let mergedLayoutData = await this.getAllLayoutFrontMatterData( - this, - frontMatterData - ); + if ("page" in data && "date" in data.page) { + debug( + "Warning: data.page.date is in use (%o) will be overwritten with: %o", + data.page.date, + newDate + ); + } - return Object.assign({}, data, mergedLayoutData, mergedLocalData); -}; + data.page.date = newDate; -Template.prototype.renderLayout = async function(tmpl, tmplData) { - let layoutName = tmplData[cfg.keys.layout]; - // TODO make layout key to be available to templates (without it causing issues with merge below) - delete tmplData[cfg.keys.layout]; + return data; + } - let layout = this.getLayoutTemplate(layoutName); - let layoutData = await layout.getData(tmplData); + addPageData(data) { + if (!("page" in data)) { + data.page = {}; + } - // console.log( await this.renderContent( tmpl.getPreRender(), tmplData ) ); - layoutData._layoutContent = await this.renderContent( - tmpl.getPreRender(), - tmplData - ); + data.page.inputPath = this.inputPath; + data.page.fileSlug = this.fileSlugStr; + data.page.filePathStem = this.filePathStem; - if (layoutData[cfg.keys.layout]) { - return this.renderLayout(layout, layoutData); + return data; } - return layout.renderContent(layout.getPreRender(), layoutData); -}; + async addPageRenderedData(data) { + if (!("page" in data)) { + data.page = {}; + } -Template.prototype.getCompiledPromise = async function() { - return this.templateRender.getCompiledTemplate(this.getPreRender()); -}; + let newUrl = await this.getOutputHref(data); + if ("page" in data && "url" in data.page) { + if (data.page.url !== newUrl) { + debug( + "Warning: data.page.url is in use (%o) will be overwritten with: %o", + data.page.url, + newUrl + ); + } + } -Template.prototype.renderContent = async function(str, data, options) { - let fn = await this.templateRender.getCompiledTemplate(str, options); - return fn(data); -}; + data.page.url = newUrl; + data.page.outputPath = await this.getOutputPath(data); -Template.prototype.render = async function(data) { - if (!data) { - data = await this.getData(); + return data; } - if (data[cfg.keys.layout]) { - return this.renderLayout(this, data); - } else { - return this.renderContent(this.getPreRender(), data); + // getData (with renderData and page.url added) + async getRenderedData() { + let data = await this.getData(); + data = await this.addPageRenderedData(data); + + if (data.renderData) { + data.renderData = await this.mapDataAsRenderedTemplates( + data.renderData, + data + ); + } + return data; } -}; -Template.prototype.addFilter = function(callback) { - this.filters.push(callback); -}; + async renderLayout(tmpl, tmplData) { + let layoutKey = tmplData[tmpl.config.keys.layout]; + let layout = TemplateLayout.getTemplate( + layoutKey, + this.getInputDir(), + this.config + ); + debug("%o is using layout %o", this.inputPath, layoutKey); -Template.prototype.runFilters = async function(str) { - let outputPath = await this.getOutputPath(); - this.filters.forEach(function(filter) { - str = filter.call(this, str, outputPath); - }); + // TODO reuse templateContent from templateMap + let templateContent = await super.render( + await this.getPreRender(), + tmplData + ); + return layout.render(tmplData, templateContent); + } - return str; -}; + async _testRenderWithoutLayouts(data) { + this.setWrapWithLayouts(false); + let ret = await this.render(data); + this.setWrapWithLayouts(true); + return ret; + } -Template.prototype.addPlugin = function(key, callback) { - this.plugins[key] = callback; -}; + async renderContent(str, data, bypassMarkdown) { + return super.render(str, data, bypassMarkdown); + } -Template.prototype.removePlugin = function(key) { - delete this.plugins[key]; -}; + async render(data) { + debugDev("%o render()", this.inputPath); + if (!data) { + data = await this.getRenderedData(); + } -Template.prototype.runPlugins = async function(data) { - let ret = true; - for (let key in this.plugins) { - let pluginRet = await this.plugins[key].call(this, data); - if (pluginRet === false) { - ret = false; + if (!this.wrapWithLayouts && data[this.config.keys.layout]) { + debugDev("Template.render is bypassing layouts for %o.", this.inputPath); } + + if (this.wrapWithLayouts && data[this.config.keys.layout]) { + debugDev( + "Template.render found layout: %o", + data[this.config.keys.layout] + ); + return this.renderLayout(this, data); + } else { + debugDev("Template.render renderContent for %o", this.inputPath); + return super.render(await this.getPreRender(), data); + } + } + + addLinter(callback) { + this.linters.push(callback); } - return ret; -}; + async runLinters(str, inputPath, outputPath) { + this.linters.forEach(function(linter) { + // these can be asynchronous but no guarantee of order when they run + linter.call(this, str, inputPath, outputPath); + }); + } -Template.prototype.write = async function() { - let outputPath = await this.getOutputPath(); - if (this.isIgnored()) { - if (this.isVerbose) { - console.log("Ignoring", outputPath); + addTransform(callback) { + this.transforms.push(callback); + } + + async runTransforms(str, outputPath, inputPath) { + for (let transform of this.transforms) { + str = await transform.call(this, str, outputPath, inputPath); + } + + return str; + } + + async getTemplates(data) { + // TODO cache this + let results = []; + + if (!Pagination.hasPagination(data)) { + data.page.url = await this.getOutputHref(data); + data.page.outputPath = await this.getOutputPath(data); + + results.push({ + template: this, + inputPath: this.inputPath, + fileSlug: this.fileSlugStr, + filePathStem: this.filePathStem, + data: data, + date: data.page.date, + outputPath: data.page.outputPath, + url: data.page.url, + set templateContent(content) { + this._templateContent = content; + }, + get templateContent() { + if (this._templateContent === undefined) { + // should at least warn here + throw new TemplateContentPrematureUseError( + `Tried to use templateContent too early (${this.inputPath})` + ); + } + return this._templateContent; + } + }); + } else { + // needs collections for pagination items + // but individual pagination entries wonā€™t be part of a collection + this.paging = new Pagination(data); + this.paging.setTemplate(this); + let templates = await this.paging.getPageTemplates(); + let pageNumber = 0; + for (let page of templates) { + // TODO try to reuse data instead of a new copy + let pageData = await page.getRenderedData(); + + // Issue #115 + if (data.collections) { + pageData.collections = data.collections; + } + + pageData.page.url = await page.getOutputHref(pageData); + pageData.page.outputPath = await page.getOutputPath(pageData); + + results.push({ + template: page, + inputPath: this.inputPath, + fileSlug: this.fileSlugStr, + filePathStem: this.filePathStem, + data: pageData, + date: pageData.page.date, + pageNumber: pageNumber++, + outputPath: pageData.page.outputPath, + url: pageData.page.url, + set templateContent(content) { + this._templateContent = content; + }, + get templateContent() { + if (!this._templateContent) { + throw new TemplateContentPrematureUseError( + `Tried to use templateContent too early (${this.inputPath} page ${this.pageNumber})` + ); + } + return this._templateContent; + } + }); + } + } + + return results; + } + + async getRenderedTemplates(data) { + let pages = await this.getTemplates(data); + for (let page of pages) { + let content = await page.template._getContent(page.outputPath, page.data); + + page.templateContent = content; + } + return pages; + } + + async _getContent(outputPath, data) { + return await this.render(data); + } + + async _write(outputPath, finalContent) { + if (outputPath === false) { + debug( + "Ignored %o from %o (permalink: false).", + outputPath, + this.inputPath + ); + return; } - } else { + this.writeCount++; - let data = await this.getData(); - if (data.renderData) { - data.renderData = await this.mapDataAsRenderedTemplates( - data.renderData, - data + let lang = { + start: "Writing", + finished: "written." + }; + + if (this.isDryRun) { + lang = { + start: "Pretending to write", + finished: "" // not used + }; + } + + if (this.isVerbose) { + console.log(`${lang.start} ${outputPath} from ${this.inputPath}.`); + } else { + debug(`${lang.start} %o from %o.`, outputPath, this.inputPath); + } + if (!this.isDryRun) { + return fs.outputFile(outputPath, finalContent).then(() => { + debug(`${outputPath} ${lang.finished}.`); + }); + } + } + + async renderPageEntry(mapEntry, page) { + let content; + let layoutKey = mapEntry.data[this.config.keys.layout]; + if (layoutKey) { + let layout = TemplateLayout.getTemplate( + layoutKey, + this.getInputDir(), + this.config ); + content = await layout.render(page.data, page.templateContent); + await this.runLinters(content, page.inputPath, page.outputPath); + content = await this.runTransforms(content, page.outputPath); // pass in page.inputPath? + return content; + } else { + content = page.templateContent; + await this.runLinters( + page.templateContent, + page.inputPath, + page.outputPath + ); + content = await this.runTransforms(content, page.outputPath); // pass in page.inputPath? } + return content; + } - let pluginRet = await this.runPlugins(data); - if (pluginRet) { - let str = await this.render(data); - let filtered = await this.runFilters(str); - await pify(fs.outputFile)(outputPath, filtered); + async writeMapEntry(mapEntry) { + let promises = []; + for (let page of mapEntry._pages) { + let content = await this.renderPageEntry(mapEntry, page); + promises.push(this._write(page.outputPath, content)); + } - if (this.isVerbose) { - console.log("Writing", outputPath, "from", this.inputPath); + return Promise.all(promises); + } + + async write(outputPath, data) { + let templates = await this.getRenderedTemplates(data); + let promises = []; + for (let tmpl of templates) { + promises.push(this._write(tmpl.outputPath, tmpl.templateContent)); + } + + return Promise.all(promises); + } + + // TODO this but better + clone() { + let tmpl = new Template( + this.inputPath, + this.inputDir, + this.outputDir, + this.templateData + ); + tmpl.config = this.config; + + for (let transform of this.transforms) { + tmpl.addTransform(transform); + } + for (let linter of this.linters) { + tmpl.addLinter(linter); + } + tmpl.setIsVerbose(this.isVerbose); + tmpl.setDryRun(this.isDryRun); + + return tmpl; + } + + getWriteCount() { + return this.writeCount; + } + + async getMappedDate(data) { + // should we use Luxon dates everywhere? Right now using built-in `Date` + if ("date" in data) { + debug( + "getMappedDate: using a date in the data for %o of %o", + this.inputPath, + data.date + ); + if (data.date instanceof Date) { + // YAML does its own date parsing + debug("getMappedDate: YAML parsed it: %o", data.date); + return data.date; + } else { + let stat = await fs.stat(this.inputPath); + // string + if (data.date.toLowerCase() === "last modified") { + return new Date(stat.ctimeMs); + } else if (data.date.toLowerCase() === "created") { + return new Date(stat.birthtimeMs); + } else { + // try to parse with Luxon + let date = DateTime.fromISO(data.date, { zone: "utc" }); + if (!date.isValid) { + throw new Error( + `date front matter value (${data.date}) is invalid for ${this.inputPath}` + ); + } + debug( + "getMappedDate: Luxon parsed %o: %o and %o", + data.date, + date, + date.toJSDate() + ); + + return date.toJSDate(); + } + } + } else { + let filenameRegex = this.inputPath.match(/(\d{4}-\d{2}-\d{2})/); + if (filenameRegex !== null) { + let dateObj = DateTime.fromISO(filenameRegex[1]).toJSDate(); + debug( + "getMappedDate: using filename regex time for %o of %o: %o", + this.inputPath, + filenameRegex[1], + dateObj + ); + return dateObj; } + + let stat = await fs.stat(this.inputPath); + let createdDate = new Date(stat.birthtimeMs); + debug( + "getMappedDate: using file created time for %o of %o (from %o)", + this.inputPath, + createdDate, + stat.birthtimeMs + ); + + // CREATED + return createdDate; } } -}; - -Template.prototype.clone = function() { - // TODO better clone - var tmpl = new Template( - this.inputPath, - this.inputDir, - this.outputDir, - this.templateData - ); - tmpl.setIsVerbose(this.isVerbose); - return tmpl; -}; - -Template.prototype.getWriteCount = function() { - return this.writeCount; -}; + + async getTemplateMapContent(pageMapEntry) { + pageMapEntry.template.setWrapWithLayouts(false); + let content = await pageMapEntry.template._getContent( + pageMapEntry.outputPath, + pageMapEntry.data + ); + pageMapEntry.template.setWrapWithLayouts(true); + + return content; + } + + async getTemplateMapEntries() { + debugDev("%o getMapped()", this.inputPath); + + let data = await this.getData(); + let entries = []; + // does not return outputPath or url, we donā€™t want to render permalinks yet + entries.push({ + template: this, + inputPath: this.inputPath, + data: data + }); + return entries; + } + + async _testCompleteRender() { + let entries = await this.getTemplateMapEntries(); + let contents = []; + + for (let entry of entries) { + entry._pages = await entry.template.getTemplates(entry.data); + + let page; + for (page of entry._pages) { + page.templateContent = await entry.template.getTemplateMapContent(page); + } + for (page of entry._pages) { + contents.push(await this.renderPageEntry(entry, page)); + } + } + return contents; + } +} module.exports = Template; diff --git a/src/TemplateCache.js b/src/TemplateCache.js new file mode 100644 index 000000000..163b27b0f --- /dev/null +++ b/src/TemplateCache.js @@ -0,0 +1,35 @@ +// Note: this is only used for TemplateLayout right now but could be used for more +// Just be careful because right now the TemplateLayout cache keys are not directly mapped to paths +// So you may get collisions if you use this for other things. +class TemplateCache { + constructor() { + this.cache = {}; + } + + clear() { + this.cache = {}; + } + + size() { + return Object.keys(this.cache).length; + } + + add(key, template) { + this.cache[key] = template; + } + + has(key) { + return key in this.cache; + } + + get(key) { + if (!this.has(key)) { + throw new Error(`Could not find ${key} in TemplateCache.`); + } + + return this.cache[key]; + } +} + +// singleton +module.exports = new TemplateCache(); diff --git a/src/TemplateCollection.js b/src/TemplateCollection.js new file mode 100644 index 000000000..8c6f0b810 --- /dev/null +++ b/src/TemplateCollection.js @@ -0,0 +1,69 @@ +const multimatch = require("multimatch"); +const Sortable = require("./Util/Sortable"); +const TemplatePath = require("./TemplatePath"); + +class TemplateCollection extends Sortable { + constructor() { + super(); + } + + // right now this is only used by the tests + async _testAddTemplate(template) { + let data = await template.getData(); + for (let map of await template.getTemplates(data)) { + this.add(map); + } + } + + getAll() { + return this.items.filter(() => true); + } + + getAllSorted() { + return this.sort(Sortable.sortFunctionDateInputPath); + } + + getSortedByDate() { + return this.sort(Sortable.sortFunctionDate); + } + + getGlobs(globs) { + if (typeof globs === "string") { + globs = [globs]; + } + + globs = globs.map(glob => TemplatePath.addLeadingDotSlash(glob)); + + return globs; + } + + getFilteredByGlob(globs) { + globs = this.getGlobs(globs); + + return this.getAllSorted().filter(item => { + if (multimatch([item.inputPath], globs).length) { + return true; + } + + return false; + }); + } + + getFilteredByTag(tagName) { + return this.getAllSorted().filter(function(item) { + let match = false; + if (!tagName) { + return true; + } else if (Array.isArray(item.data.tags)) { + item.data.tags.forEach(tag => { + if (tag === tagName) { + match = true; + } + }); + } + return match; + }); + } +} + +module.exports = TemplateCollection; diff --git a/src/TemplateConfig.js b/src/TemplateConfig.js index dd0e38d18..903544c4d 100644 --- a/src/TemplateConfig.js +++ b/src/TemplateConfig.js @@ -1,42 +1,140 @@ const fs = require("fs-extra"); -const merge = require("lodash.merge"); +const chalk = require("chalk"); +const lodashMerge = require("lodash/merge"); const TemplatePath = require("./TemplatePath"); +const EleventyBaseError = require("./EleventyBaseError"); +const dependencyTree = require("dependency-tree"); +const eleventyConfig = require("./EleventyConfig"); +const debug = require("debug")("Eleventy:TemplateConfig"); -function TemplateConfig(globalConfig, localConfigPath) { - this.localConfigPath = localConfigPath || ".eleventy.js"; - this.config = this.mergeConfig(globalConfig); -} +class EleventyConfigError extends EleventyBaseError {} + +class TemplateConfig { + constructor(customRootConfig, localProjectConfigPath) { + this.overrides = {}; + this.localProjectConfigPath = localProjectConfigPath || ".eleventy.js"; + + if (customRootConfig) { + this.customRootConfig = customRootConfig; + debug("Warning: Using custom root config!"); + } else { + this.customRootConfig = null; + } + this.initializeRootConfig(); + this.config = this.mergeConfig(this.localProjectConfigPath); + } -TemplateConfig.getDefaultConfig = function() { - let templateCfg = new TemplateConfig(require("../config.js")); - return templateCfg.getConfig(); -}; + getLocalProjectConfigFile() { + return TemplatePath.addLeadingDotSlash(this.localProjectConfigPath); + } + + reset() { + eleventyConfig.reset(); + this.initializeRootConfig(); + this.config = this.mergeConfig(this.localProjectConfigPath); + } -TemplateConfig.prototype.getConfig = function() { - return this.config; -}; + resetOnWatch() { + // nothing yet + } + + getConfig() { + return this.config; + } + + setProjectConfigPath(path) { + this.localProjectConfigPath = path; + + this.config = this.mergeConfig(path); + } -TemplateConfig.prototype.mergeConfig = function(globalConfig) { - let localConfig; - let path = TemplatePath.normalize( - TemplatePath.getWorkingDir() + "/" + this.localConfigPath - ); - try { - localConfig = require(path); - } catch (e) { - // if file does not exist, return empty obj - localConfig = {}; + setPathPrefix(pathPrefix) { + debug("Setting pathPrefix to %o", pathPrefix); + this.overrides.pathPrefix = pathPrefix; + this.config.pathPrefix = pathPrefix; } - // Object assign overrides original values (good only for templateFormats) but not good for everything else - let merged = merge({}, globalConfig, localConfig); + initializeRootConfig() { + this.rootConfig = this.customRootConfig || require("../config.js"); - let overrides = ["templateFormats"]; - for (let key of overrides) { - merged[key] = localConfig[key] || globalConfig[key]; + if (typeof this.rootConfig === "function") { + this.rootConfig = this.rootConfig(eleventyConfig); + // debug( "rootConfig is a function, after calling, eleventyConfig is %o", eleventyConfig ); + } + debug("rootConfig %o", this.rootConfig); } - return merged; -}; + mergeConfig(localProjectConfigPath) { + let overrides = ["templateFormats"]; + let localConfig = {}; + let path = TemplatePath.join( + TemplatePath.getWorkingDir(), + localProjectConfigPath + ); + debug(`Merging config with ${path}`); + + if (fs.existsSync(path)) { + try { + // remove from require cache so it will grab a fresh copy + if (path in require.cache) { + delete require.cache[path]; + } + + localConfig = require(path); + // debug( "localConfig require return value: %o", localConfig ); + } catch (err) { + throw new EleventyConfigError( + `Error in your Eleventy config file '${path}'.` + + (err.message.includes("Cannot find module") + ? chalk.blueBright(" You may need to run `npm install`.") + : ""), + err + ); + } + } else { + debug("Eleventy local project config file not found, skipping."); + } + + if (typeof localConfig === "function") { + localConfig = localConfig(eleventyConfig); + // debug( "localConfig is a function, after calling, eleventyConfig is %o", eleventyConfig ); + + if ( + typeof localConfig === "object" && + typeof localConfig.then === "function" + ) { + throw new EleventyConfigError( + `Error in your Eleventy config file '${path}': Returning a promise is not supported` + ); + } + } + + let eleventyConfigApiMergingObject = eleventyConfig.getMergingConfigObject(); + localConfig = lodashMerge(localConfig, eleventyConfigApiMergingObject); + + // blow away any templateFormats set in config return object and prefer those set in config API. + for (let localConfigKey of overrides) { + localConfig[localConfigKey] = + eleventyConfigApiMergingObject[localConfigKey] || + localConfig[localConfigKey]; + } + + // debug("eleventyConfig.getMergingConfigObject: %o", eleventyConfig.getMergingConfigObject()); + debug("localConfig: %o", localConfig); + debug("overrides: %o", this.overrides); + + // Object assign overrides original values (good only for templateFormats) but not good for anything else + let merged = lodashMerge({}, this.rootConfig, localConfig, this.overrides); + + // blow away any templateFormats upstream (donā€™t merge) + for (let key of overrides) { + merged[key] = localConfig[key] || this.rootConfig[key]; + } + + debug("Current configuration: %o", merged); + + return merged; + } +} module.exports = TemplateConfig; diff --git a/src/TemplateContent.js b/src/TemplateContent.js new file mode 100644 index 000000000..aac2a28a0 --- /dev/null +++ b/src/TemplateContent.js @@ -0,0 +1,206 @@ +const os = require("os"); +const fs = require("fs-extra"); +const normalize = require("normalize-path"); +const matter = require("gray-matter"); +const lodashSet = require("lodash/set"); + +const TemplateData = require("./TemplateData"); +const TemplateRender = require("./TemplateRender"); +const EleventyBaseError = require("./EleventyBaseError"); +const EleventyErrorUtil = require("./EleventyErrorUtil"); +const config = require("./Config"); +const debug = require("debug")("Eleventy:TemplateContent"); +const debugDev = require("debug")("Dev:Eleventy:TemplateContent"); + +class TemplateContentCompileError extends EleventyBaseError {} +class TemplateContentRenderError extends EleventyBaseError {} + +class TemplateContent { + constructor(inputPath, inputDir) { + this.inputPath = inputPath; + + if (inputDir) { + this.inputDir = normalize(inputDir); + } else { + this.inputDir = false; + } + } + + /* Used by tests */ + _setExtensionMap(map) { + this._extensionMap = map; + } + + set config(config) { + this._config = config; + } + + get config() { + if (!this._config) { + this._config = config.getConfig(); + } + + return this._config; + } + + get engine() { + return this.templateRender.engine; + } + + get templateRender() { + if (!this._templateRender) { + this._templateRender = new TemplateRender( + this.inputPath, + this.inputDir, + this._extensionMap + ); + } + + return this._templateRender; + } + + getInputPath() { + return this.inputPath; + } + + getInputDir() { + return this.inputDir; + } + + async read() { + this.inputContent = await this.getInputContent(); + + if (this.inputContent) { + let options = this.config.frontMatterParsingOptions || {}; + let fm = matter(this.inputContent, options); + if (options.excerpt && fm.excerpt) { + let excerptString = fm.excerpt + (options.excerpt_separator || "---"); + if (fm.content.startsWith(excerptString + os.EOL)) { + // with a newline after excerpt separator + fm.content = + fm.excerpt.trim() + + "\n" + + fm.content.substr((excerptString + os.EOL).length); + } else if (fm.content.startsWith(excerptString)) { + // no newline after excerpt separator + fm.content = fm.excerpt + fm.content.substr(excerptString.length); + } + + // alias, defaults to page.excerpt + let alias = options.excerpt_alias || "page.excerpt"; + lodashSet(fm.data, alias, fm.excerpt); + } + this.frontMatter = fm; + } else { + this.frontMatter = { + data: {}, + content: "", + excerpt: "" + }; + } + } + + async getInputContent() { + if (this.engine.needsToReadFileContents()) { + return fs.readFile(this.inputPath, "utf-8"); + } + + return ""; + } + + async getFrontMatter() { + if (!this.frontMatter) { + await this.read(); + } + + return this.frontMatter; + } + + async getPreRender() { + if (!this.frontMatter) { + await this.read(); + } + + return this.frontMatter.content; + } + + async getFrontMatterData() { + if (!this.frontMatter) { + await this.read(); + } + + let extraData = await this.engine.getExtraDataFromFile(this.inputPath); + let data = TemplateData.mergeDeep({}, this.frontMatter.data, extraData); + return TemplateData.cleanupData(data); + } + + async getEngineOverride() { + let frontMatterData = await this.getFrontMatterData(); + return frontMatterData[this.config.keys.engineOverride]; + } + + async setupTemplateRender(bypassMarkdown) { + let engineOverride = await this.getEngineOverride(); + if (engineOverride !== undefined) { + debugDev( + "%o overriding template engine to use %o", + this.inputPath, + engineOverride + ); + + this.templateRender.setEngineOverride(engineOverride, bypassMarkdown); + } else { + this.templateRender.setUseMarkdown(!bypassMarkdown); + } + } + + async compile(str, bypassMarkdown) { + await this.setupTemplateRender(bypassMarkdown); + + debugDev( + "%o compile() using engine: %o", + this.inputPath, + this.templateRender.engineName + ); + + try { + let fn = await this.templateRender.getCompiledTemplate(str); + debugDev("%o getCompiledTemplate function created", this.inputPath); + return fn; + } catch (e) { + debug(`Having trouble compiling template ${this.inputPath}: %O`, str); + throw new TemplateContentCompileError( + `Having trouble compiling template ${this.inputPath}`, + e + ); + } + } + + async render(str, data, bypassMarkdown) { + try { + let fn = await this.compile(str, bypassMarkdown); + let rendered = await fn(data); + debugDev( + "%o getCompiledTemplate called, rendered content created", + this.inputPath + ); + return rendered; + } catch (e) { + if (EleventyErrorUtil.isPrematureTemplateContentError(e)) { + throw e; + } else { + let engine = this.templateRender.getEnginesStr(); + debug( + `Having trouble rendering ${engine} template ${this.inputPath}: %O`, + str + ); + throw new TemplateContentRenderError( + `Having trouble rendering ${engine} template ${this.inputPath}`, + e + ); + } + } + } +} + +module.exports = TemplateContent; diff --git a/src/TemplateData.js b/src/TemplateData.js index ca13c2ce3..06912f8f5 100644 --- a/src/TemplateData.js +++ b/src/TemplateData.js @@ -1,128 +1,352 @@ const fs = require("fs-extra"); -const pify = require("pify"); -const globby = require("globby"); +const fastglob = require("fast-glob"); const parsePath = require("parse-filepath"); -const lodashset = require("lodash.set"); +const lodashset = require("lodash/set"); +const lodashUniq = require("lodash/uniq"); +const merge = require("./Util/Merge"); const TemplateRender = require("./TemplateRender"); -const TemplateConfig = require("./TemplateConfig"); const TemplatePath = require("./TemplatePath"); +const TemplateGlob = require("./TemplateGlob"); +const EleventyExtensionMap = require("./EleventyExtensionMap"); +const EleventyBaseError = require("./EleventyBaseError"); +const bench = require("./BenchmarkManager").get("Data"); +const config = require("./Config"); +const debugWarn = require("debug")("Eleventy:Warnings"); +const debug = require("debug")("Eleventy:TemplateData"); +const debugDev = require("debug")("Dev:Eleventy:TemplateData"); -let cfg = TemplateConfig.getDefaultConfig(); +class TemplateDataParseError extends EleventyBaseError {} -function TemplateData(globalDataPath) { - this.globalDataPath = globalDataPath; +class TemplateData { + constructor(inputDir) { + this.config = config.getConfig(); + this.dataTemplateEngine = this.config.dataTemplateEngine; - this.rawImports = {}; - this.rawImports[cfg.keys.package] = this.getJsonRaw( - TemplatePath.localPath("package.json") - ); + this.inputDirNeedsCheck = false; + this.setInputDir(inputDir); - this.globalData = null; -} - -TemplateData.prototype.clearData = function() { - this.globalData = null; -}; + this.rawImports = {}; + this.globalData = null; + } -TemplateData.prototype.cacheData = async function() { - this.clearData(); + /* Used by tests */ + _setConfig(config) { + this.config = config; + this.dataTemplateEngine = this.config.dataTemplateEngine; + } - return this.getData(); -}; + setInputDir(inputDir) { + this.inputDirNeedsCheck = true; + this.inputDir = inputDir; + this.dataDir = this.config.dir.data + ? TemplatePath.join(inputDir, this.config.dir.data) + : inputDir; + } -TemplateData.prototype.getGlobalDataGlob = async function() { - let dir = "."; + setDataTemplateEngine(engineName) { + this.dataTemplateEngine = engineName; + } - if (this.globalDataPath) { - let globalPathStat = await pify(fs.stat)(this.globalDataPath); + getRawImports() { + let pkgPath = TemplatePath.absolutePath("package.json"); - if (!globalPathStat.isDirectory()) { - throw new Error( - "Could not find data path directory: " + this.globalDataPath + try { + this.rawImports[this.config.keys.package] = require(pkgPath); + } catch (e) { + debug( + "Could not find and/or require package.json for data preprocessing at %o", + pkgPath ); } - dir = this.globalDataPath; + return this.rawImports; + } + + getDataDir() { + return this.dataDir; + } + + clearData() { + this.globalData = null; + } + + async cacheData() { + this.clearData(); + + return this.getData(); + } + + _getGlobalDataGlobByExtension(dir, extension) { + return TemplateGlob.normalizePath( + dir, + "/", + this.config.dir.data !== "." ? this.config.dir.data : "", + `/**/*.${extension}` + ); + } + + async _checkInputDir() { + if (this.inputDirNeedsCheck) { + let globalPathStat = await fs.stat(this.inputDir); + + if (!globalPathStat.isDirectory()) { + throw new Error("Could not find data path directory: " + this.inputDir); + } + + this.inputDirNeedsCheck = false; + } + } + + async getInputDir() { + let dir = "."; + + if (this.inputDir) { + await this._checkInputDir(); + dir = this.inputDir; + } + + return dir; + } + + async getTemplateDataFileGlob() { + let dir = await this.getInputDir(); + return TemplatePath.addLeadingDotSlashArray([ + `${dir}/**/*.json`, // covers .11tydata.json too + `${dir}/**/*${this.config.jsDataFileSuffix}.js` + ]); + } + + async getTemplateJavaScriptDataFileGlob() { + let dir = await this.getInputDir(); + return TemplatePath.addLeadingDotSlashArray([ + `${dir}/**/*${this.config.jsDataFileSuffix}.js` + ]); + } + + async getGlobalDataGlob() { + let dir = await this.getInputDir(); + return [this._getGlobalDataGlobByExtension(dir, "(json|js)")]; + } + + getWatchPathCache() { + return this.pathCache; + } + + async getGlobalDataFiles() { + let paths = await fastglob(await this.getGlobalDataGlob(), { + caseSensitiveMatch: false, + dot: true + }); + this.pathCache = paths; + return paths; + } + + getObjectPathForDataFile(path) { + let reducedPath = TemplatePath.stripLeadingSubPath(path, this.dataDir); + let parsed = parsePath(reducedPath); + let folders = parsed.dir ? parsed.dir.split("/") : []; + folders.push(parsed.name); + + return folders.join("."); + } + + async getAllGlobalData() { + let rawImports = this.getRawImports(); + let globalData = {}; + let files = TemplatePath.addLeadingDotSlashArray( + await this.getGlobalDataFiles() + ); + let dataFileConflicts = {}; + + for (let j = 0, k = files.length; j < k; j++) { + let folders = await this.getObjectPathForDataFile(files[j]); + + // TODO maybe merge these two? (if both valid objects) + if (dataFileConflicts[folders]) { + debugWarn( + `Warning: the key for a global data file (${files[j]}) will overwrite data from an already existing global data file (${dataFileConflicts[folders]})` + ); + } + dataFileConflicts[folders] = files[j]; + + debug(`Found global data file ${files[j]} and adding as: ${folders}`); + let data = await this.getDataValue(files[j], rawImports); + lodashset(globalData, folders, data); + } + + return globalData; } - return ( - TemplatePath.normalize(dir, "/", cfg.dir.data !== "." ? cfg.dir.data : "") + - "/**/*.json" - ); -}; + async getData() { + let rawImports = this.getRawImports(); -TemplateData.prototype.getGlobalDataFiles = async function() { - return globby(await this.getGlobalDataGlob(), { gitignore: true }); -}; + if (!this.globalData) { + let globalJson = await this.getAllGlobalData(); -TemplateData.prototype.getObjectPathForDataFile = function(path) { - let reducedPath = TemplatePath.stripPathFromDir( - path, - this.globalDataPath + "/" + (cfg.dir.data !== "." ? cfg.dir.data : "") - ); - let parsed = parsePath(reducedPath); - let folders = parsed.dir ? parsed.dir.split("/") : []; - folders.push(parsed.name); + // OK: Shallow merge when combining rawImports (pkg) with global data files + this.globalData = Object.assign({}, globalJson, rawImports); + } - return folders.join("."); -}; + return this.globalData; + } -TemplateData.prototype.getAllGlobalData = async function() { - let globalData = {}; - let files = await this.getGlobalDataFiles(); + /* Template and Directory data files */ + async combineLocalData(localDataPaths) { + let localData = {}; + if (!Array.isArray(localDataPaths)) { + localDataPaths = [localDataPaths]; + } + for (let path of localDataPaths) { + // clean up data for template/directory data files only. + let dataForPath = await this.getDataValue(path, null, true); + let cleanedDataForPath = TemplateData.cleanupData(dataForPath); + TemplateData.mergeDeep(this.config, localData, cleanedDataForPath); + // debug("`combineLocalData` (iterating) for %o: %O", path, localData); + } + return localData; + } - for (var j = 0, k = files.length; j < k; j++) { - let folders = await this.getObjectPathForDataFile(files[j]); - let data = await this.getJson(files[j], this.rawImports); - lodashset(globalData, folders, data); + async getLocalData(templatePath) { + let localDataPaths = await this.getLocalDataPaths(templatePath); + let importedData = await this.combineLocalData(localDataPaths); + let globalData = await this.getData(); + + // OK-ish: shallow merge when combining template/data dir files with global data files + let localData = Object.assign({}, globalData, importedData); + // debug("`getLocalData` for %o: %O", templatePath, localData); + return localData; + } + + async _getLocalJsonString(path) { + let rawInput; + try { + rawInput = await fs.readFile(path, "utf-8"); + } catch (e) { + // if file does not exist, return nothing + } + return rawInput; } - return globalData; -}; + async getDataValue(path, rawImports, ignoreProcessing) { + if (ignoreProcessing || TemplatePath.getExtension(path) === "js") { + let localPath = TemplatePath.absolutePath(path); + if (await fs.pathExists(localPath)) { + let dataBench = bench.get(`\`${path}\``); + dataBench.before(); + delete require.cache[localPath]; + let returnValue = require(localPath); + if (typeof returnValue === "function") { + returnValue = await returnValue(); + } -TemplateData.prototype.getData = async function() { - if (!this.globalData) { - let globalJson = await this.getAllGlobalData(); + dataBench.after(); + return returnValue; + } else { + return {}; + } + } else { + let rawInput = await this._getLocalJsonString(path); + let engineName = this.dataTemplateEngine; - this.globalData = Object.assign({}, globalJson, this.rawImports); + if (rawInput) { + if (ignoreProcessing || engineName === false) { + try { + return JSON.parse(rawInput); + } catch (e) { + throw new TemplateDataParseError( + `Having trouble parsing data file ${path}`, + e + ); + } + } else { + let fn = await new TemplateRender(engineName).getCompiledTemplate( + rawInput + ); + + try { + // pass in rawImports, donā€™t pass in global data, thatā€™s what weā€™re parsing + return JSON.parse(await fn(rawImports)); + } catch (e) { + throw new TemplateDataParseError( + `Having trouble parsing data file ${path}`, + e + ); + } + } + } + } + + return {}; } - return this.globalData; -}; + async getLocalDataPaths(templatePath) { + let paths = []; + let parsed = parsePath(templatePath); + let inputDir = TemplatePath.addLeadingDotSlash( + TemplatePath.normalize(this.inputDir) + ); + debugDev("getLocalDataPaths(%o)", templatePath); + debugDev("parsed.dir: %o", parsed.dir); + + if (parsed.dir) { + let fileNameNoExt = EleventyExtensionMap.removeTemplateExtension( + parsed.base + ); + let filePathNoExt = parsed.dir + "/" + fileNameNoExt; + let dataSuffix = this.config.jsDataFileSuffix; + debug("Using %o to find data files.", dataSuffix); + paths.push(filePathNoExt + dataSuffix + ".js"); + paths.push(filePathNoExt + dataSuffix + ".json"); + paths.push(filePathNoExt + ".json"); -TemplateData.prototype.getLocalData = async function(localDataPath) { - let importedData = await this.getJson(localDataPath, this.rawImports); - let globalData = await this.getData(); + let allDirs = TemplatePath.getAllDirs(parsed.dir); + debugDev("allDirs: %o", allDirs); + for (let dir of allDirs) { + let lastDir = TemplatePath.getLastPathSegment(dir); + let dirPathNoExt = dir + "/" + lastDir; - return Object.assign({}, globalData, importedData); -}; + if (!inputDir) { + paths.push(dirPathNoExt + dataSuffix + ".js"); + paths.push(dirPathNoExt + dataSuffix + ".json"); + paths.push(dirPathNoExt + ".json"); + } else { + debugDev("dirStr: %o; inputDir: %o", dir, inputDir); + if (dir.indexOf(inputDir) === 0 && dir !== inputDir) { + paths.push(dirPathNoExt + dataSuffix + ".js"); + paths.push(dirPathNoExt + dataSuffix + ".json"); + paths.push(dirPathNoExt + ".json"); + } + } + } + } -TemplateData.prototype._getLocalJson = function(path) { - // TODO convert to pify and async - let rawInput; - try { - rawInput = fs.readFileSync(path, "utf-8"); - } catch (e) { - // if file does not exist, return empty obj - return "{}"; + debug("getLocalDataPaths(%o): %o", templatePath, paths); + return lodashUniq(paths).reverse(); } - return rawInput; -}; + static mergeDeep(config, target, ...source) { + if (config.dataDeepMerge) { + return TemplateData.merge(target, ...source); + } else { + return Object.assign(target, ...source); + } + } -TemplateData.prototype.getJsonRaw = function(path) { - return JSON.parse(this._getLocalJson(path)); -}; + static merge(target, ...source) { + return merge(target, ...source); + } -TemplateData.prototype.getJson = async function(path, rawImports) { - let rawInput = this._getLocalJson(path); - let fn = await new TemplateRender(cfg.dataTemplateEngine).getCompiledTemplate( - rawInput - ); + static cleanupData(data) { + if ("tags" in data) { + if (typeof data.tags === "string") { + data.tags = data.tags ? [data.tags] : []; + } else if (data.tags === null) { + data.tags = []; + } + } - // pass in rawImports, donā€™t pass in global data, thatā€™s what weā€™re parsing - let str = await fn(rawImports); - return JSON.parse(str); -}; + return data; + } +} module.exports = TemplateData; diff --git a/src/TemplateFileSlug.js b/src/TemplateFileSlug.js new file mode 100644 index 000000000..8bb39485d --- /dev/null +++ b/src/TemplateFileSlug.js @@ -0,0 +1,49 @@ +const parsePath = require("parse-filepath"); +const TemplatePath = require("./TemplatePath"); +const EleventyExtensionMap = require("./EleventyExtensionMap"); + +class TemplateFileSlug { + constructor(inputPath, inputDir) { + if (inputDir) { + inputPath = TemplatePath.stripLeadingSubPath(inputPath, inputDir); + } + + this.inputPath = inputPath; + this.cleanInputPath = inputPath.replace(/^.\//, ""); + + let dirs = this.cleanInputPath.split("/"); + this.dirs = dirs; + this.dirs.pop(); + + this.parsed = parsePath(inputPath); + // TODO update this after the fix for issue #117 merges + this.filenameNoExt = EleventyExtensionMap.removeTemplateExtension( + this.parsed.base + ); + } + + getFullPathWithoutExtension() { + return "/" + TemplatePath.join(...this.dirs, this._getRawSlug()); + } + + _getRawSlug() { + let slug = this.filenameNoExt; + let reg = slug.match(/\d{4}-\d{2}-\d{2}-(.*)/); + if (reg) { + return reg[1]; + } + return slug; + } + + getSlug() { + let rawSlug = this._getRawSlug(); + + if (rawSlug === "index") { + return this.dirs.length ? this.dirs[this.dirs.length - 1] : ""; + } + + return rawSlug; + } +} + +module.exports = TemplateFileSlug; diff --git a/src/TemplateGlob.js b/src/TemplateGlob.js new file mode 100644 index 000000000..f72631be5 --- /dev/null +++ b/src/TemplateGlob.js @@ -0,0 +1,37 @@ +const TemplatePath = require("./TemplatePath"); + +class TemplateGlob { + static normalizePath(...paths) { + if (paths[0].charAt(0) === "!") { + throw new Error( + `TemplateGlob.normalizePath does not accept ! glob paths like: ${paths.join( + "" + )}` + ); + } + return TemplatePath.addLeadingDotSlash(TemplatePath.join(...paths)); + } + + static normalize(path) { + path = path.trim(); + if (path.charAt(0) === "!") { + return "!" + TemplateGlob.normalizePath(path.substr(1)); + } else { + return TemplateGlob.normalizePath(path); + } + } + + static map(files) { + if (typeof files === "string") { + return TemplateGlob.normalize(files); + } else if (Array.isArray(files)) { + return files.map(function(path) { + return TemplateGlob.normalize(path); + }); + } else { + return files; + } + } +} + +module.exports = TemplateGlob; diff --git a/src/TemplateLayout.js b/src/TemplateLayout.js index 989d6e730..06943db68 100644 --- a/src/TemplateLayout.js +++ b/src/TemplateLayout.js @@ -1,39 +1,143 @@ -const TemplateConfig = require("./TemplateConfig"); -const fs = require("fs-extra"); +const TemplateLayoutPathResolver = require("./TemplateLayoutPathResolver"); +const TemplateContent = require("./TemplateContent"); +const TemplateData = require("./TemplateData"); +const TemplatePath = require("./TemplatePath"); -let cfg = TemplateConfig.getDefaultConfig(); +const templateCache = require("./TemplateCache"); +const config = require("./Config"); +const debug = require("debug")("Eleventy:TemplateLayout"); +const debugDev = require("debug")("Dev:Eleventy:TemplateLayout"); -function TemplateLayout(name, dir) { - this.dir = dir; - this.name = name; - this.filename = this.findFileName(); - this.fullPath = this.dir + "/" + this.filename; -} +class TemplateLayout extends TemplateContent { + constructor(key, inputDir) { + // TODO getConfig() is duplicated in TemplateContent (super) + let cfg = config.getConfig(); + let resolvedPath = new TemplateLayoutPathResolver( + key, + inputDir + ).getFullPath(); + super(resolvedPath, inputDir); + + this.dataKeyLayoutPath = key; + this.inputPath = resolvedPath; + this.inputDir = inputDir; + this.config = cfg; + } + + static resolveFullKey(key, inputDir) { + return TemplatePath.join(inputDir, key); + } + + static getTemplate(key, inputDir, config) { + let fullKey = TemplateLayout.resolveFullKey(key, inputDir); + if (templateCache.has(fullKey)) { + debugDev("Found %o in TemplateCache", key); + return templateCache.get(fullKey); + } + + let tmpl = new TemplateLayout(key, inputDir); + tmpl.config = config; + templateCache.add(fullKey, tmpl); + + return tmpl; + } + + async getTemplateLayoutMapEntry() { + return { + key: this.dataKeyLayoutPath, + template: this, + frontMatterData: await this.getFrontMatterData() + }; + } + + async getTemplateLayoutMap() { + if (this.mapCache) { + return this.mapCache; + } + + let cfgKey = this.config.keys.layout; + let map = []; + let mapEntry = await this.getTemplateLayoutMapEntry(); + map.push(mapEntry); + + while (mapEntry.frontMatterData && cfgKey in mapEntry.frontMatterData) { + let layout = TemplateLayout.getTemplate( + mapEntry.frontMatterData[cfgKey], + this.inputDir, + this.config + ); + mapEntry = await layout.getTemplateLayoutMapEntry(); + map.push(mapEntry); + } + + this.mapCache = map; + return map; + } + + async getData() { + if (this.dataCache) { + return this.dataCache; + } -TemplateLayout.prototype.getFullPath = function() { - return this.fullPath; -}; - -TemplateLayout.prototype.findFileName = function() { - let file; - if (!fs.existsSync(this.dir)) { - throw Error( - "TemplateLayout directory does not exist for " + - this.name + - ": " + - this.dir - ); - } - cfg.templateFormats.forEach( - function(extension) { - let filename = this.name + "." + extension; - if (!file && fs.existsSync(this.dir + "/" + filename)) { - file = filename; - } - }.bind(this) - ); - - return file; -}; + let map = await this.getTemplateLayoutMap(); + let dataToMerge = []; + for (let j = map.length - 1; j >= 0; j--) { + dataToMerge.push(map[j].frontMatterData); + } + + // Deep merge of layout front matter + let data = TemplateData.mergeDeep(this.config, {}, ...dataToMerge); + delete data[this.config.keys.layout]; + + this.dataCache = data; + return data; + } + + async getCompiledLayoutFunctions() { + if (this.compileCache) { + return this.compileCache; + } + + let map = await this.getTemplateLayoutMap(); + let fns = []; + for (let layoutMap of map) { + fns.push( + await layoutMap.template.compile( + await layoutMap.template.getPreRender() + ) + ); + } + this.compileCache = fns; + return fns; + } + + static augmentDataWithContent(data, templateContent) { + data = data || {}; + + if (templateContent !== undefined) { + data.content = templateContent; + data.layoutContent = templateContent; + + // deprecated + data._layoutContent = templateContent; + } + + return data; + } + + // Inefficient? We want to compile all the templatelayouts into a single reusable callback? + // Trouble: layouts may need data variables present downstream/upstream + async render(data, templateContent) { + data = TemplateLayout.augmentDataWithContent(data, templateContent); + + let fns = await this.getCompiledLayoutFunctions(); + for (let fn of fns) { + templateContent = await fn(data); + data = TemplateLayout.augmentDataWithContent(data, templateContent); + } + + return templateContent; + } +} module.exports = TemplateLayout; diff --git a/src/TemplateLayoutPathResolver.js b/src/TemplateLayoutPathResolver.js new file mode 100644 index 000000000..9d1677e13 --- /dev/null +++ b/src/TemplateLayoutPathResolver.js @@ -0,0 +1,128 @@ +const fs = require("fs-extra"); +const config = require("./Config"); +const EleventyExtensionMap = require("./EleventyExtensionMap"); +const TemplatePath = require("./TemplatePath"); +const debug = require("debug")("Eleventy:TemplateLayoutPathResolver"); + +class TemplateLayoutPathResolver { + constructor(path, inputDir) { + this._config = config.getConfig(); + this.inputDir = inputDir; + this.originalPath = path; + this.path = path; + this.aliases = {}; + + this.init(); + } + + set inputDir(dir) { + this._inputDir = dir; + this.dir = this.getLayoutsDir(); + } + + get inputDir() { + return this._inputDir; + } + + // for testing + set config(cfg) { + this._config = cfg; + this.dir = this.getLayoutsDir(); + this.init(); + } + + get config() { + return this._config; + } + + init() { + // we might be able to move this into the constructor? + this.aliases = Object.assign({}, this.config.layoutAliases, this.aliases); + // debug("Current layout aliases: %o", this.aliases); + + if (this.path in this.aliases) { + // debug( + // "Substituting layout: %o maps to %o", + // this.path, + // this.aliases[this.path] + // ); + this.path = this.aliases[this.path]; + } + + this.pathAlreadyHasExtension = this.dir + "/" + this.path; + + if ( + this.path.split(".").length > 0 && + fs.existsSync(this.pathAlreadyHasExtension) + ) { + this.filename = this.path; + this.fullPath = this.pathAlreadyHasExtension; + } else { + this.filename = this.findFileName(); + this.fullPath = this.dir + "/" + this.filename; + } + } + + addLayoutAlias(from, to) { + this.aliases[from] = to; + } + + getFileName() { + if (!this.filename) { + throw new Error( + `Youā€™re trying to use a layout that does not exist: ${ + this.originalPath + } (${this.filename})` + ); + } + + return this.filename; + } + + getFullPath() { + if (!this.filename) { + throw new Error( + `Youā€™re trying to use a layout that does not exist: ${ + this.originalPath + } (${this.filename})` + ); + } + + return this.fullPath; + } + + findFileName() { + if (!fs.existsSync(this.dir)) { + throw Error( + "TemplateLayoutPathResolver directory does not exist for " + + this.path + + ": " + + this.dir + ); + } + + let extensionMap = new EleventyExtensionMap(this.config.templateFormats); + for (let filename of extensionMap.getFileList(this.path)) { + // TODO async + if (fs.existsSync(this.dir + "/" + filename)) { + return filename; + } + } + } + + getLayoutsDir() { + let layoutsDir; + if ("layouts" in this.config.dir) { + layoutsDir = this.config.dir.layouts; + } else if ("includes" in this.config.dir) { + layoutsDir = this.config.dir.includes; + } else { + // Should this have a default? + layoutsDir = "_includes"; + } + + return TemplatePath.join(this.inputDir, layoutsDir); + } +} + +module.exports = TemplateLayoutPathResolver; diff --git a/src/TemplateMap.js b/src/TemplateMap.js new file mode 100644 index 000000000..0c770fe2d --- /dev/null +++ b/src/TemplateMap.js @@ -0,0 +1,550 @@ +const isPlainObject = require("lodash/isPlainObject"); +const DependencyGraph = require("dependency-graph").DepGraph; +const TemplateCollection = require("./TemplateCollection"); +const EleventyErrorUtil = require("./EleventyErrorUtil"); +const UsingCircularTemplateContentReferenceError = require("./Errors/UsingCircularTemplateContentReferenceError"); +const eleventyConfig = require("./EleventyConfig"); +const debug = require("debug")("Eleventy:TemplateMap"); +const debugDev = require("debug")("Dev:Eleventy:TemplateMap"); + +const EleventyBaseError = require("./EleventyBaseError"); +class DuplicatePermalinkOutputError extends EleventyBaseError { + get removeDuplicateErrorStringFromOutput() { + return true; + } +} + +class TemplateMap { + constructor() { + this.map = []; + this.graph = new DependencyGraph(); + this.collectionsData = null; + this.cached = false; + this.configCollections = null; + this.verboseOutput = true; + } + + get tagPrefix() { + return "___TAG___"; + } + + get specialPrefix() { + return "___SPECIAL___"; + } + + async add(template) { + for (let map of await template.getTemplateMapEntries()) { + this.map.push(map); + } + } + + getMap() { + return this.map; + } + + get collection() { + if (!this._collection) { + this._collection = new TemplateCollection(); + } + return this._collection; + } + + getTagTarget(str) { + if (str.startsWith("collections.")) { + return str.substr("collections.".length); + } + } + + /* --- + * pagination: + * data: collections + * --- + */ + isPaginationOverAllCollections(entry) { + if (entry.data.pagination && entry.data.pagination.data) { + return ( + entry.data.pagination.data === "collections" || + entry.data.pagination.data === "collections.all" + ); + } + } + + getPaginationTagTarget(entry) { + if (entry.data.pagination && entry.data.pagination.data) { + return this.getTagTarget(entry.data.pagination.data); + } + } + + getMappedDependencies() { + let graph = new DependencyGraph(); + let tagPrefix = this.tagPrefix; + + graph.addNode(tagPrefix + "all"); + + for (let entry of this.map) { + if (this.isPaginationOverAllCollections(entry)) { + continue; + } + + // using Pagination (but not targeting a user config collection) + let paginationTagTarget = this.getPaginationTagTarget(entry); + if (paginationTagTarget) { + if (this.isUserConfigCollectionName(paginationTagTarget)) { + // delay this one to the second stage + continue; + } else { + // using pagination but over a tagged collection + graph.addNode(entry.inputPath); + if (!graph.hasNode(tagPrefix + paginationTagTarget)) { + graph.addNode(tagPrefix + paginationTagTarget); + } + graph.addDependency(entry.inputPath, tagPrefix + paginationTagTarget); + } + } else { + // not using pagination + graph.addNode(entry.inputPath); + } + + if (!entry.data.eleventyExcludeFromCollections) { + // collections.all + graph.addDependency(tagPrefix + "all", entry.inputPath); + + if (entry.data.tags) { + for (let tag of entry.data.tags) { + if (!graph.hasNode(tagPrefix + tag)) { + graph.addNode(tagPrefix + tag); + } + + // collections.tagName + // Dependency from tag to inputPath + graph.addDependency(tagPrefix + tag, entry.inputPath); + } + } + } + } + + return graph.overallOrder(); + } + + getDelayedMappedDependencies() { + let graph = new DependencyGraph(); + let tagPrefix = this.tagPrefix; + + graph.addNode(tagPrefix + "all"); + + let userConfigCollections = this.getUserConfigCollectionNames(); + // Add tags from named user config collections + for (let tag of userConfigCollections) { + graph.addNode(tagPrefix + tag); + // graph.addDependency( tagPrefix + tag, tagPrefix + "all" ); + } + + for (let entry of this.map) { + if (this.isPaginationOverAllCollections(entry)) { + continue; + } + + let paginationTagTarget = this.getPaginationTagTarget(entry); + if ( + paginationTagTarget && + this.isUserConfigCollectionName(paginationTagTarget) + ) { + if (!graph.hasNode(entry.inputPath)) { + graph.addNode(entry.inputPath); + } + graph.addDependency(entry.inputPath, tagPrefix + paginationTagTarget); + + if (!entry.data.eleventyExcludeFromCollections) { + // collections.all + graph.addDependency(tagPrefix + "all", entry.inputPath); + + if (entry.data.tags) { + for (let tag of entry.data.tags) { + if (!graph.hasNode(tagPrefix + tag)) { + graph.addNode(tagPrefix + tag); + } + // collections.tagName + // Dependency from tag to inputPath + graph.addDependency(tagPrefix + tag, entry.inputPath); + } + } + } + } + } + return graph.overallOrder(); + } + + getPaginatedOverCollectionsMappedDependencies() { + let graph = new DependencyGraph(); + let tagPrefix = this.tagPrefix; + let allNodeAdded = false; + + for (let entry of this.map) { + if ( + this.isPaginationOverAllCollections(entry) && + !this.getPaginationTagTarget(entry) + ) { + if (!allNodeAdded) { + graph.addNode(tagPrefix + "all"); + allNodeAdded = true; + } + + if (!graph.hasNode(entry.inputPath)) { + graph.addNode(entry.inputPath); + } + + if (!entry.data.eleventyExcludeFromCollections) { + // collections.all + graph.addDependency(tagPrefix + "all", entry.inputPath); + } + } + } + + return graph.overallOrder(); + } + + getPaginatedOverAllCollectionMappedDependencies() { + let graph = new DependencyGraph(); + let tagPrefix = this.tagPrefix; + let allNodeAdded = false; + + for (let entry of this.map) { + if ( + this.isPaginationOverAllCollections(entry) && + this.getPaginationTagTarget(entry) === "all" + ) { + if (!allNodeAdded) { + graph.addNode(tagPrefix + "all"); + allNodeAdded = true; + } + + if (!graph.hasNode(entry.inputPath)) { + graph.addNode(entry.inputPath); + } + + if (!entry.data.eleventyExcludeFromCollections) { + // collections.all + graph.addDependency(tagPrefix + "all", entry.inputPath); + } + } + } + + return graph.overallOrder(); + } + + async initDependencyMap(dependencyMap) { + let tagPrefix = this.tagPrefix; + for (let depEntry of dependencyMap) { + if (depEntry.startsWith(tagPrefix)) { + let tagName = depEntry.substr(tagPrefix.length); + if (this.isUserConfigCollectionName(tagName)) { + // async + this.collectionsData[tagName] = await this.getUserConfigCollection( + tagName + ); + } else { + this.collectionsData[tagName] = this.getTaggedCollection(tagName); + } + } else { + let map = this.getMapEntryForInputPath(depEntry); + map._pages = await map.template.getTemplates(map.data); + + let counter = 0; + for (let page of map._pages) { + // TODO do we need this in map entries? + if (!map.outputPath) { + map.outputPath = page.outputPath; + } + if ( + counter === 0 || + (map.data.pagination && + map.data.pagination.addAllPagesToCollections) + ) { + if (!map.data.eleventyExcludeFromCollections) { + // TODO do we need .template in collection entries? + this.collection.add(page); + } + } + counter++; + } + } + } + } + + async cache() { + debug("Caching collections objects."); + this.collectionsData = {}; + + for (let entry of this.map) { + entry.data.collections = this.collectionsData; + } + + let dependencyMap = this.getMappedDependencies(); + await this.initDependencyMap(dependencyMap); + + let delayedDependencyMap = this.getDelayedMappedDependencies(); + await this.initDependencyMap(delayedDependencyMap); + + let firstPaginatedDepMap = this.getPaginatedOverCollectionsMappedDependencies(); + await this.initDependencyMap(firstPaginatedDepMap); + + let secondPaginatedDepMap = this.getPaginatedOverAllCollectionMappedDependencies(); + await this.initDependencyMap(secondPaginatedDepMap); + + let orderedPaths = this.getOrderedInputPaths( + dependencyMap, + delayedDependencyMap, + firstPaginatedDepMap, + secondPaginatedDepMap + ); + let orderedMap = orderedPaths.map( + function(inputPath) { + return this.getMapEntryForInputPath(inputPath); + }.bind(this) + ); + await this.populateContentDataInMap(orderedMap); + + this.populateCollectionsWithContent(); + this.cached = true; + + this.checkForDuplicatePermalinks(); + } + + getMapEntryForInputPath(inputPath) { + for (let map of this.map) { + if (map.inputPath === inputPath) { + return map; + } + } + } + + getOrderedInputPaths( + dependencyMap, + delayedDependencyMap, + firstPaginatedDepMap, + secondPaginatedDepMap + ) { + let orderedMap = []; + let tagPrefix = this.tagPrefix; + + for (let dep of dependencyMap) { + if (!dep.startsWith(tagPrefix)) { + orderedMap.push(dep); + } + } + for (let dep of delayedDependencyMap) { + if (!dep.startsWith(tagPrefix)) { + orderedMap.push(dep); + } + } + for (let dep of firstPaginatedDepMap) { + if (!dep.startsWith(tagPrefix)) { + orderedMap.push(dep); + } + } + for (let dep of secondPaginatedDepMap) { + if (!dep.startsWith(tagPrefix)) { + orderedMap.push(dep); + } + } + return orderedMap; + } + + async populateContentDataInMap(orderedMap) { + let usedTemplateContentTooEarlyMap = []; + for (let map of orderedMap) { + if (!map._pages) { + throw new Error(`Content pages not found for ${map.inputPath}`); + } + try { + for (let pageEntry of map._pages) { + pageEntry.templateContent = await map.template.getTemplateMapContent( + pageEntry + ); + } + } catch (e) { + if (EleventyErrorUtil.isPrematureTemplateContentError(e)) { + usedTemplateContentTooEarlyMap.push(map); + } else { + throw e; + } + } + debugDev( + "Added this.map[...].templateContent, outputPath, et al for one map entry" + ); + } + + for (let map of usedTemplateContentTooEarlyMap) { + try { + for (let pageEntry of map._pages) { + pageEntry.templateContent = await map.template.getTemplateMapContent( + pageEntry + ); + } + } catch (e) { + if (EleventyErrorUtil.isPrematureTemplateContentError(e)) { + throw new UsingCircularTemplateContentReferenceError( + `${map.inputPath} contains a circular reference (using collections) to its own templateContent.` + ); + } else { + // rethrow? + throw e; + } + } + } + } + + _testGetAllTags() { + let allTags = {}; + for (let map of this.map) { + let tags = map.data.tags; + if (Array.isArray(tags)) { + for (let tag of tags) { + allTags[tag] = true; + } + } + } + return Object.keys(allTags); + } + + getTaggedCollection(tag) { + let result; + if (!tag || tag === "all") { + result = this.collection.getAllSorted(); + } else { + result = this.collection.getFilteredByTag(tag); + } + debug(`Collection: collections.${tag || "all"} size: ${result.length}`); + + return result; + } + + async _testGetTaggedCollectionsData() { + let collections = {}; + collections.all = this.collection.getAllSorted(); + debug(`Collection: collections.all size: ${collections.all.length}`); + + let tags = this._testGetAllTags(); + for (let tag of tags) { + collections[tag] = this.collection.getFilteredByTag(tag); + debug(`Collection: collections.${tag} size: ${collections[tag].length}`); + } + return collections; + } + + setUserConfigCollections(configCollections) { + return (this.configCollections = configCollections); + } + + isUserConfigCollectionName(name) { + let collections = this.configCollections || eleventyConfig.getCollections(); + return name && !!collections[name]; + } + + getUserConfigCollectionNames() { + return Object.keys( + this.configCollections || eleventyConfig.getCollections() + ); + } + + async getUserConfigCollection(name) { + let configCollections = + this.configCollections || eleventyConfig.getCollections(); + + // This works with async now + let result = await configCollections[name](this.collection); + + debug(`Collection: collections.${name} size: ${result.length}`); + return result; + } + + async _testGetUserConfigCollectionsData() { + let collections = {}; + let configCollections = + this.configCollections || eleventyConfig.getCollections(); + + for (let name in configCollections) { + collections[name] = configCollections[name](this.collection); + + debug( + `Collection: collections.${name} size: ${collections[name].length}` + ); + } + + return collections; + } + + async _testGetAllCollectionsData() { + let collections = {}; + let taggedCollections = await this._testGetTaggedCollectionsData(); + Object.assign(collections, taggedCollections); + + let userConfigCollections = await this._testGetUserConfigCollectionsData(); + Object.assign(collections, userConfigCollections); + + return collections; + } + + populateCollectionsWithContent() { + for (let collectionName in this.collectionsData) { + if (!Array.isArray(this.collectionsData[collectionName])) { + continue; + } + + for (let item of this.collectionsData[collectionName]) { + if (!isPlainObject(item) || !("inputPath" in item)) { + continue; + } + + let entry = this.getMapEntryForInputPath(item.inputPath); + let index = item.pageNumber || 0; + item.templateContent = entry._pages[index]._templateContent; + } + } + } + + checkForDuplicatePermalinks() { + let permalinks = {}; + let warnings = {}; + for (let entry of this.map) { + for (let page of entry._pages) { + if (page.url === false) { + // do nothing + } else if (!permalinks[page.url]) { + permalinks[page.url] = [entry.inputPath]; + } else { + warnings[ + page.outputPath + ] = `Output conflict: multiple input files are writing to \`${ + page.outputPath + }\`. Use distinct \`permalink\` values to resolve this conflict. + 1. ${entry.inputPath} +${permalinks[page.url] + .map(function(inputPath, index) { + return ` ${index + 2}. ${inputPath}\n`; + }) + .join("")} +`; + + permalinks[page.url].push(entry.inputPath); + } + } + } + + let warningList = Object.values(warnings); + if (warningList.length) { + // throw one at a time + throw new DuplicatePermalinkOutputError(warningList[0]); + } + } + + async getCollectionsData() { + if (!this.cached) { + await this.cache(); + } + + return this.collectionsData; + } +} + +module.exports = TemplateMap; diff --git a/src/TemplatePassthrough.js b/src/TemplatePassthrough.js new file mode 100644 index 000000000..714f5859f --- /dev/null +++ b/src/TemplatePassthrough.js @@ -0,0 +1,110 @@ +const copy = require("recursive-copy"); +const fs = require("fs"); +const TemplatePath = require("./TemplatePath"); +const debug = require("debug")("Eleventy:TemplatePassthrough"); +const fastglob = require("fast-glob"); +const EleventyBaseError = require("./EleventyBaseError"); + +class TemplatePassthroughError extends EleventyBaseError {} + +class TemplatePassthrough { + constructor(path, outputDir, inputDir) { + this.inputPath = path.inputPath; + this.outputPath = path.outputPath; + this.outputDir = outputDir; + this.inputDir = inputDir; + this.isDryRun = false; + } + + getOutputPath() { + const { inputDir, outputDir, outputPath, inputPath } = this; + if (outputPath === true) { + return TemplatePath.normalize( + TemplatePath.join( + outputDir, + TemplatePath.stripLeadingSubPath(inputPath, inputDir) + ) + ); + } + return TemplatePath.normalize(TemplatePath.join(outputDir, outputPath)); + } + + getOutputPathForGlobFile(globFile) { + return TemplatePath.join( + this.getOutputPath(), + TemplatePath.getLastPathSegment(globFile) + ); + } + + setDryRun(isDryRun) { + this.isDryRun = !!isDryRun; + } + + async getFiles(glob) { + debug("Searching for: %o", glob); + const files = TemplatePath.addLeadingDotSlashArray( + await fastglob(glob, { + caseSensitiveMatch: false, + dot: true + }) + ); + return files; + } + + copy(src, dest, copyOptions) { + if ( + TemplatePath.stripLeadingDotSlash(dest).includes( + TemplatePath.stripLeadingDotSlash(this.outputDir) + ) + ) { + return copy(src, dest, copyOptions); + } + return Promise.reject( + new TemplatePassthroughError( + "Destination is not in the site output directory. Check your passthrough paths." + ) + ); + } + + async write() { + const copyOptions = { + overwrite: true, + dot: true, + junk: false, + results: false + }; + + if (!this.isDryRun) { + debug("Copying %o", this.inputPath); + const isDirectory = TemplatePath.isDirectorySync(this.inputPath); + const isFile = fs.existsSync(this.inputPath); + + // If directory or file, recursive copy + if (isDirectory || isFile) { + // IMPORTANT: this returns a promise, does not await for promise to finish + return this.copy(this.inputPath, this.getOutputPath(), copyOptions); + } + + // If not directory or file, attempt to get globs + const files = await this.getFiles(this.inputPath); + + const promises = files.map(inputFile => + this.copy( + inputFile, + this.getOutputPathForGlobFile(inputFile), + copyOptions + ) + ); + + // TODO this should return promises, not await for them to finish + return Promise.all(promises).catch(err => { + throw new TemplatePassthroughError( + `Error copying passthrough files: ${err.message}`, + err + ); + }); + } + } +} + +module.exports = TemplatePassthrough; diff --git a/src/TemplatePassthroughManager.js b/src/TemplatePassthroughManager.js new file mode 100644 index 000000000..e88c0663a --- /dev/null +++ b/src/TemplatePassthroughManager.js @@ -0,0 +1,140 @@ +const config = require("./Config"); +const EleventyBaseError = require("./EleventyBaseError"); +const TemplatePassthrough = require("./TemplatePassthrough"); +const TemplateRender = require("./TemplateRender"); +const TemplatePath = require("./TemplatePath"); +const debug = require("debug")("Eleventy:TemplatePassthroughManager"); + +class TemplatePassthroughManagerCopyError extends EleventyBaseError {} + +class TemplatePassthroughManager { + constructor() { + this.config = config.getConfig(); + this.reset(); + } + + reset() { + this.count = 0; + debug("Resetting counts to 0"); + } + + setConfig(configOverride) { + this.config = configOverride || {}; + } + + setOutputDir(outputDir) { + this.outputDir = outputDir; + } + + setInputDir(inputDir) { + this.inputDir = inputDir; + } + + setDryRun(isDryRun) { + this.isDryRun = !!isDryRun; + } + + _normalizePaths(path, outputPath) { + return { + inputPath: TemplatePath.addLeadingDotSlash(path), + outputPath: TemplatePath.stripLeadingDotSlash( + outputPath !== undefined ? outputPath : path + ) + }; + } + + getConfigPaths() { + if (!this.config.passthroughFileCopy) { + debug("`passthroughFileCopy` is disabled in config, bypassing."); + return []; + } + + let paths = []; + let target = this.config.passthroughCopies || {}; + debug("`passthroughFileCopy` config paths: %o", target); + for (let path in target) { + paths.push(this._normalizePaths(path, target[path])); + } + debug("`passthroughFileCopy` config normalized paths: %o", paths); + return paths; + } + + getConfigPathGlobs() { + return this.getConfigPaths().map(path => { + return TemplatePath.convertToRecursiveGlob(path.inputPath); + }); + } + + getFilePaths(paths) { + if (!this.config.passthroughFileCopy) { + debug("`passthroughFileCopy` is disabled in config, bypassing."); + return []; + } + + let matches = []; + for (let path of paths) { + if (!TemplateRender.hasEngine(path)) { + matches.push(path); + } + } + + return matches; + } + + getCopyCount() { + return this.count; + } + + async copyPath(path) { + let pass = new TemplatePassthrough(path, this.outputDir, this.inputDir); + pass.setDryRun(this.isDryRun); + return pass + .write() + .then( + function() { + this.count++; + debug("Copied %o", path.inputPath); + }.bind(this) + ) + .catch(function(e) { + return Promise.reject( + new TemplatePassthroughManagerCopyError( + `Having trouble copying '${path.inputPath}'`, + e + ) + ); + }); + } + + // Performance note: these can actually take a fair bit of time, but arenā€™t a + // bottleneck to eleventy. The copies are performed asynchronously and donā€™t affect eleventy + // write times in a significant way. + async copyAll(paths) { + if (!this.config.passthroughFileCopy) { + debug("`passthroughFileCopy` is disabled in config, bypassing."); + return; + } + + let promises = []; + debug("TemplatePassthrough copy started."); + for (let path of this.getConfigPaths()) { + debug(`TemplatePassthrough copying from config: ${path}`); + promises.push(this.copyPath(path)); + } + + let passthroughPaths = this.getFilePaths(paths); + for (let path of passthroughPaths) { + let normalizedPath = this._normalizePaths(path); + debug( + `TemplatePassthrough copying from non-matching file extension: ${normalizedPath}` + ); + promises.push(this.copyPath(normalizedPath)); + } + + return Promise.all(promises).then(() => { + debug(`TemplatePassthrough copy finished. Current count: ${this.count}`); + }); + } +} + +module.exports = TemplatePassthroughManager; diff --git a/src/TemplatePath.js b/src/TemplatePath.js index 20141b088..e006a1fcc 100644 --- a/src/TemplatePath.js +++ b/src/TemplatePath.js @@ -1,45 +1,285 @@ const path = require("path"); const normalize = require("normalize-path"); +const parsePath = require("parse-filepath"); +const fs = require("fs-extra"); function TemplatePath() {} -TemplatePath.getModuleDir = function() { - return path.resolve(__dirname, ".."); +/** + * @returns {String} the absolute path to Eleventyā€™s project directory. + */ +TemplatePath.getWorkingDir = function() { + return TemplatePath.normalize(path.resolve(".")); }; -TemplatePath.getWorkingDir = function() { - return path.resolve("./"); +/** + * Returns the directory portion of a path. + * Works for directory and file paths and paths ending in a glob pattern. + * + * @param {String} path A path + * @returns {String} the directory portion of a path. + */ +TemplatePath.getDir = function(path) { + if (TemplatePath.isDirectorySync(path)) { + return path; + } + + return TemplatePath.getDirFromFilePath(path); +}; + +/** + * Returns the directory portion of a path that either points to a file + * or ends in a glob pattern. If `path` points to a directory, + * the returned value will have its last path segment stripped + * due to how [`parsePath`][1] works. + * + * [1]: https://www.npmjs.com/package/parse-filepath + * + * @param {String} path A path + * @returns {String} the directory portion of a path. + */ +TemplatePath.getDirFromFilePath = function(path) { + return parsePath(path).dir || "."; +}; + +/** + * Returns the last path segment in a path (no leading/trailing slashes). + * + * Assumes [`parsePath`][1] was called on `path` before. + * + * [1]: https://www.npmjs.com/package/parse-filepath + * + * @param {String} path A path + * @returns {String} the last path segment in a path + */ +TemplatePath.getLastPathSegment = function(path) { + if (!path.includes("/")) { + return path; + } + + // Trim a trailing slash if there is one + path = path.replace(/\/$/, ""); + + return path.substr(path.lastIndexOf("/") + 1); }; -/* Outputs ./SAFE/LOCAL/PATHS/WITHOUT/TRAILING/SLASHES */ -TemplatePath.normalize = function(...paths) { +/** + * @param {String} path A path + * @returns {String[]} an array of paths pointing to each path segment of the + * provided `path`. + */ +TemplatePath.getAllDirs = function(path) { + // Trim a trailing slash if there is one + path = path.replace(/\/$/, ""); + + if (!path.includes("/")) { + return [path]; + } + + return path + .split("/") + .map((segment, index, array) => array.slice(0, index + 1).join("/")) + .filter(path => path !== ".") + .reverse(); +}; + +/** + * Normalizes a path, resolving single-dot and double-dot segments. + * + * Node.jsā€™ [`path.normalize`][1] is called to strip a possible leading `"./"` segment. + * + * [1]: https://nodejs.org/api/path.html#path_path_normalize_path + * + * @param {String} thePath The path that should be normalized. + * @returns {String} the normalized path. + */ +TemplatePath.normalize = function(thePath) { + return normalize(path.normalize(thePath)); +}; + +/** + * Joins all given path segments together. + * + * It uses Node.jsā€™ [`path.join`][1] method and the [normalize-path][2] package. + * + * [1]: https://nodejs.org/api/path.html#path_path_join_paths + * [2]: https://www.npmjs.com/package/normalize-path + * + * @param {String[]} paths An arbitrary amount of path segments. + * @returns {String} the normalized and joined path. + */ +TemplatePath.join = function(...paths) { return normalize(path.join(...paths)); }; -TemplatePath.localPath = function(...paths) { - return normalize(path.join(TemplatePath.getWorkingDir(), ...paths)); +/** + * Joins the given URL path segments and normalizes the resulting path. + * Maintains traling a single trailing slash if the last URL path argument + * had atleast one. + * + * @param {String[]} urlPaths + * @returns {String} a normalized URL path described by the given URL path segments. + */ +TemplatePath.normalizeUrlPath = function(...urlPaths) { + const urlPath = path.posix.join(...urlPaths); + return urlPath.replace(/\/+$/, "/"); +}; + +/** + * Joins the given path segments. Since the first path is absolute, + * the resulting path will be absolute as well. + * + * @param {String[]} paths + * @returns {String} the absolute path described by the given path segments. + */ +TemplatePath.absolutePath = function(...paths) { + return TemplatePath.join(TemplatePath.getWorkingDir(), ...paths); +}; + +/** + * Turns an absolute path into a path relative Eleventyā€™s project directory. + * + * @param {String} absolutePath + * @returns {String} the relative path. + */ +TemplatePath.relativePath = function(absolutePath) { + return TemplatePath.stripLeadingSubPath( + absolutePath, + TemplatePath.getWorkingDir() + ); +}; + +/** + * Adds a leading dot-slash segment to each path in the `paths` array. + * + * @param {String[]} paths + * @returns {String[]} + */ +TemplatePath.addLeadingDotSlashArray = function(paths) { + return paths.map(path => TemplatePath.addLeadingDotSlash(path)); }; +/** + * Adds a leading dot-slash segment to `path`. + * + * @param {String} path + * @returns {String} + */ TemplatePath.addLeadingDotSlash = function(path) { - if (path.indexOf("/") === 0 || path.indexOf(".") === 0) { + if (path === "." || path === "..") { + return path + "/"; + } + + if (path.startsWith("/") || path.startsWith("./") || path.startsWith("../")) { return path; } + return "./" + path; }; -TemplatePath.stripLeadingDotSlash = function(dir) { - return dir.replace(/^\.\//, ""); +/** + * Removes a leading dot-slash segment. + * + * @param {String} path + * @returns {String} the `path` without a leading dot-slash segment. + */ +TemplatePath.stripLeadingDotSlash = function(path) { + return typeof path === "string" ? path.replace(/^\.\//, "") : path; +}; + +/** + * Determines whether a path starts with a given sub path. + * + * @param {String} path A path + * @param {String} subPath A path + * @returns {Boolean} whether `path` starts with `subPath`. + */ +TemplatePath.startsWithSubPath = function(path, subPath) { + path = TemplatePath.normalize(path); + subPath = TemplatePath.normalize(subPath); + + return path.startsWith(subPath); }; -TemplatePath.stripPathFromDir = function(targetDir, prunedPath) { - targetDir = TemplatePath.stripLeadingDotSlash(normalize(targetDir)); - prunedPath = TemplatePath.stripLeadingDotSlash(normalize(prunedPath)); +/** + * Removes the `subPath` at the start of `path` if present + * and returns the remainding path. + * + * @param {String} path A path + * @param {String} subPath A path + * @returns {String} the `path` without `subPath` at the start of it. + */ +TemplatePath.stripLeadingSubPath = function(path, subPath) { + path = TemplatePath.normalize(path); + subPath = TemplatePath.normalize(subPath); + + if (subPath !== "." && path.startsWith(subPath)) { + return path.substr(subPath.length + 1); + } + + return path; +}; + +/** + * @param {String} path A path + * @returns {Boolean} whether `path` points to an existing directory. + */ +TemplatePath.isDirectorySync = function(path) { + return fs.existsSync(path) && fs.statSync(path).isDirectory(); +}; + +/** + * Appends a recursive wildcard glob pattern to `path` + * unless `path` is not a directory; then, `path` is assumed to be a file path + * and is left unchaged. + * + * @param {String} path + * @returns {String} + */ +TemplatePath.convertToRecursiveGlob = function(path) { + if (path === "") { + return "./**"; + } + + path = TemplatePath.addLeadingDotSlash(path); + + if (TemplatePath.isDirectorySync(path)) { + return path + (!path.endsWith("/") ? "/" : "") + "**"; + } + + return path; +}; + +/** + * Returns the extension of the path without the leading dot. + * If the path has no extensions, the empty string is returned. + * + * @param {String} thePath + * @returns {String} the pathā€™s extension if it exists; + * otherwise, the empty string. + */ +TemplatePath.getExtension = function(thePath) { + return path.extname(thePath).replace(/^\./, ""); +}; + +/** + * Removes the extension from a path. + * + * @param {String} path + * @param {String} extension + * @returns {String} + */ +TemplatePath.removeExtension = function(path, extension = undefined) { + if (extension === undefined) { + return path; + } - if (targetDir.indexOf(prunedPath) === 0) { - return targetDir.substr(prunedPath.length + 1); + const pathExtension = TemplatePath.getExtension(path); + if (pathExtension !== "" && extension.endsWith(pathExtension)) { + return path.substring(0, path.lastIndexOf(pathExtension) - 1); } - return targetDir; + return path; }; module.exports = TemplatePath; diff --git a/src/TemplatePermalink.js b/src/TemplatePermalink.js index 3b454db39..13f55657a 100644 --- a/src/TemplatePermalink.js +++ b/src/TemplatePermalink.js @@ -1,5 +1,4 @@ const parsePath = require("parse-filepath"); -const normalize = require("normalize-path"); const TemplatePath = require("./TemplatePath"); function TemplatePermalink(link, extraSubdir) { @@ -14,15 +13,28 @@ TemplatePermalink.prototype._cleanLink = function(link) { TemplatePermalink.prototype.resolve = function() { let parsed = parsePath(this.link); - return TemplatePath.normalize( - parsed.dir + "/" + this.extraSubdir + parsed.base // name with extension - ); + return TemplatePath.join(parsed.dir, this.extraSubdir, parsed.base); }; TemplatePermalink.prototype.toString = function() { return this.resolve(); }; +// remove all index.htmlā€™s from links +// index.html becomes / +// test/index.html becomes test/ +TemplatePermalink.prototype.toHref = function() { + let str = this.toString(); + let original = (str.charAt(0) !== "/" ? "/" : "") + this.toString(); + let needle = "/index.html"; + if (original === needle) { + return "/"; + } else if (original.substr(-1 * needle.length) === needle) { + return original.substr(0, original.length - needle.length) + "/"; + } + return original; +}; + TemplatePermalink._hasDuplicateFolder = function(dir, base) { let folders = dir.split("/"); if (!folders[folders.length - 1]) { diff --git a/src/TemplatePermalinkNoWrite.js b/src/TemplatePermalinkNoWrite.js new file mode 100644 index 000000000..02174a228 --- /dev/null +++ b/src/TemplatePermalinkNoWrite.js @@ -0,0 +1,11 @@ +class TemplatePermalinkNoWrite { + toString() { + return false; + } + + toHref() { + return false; + } +} + +module.exports = TemplatePermalinkNoWrite; diff --git a/src/TemplateRender.js b/src/TemplateRender.js index 73eb71d8e..0c1b4436b 100644 --- a/src/TemplateRender.js +++ b/src/TemplateRender.js @@ -1,75 +1,206 @@ -const parsePath = require("parse-filepath"); const TemplatePath = require("./TemplatePath"); const TemplateEngine = require("./Engines/TemplateEngine"); -const TemplateConfig = require("./TemplateConfig"); +const EleventyBaseError = require("./EleventyBaseError"); +const EleventyExtensionMap = require("./EleventyExtensionMap"); +const config = require("./Config"); +// const debug = require("debug")("Eleventy:TemplateRender"); -let cfg = TemplateConfig.getDefaultConfig(); +class TemplateRenderUnknownEngineError extends EleventyBaseError {} // works with full path names or short engine name -function TemplateRender(tmplPath, inputDir) { - this.path = tmplPath; - this.parsed = tmplPath ? parsePath(tmplPath) : undefined; - this.engineName = - this.parsed && this.parsed.ext ? this.parsed.ext.substr(1) : tmplPath; - this.inputDir = this._normalizeInputDir(inputDir); - this.engine = TemplateEngine.getEngine(this.engineName, this.inputDir); - - this.defaultMarkdownEngine = cfg.markdownTemplateEngine; - this.defaultHtmlEngine = cfg.htmlTemplateEngine; -} +class TemplateRender { + constructor(tmplPath, inputDir, extensionMap) { + if (!tmplPath) { + throw new Error( + `TemplateRender requires a tmplPath argument, instead of ${tmplPath}` + ); + } + + this.path = tmplPath; + this.extensionMap = extensionMap; + + // optional + this.includesDir = this._normalizeIncludesDir(inputDir); + + this.parseMarkdownWith = this.config.markdownTemplateEngine; + this.parseHtmlWith = this.config.htmlTemplateEngine; + + this.init(tmplPath); + + this.useMarkdown = this.engineName === "md"; + } + + get config() { + if (!this._config) { + this._config = config.getConfig(); + } + return this._config; + } + + set config(config) { + this._config = config; + + if (this.engine) { + this.engine.config = config; + } + } + + init(engineNameOrPath) { + this.engineName = this.cleanupEngineName(engineNameOrPath); + if (!this.engineName) { + throw new TemplateRenderUnknownEngineError( + `Unknown engine for ${engineNameOrPath}` + ); + } + this.engine = TemplateEngine.getEngine(this.engineName, this.includesDir); + this.engine.initRequireCache(this.path); + } + + cleanupEngineName(tmplPath) { + return TemplateRender._cleanupEngineName( + tmplPath, + this.extensionMap || EleventyExtensionMap + ); + } + static cleanupEngineName(tmplPath) { + return TemplateRender._cleanupEngineName(tmplPath, EleventyExtensionMap); + } + static _cleanupEngineName(tmplPath, extensionMapRef) { + return extensionMapRef.getKey(tmplPath); + } + + static hasEngine(tmplPath) { + let name = TemplateRender.cleanupEngineName(tmplPath); + return TemplateEngine.hasEngine(name); + } + + static parseEngineOverrides(engineName) { + let overlappingEngineWarningCount = 0; + let engines = []; + let uniqueLookup = {}; + let usingMarkdown = false; + (engineName || "") + .split(",") + .map(name => { + return name.toLowerCase().trim(); + }) + .forEach(name => { + // html is assumed (treated as plaintext by the system) + if (!name || name === "html") { + return; + } + + if (name === "md") { + usingMarkdown = true; + return; + } + + if (!uniqueLookup[name]) { + engines.push(name); + uniqueLookup[name] = true; + + // we already short circuit md and html types above + overlappingEngineWarningCount++; + } + }); + + if (overlappingEngineWarningCount > 1) { + throw new Error( + `Donā€™t mix multiple templating engines in your front matter overrides (exceptions for HTML and Markdown). You used: ${engineName}` + ); + } + + // markdown should always be first + if (usingMarkdown) { + // todo use unshift or something (no wifi here to look up docs :D) + engines = ["md"].concat(engines); + } + + return engines; + } + + // used for error logging. + getEnginesStr() { + if (this.engineName === "md" && this.useMarkdown) { + return this.parseMarkdownWith + " (and markdown)"; + } + return this.engineName; + } + + setEngineOverride(engineName, bypassMarkdown) { + let engines = TemplateRender.parseEngineOverrides(engineName); + + // when overriding, Template Engines with HTML will instead use the Template Engine as primary and output HTML + // So any HTML engine usage here will never use a preprocessor templating engine. + this.setHtmlEngine(false); + + if (!engines.length) { + this.init("html"); + return; + } + + this.init(engines[0]); + + let usingMarkdown = engines[0] === "md" && !bypassMarkdown; + + this.setUseMarkdown(usingMarkdown); + + if (usingMarkdown) { + // false means only parse markdown and not with a preprocessor template engine + this.setMarkdownEngine(engines.length > 1 ? engines[1] : false); + } + } + + getEngineName() { + return this.engineName; + } + + getIncludesDir() { + return this.includesDir; + } -TemplateRender.prototype.setDefaultMarkdownEngine = function(markdownEngine) { - this.defaultMarkdownEngine = markdownEngine; -}; - -TemplateRender.prototype.setDefaultHtmlEngine = function(htmlEngine) { - this.defaultHtmlEngine = htmlEngine; -}; - -TemplateRender.prototype.getEngineName = function() { - return this.engineName; -}; - -TemplateRender.prototype._normalizeInputDir = function(dir) { - return dir - ? TemplatePath.normalize(dir, cfg.dir.includes) - : TemplatePath.normalize(cfg.dir.input, cfg.dir.includes); -}; - -TemplateRender.prototype.getInputDir = function() { - return this.inputDir; -}; - -TemplateRender.prototype.isEngine = function(engine) { - return this.engineName === engine; -}; - -TemplateRender.prototype.render = async function(str, data) { - return this.engine.render(str, data); -}; - -TemplateRender.prototype.getCompiledTemplate = async function(str, options) { - options = Object.assign( - { - parseMarkdownWith: this.defaultMarkdownEngine, - parseHtmlWith: this.defaultHtmlEngine, - bypassMarkdown: false - }, - options - ); - - // TODO refactor better - if (this.engineName === "md") { - return this.engine.compile( - str, - options.parseMarkdownWith, - options.bypassMarkdown + _normalizeIncludesDir(dir) { + return TemplatePath.join( + dir ? dir : this.config.dir.input, + this.config.dir.includes ); - } else if (this.engineName === "html") { - return this.engine.compile(str, options.parseHtmlWith); - } else { - return this.engine.compile(str); } -}; + + isEngine(engine) { + return this.engineName === engine; + } + + setUseMarkdown(useMarkdown) { + this.useMarkdown = !!useMarkdown; + } + + setMarkdownEngine(markdownEngine) { + this.parseMarkdownWith = markdownEngine; + } + + setHtmlEngine(htmlEngineName) { + this.parseHtmlWith = htmlEngineName; + } + + async render(str, data) { + return this.engine.render(str, data); + } + + async getCompiledTemplate(str) { + // TODO refactor better, move into TemplateEngine logic + if (this.engineName === "md") { + return this.engine.compile( + str, + this.path, + this.parseMarkdownWith, + !this.useMarkdown + ); + } else if (this.engineName === "html") { + return this.engine.compile(str, this.path, this.parseHtmlWith); + } else { + return this.engine.compile(str, this.path); + } + } +} module.exports = TemplateRender; diff --git a/src/TemplateWriter.js b/src/TemplateWriter.js index e6fb60595..a0d3de4e0 100644 --- a/src/TemplateWriter.js +++ b/src/TemplateWriter.js @@ -1,112 +1,83 @@ -const globby = require("globby"); -const normalize = require("normalize-path"); -const fs = require("fs-extra"); const Template = require("./Template"); const TemplatePath = require("./TemplatePath"); +const TemplateMap = require("./TemplateMap"); const TemplateRender = require("./TemplateRender"); -const TemplateConfig = require("./TemplateConfig"); -const EleventyError = require("./EleventyError"); -const Pagination = require("./Plugins/Pagination"); -const pkg = require("../package.json"); - -let cfg = TemplateConfig.getDefaultConfig(); - -function TemplateWriter(baseDir, outputDir, extensions, templateData) { - this.baseDir = baseDir; - this.templateExtensions = extensions; +const EleventyFiles = require("./EleventyFiles"); +const EleventyBaseError = require("./EleventyBaseError"); +const EleventyErrorHandler = require("./EleventyErrorHandler"); +const EleventyErrorUtil = require("./EleventyErrorUtil"); + +const config = require("./Config"); +const debug = require("debug")("Eleventy:TemplateWriter"); +const debugDev = require("debug")("Dev:Eleventy:TemplateWriter"); + +class TemplateWriterWriteError extends EleventyBaseError {} + +function TemplateWriter( + inputPath, + outputDir, + templateFormats, // TODO remove this, see `.getFileManager()` first + templateData, + isPassthroughAll +) { + this.config = config.getConfig(); + this.input = inputPath; + this.inputDir = TemplatePath.getDir(inputPath); this.outputDir = outputDir; + this.templateFormats = templateFormats; this.templateData = templateData; this.isVerbose = true; + this.isDryRun = false; this.writeCount = 0; - this.rawFiles = this.templateExtensions.map( - function(extension) { - return normalize(this.baseDir + "/**/*." + extension); - }.bind(this) - ); - - this.watchedFiles = this.addIgnores(baseDir, this.rawFiles); - this.files = this.addWritingIgnores(baseDir, this.watchedFiles); + // TODO can we get rid of this? Itā€™s only used for tests in getFileManager() + this.passthroughAll = isPassthroughAll; } -TemplateWriter.prototype.getRawFiles = function() { - return this.rawFiles; -}; - -TemplateWriter.prototype.getWatchedIgnores = function() { - return this.addIgnores(this.baseDir, []).map(ignore => - TemplatePath.stripLeadingDotSlash(ignore.substr(1)) - ); +/* For testing */ +TemplateWriter.prototype.overrideConfig = function(config) { + this.config = config; }; -TemplateWriter.prototype.getFiles = function() { - return this.files; +TemplateWriter.prototype.restart = function() { + this.writeCount = 0; + debugDev("Resetting counts to 0"); }; -TemplateWriter.getFileIgnores = function(baseDir) { - let ignorePath = TemplatePath.normalize(baseDir + "/.eleventyignore"); - let ignoreContent; - try { - ignoreContent = fs.readFileSync(ignorePath, "utf-8"); - } catch (e) { - ignoreContent = ""; - } - let ignores = []; - - if (ignoreContent) { - ignores = ignoreContent - .split("\n") - .filter(line => { - return line.trim().length > 0; - }) - .map(line => { - line = line.trim(); - path = TemplatePath.addLeadingDotSlash( - TemplatePath.normalize(baseDir, "/", line) - ); - if (fs.statSync(path).isDirectory()) { - return "!" + path + "/**"; - } - return "!" + path; - }); - } - - return ignores; +TemplateWriter.prototype.setEleventyFiles = function(eleventyFiles) { + this.eleventyFiles = eleventyFiles; }; -TemplateWriter.prototype.addIgnores = function(baseDir, files) { - files = files.concat(TemplateWriter.getFileIgnores(baseDir)); - if (cfg.dir.output) { - files = files.concat( - "!" + normalize(baseDir + "/" + cfg.dir.output + "/**") +TemplateWriter.prototype.getFileManager = function() { + // usually Eleventy.js will setEleventyFiles with the EleventyFiles manager + if (!this.eleventyFiles) { + // if not, we can create one (used only by tests) + this.eleventyFiles = new EleventyFiles( + this.input, + this.outputDir, + this.templateFormats, + this.passthroughAll ); + this.eleventyFiles.init(); } - return files; + return this.eleventyFiles; }; -TemplateWriter.prototype.addWritingIgnores = function(baseDir, files) { - if (cfg.dir.includes) { - files = files.concat( - "!" + normalize(baseDir + "/" + cfg.dir.includes + "/**") - ); - } - if (cfg.dir.data && cfg.dir.data !== ".") { - files = files.concat("!" + normalize(baseDir + "/" + cfg.dir.data + "/**")); - } - - return files; +TemplateWriter.prototype._getAllPaths = async function() { + return await this.getFileManager().getFiles(); }; -TemplateWriter.prototype._getTemplate = function(path) { +TemplateWriter.prototype._createTemplate = function(path) { let tmpl = new Template( path, - this.baseDir, + this.inputDir, this.outputDir, this.templateData ); tmpl.setIsVerbose(this.isVerbose); + tmpl.setDryRun(this.isDryRun); /* * Sample filter: arg str, return pretty HTML string @@ -114,53 +85,134 @@ TemplateWriter.prototype._getTemplate = function(path) { * return pretty(str, { ocd: true }); * } */ - for (let filterName in cfg.filters) { - let filter = cfg.filters[filterName]; - if (typeof filter === "function") { - tmpl.addFilter(filter); + for (let transformName in this.config.filters) { + let transform = this.config.filters[transformName]; + if (typeof transform === "function") { + tmpl.addTransform(transform); } } - let writer = this; - tmpl.addPlugin("pagination", async function(data) { - let paging = new Pagination(data); - paging.setTemplate(this); - await paging.write(); - writer.writeCount += paging.getWriteCount(); - - if (paging.cancel()) { - return false; + for (let linterName in this.config.linters) { + let linter = this.config.linters[linterName]; + if (typeof linter === "function") { + tmpl.addLinter(linter); } - }); + } return tmpl; }; -TemplateWriter.prototype._writeTemplate = async function(path) { - let tmpl = this._getTemplate(path); - try { - await tmpl.write(); - } catch (e) { - throw EleventyError.make( - new Error(`Having trouble writing template: ${path}`), - e - ); +TemplateWriter.prototype._addToTemplateMap = async function(paths) { + let promises = []; + for (let path of paths) { + if (TemplateRender.hasEngine(path)) { + promises.push( + this.templateMap.add(this._createTemplate(path)).then(() => { + debug(`${path} added to map.`); + }) + ); + } } - this.writeCount += tmpl.getWriteCount(); - return tmpl; + + return Promise.all(promises); +}; + +TemplateWriter.prototype._createTemplateMap = async function(paths) { + this.templateMap = new TemplateMap(); + + await this._addToTemplateMap(paths); + await this.templateMap.cache(); + + debugDev("TemplateMap cache complete."); + return this.templateMap; +}; + +TemplateWriter.prototype._writeTemplate = async function(mapEntry) { + let tmpl = mapEntry.template; + // we donā€™t re-use the map templateContent because it doesnā€™t include layouts + return tmpl.writeMapEntry(mapEntry).then(() => { + this.writeCount += tmpl.getWriteCount(); + }); }; TemplateWriter.prototype.write = async function() { - var paths = await globby(this.files, { gitignore: true }); - for (var j = 0, k = paths.length; j < k; j++) { - await this._writeTemplate(paths[j]); + let promises = []; + let paths = await this._getAllPaths(); + debug("Found: %o", paths); + promises.push( + this.getFileManager() + .getPassthroughManager() + .copyAll(paths) + .catch(e => { + EleventyErrorHandler.warn(e, "Error with passthrough copy"); + return Promise.reject( + new TemplateWriterWriteError(`Having trouble copying`, e) + ); + }) + ); + + // TODO optimize await here + await this._createTemplateMap(paths); + debug("Template map created."); + + let mapEntry; + let usedTemplateContentTooEarlyMap = []; + for (mapEntry of this.templateMap.getMap()) { + promises.push( + this._writeTemplate(mapEntry).catch(function(e) { + // Premature templateContent in layout render, this also happens in + // TemplateMap.populateContentDataInMap for non-layout content + if (EleventyErrorUtil.isPrematureTemplateContentError(e)) { + usedTemplateContentTooEarlyMap.push(mapEntry); + } else { + return Promise.reject( + TemplateWriterWriteError( + `Having trouble writing template: ${mapEntry.outputPath}`, + e + ) + ); + } + }) + ); } + + for (mapEntry of usedTemplateContentTooEarlyMap) { + promises.push( + this._writeTemplate(mapEntry).catch(function(e) { + return Promise.reject( + TemplateWriterWriteError( + `Having trouble writing template (second pass): ${mapEntry.outputPath}`, + e + ) + ); + }) + ); + } + + return Promise.all(promises).catch(e => { + EleventyErrorHandler.error(e, "Error writing templates"); + throw e; + }); }; TemplateWriter.prototype.setVerboseOutput = function(isVerbose) { this.isVerbose = isVerbose; }; +TemplateWriter.prototype.setDryRun = function(isDryRun) { + this.isDryRun = !!isDryRun; + + this.getFileManager() + .getPassthroughManager() + .setDryRun(this.isDryRun); +}; + +TemplateWriter.prototype.getCopyCount = function() { + return this.getFileManager() + .getPassthroughManager() + .getCopyCount(); +}; + TemplateWriter.prototype.getWriteCount = function() { return this.writeCount; }; diff --git a/src/UserConfig.js b/src/UserConfig.js new file mode 100644 index 000000000..bf97e89b5 --- /dev/null +++ b/src/UserConfig.js @@ -0,0 +1,549 @@ +const EventEmitter = require("events"); +const chalk = require("chalk"); +const semver = require("semver"); +const { DateTime } = require("luxon"); +const EleventyBaseError = require("./EleventyBaseError"); +const bench = require("./BenchmarkManager").get("Configuration"); +const debug = require("debug")("Eleventy:UserConfig"); +const pkg = require("../package.json"); + +class UserConfigError extends EleventyBaseError {} + +// API to expose configuration options in config file +class UserConfig { + constructor() { + this.reset(); + } + + reset() { + debug("Resetting EleventyConfig to initial values."); + this.events = new EventEmitter(); + this.collections = {}; + + this.liquidOptions = {}; + this.liquidTags = {}; + this.liquidFilters = {}; + this.liquidShortcodes = {}; + this.liquidPairedShortcodes = {}; + this.nunjucksFilters = {}; + this.nunjucksAsyncFilters = {}; + this.nunjucksTags = {}; + this.nunjucksShortcodes = {}; + this.nunjucksPairedShortcodes = {}; + this.handlebarsHelpers = {}; + this.handlebarsShortcodes = {}; + this.handlebarsPairedShortcodes = {}; + this.javascriptFunctions = {}; + this.pugOptions = {}; + this.ejsOptions = {}; + this.markdownHighlighter = null; + this.libraryOverrides = {}; + + this.passthroughCopies = {}; + this.layoutAliases = {}; + this.linters = {}; + // now named `transforms` in API + this.filters = {}; + this.activeNamespace = ""; + this.DateTime = DateTime; + this.dynamicPermalinks = true; + this.useGitIgnore = true; + this.dataDeepMerge = false; + this.experiments = new Set(); + // this.userExtensionMap = {}; + // this.templateExtensionAliases = {}; + this.watchJavaScriptDependencies = true; + this.browserSyncConfig = {}; + } + + versionCheck(expected) { + if (!semver.satisfies(pkg.version, expected)) { + throw new UserConfigError( + `This project requires the eleventy version to match '${expected}' but found ${pkg.version}. Use \`npm update @11ty/eleventy -g\` to upgrade the eleventy global or \`npm update @11ty/eleventy --save\` to upgrade your local project version.` + ); + } + } + + on(eventName, callback) { + return this.events.on(eventName, callback); + } + + emit(eventName, ...args) { + return this.events.emit(eventName, ...args); + } + + // This is a method for plugins, probably shouldnā€™t use this in projects. + // Projects should use `setLibrary` as documented here: + // https://github.com/11ty/eleventy/blob/master/docs/engines/markdown.md#use-your-own-options + addMarkdownHighlighter(highlightFn) { + this.markdownHighlighter = highlightFn; + } + + // tagCallback: function(liquidEngine) { return { parse: ā€¦, render: ā€¦ }} }; + addLiquidTag(name, tagFn) { + name = this.getNamespacedName(name); + + if (typeof tagFn !== "function") { + throw new UserConfigError( + `EleventyConfig.addLiquidTag expects a callback function to be passed in for ${name}: addLiquidTag(name, function(liquidEngine) { return { parse: ā€¦, render: ā€¦ } })` + ); + } + + if (this.liquidTags[name]) { + debug( + chalk.yellow( + "Warning, overwriting a Liquid tag with `addLiquidTag(%o)`" + ), + name + ); + } + this.liquidTags[name] = bench.add(`"${name}" Liquid Custom Tag`, tagFn); + } + + addLiquidFilter(name, callback) { + name = this.getNamespacedName(name); + + if (this.liquidFilters[name]) { + debug( + chalk.yellow( + "Warning, overwriting a Liquid filter with `addLiquidFilter(%o)`" + ), + name + ); + } + + this.liquidFilters[name] = bench.add(`"${name}" Liquid Filter`, callback); + } + + addNunjucksAsyncFilter(name, callback) { + name = this.getNamespacedName(name); + + if (this.nunjucksAsyncFilters[name]) { + debug( + chalk.yellow( + "Warning, overwriting a Nunjucks filter with `addNunjucksAsyncFilter(%o)`" + ), + name + ); + } + + this.nunjucksAsyncFilters[name] = bench.add( + `"${name}" Nunjucks Async Filter`, + callback + ); + } + + // Support the nunjucks style syntax for asynchronous filter add + addNunjucksFilter(name, callback, isAsync) { + if (isAsync) { + // namespacing happens downstream + this.addNunjucksAsyncFilter(name, callback); + } else { + name = this.getNamespacedName(name); + + if (this.nunjucksFilters[name]) { + debug( + chalk.yellow( + "Warning, overwriting a Nunjucks filter with `addNunjucksFilter(%o)`" + ), + name + ); + } + + this.nunjucksFilters[name] = bench.add( + `"${name}" Nunjucks Filter`, + callback + ); + } + } + + addHandlebarsHelper(name, callback) { + name = this.getNamespacedName(name); + + if (this.handlebarsHelpers[name]) { + debug( + chalk.yellow( + "Warning, overwriting a Handlebars helper with `addHandlebarsHelper(%o)`." + ), + name + ); + } + + this.handlebarsHelpers[name] = bench.add( + `"${name}" Handlebars Helper`, + callback + ); + } + + addFilter(name, callback) { + debug("Adding universal filter %o", this.getNamespacedName(name)); + + // namespacing happens downstream + this.addLiquidFilter(name, callback); + this.addNunjucksFilter(name, callback); + this.addJavaScriptFunction(name, callback); + + // TODO remove Handlebars helpers in Universal Filters. Use shortcodes instead (the Handlebars template syntax is the same). + this.addHandlebarsHelper(name, callback); + } + + addNunjucksTag(name, tagFn) { + name = this.getNamespacedName(name); + + if (typeof tagFn !== "function") { + throw new UserConfigError( + `EleventyConfig.addNunjucksTag expects a callback function to be passed in for ${name}: addNunjucksTag(name, function(nunjucksEngine) {})` + ); + } + + if (this.nunjucksTags[name]) { + debug( + chalk.yellow( + "Warning, overwriting a Nunjucks tag with `addNunjucksTag(%o)`" + ), + name + ); + } + + this.nunjucksTags[name] = bench.add(`"${name}" Nunjucks Custom Tag`, tagFn); + } + + addTransform(name, callback) { + name = this.getNamespacedName(name); + + // these are now called transforms + // this naming is kept here for backwards compatibility + // TODO major version change + this.filters[name] = callback; + } + + addLinter(name, callback) { + name = this.getNamespacedName(name); + + this.linters[name] = callback; + } + + addLayoutAlias(from, to) { + this.layoutAliases[from] = to; + } + + // get config defined collections + getCollections() { + return this.collections; + } + + addCollection(name, callback) { + name = this.getNamespacedName(name); + + if (this.collections[name]) { + throw new UserConfigError( + `config.addCollection(${name}) already exists. Try a different name for your collection.` + ); + } + + this.collections[name] = callback; + } + + addPlugin(plugin, options) { + debug("Adding plugin (unknown name: check your config file)."); + if (typeof plugin === "function") { + plugin(this); + } else if (plugin && plugin.configFunction) { + if (options && typeof options.init === "function") { + options.init.call(this, plugin.initArguments || {}); + } + + plugin.configFunction(this, options); + } else { + throw new UserConfigError( + "Invalid EleventyConfig.addPlugin signature. Should be a function or a valid Eleventy plugin object." + ); + } + } + + getNamespacedName(name) { + return this.activeNamespace + name; + } + + namespace(pluginNamespace, callback) { + let validNamespace = pluginNamespace && typeof pluginNamespace === "string"; + if (validNamespace) { + this.activeNamespace = pluginNamespace || ""; + } + + callback(); + + if (validNamespace) { + this.activeNamespace = ""; + } + } + + /** + * Adds a path to a file or directory to the list of pass-through copies + * which are copied as-is to the output. + * + * @param {string|object} fileOrDir The path to the file or directory that should + * be copied. OR an object where the key is the input glob and the property is the output directory + * @returns {any} a reference to the `EleventyConfig` object. + * @memberof EleventyConfig + */ + addPassthroughCopy(fileOrDir) { + if (fileOrDir instanceof Object) { + this.passthroughCopies = { + ...this.passthroughCopies, + ...fileOrDir + }; + } else { + // Glob patterns will not work string method + this.passthroughCopies[fileOrDir] = fileOrDir; + } + + return this; + } + + setTemplateFormats(templateFormats) { + if (typeof templateFormats === "string") { + templateFormats = templateFormats.split(",").map(format => format.trim()); + } + + this.templateFormats = templateFormats; + } + + setLibrary(engineName, libraryInstance) { + // Pug options are passed to `compile` and not in the library constructor so we donā€™t need to warn + if (engineName === "liquid" && this.mdOptions) { + debug( + "WARNING: using `eleventyConfig.setLibrary` will override any configuration set using `.setLiquidOptions` or with the `liquidOptions` key in the config object. Youā€™ll need to pass these options to the library yourself." + ); + } + + this.libraryOverrides[engineName.toLowerCase()] = libraryInstance; + } + + setPugOptions(options) { + this.pugOptions = options; + } + + setLiquidOptions(options) { + this.liquidOptions = options; + } + + setEjsOptions(options) { + this.ejsOptions = options; + } + + setDynamicPermalinks(enabled) { + this.dynamicPermalinks = !!enabled; + } + + setUseGitIgnore(enabled) { + this.useGitIgnore = !!enabled; + } + + addShortcode(name, callback) { + debug("Adding universal shortcode %o", this.getNamespacedName(name)); + this.addNunjucksShortcode(name, callback); + this.addLiquidShortcode(name, callback); + this.addHandlebarsShortcode(name, callback); + this.addJavaScriptFunction(name, callback); + } + + addNunjucksShortcode(name, callback) { + name = this.getNamespacedName(name); + + if (this.nunjucksShortcodes[name]) { + debug( + chalk.yellow( + "Warning, overwriting a Nunjucks Shortcode with `addNunjucksShortcode(%o)`" + ), + name + ); + } + + this.nunjucksShortcodes[name] = bench.add( + `"${name}" Nunjucks Shortcode`, + callback + ); + } + + addLiquidShortcode(name, callback) { + name = this.getNamespacedName(name); + + if (this.liquidShortcodes[name]) { + debug( + chalk.yellow( + "Warning, overwriting a Liquid Shortcode with `addLiquidShortcode(%o)`" + ), + name + ); + } + + this.liquidShortcodes[name] = bench.add( + `"${name}" Liquid Shortcode`, + callback + ); + } + + addHandlebarsShortcode(name, callback) { + name = this.getNamespacedName(name); + + if (this.handlebarsShortcodes[name]) { + debug( + chalk.yellow( + "Warning, overwriting a Handlebars Shortcode with `addHandlebarsShortcode(%o)`" + ), + name + ); + } + + this.handlebarsShortcodes[name] = bench.add( + `"${name}" Handlebars Shortcode`, + callback + ); + } + + addPairedShortcode(name, callback) { + this.addPairedNunjucksShortcode(name, callback); + this.addPairedLiquidShortcode(name, callback); + this.addPairedHandlebarsShortcode(name, callback); + this.addJavaScriptFunction(name, callback); + } + + addPairedNunjucksShortcode(name, callback) { + name = this.getNamespacedName(name); + + if (this.nunjucksPairedShortcodes[name]) { + debug( + chalk.yellow( + "Warning, overwriting a Nunjucks Paired Shortcode with `addPairedNunjucksShortcode(%o)`" + ), + name + ); + } + + this.nunjucksPairedShortcodes[name] = bench.add( + `"${name}" Nunjucks Paired Shortcode`, + callback + ); + } + + addPairedLiquidShortcode(name, callback) { + name = this.getNamespacedName(name); + + if (this.liquidPairedShortcodes[name]) { + debug( + chalk.yellow( + "Warning, overwriting a Liquid Paired Shortcode with `addPairedLiquidShortcode(%o)`" + ), + name + ); + } + + this.liquidPairedShortcodes[name] = bench.add( + `"${name}" Liquid Paired Shortcode`, + callback + ); + } + + addPairedHandlebarsShortcode(name, callback) { + name = this.getNamespacedName(name); + + if (this.handlebarsPairedShortcodes[name]) { + debug( + chalk.yellow( + "Warning, overwriting a Handlebars Paired Shortcode with `addPairedHandlebarsShortcode(%o)`" + ), + name + ); + } + + this.handlebarsPairedShortcodes[name] = bench.add( + `"${name}" Handlebars Paired Shortcode`, + callback + ); + } + + addJavaScriptFunction(name, callback) { + name = this.getNamespacedName(name); + + if (this.javascriptFunctions[name]) { + debug( + chalk.yellow( + "Warning, overwriting a JavaScript template function with `addJavaScriptFunction(%o)`" + ), + name + ); + } + + this.javascriptFunctions[name] = bench.add( + `"${name}" JavaScript Function`, + callback + ); + } + + addExperiment(key) { + this.experiments.add(key); + } + + setDataDeepMerge(deepMerge) { + this.dataDeepMerge = !!deepMerge; + } + + // addTemplateExtensionAlias(targetKey, extension) { + // this.templateExtensionAliases[extension] = targetKey; + // } + + setWatchJavaScriptDependencies(watchEnabled) { + this.watchJavaScriptDependencies = !!watchEnabled; + } + + setBrowserSyncConfig(options = {}) { + this.browserSyncConfig = options; + } + + setFrontMatterParsingOptions(options = {}) { + this.frontMatterParsingOptions = options; + } + + getMergingConfigObject() { + return { + templateFormats: this.templateFormats, + filters: this.filters, // now called transforms + linters: this.linters, + layoutAliases: this.layoutAliases, + passthroughCopies: this.passthroughCopies, + liquidOptions: this.liquidOptions, + liquidTags: this.liquidTags, + liquidFilters: this.liquidFilters, + liquidShortcodes: this.liquidShortcodes, + liquidPairedShortcodes: this.liquidPairedShortcodes, + nunjucksFilters: this.nunjucksFilters, + nunjucksAsyncFilters: this.nunjucksAsyncFilters, + nunjucksTags: this.nunjucksTags, + nunjucksShortcodes: this.nunjucksShortcodes, + nunjucksPairedShortcodes: this.nunjucksPairedShortcodes, + handlebarsHelpers: this.handlebarsHelpers, + handlebarsShortcodes: this.handlebarsShortcodes, + handlebarsPairedShortcodes: this.handlebarsPairedShortcodes, + javascriptFunctions: this.javascriptFunctions, + pugOptions: this.pugOptions, + ejsOptions: this.ejsOptions, + markdownHighlighter: this.markdownHighlighter, + libraryOverrides: this.libraryOverrides, + dynamicPermalinks: this.dynamicPermalinks, + useGitIgnore: this.useGitIgnore, + dataDeepMerge: this.dataDeepMerge, + experiments: this.experiments, + // templateExtensionAliases: this.templateExtensionAliases, + watchJavaScriptDependencies: this.watchJavaScriptDependencies, + browserSyncConfig: this.browserSyncConfig, + frontMatterParsingOptions: this.frontMatterParsingOptions + }; + } + + // addExtension(fileExtension, userClass) { + // this.userExtensionMap[ fileExtension ] = userClass; + // } +} + +module.exports = UserConfig; diff --git a/src/Util/Capitalize.js b/src/Util/Capitalize.js new file mode 100644 index 000000000..a946db29c --- /dev/null +++ b/src/Util/Capitalize.js @@ -0,0 +1,20 @@ +module.exports = function(str, options) { + options = Object.assign( + { + lowercaseRestOfWord: false + }, + options + ); + + return str + .split(" ") + .map(function(word) { + return ( + word.substr(0, 1).toUpperCase() + + (options.lowercaseRestOfWord + ? word.substr(1).toLowerCase() + : word.substr(1)) + ); + }) + .join(" "); +}; diff --git a/src/Util/Merge.js b/src/Util/Merge.js new file mode 100644 index 000000000..3eb57b905 --- /dev/null +++ b/src/Util/Merge.js @@ -0,0 +1,51 @@ +const isPlainObject = require("lodash/isPlainObject"); +const OVERRIDE_PREFIX = "override:"; + +function getMergedItem(target, source, parentKey) { + // if key is prefixed with OVERRIDE_PREFIX, it just keeps the new source value (no merging) + if (parentKey && parentKey.indexOf(OVERRIDE_PREFIX) === 0) { + return source; + } + + if (!target) { + return source; + } else if (Array.isArray(target) && Array.isArray(source)) { + return target.concat(source); + } else if (isPlainObject(target)) { + if (isPlainObject(source)) { + for (var key in source) { + let newKey = key; + if (key.indexOf(OVERRIDE_PREFIX) === 0) { + newKey = key.substr(OVERRIDE_PREFIX.length); + } + target[newKey] = getMergedItem(target[key], source[key], newKey); + } + } + return target; + } else { + // number, string, class instance, etc + return source; + } +} +function Merge(target, ...sources) { + // Remove override prefixes from root target. + if (isPlainObject(target)) { + for (var key in target) { + if (key.indexOf(OVERRIDE_PREFIX) === 0) { + target[key.substr(OVERRIDE_PREFIX.length)] = target[key]; + delete target[key]; + } + } + } + + for (var source of sources) { + if (!source) { + continue; + } + target = getMergedItem(target, source); + } + + return target; +} + +module.exports = Merge; diff --git a/src/Util/Pluralize.js b/src/Util/Pluralize.js new file mode 100644 index 000000000..b36f60cde --- /dev/null +++ b/src/Util/Pluralize.js @@ -0,0 +1,3 @@ +module.exports = function(count, singleWord, pluralWord) { + return count === 1 ? singleWord : pluralWord; +}; diff --git a/src/Util/Sortable.js b/src/Util/Sortable.js new file mode 100644 index 000000000..176f90181 --- /dev/null +++ b/src/Util/Sortable.js @@ -0,0 +1,144 @@ +const capitalize = require("./Capitalize"); + +class Sortable { + constructor() { + this.sortAscending = true; + this.sortNumeric = false; + this.items = []; + + this.sortFunctionStringMap = { + "A-Z": "Ascending", + "Z-A": "Descending", + "0-9": "NumericAscending", + "9-0": "NumericDescending" + }; + } + + get length() { + return this.items.length; + } + + add(item) { + this.items.push(item); + } + + sort(sortFunction) { + if (!sortFunction) { + sortFunction = this.getSortFunction(); + } else if (typeof sortFunction === "string") { + if (sortFunction in this.sortFunctionStringMap) { + sortFunction = this.sortFunctionStringMap[sortFunction]; + } + + sortFunction = Sortable["sortFunction" + capitalize(sortFunction)]; + } + + return this.items.filter(() => true).sort(sortFunction); + } + + sortAscending() { + return this.sort(this.getSortFunctionAscending); + } + + sortDescending() { + return this.sort(this.getSortFunctionDescending); + } + + isSortAscending() { + return this.sortAscending; + } + + isSortNumeric() { + return this.sortNumeric; + } + + setSortDescending() { + this.sortAscending = false; + } + + setSortAscending(isAscending) { + this.sortAscending = isAscending; + } + + setSortNumeric(isNumeric) { + this.sortNumeric = isNumeric; + } + + /* Sort functions */ + static sortFunctionNumericAscending(a, b) { + return a - b; + } + + static sortFunctionNumericDescending(a, b) { + return b - a; + } + + static sortFunctionAscending(a, b) { + if (a > b) { + return 1; + } else if (a < b) { + return -1; + } + return 0; + } + + static sortFunctionDescending(a, b) { + return Sortable.sortFunctionAscending(b, a); + } + + static sortFunctionAlphabeticAscending(a, b) { + return Sortable.sortFunctionAscending(a, b); + } + + static sortFunctionAlphabeticDescending(a, b) { + return Sortable.sortFunctionAscending(b, a); + } + + static sortFunctionDate(mapA, mapB) { + return Sortable.sortFunctionNumericAscending( + mapA.date.getTime(), + mapB.date.getTime() + ); + } + + static sortFunctionDateInputPath(mapA, mapB) { + let sortDate = Sortable.sortFunctionNumericAscending( + mapA.date.getTime(), + mapB.date.getTime() + ); + if (sortDate === 0) { + return Sortable.sortFunctionAlphabeticAscending( + mapA.inputPath, + mapB.inputPath + ); + } + return sortDate; + } + /* End sort functions */ + + getSortFunction() { + if (this.sortAscending) { + return this.getSortFunctionAscending(); + } else { + return this.getSortFunctionDescending(); + } + } + + getSortFunctionAscending() { + if (this.sortNumeric) { + return Sortable.sortFunctionNumericAscending; + } else { + return Sortable.sortFunctionAlphabeticAscending; + } + } + + getSortFunctionDescending() { + if (this.sortNumeric) { + return Sortable.sortFunctionNumericDescending; + } else { + return Sortable.sortFunctionAlphabeticDescending; + } + } +} + +module.exports = Sortable; diff --git a/src/VersionCheck.js b/src/VersionCheck.js deleted file mode 100644 index e46c9427c..000000000 --- a/src/VersionCheck.js +++ /dev/null @@ -1,22 +0,0 @@ -const pkg = require("../package.json"); -const versionCheck = require("check-node-version"); - -/* - * This file should have maximum compatibility to ensure node checks can - * report requirements on unsupported versions. - */ -module.exports = function() { - return new Promise(function(resolve, reject) { - versionCheck({ node: pkg.engines.node }, function(err, result) { - if (!result.versions.node.isSatisfied) { - console.log( - "Eleventy requires Node version 8 or above. Youā€™re currently using " + - result.versions.node.version + - "." - ); - } else { - resolve(); - } - }); - }); -}; diff --git a/test/BenchmarkTest.js b/test/BenchmarkTest.js new file mode 100644 index 000000000..19c3c51f3 --- /dev/null +++ b/test/BenchmarkTest.js @@ -0,0 +1,53 @@ +import test from "ava"; +import Benchmark from "../src/Benchmark"; + +function between(t, value, lowerBound, upperBound) { + t.truthy(value >= lowerBound); + t.truthy(value <= upperBound); +} + +test.cb("Standard Benchmark", t => { + let b = new Benchmark(); + b.before(); + setTimeout(function() { + b.after(); + t.truthy(b.getTotal() >= 10); + t.end(); + }, 10); +}); + +test.cb( + "Nested Benchmark (nested calls are ignored while a parent is measuring)", + t => { + let b = new Benchmark(); + b.before(); + + setTimeout(function() { + b.before(); + b.after(); + t.is(b.getTotal(), 0); + + b.after(); + t.truthy(b.getTotal() >= 10); + t.end(); + }, 10); + } +); + +test.cb("Reset Benchmark", t => { + let b = new Benchmark(); + b.before(); + b.reset(); + + setTimeout(function() { + b.before(); + b.after(); + t.is(b.getTotal(), 0); + + t.throws(function() { + // throws because we reset + b.after(); + }); + t.end(); + }, 10); +}); diff --git a/test/CapitalizeTest.js b/test/CapitalizeTest.js new file mode 100644 index 000000000..8c845ec74 --- /dev/null +++ b/test/CapitalizeTest.js @@ -0,0 +1,12 @@ +import test from "ava"; +import capitalize from "../src/Util/Capitalize"; + +test("capitalize", t => { + t.is(capitalize("hello"), "Hello"); + t.is(capitalize("hello world"), "Hello World"); + t.is(capitalize("Testing TESTING"), "Testing TESTING"); + t.is( + capitalize("Testing TESTING", { lowercaseRestOfWord: true }), + "Testing Testing" + ); +}); diff --git a/test/EleventyCommandCheckTest.js b/test/EleventyCommandCheckTest.js new file mode 100644 index 000000000..1a26e1af5 --- /dev/null +++ b/test/EleventyCommandCheckTest.js @@ -0,0 +1,51 @@ +import test from "ava"; +import EleventyCommandCheck from "../src/EleventyCommandCheck"; + +test("Constructor", t => { + let cmdCheck = new EleventyCommandCheck({}); + t.is(cmdCheck.toString(), ""); +}); + +test("Has an argument", t => { + let cmdCheck = new EleventyCommandCheck({ + input: "src" + }); + t.is(cmdCheck.toString(), "--input=src"); +}); + +test("Boolean argument", t => { + let cmdCheck = new EleventyCommandCheck({ + version: true + }); + t.is(cmdCheck.toString(), "--version"); +}); + +test("Multiple arguments", t => { + let cmdCheck = new EleventyCommandCheck({ + input: "src", + version: true + }); + + // technically invalid but eleventy should quit early on --version + t.is(cmdCheck.toString(), "--input=src --version"); +}); + +test("getArgumentLookupMap", t => { + let cmdCheck = new EleventyCommandCheck({}); + t.is(cmdCheck.getArgumentLookupMap()["input"], true); + t.is(cmdCheck.getArgumentLookupMap()["version"], true); + t.falsy(cmdCheck.getArgumentLookupMap()["not-an-arg"]); + + t.is(cmdCheck.isKnownArgument("input"), true); + t.is(cmdCheck.isKnownArgument("version"), true); + t.is(cmdCheck.isKnownArgument("unknown-thing"), false); + + t.is(cmdCheck.isKnownArgument("_"), true); +}); + +test("throws", t => { + let cmdCheck = new EleventyCommandCheck({ "unknown-argument": true }); + t.throws(() => { + cmdCheck.hasUnknownArguments(); + }); +}); diff --git a/test/EleventyConfigTest.js b/test/EleventyConfigTest.js new file mode 100644 index 000000000..afdc9e4d0 --- /dev/null +++ b/test/EleventyConfigTest.js @@ -0,0 +1,88 @@ +import test from "ava"; +import eleventyConfig from "../src/EleventyConfig"; + +// more in TemplateConfigTest.js + +test.cb("Events", t => { + eleventyConfig.on("testEvent", function(arg1, arg2, arg3) { + t.is(arg1, "arg1"); + t.is(arg2, "arg2"); + t.is(arg3, "arg3"); + t.end(); + }); + + eleventyConfig.emit("testEvent", "arg1", "arg2", "arg3"); +}); + +test("Add Collections", t => { + eleventyConfig.addCollection("myCollection", function(collection) {}); + t.deepEqual(Object.keys(eleventyConfig.getCollections()), ["myCollection"]); +}); + +test("Add Collections throws error on key collision", t => { + eleventyConfig.addCollection("myCollectionCollision", function( + collection + ) {}); + + t.throws(() => { + eleventyConfig.addCollection("myCollectionCollision", function( + collection + ) {}); + }); +}); + +test("Set manual Pass-through File Copy (single call)", t => { + eleventyConfig.addPassthroughCopy("img"); + + t.is(eleventyConfig.passthroughCopies["img"], "img"); +}); + +test("Set manual Pass-through File Copy (chained calls)", t => { + eleventyConfig + .addPassthroughCopy("css") + .addPassthroughCopy("js") + .addPassthroughCopy({ "./src/static": "static" }) + .addPassthroughCopy({ "./src/empty": "./" }); + + t.is(eleventyConfig.passthroughCopies["css"], "css"); + t.is(eleventyConfig.passthroughCopies["js"], "js"); + t.is(eleventyConfig.passthroughCopies["./src/static"], "static"); + t.is(eleventyConfig.passthroughCopies["./src/empty"], "./"); +}); + +test("Set manual Pass-through File Copy (glob patterns)", t => { + eleventyConfig.addPassthroughCopy({ + "./src/static/**/*": "renamed", + "./src/markdown/*.md": "" + }); + + t.is(eleventyConfig.passthroughCopies["css/**"], undefined); + t.is(eleventyConfig.passthroughCopies["js/**"], undefined); + t.is(eleventyConfig.passthroughCopies["./src/static/**/*"], "renamed"); + t.is(eleventyConfig.passthroughCopies["./src/markdown/*.md"], ""); +}); + +test("Set Template Formats (string)", t => { + eleventyConfig.setTemplateFormats("ejs, njk, liquid"); + t.deepEqual(eleventyConfig.templateFormats, ["ejs", "njk", "liquid"]); +}); + +test("Set Template Formats (array)", t => { + eleventyConfig.setTemplateFormats(["ejs", "njk", "liquid"]); + t.deepEqual(eleventyConfig.templateFormats, ["ejs", "njk", "liquid"]); +}); + +test("Set Template Formats (js passthrough copy)", t => { + eleventyConfig.setTemplateFormats("ejs, njk, liquid, js"); + t.deepEqual(eleventyConfig.templateFormats, ["ejs", "njk", "liquid", "js"]); +}); + +test("Set Template Formats (11ty.js)", t => { + eleventyConfig.setTemplateFormats("ejs, njk, liquid, 11ty.js"); + t.deepEqual(eleventyConfig.templateFormats, [ + "ejs", + "njk", + "liquid", + "11ty.js" + ]); +}); diff --git a/test/EleventyErrorHandlerTest.js b/test/EleventyErrorHandlerTest.js new file mode 100644 index 000000000..5bc98f80e --- /dev/null +++ b/test/EleventyErrorHandlerTest.js @@ -0,0 +1,56 @@ +import test from "ava"; +import EleventyErrorHandler from "../src/EleventyErrorHandler"; + +let output = []; + +test.beforeEach(t => { + output = []; + + EleventyErrorHandler.isChalkEnabled = false; + EleventyErrorHandler.logger = { + log: function(str) { + output.push(str); + }, + warn: function(str) { + output.push(str); + }, + error: function(str) { + output.push(str); + } + }; +}); + +test.afterEach(t => { + EleventyErrorHandler.isChalkEnabled = true; + EleventyErrorHandler.logger = null; +}); + +test("Disable chalk", t => { + EleventyErrorHandler.isChalkEnabled = false; + t.is(EleventyErrorHandler.isChalkEnabled, false); +}); + +test("Log a warning, error", t => { + EleventyErrorHandler.warn(new Error("Test warning"), "Hello"); + + let expected = `Hello: (more in DEBUG output) +> Test warning + +\`Error\` was thrown: + Error: Test warning`; + t.is(output.join("\n").substr(0, expected.length), expected); + + // normally this would be multiple tests but we do this garbage because + // tests run async and it doesnā€™t work with the static methods in + // EleventyErrorHandler + output = []; + + EleventyErrorHandler.error(new Error("Test error"), "Itā€™s me"); + + expected = `Itā€™s me: (more in DEBUG output) +> Test error + +\`Error\` was thrown: + Error: Test error`; + t.is(output.join("\n").substr(0, expected.length), expected); +}); diff --git a/test/EleventyExtensionMapTest.js b/test/EleventyExtensionMapTest.js new file mode 100644 index 000000000..4a3b1607c --- /dev/null +++ b/test/EleventyExtensionMapTest.js @@ -0,0 +1,143 @@ +import test from "ava"; +import EleventyExtensionMap from "../src/EleventyExtensionMap"; + +test("Empty formats", t => { + let map = new EleventyExtensionMap([]); + t.deepEqual(map.getGlobs("."), []); +}); +test("Single format", t => { + let map = new EleventyExtensionMap(["pug"]); + t.deepEqual(map.getGlobs("."), ["./**/*.pug"]); + t.deepEqual(map.getGlobs("src"), ["./src/**/*.pug"]); +}); +test("Multiple formats", t => { + let map = new EleventyExtensionMap(["njk", "pug"]); + t.deepEqual(map.getGlobs("."), ["./**/*.njk", "./**/*.pug"]); + t.deepEqual(map.getGlobs("src"), ["./src/**/*.njk", "./src/**/*.pug"]); +}); + +test("Invalid keys are filtered (no passthrough copy)", t => { + let map = new EleventyExtensionMap(["lksdjfjlsk"]); + map.config = { + passthroughFileCopy: false + }; + t.deepEqual(map.getGlobs("."), []); +}); + +test("Invalid keys are filtered (using passthrough copy)", t => { + let map = new EleventyExtensionMap(["lksdjfjlsk"]); + map.config = { + passthroughFileCopy: true + }; + t.deepEqual(map.getGlobs("."), ["./**/*.lksdjfjlsk"]); +}); + +test("Keys are mapped to lower case", t => { + let map = new EleventyExtensionMap(["PUG", "NJK"]); + t.deepEqual(map.getGlobs("."), ["./**/*.pug", "./**/*.njk"]); +}); + +test("Pruned globs", t => { + let map = new EleventyExtensionMap(["pug", "njk", "png"]); + t.deepEqual(map.getPrunedGlobs("."), ["./**/*.png"]); +}); + +test("Empty path for fileList", t => { + let map = new EleventyExtensionMap(["njk", "pug"]); + t.deepEqual(map.getFileList(), []); +}); + +test("fileList", t => { + let map = new EleventyExtensionMap(["njk", "pug"]); + t.deepEqual(map.getFileList("filename"), ["filename.njk", "filename.pug"]); +}); + +test("fileList with dir", t => { + let map = new EleventyExtensionMap(["njk", "pug"]); + t.deepEqual(map.getFileList("filename", "_includes"), [ + "_includes/filename.njk", + "_includes/filename.pug" + ]); +}); + +test("fileList with dir in path", t => { + let map = new EleventyExtensionMap(["njk", "pug"]); + t.deepEqual(map.getFileList("layouts/filename"), [ + "layouts/filename.njk", + "layouts/filename.pug" + ]); +}); + +test("fileList with dir in path and dir", t => { + let map = new EleventyExtensionMap(["njk", "pug"]); + t.deepEqual(map.getFileList("layouts/filename", "_includes"), [ + "_includes/layouts/filename.njk", + "_includes/layouts/filename.pug" + ]); +}); + +test("removeTemplateExtension", t => { + t.is( + EleventyExtensionMap.removeTemplateExtension("component.njk"), + "component" + ); + t.is( + EleventyExtensionMap.removeTemplateExtension("component.11ty.js"), + "component" + ); + + t.is(EleventyExtensionMap.removeTemplateExtension(""), ""); + t.is(EleventyExtensionMap.removeTemplateExtension("component"), "component"); + t.is( + EleventyExtensionMap.removeTemplateExtension("component.js"), + "component.js" + ); +}); + +test("getKey", t => { + t.is(EleventyExtensionMap.getKey("component.njk"), "njk"); + t.is(EleventyExtensionMap.getKey("component.11ty.js"), "11ty.js"); + t.is(EleventyExtensionMap.getKey("11ty.js"), "11ty.js"); + t.is(EleventyExtensionMap.getKey(".11ty.js"), "11ty.js"); + + t.is(EleventyExtensionMap.getKey("sample.md"), "md"); + + t.is(EleventyExtensionMap.getKey(""), undefined); + t.is(EleventyExtensionMap.getKey("js"), undefined); + t.is(EleventyExtensionMap.getKey("component"), undefined); + t.is(EleventyExtensionMap.getKey("component.js"), undefined); +}); + +test("Extension aliasing (one format key)", t => { + let map = new EleventyExtensionMap(["md"]); + map.config = { + templateExtensionAliases: { + markdown: "md", + nunjucks: "njk" // N/A to current format list + } + }; + t.deepEqual(map.getExtensionsFromKey("md"), ["md", "markdown"]); + t.deepEqual(map.getExtensionsFromKey("njk"), ["njk", "nunjucks"]); + + // should filter out N/A aliases + t.deepEqual(map.getGlobs("."), ["./**/*.md", "./**/*.markdown"]); +}); + +test("Extension aliasing (two format keys)", t => { + let map = new EleventyExtensionMap(["md", "njk"]); + map.config = { + templateExtensionAliases: { + markdown: "md", + nunjucks: "njk" + } + }; + t.deepEqual(map.getExtensionsFromKey("md"), ["md", "markdown"]); + t.deepEqual(map.getExtensionsFromKey("njk"), ["njk", "nunjucks"]); + + t.deepEqual(map.getGlobs("."), [ + "./**/*.md", + "./**/*.markdown", + "./**/*.njk", + "./**/*.nunjucks" + ]); +}); diff --git a/test/EleventyFilesTest.js b/test/EleventyFilesTest.js new file mode 100644 index 000000000..be6850ea3 --- /dev/null +++ b/test/EleventyFilesTest.js @@ -0,0 +1,597 @@ +import test from "ava"; +import fastglob from "fast-glob"; +import EleventyFiles from "../src/EleventyFiles"; +import TemplatePath from "../src/TemplatePath"; +import EleventyExtensionMap from "../src/EleventyExtensionMap"; +import TemplateRender from "../src/TemplateRender"; +import TemplatePassthroughManager from "../src/TemplatePassthroughManager"; + +test("getFiles", async t => { + let evf = new EleventyFiles( + "./test/stubs/writeTest", + "./test/stubs/_writeTestSite", + ["ejs", "md"] + ); + evf.init(); + + t.deepEqual(await evf.getFiles(), ["./test/stubs/writeTest/test.md"]); +}); + +test("getFiles (without 11ty.js)", async t => { + let evf = new EleventyFiles( + "./test/stubs/writeTestJS", + "./test/stubs/_writeTestJSSite", + ["ejs", "md"] + ); + evf.init(); + + t.deepEqual(await evf.getFiles(), []); +}); + +test("getFiles (with 11ty.js)", async t => { + let evf = new EleventyFiles( + "./test/stubs/writeTestJS", + "./test/stubs/_writeTestJSSite", + ["ejs", "md", "11ty.js"] + ); + evf.init(); + + t.deepEqual(await evf.getFiles(), ["./test/stubs/writeTestJS/test.11ty.js"]); +}); + +test("getFiles (with js, treated as passthrough copy)", async t => { + let evf = new EleventyFiles( + "./test/stubs/writeTestJS", + "./test/stubs/_writeTestJSSite", + ["ejs", "md", "js"] + ); + evf.init(); + + const files = await evf.getFiles(); + t.deepEqual( + files.sort(), + [ + "./test/stubs/writeTestJS/sample.js", + "./test/stubs/writeTestJS/test.11ty.js" + ].sort() + ); + + t.false(TemplateRender.hasEngine("./test/stubs/writeTestJS/sample.js")); + t.true(TemplateRender.hasEngine("./test/stubs/writeTestJS/test.11ty.js")); +}); + +test("getFiles (with case insensitivity)", async t => { + let evf = new EleventyFiles( + "./test/stubs/writeTestJS", + "./test/stubs/_writeTestJSSite", + ["JS"] + ); + evf.init(); + + t.deepEqual( + (await evf.getFiles()).sort(), + [ + "./test/stubs/writeTestJS/sample.js", + "./test/stubs/writeTestJS/test.11ty.js" + ].sort() + ); + t.false(TemplateRender.hasEngine("./test/stubs/writeTestJS/sample.js")); + t.true(TemplateRender.hasEngine("./test/stubs/writeTestJS/test.11ty.js")); +}); + +test("Mutually exclusive Input and Output dirs", async t => { + let evf = new EleventyFiles( + "./test/stubs/writeTest", + "./test/stubs/_writeTestSite", + ["ejs", "md"] + ); + evf.init(); + + let files = await fastglob(evf.getFileGlobs()); + t.is(evf.getRawFiles().length, 2); + t.true(files.length > 0); + t.is(files[0], "./test/stubs/writeTest/test.md"); +}); + +test("Single File Input (deep path)", async t => { + let evf = new EleventyFiles("./test/stubs/index.html", "./test/stubs/_site", [ + "ejs", + "md" + ]); + evf.init(); + + let files = await fastglob(evf.getFileGlobs()); + t.is(evf.getRawFiles().length, 1); + t.is(files.length, 1); + t.is(files[0], "./test/stubs/index.html"); +}); + +test("Single File Input (shallow path)", async t => { + let evf = new EleventyFiles("README.md", "./test/stubs/_site", ["md"]); + evf.init(); + + let globs = evf.getFileGlobs().filter(path => path !== "!./README.md"); + let files = await fastglob(globs); + t.is(evf.getRawFiles().length, 1); + t.is(files.length, 1); + t.is(files[0], "./README.md"); +}); + +test("Glob Input", async t => { + let evf = new EleventyFiles( + "./test/stubs/glob-pages/!(contact.md)", + "./test/stubs/_site", + ["md"] + ); + evf.init(); + + let globs = evf.getFileGlobs(); + let files = await fastglob(globs); + t.is(files.length, 2); + t.is(files[0], "./test/stubs/glob-pages/about.md"); + t.is(files[1], "./test/stubs/glob-pages/home.md"); +}); + +test(".eleventyignore parsing", t => { + let ignores = EleventyFiles.getFileIgnores("./test/stubs/.eleventyignore"); + t.is(ignores.length, 2); + t.is(ignores[0], "!./test/stubs/ignoredFolder/**"); + t.is(ignores[1], "!./test/stubs/ignoredFolder/ignored.md"); +}); + +test("Parse multiple .eleventyignores", t => { + let ignores = EleventyFiles.getFileIgnores([ + "./test/stubs/multiple-ignores/.eleventyignore", + "./test/stubs/multiple-ignores/subfolder/.eleventyignore" + ]); + t.is(ignores.length, 4); + // Note these folders must exist! + t.is(ignores[0], "!./test/stubs/multiple-ignores/ignoredFolder/**"); + t.is(ignores[1], "!./test/stubs/multiple-ignores/ignoredFolder/ignored.md"); + t.is( + ignores[2], + "!./test/stubs/multiple-ignores/subfolder/ignoredFolder2/**" + ); + t.is( + ignores[3], + "!./test/stubs/multiple-ignores/subfolder/ignoredFolder2/ignored2.md" + ); +}); + +test("defaults if passed file name does not exist", t => { + let ignores = EleventyFiles.getFileIgnores( + ".thisfiledoesnotexist", + "node_modules/**" + ); + t.truthy(ignores.length); + t.is(ignores[0], "!./node_modules/**"); +}); + +test(".eleventyignore files", async t => { + let evf = new EleventyFiles("test/stubs", "test/stubs/_site", ["ejs", "md"]); + evf.init(); + let ignoredFiles = await fastglob("test/stubs/ignoredFolder/*.md"); + t.is(ignoredFiles.length, 1); + + let files = await fastglob(evf.getFileGlobs()); + t.true(files.length > 0); + + t.is( + files.filter(file => { + return file.indexOf("./test/stubs/ignoredFolder") > -1; + }).length, + 0 + ); +}); + +/* .eleventyignore and .gitignore combos */ +test("Get ignores (no .eleventyignore no .gitignore)", t => { + let evf = new EleventyFiles( + "test/stubs/ignore1", + "test/stubs/ignore1/_site", + [] + ); + evf.init(); + + evf._setLocalPathRoot("./test/stubs/ignorelocalroot"); + + t.deepEqual(evf.getIgnores(), [ + "!./node_modules/**", + "!./test/stubs/ignorelocalroot/node_modules/**", + "!./test/stubs/ignore1/node_modules/**", + "!./test/stubs/ignorelocalroot/test.md", + "!./test/stubs/ignore1/_site/**" + ]); +}); + +test("Get ignores (no .eleventyignore)", t => { + let evf = new EleventyFiles( + "test/stubs/ignore2", + "test/stubs/ignore2/_site", + [] + ); + evf.init(); + evf._setLocalPathRoot("./test/stubs/ignorelocalroot"); + + t.deepEqual(evf.getIgnores(), [ + "!./test/stubs/ignore2/thisshouldnotexist12345", + "!./test/stubs/ignorelocalroot/test.md", + "!./test/stubs/ignore2/_site/**" + ]); +}); + +test("Get ignores (no .eleventyignore, using setUseGitIgnore(false))", t => { + let evf = new EleventyFiles( + "test/stubs/ignore2", + "test/stubs/ignore2/_site", + [] + ); + evf.init(); + + evf._setConfig({ + useGitIgnore: false, + dir: { + includes: "_includes" + } + }); + evf._setLocalPathRoot("./test/stubs/ignorelocalroot"); + + t.deepEqual(evf.getIgnores(), [ + "!./test/stubs/ignorelocalroot/test.md", + "!./test/stubs/ignore2/_site/**" + ]); +}); + +test("Get ignores (no .gitignore)", t => { + let evf = new EleventyFiles( + "test/stubs/ignore3", + "test/stubs/ignore3/_site", + [] + ); + evf.init(); + evf._setLocalPathRoot("./test/stubs/ignorelocalroot"); + + t.deepEqual(evf.getIgnores(), [ + "!./node_modules/**", + "!./test/stubs/ignorelocalroot/node_modules/**", + "!./test/stubs/ignore3/node_modules/**", + "!./test/stubs/ignorelocalroot/test.md", + "!./test/stubs/ignore3/ignoredFolder/**", + "!./test/stubs/ignore3/ignoredFolder/ignored.md", + "!./test/stubs/ignore3/_site/**" + ]); +}); + +test("Get ignores (both .eleventyignore and .gitignore)", t => { + let evf = new EleventyFiles( + "test/stubs/ignore4", + "test/stubs/ignore4/_site", + [] + ); + evf.init(); + evf._setLocalPathRoot("./test/stubs/ignorelocalroot"); + + t.deepEqual(evf.getIgnores(), [ + "!./test/stubs/ignore4/thisshouldnotexist12345", + "!./test/stubs/ignorelocalroot/test.md", + "!./test/stubs/ignore4/ignoredFolder/**", + "!./test/stubs/ignore4/ignoredFolder/ignored.md", + "!./test/stubs/ignore4/_site/**" + ]); +}); + +test("Get ignores (both .eleventyignore and .gitignore, using setUseGitIgnore(false))", t => { + let evf = new EleventyFiles( + "test/stubs/ignore4", + "test/stubs/ignore4/_site", + [] + ); + evf.init(); + + evf._setConfig({ + useGitIgnore: false, + dir: { + includes: "_includes" + } + }); + evf._setLocalPathRoot("./test/stubs/ignorelocalroot"); + + t.deepEqual(evf.getIgnores(), [ + "!./test/stubs/ignorelocalroot/test.md", + "!./test/stubs/ignore4/ignoredFolder/**", + "!./test/stubs/ignore4/ignoredFolder/ignored.md", + "!./test/stubs/ignore4/_site/**" + ]); +}); + +test("Get ignores (no .eleventyignore .gitignore exists but empty)", t => { + let evf = new EleventyFiles( + "test/stubs/ignore5", + "test/stubs/ignore5/_site", + [] + ); + evf.init(); + + evf._setLocalPathRoot("./test/stubs/ignorelocalroot"); + + t.deepEqual(evf.getIgnores(), [ + "!./node_modules/**", + "!./test/stubs/ignorelocalroot/node_modules/**", + "!./test/stubs/ignore5/node_modules/**", + "!./test/stubs/ignorelocalroot/test.md", + "!./test/stubs/ignore5/_site/**" + ]); +}); + +test("Get ignores (both .eleventyignore and .gitignore exists, but .gitignore is empty)", t => { + let evf = new EleventyFiles( + "test/stubs/ignore6", + "test/stubs/ignore6/_site", + [] + ); + evf.init(); + evf._setLocalPathRoot("./test/stubs/ignorelocalroot"); + + t.deepEqual(evf.getIgnores(), [ + "!./node_modules/**", + "!./test/stubs/ignorelocalroot/node_modules/**", + "!./test/stubs/ignore6/node_modules/**", + "!./test/stubs/ignorelocalroot/test.md", + "!./test/stubs/ignore6/ignoredFolder/**", + "!./test/stubs/ignore6/ignoredFolder/ignored.md", + "!./test/stubs/ignore6/_site/**" + ]); +}); + +test("Get ignores (no .eleventyignore .gitignore exists but has spaces inside)", t => { + let evf = new EleventyFiles( + "test/stubs/ignore7", + "test/stubs/ignore7/_site", + [] + ); + evf.init(); + + evf._setLocalPathRoot("./test/stubs/ignorelocalroot"); + + t.deepEqual(evf.getIgnores(), [ + "!./node_modules/**", + "!./test/stubs/ignorelocalroot/node_modules/**", + "!./test/stubs/ignore7/node_modules/**", + "!./test/stubs/ignorelocalroot/test.md", + "!./test/stubs/ignore7/_site/**" + ]); +}); + +test("Get ignores (both .eleventyignore and .gitignore exists, but .gitignore has spaces inside)", t => { + let evf = new EleventyFiles( + "test/stubs/ignore8", + "test/stubs/ignore8/_site", + [] + ); + evf.init(); + evf._setLocalPathRoot("./test/stubs/ignorelocalroot"); + + t.deepEqual(evf.getIgnores(), [ + "!./node_modules/**", + "!./test/stubs/ignorelocalroot/node_modules/**", + "!./test/stubs/ignore8/node_modules/**", + "!./test/stubs/ignorelocalroot/test.md", + "!./test/stubs/ignore8/ignoredFolder/**", + "!./test/stubs/ignore8/ignoredFolder/ignored.md", + "!./test/stubs/ignore8/_site/**" + ]); +}); +/* End .eleventyignore and .gitignore combos */ + +test("getDataDir", t => { + let evf = new EleventyFiles(".", "_site", []); + evf.init(); + t.is(evf.getDataDir(), "_data"); +}); + +test("getDataDir subdir", t => { + let evf = new EleventyFiles("test/stubs", "test/stubs/_site", []); + evf.init(); + t.is(evf.getDataDir(), "test/stubs/_data"); +}); + +test("Include and Data Dirs", t => { + let evf = new EleventyFiles("test/stubs", "test/stubs/_site", []); + evf.init(); + + t.deepEqual(evf.getIncludesAndDataDirs(), [ + "./test/stubs/_includes/**", + "./test/stubs/_data/**" + ]); +}); + +test("Ignore Include and Data Dirs", t => { + let evf = new EleventyFiles("test/stubs", "test/stubs/_site", []); + evf.init(); + + t.deepEqual(evf.getTemplateIgnores(), [ + "!./test/stubs/_includes/**", + "!./test/stubs/_data/**" + ]); +}); + +test("Input to 'src' and empty includes dir (issue #403)", t => { + let evf = new EleventyFiles("src", "src/_site", ["md", "liquid", "html"]); + evf._setConfig({ + useGitIgnore: false, + eleventyignoreOverride: "!./src/_includes/**", + dir: { + input: ".", + output: "_site", + includes: "", + data: "_data" + } + }); + evf.init(); + + t.deepEqual(evf.getFileGlobs(), [ + "./src/**/*.md", + "./src/**/*.liquid", + "./src/**/*.html", + "!./src/_includes/**", + "!./src/_site/**", + "!./src/_data/**" + ]); +}); + +test("Bad expected output, this indicates a bug upstream in a dependency. Input to 'src' and empty includes dir (issue #403, full paths in eleventyignore)", async t => { + let evf = new EleventyFiles("test/stubs-403", "test/stubs-403/_site", [ + "liquid" + ]); + evf._setConfig({ + useGitIgnore: false, + eleventyignoreOverride: + "!" + TemplatePath.absolutePath("test/stubs-403/_includes") + "/**", + dir: { + input: "test/stubs-403", + output: "_site", + includes: "", + data: false + } + }); + evf.init(); + + t.deepEqual(await evf.getFiles(), [ + "./test/stubs-403/template.liquid", + // This is bad, because it uses an absolutePath above. it should be excluded + "./test/stubs-403/_includes/include.liquid" + ]); +}); + +test("Workaround for Bad expected output, this indicates a bug upstream in a dependency. Input to 'src' and empty includes dir (issue #403, full paths in eleventyignore)", async t => { + let evf = new EleventyFiles("test/stubs-403", "test/stubs-403/_site", [ + "liquid" + ]); + evf._setConfig({ + useGitIgnore: false, + eleventyignoreOverride: "!./test/stubs-403/_includes/**", + dir: { + input: "test/stubs-403", + output: "_site", + includes: "", + data: false + } + }); + evf.init(); + + t.deepEqual(await evf.getFiles(), ["./test/stubs-403/template.liquid"]); +}); + +test("Issue #403: all .eleventyignores should be relative paths not absolute paths", async t => { + let evf = new EleventyFiles("test/stubs-403", "test/stubs-403/_site", [ + "liquid" + ]); + evf._setConfig({ + useGitIgnore: false, + dir: { + input: "test/stubs-403", + output: "_site", + includes: "", + data: false + } + }); + evf.init(); + + let globs = await evf.getFileGlobs(); + t.is( + globs.filter(glob => { + return glob.indexOf(TemplatePath.absolutePath()) > -1; + }).length, + 0 + ); +}); + +test("Glob Watcher Files", async t => { + let evf = new EleventyFiles("test/stubs", "test/stubs/_site", ["njk"]); + evf.init(); + + t.deepEqual(evf.getGlobWatcherFiles(), [ + "./test/stubs/**/*.njk", + "./test/stubs/_includes/**", + "./test/stubs/_data/**" + ]); +}); + +test("Glob Watcher Files with File Extension Passthroughs", async t => { + let evf = new EleventyFiles("test/stubs", "test/stubs/_site", ["njk", "png"]); + evf.init(); + + t.deepEqual(evf.getGlobWatcherFiles(), [ + "./test/stubs/**/*.njk", + "./test/stubs/**/*.png", + "./test/stubs/_includes/**", + "./test/stubs/_data/**" + ]); +}); + +test("Glob Watcher Files with Config Passthroughs (one template format)", async t => { + let evf = new EleventyFiles("test/stubs", "test/stubs/_site", ["njk"]); + evf.init(); + + let mgr = new TemplatePassthroughManager(); + mgr.setInputDir("test/stubs"); + mgr.setOutputDir("test/stubs/_site"); + mgr.setConfig({ + passthroughFileCopy: true, + passthroughCopies: { + "test/stubs/img/": true + } + }); + evf.setPassthroughManager(mgr); + + t.deepEqual(evf.getGlobWatcherFiles(), [ + "./test/stubs/**/*.njk", + "./test/stubs/_includes/**", + "./test/stubs/_data/**", + "./test/stubs/img/**" + ]); +}); + +test("Glob Watcher Files with Config Passthroughs (no template formats)", async t => { + let evf = new EleventyFiles("test/stubs", "test/stubs/_site", []); + evf.init(); + + t.deepEqual(await evf.getGlobWatcherTemplateDataFiles(), [ + "./test/stubs/**/*.json", + "./test/stubs/**/*.11tydata.js" + ]); +}); + +test("Glob Watcher Files with passthroughAll", async t => { + let evf = new EleventyFiles("test/stubs", "test/stubs/_site", [], true); + evf.init(); + + t.is((await evf.getFileGlobs())[0], "./test/stubs/**"); +}); + +test("File extension aliasing", async t => { + let map = new EleventyExtensionMap(["md"]); + map.config = { + templateExtensionAliases: { + markdown: "md" + } + }; + + let evf = new EleventyFiles( + "./test/stubs/writeTestMarkdown", + "./test/stubs/_writeTestMarkdownSite", + ["md"] + ); + evf._setExtensionMap(map); + evf.init(); + + const files = await evf.getFiles(); + + t.deepEqual( + files.sort(), + [ + "./test/stubs/writeTestMarkdown/sample.md", + "./test/stubs/writeTestMarkdown/sample2.markdown" + ].sort() + ); +}); diff --git a/test/EleventyServeTest.js b/test/EleventyServeTest.js new file mode 100644 index 000000000..3fedc9002 --- /dev/null +++ b/test/EleventyServeTest.js @@ -0,0 +1,80 @@ +import test from "ava"; +import EleventyServe from "../src/EleventyServe"; + +test("Constructor", t => { + let es = new EleventyServe(); + t.is(es.getPathPrefix(), "/"); +}); + +test("Directories", t => { + let es = new EleventyServe(); + es.setOutputDir("_site"); + t.is(es.getRedirectDir("test"), "_site/test"); + t.is(es.getRedirectFilename("test"), "_site/test/index.html"); +}); + +test("Get Options", t => { + let es = new EleventyServe(); + es.config = { + pathPrefix: "/" + }; + es.setOutputDir("_site"); + + t.deepEqual(es.getOptions(), { + ignore: ["node_modules"], + index: "index.html", + notify: false, + open: false, + port: 8080, + server: { + baseDir: "_site" + }, + watch: false + }); +}); + +test("Get Options (with a pathPrefix)", t => { + let es = new EleventyServe(); + es.config = { + pathPrefix: "/web/" + }; + es.setOutputDir("_site"); + + t.deepEqual(es.getOptions(), { + ignore: ["node_modules"], + index: "index.html", + notify: false, + open: false, + port: 8080, + server: { + baseDir: "_site/_eleventy_redirect", + routes: { + "/web/": "_site" + } + }, + watch: false + }); +}); + +test("Get Options (override in config)", t => { + let es = new EleventyServe(); + es.config = { + pathPrefix: "/", + browserSyncConfig: { + notify: true + } + }; + es.setOutputDir("_site"); + + t.deepEqual(es.getOptions(), { + ignore: ["node_modules"], + index: "index.html", + notify: true, + open: false, + port: 8080, + server: { + baseDir: "_site" + }, + watch: false + }); +}); diff --git a/test/EleventyTest.js b/test/EleventyTest.js index c746586b0..19920cdaf 100644 --- a/test/EleventyTest.js +++ b/test/EleventyTest.js @@ -1,16 +1,17 @@ import test from "ava"; import Eleventy from "../src/Eleventy"; -import TemplateConfig from "../src/TemplateConfig"; +import EleventyWatchTargets from "../src/EleventyWatchTargets"; +import templateConfig from "../src/Config"; -let cfg = TemplateConfig.getDefaultConfig(); +const config = templateConfig.getConfig(); test("Eleventy, defaults inherit from config", async t => { let elev = new Eleventy(); t.truthy(elev.input); - t.truthy(elev.output); - t.is(elev.input, cfg.dir.input); - t.is(elev.output, cfg.dir.output); + t.truthy(elev.outputDir); + t.is(elev.input, config.dir.input); + t.is(elev.outputDir, config.dir.output); }); test("Eleventy, get version", t => { @@ -36,9 +37,93 @@ test("Eleventy set input/output", async t => { let elev = new Eleventy("./test/stubs", "./test/stubs/_site"); t.is(elev.input, "./test/stubs"); - t.is(elev.output, "./test/stubs/_site"); + t.is(elev.outputDir, "./test/stubs/_site"); await elev.init(); - t.truthy(elev.data); + t.truthy(elev.templateData); t.truthy(elev.writer); }); + +test("Eleventy file watching", async t => { + let elev = new Eleventy("./test/stubs", "./test/stubs/_site"); + elev.setFormats("njk"); + + await elev.init(); + await elev.initWatch(); + t.deepEqual(await elev.getWatchedFiles(), [ + "./test/stubs/**/*.njk", + "./test/stubs/_includes/**", + "./test/stubs/_data/**", + "./.eleventy.js", + "./test/stubs/**/*.json", + "./test/stubs/**/*.11tydata.js", + "./test/stubs/deps/dep1.js", + "./test/stubs/deps/dep2.js" + ]); +}); + +test("Eleventy file watching (no JS dependencies)", async t => { + let elev = new Eleventy("./test/stubs", "./test/stubs/_site"); + elev.setFormats("njk"); + + let wt = new EleventyWatchTargets(); + wt.watchJavaScriptDependencies = false; + elev.setWatchTargets(wt); + + await elev.init(); + await elev.initWatch(); + t.deepEqual(await elev.getWatchedFiles(), [ + "./test/stubs/**/*.njk", + "./test/stubs/_includes/**", + "./test/stubs/_data/**", + "./.eleventy.js", + "./test/stubs/**/*.json", + "./test/stubs/**/*.11tydata.js" + ]); +}); + +test("Eleventy set input/output, one file input", async t => { + let elev = new Eleventy("./test/stubs/index.html", "./test/stubs/_site"); + + t.is(elev.input, "./test/stubs/index.html"); + t.is(elev.inputDir, "./test/stubs"); + t.is(elev.outputDir, "./test/stubs/_site"); +}); + +test("Eleventy set input/output, one file input root dir", async t => { + let elev = new Eleventy("./README.md", "./test/stubs/_site"); + + t.is(elev.input, "./README.md"); + t.is(elev.inputDir, "."); + t.is(elev.outputDir, "./test/stubs/_site"); +}); + +test("Eleventy set input/output, one file input root dir without leading dot/slash", async t => { + let elev = new Eleventy("README.md", "./test/stubs/_site"); + + t.is(elev.input, "README.md"); + t.is(elev.inputDir, "."); + t.is(elev.outputDir, "./test/stubs/_site"); +}); + +test("Eleventy set input/output, one file input exitCode", async t => { + let previousExitCode = process.exitCode; + let elev = new Eleventy( + "./test/stubs/exitCode/failure.njk", + "./test/stubs/exitCode/_site" + ); + + // TODO make this output quieter + elev.setLogger({ + log: function() {}, + warn: function() {}, + error: function() {} + }); + + await elev.init(); + await elev.write(); + + t.is(process.exitCode, 1); + + process.exitCode = previousExitCode; +}); diff --git a/test/EleventyWatchTargetsTest.js b/test/EleventyWatchTargetsTest.js new file mode 100644 index 000000000..ea5ee01cd --- /dev/null +++ b/test/EleventyWatchTargetsTest.js @@ -0,0 +1,98 @@ +import test from "ava"; +import EleventyWatchTargets from "../src/EleventyWatchTargets"; + +test("Basic", t => { + let targets = new EleventyWatchTargets(); + t.deepEqual(targets.getTargets(), []); + + targets.add(".eleventy.js"); + t.deepEqual(targets.getTargets(), ["./.eleventy.js"]); +}); + +test("Removes duplicates", t => { + let targets = new EleventyWatchTargets(); + + targets.add(".eleventy.js"); + targets.add("./.eleventy.js"); + t.deepEqual(targets.getTargets(), ["./.eleventy.js"]); +}); + +test("Add array", t => { + let targets = new EleventyWatchTargets(); + + targets.add([".eleventy.js", "b.js"]); + targets.add(["b.js", "c.js"]); + t.deepEqual(targets.getTargets(), ["./.eleventy.js", "./b.js", "./c.js"]); +}); + +test("JavaScript get dependencies", t => { + let targets = new EleventyWatchTargets(); + t.deepEqual( + targets.getJavaScriptDependenciesFromList(["./test/stubs/config-deps.js"]), + ["./test/stubs/config-deps-upstream.js"] + ); +}); + +test("JavaScript addDependencies", t => { + let targets = new EleventyWatchTargets(); + targets.addDependencies("./test/stubs/config-deps.js"); + t.deepEqual(targets.getTargets(), ["./test/stubs/config-deps-upstream.js"]); +}); + +test("JavaScript addDependencies (one file has two dependencies)", t => { + let targets = new EleventyWatchTargets(); + targets.addDependencies("./test/stubs/dependencies/two-deps.11ty.js"); + t.deepEqual(targets.getTargets(), [ + "./test/stubs/dependencies/dep1.js", + "./test/stubs/dependencies/dep2.js" + ]); +}); + +test("JavaScript addDependencies (skip JS deps)", t => { + let targets = new EleventyWatchTargets(); + targets.watchJavaScriptDependencies = false; + targets.addDependencies("./test/stubs/dependencies/two-deps.11ty.js"); + t.deepEqual(targets.getTargets(), []); +}); + +test("JavaScript addDependencies with a filter", t => { + let targets = new EleventyWatchTargets(); + targets.addDependencies("./test/stubs/config-deps.js", function(path) { + return path.indexOf("./test/stubs/") === -1; + }); + t.deepEqual(targets.getTargets(), []); +}); + +test("add, addDependencies falsy values are filtered", t => { + let targets = new EleventyWatchTargets(); + targets.add(""); + targets.addDependencies(""); + t.deepEqual(targets.getTargets(), []); +}); + +test("add, addDependencies file does not exist", t => { + let targets = new EleventyWatchTargets(); + targets.add("./.eleventy-notfound.js"); // does not exist + targets.addDependencies("./.eleventy-notfound.js"); // does not exist + t.deepEqual(targets.getTargets(), ["./.eleventy-notfound.js"]); +}); + +test("getNewTargetsSinceLastReset", t => { + let targets = new EleventyWatchTargets(); + targets.add("./.eleventy-notfound.js"); // does not exist + t.deepEqual(targets.getNewTargetsSinceLastReset(), [ + "./.eleventy-notfound.js" + ]); + t.deepEqual(targets.getNewTargetsSinceLastReset(), [ + "./.eleventy-notfound.js" + ]); + + targets.reset(); + targets.add("./.eleventy-notfound2.js"); + t.deepEqual(targets.getNewTargetsSinceLastReset(), [ + "./.eleventy-notfound2.js" + ]); + + targets.reset(); + t.deepEqual(targets.getNewTargetsSinceLastReset(), []); +}); diff --git a/test/MergeTest.js b/test/MergeTest.js new file mode 100644 index 000000000..f5e99fdd8 --- /dev/null +++ b/test/MergeTest.js @@ -0,0 +1,147 @@ +import test from "ava"; +import Merge from "../src/Util/Merge"; + +test("Shallow Merge", t => { + t.deepEqual(Merge({}, {}), {}); + t.deepEqual(Merge({ a: 1 }, { a: 2 }), { a: 2 }); + t.deepEqual(Merge({ a: 1 }, { a: 2 }, { a: 3 }), { a: 3 }); + + t.deepEqual(Merge({ a: 1 }, { b: 1 }), { a: 1, b: 1 }); + t.deepEqual(Merge({ a: 1 }, { b: 1 }, { c: 1 }), { a: 1, b: 1, c: 1 }); + + t.deepEqual(Merge({ a: [1] }, { a: [2] }), { a: [1, 2] }); +}); + +test("Invalid", t => { + t.deepEqual(Merge({}, 1), {}); + t.deepEqual(Merge({}, [1]), {}); + t.deepEqual(Merge({}, "string"), {}); +}); + +test("Deep", t => { + t.deepEqual(Merge({ a: { b: 1 } }, { a: { c: 1 } }), { a: { b: 1, c: 1 } }); +}); + +test("Deep, override: prefix", t => { + t.deepEqual(Merge({ a: { b: [1, 2] } }, { a: { b: [3, 4] } }), { + a: { b: [1, 2, 3, 4] } + }); + t.deepEqual(Merge({ a: [1] }, { a: [2] }), { a: [1, 2] }); + t.deepEqual(Merge({ a: [1] }, { "override:a": [2] }), { a: [2] }); + t.deepEqual(Merge({ a: { b: [1, 2] } }, { a: { "override:b": [3, 4] } }), { + a: { b: [3, 4] } + }); +}); + +test("Deep, override: prefix at root", t => { + t.deepEqual(Merge({ "override:a": [1] }, { a: [2] }), { a: [1, 2] }); +}); + +test("Deep, override: prefix at other placements", t => { + t.deepEqual( + Merge( + { + a: { + a: [1] + } + }, + { + a: { + a: [2] + } + } + ), + { + a: { + a: [1, 2] + } + } + ); + + t.deepEqual( + Merge( + { + a: { + a: [1] + } + }, + { + a: { + "override:a": [2] + } + } + ), + { + a: { + a: [2] + } + } + ); + + t.deepEqual( + Merge( + { + "override:a": { + a: [1] + } + }, + { + a: { + a: [2] + } + } + ), + { + a: { + a: [1, 2] + } + } + ); + + t.deepEqual( + Merge( + { + a: { + a: [1], + b: [1] + } + }, + { + "override:a": { + a: [2] + } + } + ), + { + a: { + a: [2] + } + } + ); + + t.deepEqual( + Merge( + { + a: { + a: { + a: [1] + } + } + }, + { + a: { + "override:a": { + a: [2] + } + } + } + ), + { + a: { + a: { + a: [2] + } + } + } + ); +}); diff --git a/test/PaginationTest.js b/test/PaginationTest.js index a107738bb..d8349485e 100644 --- a/test/PaginationTest.js +++ b/test/PaginationTest.js @@ -14,7 +14,7 @@ test("No data passed to pagination", async t => { paging.setTemplate(tmpl); t.is(paging.getPagedItems().length, 0); - t.is((await paging.getTemplates()).length, 0); + t.is((await paging.getPageTemplates()).length, 0); }); test("No pagination", async t => { @@ -29,8 +29,9 @@ test("No pagination", async t => { paging.setTemplate(tmpl); t.falsy(data.pagination); + t.is(paging.getPageCount(), 0); t.is(paging.getPagedItems().length, 0); - t.is((await paging.getTemplates()).length, 0); + t.is((await paging.getPageTemplates()).length, 0); }); test("Pagination enabled in frontmatter", async t => { @@ -49,6 +50,7 @@ test("Pagination enabled in frontmatter", async t => { t.truthy(data.pagination); t.is(data.pagination.data, "testdata.sub"); + t.is(paging.getPageCount(), 2); t.is(data.pagination.size, 4); }); @@ -59,9 +61,11 @@ test("Resolve paged data in frontmatter", async t => { "./dist" ); - let paging = new Pagination(await tmpl.getData()); + let data = await tmpl.getData(); + let paging = new Pagination(data); paging.setTemplate(tmpl); t.is(paging._resolveItems().length, 8); + t.is(paging.getPageCount(), 2); t.is(paging.getPagedItems().length, 2); }); @@ -75,7 +79,8 @@ test("Paginate data in frontmatter", async t => { let data = await tmpl.getData(); let paging = new Pagination(data); paging.setTemplate(tmpl); - let pages = await paging.getTemplates(); + t.is(paging.getPageCount(), 2); + let pages = await paging.getPageTemplates(); t.is(pages.length, 2); t.is( @@ -98,7 +103,7 @@ test("Paginate data in frontmatter", async t => { }); test("Paginate external data file", async t => { - let dataObj = new TemplateData(); + let dataObj = new TemplateData("./test/stubs/"); await dataObj.cacheData(); let tmpl = new Template( @@ -115,7 +120,8 @@ test("Paginate external data file", async t => { let paging = new Pagination(data); paging.setTemplate(tmpl); - let pages = await paging.getTemplates(); + t.is(paging.getPageCount(), 2); + let pages = await paging.getPageTemplates(); t.is(pages.length, 2); t.is(await pages[0].getOutputPath(), "./dist/paged/index.html"); @@ -148,7 +154,8 @@ test("Permalink with pagination variables", async t => { let data = await tmpl.getData(); let paging = new Pagination(data); paging.setTemplate(tmpl); - let pages = await paging.getTemplates(); + t.is(paging.getPageCount(), 2); + let pages = await paging.getPageTemplates(); t.is( await pages[0].getOutputPath(), @@ -170,19 +177,30 @@ test("Permalink with pagination variables (numeric)", async t => { let data = await tmpl.getData(); let paging = new Pagination(data); paging.setTemplate(tmpl); - let pages = await paging.getTemplates(); + t.is(paging.getPageCount(), 2); + let pages = await paging.getPageTemplates(); let page0Data = await pages[0].getData(); + t.truthy(page0Data.pagination.firstPageLink); + t.truthy(page0Data.pagination.firstPageHref); + t.truthy(page0Data.pagination.lastPageLink); + t.truthy(page0Data.pagination.lastPageHref); t.is(await pages[0].getOutputPath(), "./dist/paged/page-0/index.html"); t.falsy(page0Data.pagination.previousPageLink); t.is(page0Data.pagination.nextPageLink, "/paged/page-1/index.html"); + t.is(page0Data.pagination.nextPageHref, "/paged/page-1/"); t.is(page0Data.pagination.pageLinks.length, 2); + t.is(page0Data.pagination.links.length, 2); + t.is(page0Data.pagination.hrefs.length, 2); let page1Data = await pages[1].getData(); t.is(await pages[1].getOutputPath(), "./dist/paged/page-1/index.html"); t.is(page1Data.pagination.previousPageLink, "/paged/page-0/index.html"); + t.is(page1Data.pagination.previousPageHref, "/paged/page-0/"); t.falsy(page1Data.pagination.nextPageLink); t.is(page1Data.pagination.pageLinks.length, 2); + t.is(page1Data.pagination.links.length, 2); + t.is(page1Data.pagination.hrefs.length, 2); }); test("Permalink with pagination variables (numeric, one indexed)", async t => { @@ -195,19 +213,67 @@ test("Permalink with pagination variables (numeric, one indexed)", async t => { let data = await tmpl.getData(); let paging = new Pagination(data); paging.setTemplate(tmpl); - let pages = await paging.getTemplates(); + let pages = await paging.getPageTemplates(); let page0Data = await pages[0].getData(); t.is(await pages[0].getOutputPath(), "./dist/paged/page-1/index.html"); t.falsy(page0Data.pagination.previousPageLink); t.is(page0Data.pagination.nextPageLink, "/paged/page-2/index.html"); + t.is(page0Data.pagination.nextPageHref, "/paged/page-2/"); t.is(page0Data.pagination.pageLinks.length, 2); + t.is(page0Data.pagination.links.length, 2); + t.is(page0Data.pagination.hrefs.length, 2); let page1Data = await pages[1].getData(); t.is(await pages[1].getOutputPath(), "./dist/paged/page-2/index.html"); t.is(page1Data.pagination.previousPageLink, "/paged/page-1/index.html"); + t.is(page1Data.pagination.previousPageHref, "/paged/page-1/"); t.falsy(page1Data.pagination.nextPageLink); t.is(page1Data.pagination.pageLinks.length, 2); + t.is(page1Data.pagination.links.length, 2); + t.is(page1Data.pagination.hrefs.length, 2); +}); + +test("Permalink first and last page link with pagination variables (numeric)", async t => { + let tmpl = new Template( + "./test/stubs/paged/pagedpermalinknumeric.njk", + "./test/stubs/", + "./dist" + ); + + let data = await tmpl.getData(); + let paging = new Pagination(data); + paging.setTemplate(tmpl); + let pages = await paging.getPageTemplates(); + + let page0Data = await pages[0].getData(); + t.is(page0Data.pagination.firstPageLink, "/paged/page-0/index.html"); + t.is(page0Data.pagination.lastPageLink, "/paged/page-1/index.html"); + + let page1Data = await pages[1].getData(); + t.is(page1Data.pagination.firstPageLink, "/paged/page-0/index.html"); + t.is(page1Data.pagination.lastPageLink, "/paged/page-1/index.html"); +}); + +test("Permalink first and last page link with pagination variables (numeric, one indexed)", async t => { + let tmpl = new Template( + "./test/stubs/paged/pagedpermalinknumericoneindexed.njk", + "./test/stubs/", + "./dist" + ); + + let data = await tmpl.getData(); + let paging = new Pagination(data); + paging.setTemplate(tmpl); + let pages = await paging.getPageTemplates(); + + let page0Data = await pages[0].getData(); + t.is(page0Data.pagination.firstPageLink, "/paged/page-1/index.html"); + t.is(page0Data.pagination.lastPageLink, "/paged/page-2/index.html"); + + let page1Data = await pages[1].getData(); + t.is(page0Data.pagination.firstPageLink, "/paged/page-1/index.html"); + t.is(page0Data.pagination.lastPageLink, "/paged/page-2/index.html"); }); test("Alias to page data", async t => { @@ -220,7 +286,7 @@ test("Alias to page data", async t => { let data = await tmpl.getData(); let paging = new Pagination(data); paging.setTemplate(tmpl); - let pages = await paging.getTemplates(); + let pages = await paging.getPageTemplates(); t.is(await pages[0].getOutputPath(), "./dist/pagedalias/item1/index.html"); t.is(await pages[1].getOutputPath(), "./dist/pagedalias/item2/index.html"); @@ -239,7 +305,7 @@ test("Alias to page data (size 2)", async t => { let data = await tmpl.getData(); let paging = new Pagination(data); paging.setTemplate(tmpl); - let pages = await paging.getTemplates(); + let pages = await paging.getPageTemplates(); t.is(await pages[0].getOutputPath(), "./dist/pagedalias/item1/index.html"); t.is(await pages[1].getOutputPath(), "./dist/pagedalias/item3/index.html"); @@ -247,3 +313,345 @@ test("Alias to page data (size 2)", async t => { t.is((await pages[0].render()).trim(), "item1"); t.is((await pages[1].render()).trim(), "item3"); }); + +test("Permalink with pagination variables (and an if statement, nunjucks)", async t => { + let tmpl = new Template( + "./test/stubs/paged/pagedpermalinkif.njk", + "./test/stubs/", + "./dist" + ); + + let data = await tmpl.getData(); + let paging = new Pagination(data); + paging.setTemplate(tmpl); + let pages = await paging.getPageTemplates(); + + t.is(await pages[0].getOutputPath(), "./dist/paged/index.html"); + t.is(await pages[1].getOutputPath(), "./dist/paged/page-1/index.html"); +}); + +test("Permalink with pagination variables (and an if statement, liquid)", async t => { + let tmpl = new Template( + "./test/stubs/paged/pagedpermalinkif.liquid", + "./test/stubs/", + "./dist" + ); + + let data = await tmpl.getData(); + let paging = new Pagination(data); + paging.setTemplate(tmpl); + let pages = await paging.getPageTemplates(); + + t.is(await pages[0].getOutputPath(), "./dist/paged/index.html"); + t.is(await pages[1].getOutputPath(), "./dist/paged/page-1/index.html"); +}); + +test("Template with Pagination, getRenderedTemplates", async t => { + let tmpl = new Template( + "./test/stubs/paged/pagedpermalinkif.njk", + "./test/stubs/", + "./dist" + ); + + let outputPath = await tmpl.getOutputPath(); + let data = await tmpl.getData(); + t.is(outputPath, "./dist/paged/index.html"); + + let templates = await tmpl.getRenderedTemplates(data); + t.is(templates.length, 2); +}); + +test("Issue 135", async t => { + let dataObj = new TemplateData("./test/stubs/"); + await dataObj.cacheData(); + + let tmpl = new Template( + "./test/stubs/issue-135/template.njk", + "./test/stubs/", + "./dist", + dataObj + ); + + let data = await tmpl.getData(); + let templates = await tmpl.getRenderedTemplates(data); + t.is(data.articles.length, 1); + t.is(data.articles[0].title, "Do you even paginate bro?"); + t.is( + await templates[0].outputPath, + "./dist/blog/do-you-even-paginate-bro/index.html" + ); + + let paging = new Pagination(data); + paging.setTemplate(tmpl); + let pages = await paging.getPageTemplates(); + t.is(pages.length, 1); + t.is( + await pages[0].getOutputPath(), + "./dist/blog/do-you-even-paginate-bro/index.html" + ); +}); + +test("Template with Pagination, getTemplates has page variables set", async t => { + let tmpl = new Template( + "./test/stubs/paged/pagedpermalinkif.njk", + "./test/stubs/", + "./dist" + ); + + let data = await tmpl.getData(); + let templates = await tmpl.getTemplates(data); + t.is(templates[0].data.page.url, "/paged/"); + t.is(templates[0].data.page.outputPath, "./dist/paged/index.html"); + + t.is(templates[1].data.page.url, "/paged/page-1/"); + t.is(templates[1].data.page.outputPath, "./dist/paged/page-1/index.html"); +}); + +test("Template with Pagination, getRenderedTemplates has page variables set", async t => { + let tmpl = new Template( + "./test/stubs/paged/pagedpermalinkif.njk", + "./test/stubs/", + "./dist" + ); + + let data = await tmpl.getData(); + let templates = await tmpl.getRenderedTemplates(data); + t.is(templates[0].data.page.url, "/paged/"); + t.is(templates[0].data.page.outputPath, "./dist/paged/index.html"); + + t.is(templates[1].data.page.url, "/paged/page-1/"); + t.is(templates[1].data.page.outputPath, "./dist/paged/page-1/index.html"); +}); + +test("Page over an object (use keys)", async t => { + let tmpl = new Template( + "./test/stubs/paged/pagedobject.njk", + "./test/stubs/", + "./dist" + ); + + let data = await tmpl.getData(); + let paging = new Pagination(data); + paging.setTemplate(tmpl); + let pages = await paging.getPageTemplates(); + t.is(pages.length, 3); + + t.is(await pages[0].getOutputPath(), "./dist/paged/pagedobject/index.html"); + t.is( + (await pages[0].render()).trim(), + "
  1. item1
  2. item2
  3. item3
  4. item4
" + ); + + t.is(await pages[1].getOutputPath(), "./dist/paged/pagedobject/1/index.html"); + t.is( + (await pages[1].render()).trim(), + "
  1. item5
  2. item6
  3. item7
  4. item8
" + ); +}); + +test("Page over an object (use values)", async t => { + let tmpl = new Template( + "./test/stubs/paged/pagedobjectvalues.njk", + "./test/stubs/", + "./dist" + ); + + let data = await tmpl.getData(); + let paging = new Pagination(data); + paging.setTemplate(tmpl); + let pages = await paging.getPageTemplates(); + t.is(pages.length, 3); + + t.is( + await pages[0].getOutputPath(), + "./dist/paged/pagedobjectvalues/index.html" + ); + t.is( + (await pages[0].render()).trim(), + "
  1. itemvalue1
  2. itemvalue2
  3. itemvalue3
  4. itemvalue4
" + ); + + t.is( + await pages[1].getOutputPath(), + "./dist/paged/pagedobjectvalues/1/index.html" + ); + t.is( + (await pages[1].render()).trim(), + "
  1. itemvalue5
  2. itemvalue6
  3. itemvalue7
  4. itemvalue8
" + ); +}); + +test("Page over an object (filtered, array)", async t => { + let tmpl = new Template( + "./test/stubs/paged/pagedobjectfilterarray.njk", + "./test/stubs/", + "./dist" + ); + + let data = await tmpl.getData(); + let paging = new Pagination(data); + paging.setTemplate(tmpl); + let pages = await paging.getPageTemplates(); + t.is(pages.length, 2); + + t.is( + (await pages[0].render()).trim(), + "
  1. item1
  2. item2
  3. item3
  4. item5
" + ); + + t.is( + (await pages[1].render()).trim(), + "
  1. item6
  2. item7
  3. item8
  4. item9
" + ); +}); + +test("Page over an object (filtered, string)", async t => { + let tmpl = new Template( + "./test/stubs/paged/pagedobjectfilterstring.njk", + "./test/stubs/", + "./dist" + ); + + let data = await tmpl.getData(); + let paging = new Pagination(data); + paging.setTemplate(tmpl); + let pages = await paging.getPageTemplates(); + t.is(pages.length, 2); + + t.is( + (await pages[0].render()).trim(), + "
  1. item1
  2. item2
  3. item3
  4. item5
" + ); + + t.is( + (await pages[1].render()).trim(), + "
  1. item6
  2. item7
  3. item8
  4. item9
" + ); +}); + +test("Pagination with deep data merge #147", async t => { + let tmpl = new Template( + "./test/stubs/paged/pagedinlinedata.njk", + "./test/stubs/", + "./dist" + ); + tmpl.config = { + keys: { + layout: "layout" + }, + dataDeepMerge: true + }; + + let data = await tmpl.getData(); + let paging = new Pagination(data); + paging.setTemplate(tmpl); + let pages = await paging.getPageTemplates(); + t.is(pages.length, 2); + + t.is( + await pages[0].getOutputPath(), + "./dist/paged/pagedinlinedata/index.html" + ); + t.is( + (await pages[0].render()).trim(), + "
  1. item1
  2. item2
  3. item3
  4. item4
" + ); + + t.is( + await pages[1].getOutputPath(), + "./dist/paged/pagedinlinedata/1/index.html" + ); + t.is( + (await pages[1].render()).trim(), + "
  1. item5
  2. item6
  3. item7
  4. item8
" + ); +}); + +test("Pagination with deep data merge with alias #147", async t => { + let tmpl = new Template( + "./test/stubs/paged/pagedalias.njk", + "./test/stubs/", + "./dist" + ); + tmpl.config = { + keys: { + layout: "layout", + permalink: "permalink" + }, + dynamicPermalinks: true, + dataDeepMerge: true + }; + + let data = await tmpl.getData(); + let paging = new Pagination(data); + paging.setTemplate(tmpl); + let pages = await paging.getPageTemplates(); + t.is(await pages[0].getOutputPath(), "./dist/pagedalias/item1/index.html"); + t.is(await pages[1].getOutputPath(), "./dist/pagedalias/item2/index.html"); + + t.is((await pages[0].render()).trim(), "item1"); + t.is((await pages[1].render()).trim(), "item2"); +}); + +test("Paginate data in frontmatter (reversed)", async t => { + let tmpl = new Template( + "./test/stubs/paged/pagedinlinedata-reverse.njk", + "./test/stubs/", + "./dist" + ); + + let data = await tmpl.getData(); + let paging = new Pagination(data); + paging.setTemplate(tmpl); + let pages = await paging.getPageTemplates(); + t.is(pages.length, 2); + + t.is( + await pages[0].getOutputPath(), + "./dist/paged/pagedinlinedata-reverse/index.html" + ); + t.is( + (await pages[0].render()).trim(), + "
  1. item8
  2. item7
  3. item6
  4. item5
" + ); + + t.is( + await pages[1].getOutputPath(), + "./dist/paged/pagedinlinedata-reverse/1/index.html" + ); + t.is( + (await pages[1].render()).trim(), + "
  1. item4
  2. item3
  3. item2
  4. item1
" + ); +}); + +test("No circular dependency (does not throw)", t => { + new Pagination({ + collections: { + tag1: [] + }, + pagination: { + data: "collections.tag1", + size: 1 + }, + tags: ["tag2"] + }); + + t.true(true); +}); + +test("Circular dependency (pagination iterates over tag1 but also supplies pages to tag1)", t => { + t.throws(() => { + new Pagination({ + collections: { + tag1: [], + tag2: [] + }, + pagination: { + data: "collections.tag1", + size: 1 + }, + tags: ["tag1"] + }); + }); +}); diff --git a/test/PluralizeTest.js b/test/PluralizeTest.js new file mode 100644 index 000000000..17bf9f600 --- /dev/null +++ b/test/PluralizeTest.js @@ -0,0 +1,10 @@ +import test from "ava"; +import pluralize from "../src/Util/Pluralize"; + +test("Pluralize", t => { + t.is(pluralize(0, "test", "tests"), "tests"); + t.is(pluralize(1, "test", "tests"), "test"); + t.is(pluralize(2, "test", "tests"), "tests"); + t.is(pluralize(3, "test", "tests"), "tests"); + t.is(pluralize(3.5, "test", "tests"), "tests"); +}); diff --git a/test/SortableTest.js b/test/SortableTest.js new file mode 100644 index 000000000..d43570088 --- /dev/null +++ b/test/SortableTest.js @@ -0,0 +1,151 @@ +import test from "ava"; +import { DateTime } from "luxon"; +import Sortable from "../src/Util/Sortable"; + +test("get Sort Function", t => { + let s = new Sortable(); + t.deepEqual(s.getSortFunction(), Sortable.sortFunctionAlphabeticAscending); +}); + +test("Alphabetic Ascending", t => { + let s = new Sortable(); + s.add("a"); + s.add("z"); + s.add("m"); + t.deepEqual(s.sort(), ["a", "m", "z"]); +}); + +test("Alphabetic Descending", t => { + let s = new Sortable(); + s.setSortDescending(); + s.add("a"); + s.add("z"); + s.add("m"); + t.deepEqual(s.sort(), ["z", "m", "a"]); +}); + +test("Numeric Ascending", t => { + let s = new Sortable(); + s.setSortNumeric(true); + s.add(1); + s.add(4); + s.add(2); + t.deepEqual(s.sort(), [1, 2, 4]); +}); + +test("Numeric Descending", t => { + let s = new Sortable(); + s.setSortNumeric(true); + s.setSortDescending(); + s.add(1); + s.add(4); + s.add(2); + t.deepEqual(s.sort(), [4, 2, 1]); +}); + +test("Date Assumptions", t => { + t.is(DateTime.fromISO("2007-10-10") - new Date(2007, 9, 10).getTime(), 0); + t.is(DateTime.fromISO("2008-10-10") - new Date(2008, 9, 10).getTime(), 0); + t.not(DateTime.fromISO("2008-10-10") - new Date(2007, 9, 10).getTime(), 0); +}); + +test("Date and Sortable Assumptions", t => { + // Sortable works here without extra code because Luxonā€™s valueOf works in equality comparison (for alphabetic lists) + t.is( + Sortable.sortFunctionAlphabeticAscending( + DateTime.fromISO("2007-10-10"), + new Date(2007, 9, 10).getTime() + ), + 0 + ); + t.is( + Sortable.sortFunctionAlphabeticDescending( + DateTime.fromISO("2007-10-10"), + new Date(2007, 9, 10).getTime() + ), + 0 + ); + + t.is( + Sortable.sortFunctionAlphabeticAscending( + DateTime.fromISO("2008-10-10"), + new Date(2007, 9, 10).getTime() + ), + 1 + ); + t.is( + Sortable.sortFunctionAlphabeticDescending( + DateTime.fromISO("2008-10-10"), + new Date(2007, 9, 10).getTime() + ), + -1 + ); + + // Sortable works here without extra code because Luxonā€™s valueOf works in subtraction (for numeric lists) + t.is( + Sortable.sortFunctionNumericAscending( + DateTime.fromISO("2008-10-10"), + new Date(2008, 9, 10).getTime() + ), + 0 + ); + t.is( + Sortable.sortFunctionNumericDescending( + DateTime.fromISO("2008-10-10"), + new Date(2008, 9, 10).getTime() + ), + 0 + ); + + t.true( + Sortable.sortFunctionNumericAscending( + DateTime.fromISO("2008-10-10"), + new Date(2007, 9, 10).getTime() + ) > 0 + ); + t.true( + Sortable.sortFunctionNumericDescending( + DateTime.fromISO("2008-10-10"), + new Date(2007, 9, 10).getTime() + ) < 0 + ); +}); + +test("Date Ascending", t => { + let s = new Sortable(); + let date1 = DateTime.fromISO("2007-10-10"); + let date2 = DateTime.fromISO("2008-10-10"); + let date3 = DateTime.fromISO("2009-10-10"); + s.add(date3); + s.add(date2); + s.add(date1); + t.deepEqual(s.sort(), [date1, date2, date3]); +}); + +test("Date Descending", t => { + let s = new Sortable(); + s.setSortDescending(); + let date1 = DateTime.fromISO("2007-10-10"); + let date2 = DateTime.fromISO("2008-10-10"); + let date3 = DateTime.fromISO("2009-10-10"); + s.add(date2); + s.add(date3); + s.add(date1); + t.deepEqual(s.sort(), [date3, date2, date1]); +}); + +test("Alphabetic Ascending (str sort arg)", t => { + let s = new Sortable(); + s.add("a"); + s.add("z"); + s.add("m"); + t.deepEqual(s.sort("ascending"), ["a", "m", "z"]); +}); + +test("Alphabetic Descending (str sort arg)", t => { + let s = new Sortable(); + s.add("a"); + s.add("z"); + s.add("m"); + t.deepEqual(s.sort("descending"), ["z", "m", "a"]); +}); diff --git a/test/TemplateCacheTest.js b/test/TemplateCacheTest.js new file mode 100644 index 000000000..80a0a5adb --- /dev/null +++ b/test/TemplateCacheTest.js @@ -0,0 +1,72 @@ +import test from "ava"; +import Template from "../src/Template"; +import templateCache from "../src/TemplateCache"; + +test("Cache can save templates", t => { + templateCache.clear(); + + let tmpl = new Template( + "./test/stubs/template.ejs", + "./test/stubs/", + "./dist" + ); + + templateCache.add("./test/stubs/template.ejs", tmpl); + t.is(templateCache.size(), 1); +}); + +test("TemplateCache clear", t => { + templateCache.clear(); + + let tmpl = new Template( + "./test/stubs/template.ejs", + "./test/stubs/", + "./dist" + ); + + templateCache.add("./test/stubs/template.ejs", tmpl); + t.is(templateCache.size(), 1); + templateCache.clear(); + t.is(templateCache.size(), 0); +}); + +test("TemplateCache has", t => { + templateCache.clear(); + + let tmpl = new Template( + "./test/stubs/template.ejs", + "./test/stubs/", + "./dist" + ); + + templateCache.add("./test/stubs/template.ejs", tmpl); + t.is(templateCache.has("./test/stubs/template.ejs"), true); +}); + +test("TemplateCache get success", t => { + templateCache.clear(); + + let tmpl = new Template( + "./test/stubs/template.ejs", + "./test/stubs/", + "./dist" + ); + + templateCache.add("./test/stubs/template.ejs", tmpl); + t.truthy(templateCache.get("./test/stubs/template.ejs")); +}); + +test("TemplateCache get fail", t => { + templateCache.clear(); + + let tmpl = new Template( + "./test/stubs/template.ejs", + "./test/stubs/", + "./dist" + ); + + templateCache.add("./test/stubs/template.ejs", tmpl); + t.throws(function() { + templateCache.get("./test/stubs/template298374892.ejs"); + }); +}); diff --git a/test/TemplateCollectionTest.js b/test/TemplateCollectionTest.js new file mode 100644 index 000000000..842a4e7e8 --- /dev/null +++ b/test/TemplateCollectionTest.js @@ -0,0 +1,207 @@ +import test from "ava"; +import multimatch from "multimatch"; +import Template from "../src/Template"; +import Collection from "../src/TemplateCollection"; +import Sortable from "../src/Util/Sortable"; + +let tmpl1 = new Template( + "./test/stubs/collection/test1.md", + "./test/stubs/", + "./test/stubs/_site" +); +let tmpl2 = new Template( + "./test/stubs/collection/test2.md", + "./test/stubs/", + "./test/stubs/_site" +); +let tmpl3 = new Template( + "./test/stubs/collection/test3.md", + "./test/stubs/", + "./test/stubs/_site" +); +let tmpl4 = new Template( + "./test/stubs/collection/test4.md", + "./test/stubs/", + "./test/stubs/_site" +); +let tmpl5 = new Template( + "./test/stubs/collection/test5.md", + "./test/stubs/", + "./test/stubs/_site" +); +let tmpl6 = new Template( + "./test/stubs/collection/test6.html", + "./test/stubs/", + "./test/stubs/_site" +); +let tmpl7 = new Template( + "./test/stubs/collection/test7.njk", + "./test/stubs/", + "./test/stubs/_site" +); + +test("Basic setup", async t => { + let c = new Collection(); + await c._testAddTemplate(tmpl1); + await c._testAddTemplate(tmpl2); + await c._testAddTemplate(tmpl3); + + t.is(c.length, 3); +}); + +test("sortFunctionDateInputPath", async t => { + let c = new Collection(); + await c._testAddTemplate(tmpl1); + await c._testAddTemplate(tmpl4); + await c._testAddTemplate(tmpl5); + + let posts = c.sort(Sortable.sortFunctionDateInputPath); + t.is(posts.length, 3); + t.deepEqual(posts[0].template, tmpl4); + t.deepEqual(posts[1].template, tmpl1); + t.deepEqual(posts[2].template, tmpl5); +}); + +test("getFilteredByTag", async t => { + let c = new Collection(); + await c._testAddTemplate(tmpl1); + await c._testAddTemplate(tmpl2); + await c._testAddTemplate(tmpl3); + + let posts = c.getFilteredByTag("post"); + t.is(posts.length, 2); + t.deepEqual(posts[0].template, tmpl1); + t.deepEqual(posts[1].template, tmpl3); + + let cats = c.getFilteredByTag("cat"); + t.is(cats.length, 2); + t.deepEqual(cats[0].template, tmpl2); + + let dogs = c.getFilteredByTag("dog"); + t.is(dogs.length, 1); + t.deepEqual(dogs[0].template, tmpl1); +}); + +test("getFilteredByTag (added out of order, sorted)", async t => { + let c = new Collection(); + await c._testAddTemplate(tmpl3); + await c._testAddTemplate(tmpl2); + await c._testAddTemplate(tmpl1); + + let posts = c.getFilteredByTag("post"); + t.is(posts.length, 2); + t.deepEqual(posts[0].template, tmpl1); + t.deepEqual(posts[1].template, tmpl3); + + let cats = c.getFilteredByTag("cat"); + t.truthy(cats.length); + t.is(cats.length, 2); + t.deepEqual(cats[0].template, tmpl2); + + let dogs = c.getFilteredByTag("dog"); + t.truthy(dogs.length); + t.deepEqual(dogs[0].template, tmpl1); +}); + +test("getFilteredByGlob", async t => { + let c = new Collection(); + await c._testAddTemplate(tmpl1); + await c._testAddTemplate(tmpl6); + await c._testAddTemplate(tmpl7); + + let markdowns = c.getFilteredByGlob("./**/*.md"); + t.is(markdowns.length, 1); + t.deepEqual(markdowns[0].template, tmpl1); +}); + +test("getFilteredByGlob no dash dot", async t => { + let c = new Collection(); + await c._testAddTemplate(tmpl1); + await c._testAddTemplate(tmpl6); + await c._testAddTemplate(tmpl7); + + let markdowns = c.getFilteredByGlob("**/*.md"); + t.is(markdowns.length, 1); + t.deepEqual(markdowns[0].template, tmpl1); + + let htmls = c.getFilteredByGlob("**/*.{html,njk}"); + t.is(htmls.length, 2); + t.deepEqual(htmls[0].template, tmpl6); + t.deepEqual(htmls[1].template, tmpl7); +}); + +test("partial match on tag string, issue 95", async t => { + let cat = new Template( + "./test/stubs/issue-95/cat.md", + "./test/stubs/", + "./test/stubs/_site" + ); + let notacat = new Template( + "./test/stubs/issue-95/notacat.md", + "./test/stubs/", + "./test/stubs/_site" + ); + + let c = new Collection(); + await c._testAddTemplate(cat); + await c._testAddTemplate(notacat); + + let posts = c.getFilteredByTag("cat"); + t.is(posts.length, 1); +}); + +test("multimatch assumptions, issue #127", async t => { + t.deepEqual( + multimatch( + ["src/bookmarks/test.md"], + "**/+(bookmarks|posts|screencasts)/**/!(index)*.md" + ), + ["src/bookmarks/test.md"] + ); + t.deepEqual( + multimatch( + ["./src/bookmarks/test.md"], + "./**/+(bookmarks|posts|screencasts)/**/!(index)*.md" + ), + ["./src/bookmarks/test.md"] + ); + + let c = new Collection(); + let globs = c.getGlobs("**/+(bookmarks|posts|screencasts)/**/!(index)*.md"); + t.deepEqual(globs, ["./**/+(bookmarks|posts|screencasts)/**/!(index)*.md"]); + + t.deepEqual(multimatch(["./src/bookmarks/test.md"], globs), [ + "./src/bookmarks/test.md" + ]); + t.deepEqual(multimatch(["./src/bookmarks/index.md"], globs), []); + t.deepEqual(multimatch(["./src/bookmarks/index2.md"], globs), []); + t.deepEqual( + multimatch(["./src/_content/bookmarks/2018-03-27-git-message.md"], globs), + ["./src/_content/bookmarks/2018-03-27-git-message.md"] + ); +}); + +test("Sort in place (issue #352)", async t => { + let c = new Collection(); + await c._testAddTemplate(tmpl1); + await c._testAddTemplate(tmpl4); + await c._testAddTemplate(tmpl5); + + let posts = c.getAllSorted(); + t.is(posts.length, 3); + t.deepEqual(posts[0].template, tmpl4); + t.deepEqual(posts[1].template, tmpl1); + t.deepEqual(posts[2].template, tmpl5); + + let posts2 = c.getAllSorted().reverse(); + t.is(posts2.length, 3); + t.deepEqual(posts2[0].template, tmpl5); + t.deepEqual(posts2[1].template, tmpl1); + t.deepEqual(posts2[2].template, tmpl4); + + let posts3 = c.getAllSorted().reverse(); + t.is(posts3.length, 3); + t.deepEqual(posts3[0].template, tmpl5); + t.deepEqual(posts3[1].template, tmpl1); + t.deepEqual(posts3[2].template, tmpl4); +}); diff --git a/test/TemplateConfigTest.js b/test/TemplateConfigTest.js index bf08f719f..8815233cd 100644 --- a/test/TemplateConfigTest.js +++ b/test/TemplateConfigTest.js @@ -1,5 +1,7 @@ import test from "ava"; +import md from "markdown-it"; import TemplateConfig from "../src/TemplateConfig"; +import eleventyConfig from "../src/EleventyConfig"; test("Template Config local config overrides base config", async t => { let templateCfg = new TemplateConfig( @@ -13,14 +15,16 @@ test("Template Config local config overrides base config", async t => { // merged, not overwritten t.true(Object.keys(cfg.keys).length > 1); + t.truthy(Object.keys(cfg.handlebarsHelpers).length); + t.truthy(Object.keys(cfg.nunjucksFilters).length); - t.is(Object.keys(cfg.handlebarsHelpers).length, 0); - t.is(Object.keys(cfg.nunjucksFilters).length, 2); - - t.true(Object.keys(cfg.filters).length >= 1); + t.is(Object.keys(cfg.filters).length, 1); t.is( - cfg.filters.prettyHtml(`
`), + cfg.filters.prettyHtml( + `
`, + "test.html" + ), `
@@ -28,3 +32,299 @@ test("Template Config local config overrides base config", async t => { ` ); }); + +test("Add liquid tag", t => { + eleventyConfig.reset(); + eleventyConfig.addLiquidTag("myTagName", function() {}); + + let templateCfg = new TemplateConfig( + require("../config.js"), + "./test/stubs/config.js" + ); + let cfg = templateCfg.getConfig(); + t.not(Object.keys(cfg.liquidTags).indexOf("myTagName"), -1); +}); + +test("Add nunjucks tag", t => { + eleventyConfig.reset(); + eleventyConfig.addNunjucksTag("myNunjucksTag", function() {}); + + let templateCfg = new TemplateConfig( + require("../config.js"), + "./test/stubs/config.js" + ); + let cfg = templateCfg.getConfig(); + t.not(Object.keys(cfg.nunjucksTags).indexOf("myNunjucksTag"), -1); +}); + +test("Add liquid filter", t => { + eleventyConfig.reset(); + eleventyConfig.addLiquidFilter("myFilterName", function(liquidEngine) { + return {}; + }); + + let templateCfg = new TemplateConfig( + require("../config.js"), + "./test/stubs/config.js" + ); + let cfg = templateCfg.getConfig(); + t.not(Object.keys(cfg.liquidFilters).indexOf("myFilterName"), -1); +}); + +test("Add handlebars helper", t => { + eleventyConfig.reset(); + eleventyConfig.addHandlebarsHelper("myHelperName", function() {}); + + let templateCfg = new TemplateConfig( + require("../config.js"), + "./test/stubs/config.js" + ); + let cfg = templateCfg.getConfig(); + t.not(Object.keys(cfg.handlebarsHelpers).indexOf("myHelperName"), -1); +}); + +test("Add nunjucks filter", t => { + eleventyConfig.reset(); + eleventyConfig.addNunjucksFilter("myFilterName", function() {}); + + let templateCfg = new TemplateConfig( + require("../config.js"), + "./test/stubs/config.js" + ); + let cfg = templateCfg.getConfig(); + t.not(Object.keys(cfg.nunjucksFilters).indexOf("myFilterName"), -1); +}); + +test("Add universal filter", t => { + eleventyConfig.reset(); + eleventyConfig.addFilter("myFilterName", function() {}); + + let templateCfg = new TemplateConfig( + require("../config.js"), + "./test/stubs/config.js" + ); + let cfg = templateCfg.getConfig(); + t.not(Object.keys(cfg.liquidFilters).indexOf("myFilterName"), -1); + t.not(Object.keys(cfg.handlebarsHelpers).indexOf("myFilterName"), -1); + t.not(Object.keys(cfg.nunjucksFilters).indexOf("myFilterName"), -1); +}); + +test("Add namespaced universal filter", t => { + eleventyConfig.reset(); + eleventyConfig.namespace("testNamespace", function() { + eleventyConfig.addFilter("MyFilterName", function() {}); + }); + + let templateCfg = new TemplateConfig( + require("../config.js"), + "./test/stubs/config.js" + ); + let cfg = templateCfg.getConfig(); + t.not( + Object.keys(cfg.liquidFilters).indexOf("testNamespaceMyFilterName"), + -1 + ); + t.not( + Object.keys(cfg.handlebarsHelpers).indexOf("testNamespaceMyFilterName"), + -1 + ); + t.not( + Object.keys(cfg.nunjucksFilters).indexOf("testNamespaceMyFilterName"), + -1 + ); +}); + +test("Add namespaced universal filter using underscore", t => { + eleventyConfig.reset(); + eleventyConfig.namespace("testNamespace_", function() { + eleventyConfig.addFilter("myFilterName", function() {}); + }); + + let templateCfg = new TemplateConfig( + require("../config.js"), + "./test/stubs/config.js" + ); + let cfg = templateCfg.getConfig(); + t.not( + Object.keys(cfg.liquidFilters).indexOf("testNamespace_myFilterName"), + -1 + ); + t.not( + Object.keys(cfg.handlebarsHelpers).indexOf("testNamespace_myFilterName"), + -1 + ); + t.not( + Object.keys(cfg.nunjucksFilters).indexOf("testNamespace_myFilterName"), + -1 + ); +}); + +test("Empty namespace", t => { + eleventyConfig.reset(); + eleventyConfig.namespace("", function() { + eleventyConfig.addNunjucksFilter("myFilterName", function() {}); + }); + + let templateCfg = new TemplateConfig( + require("../config.js"), + "./test/stubs/config.js" + ); + let cfg = templateCfg.getConfig(); + t.not(Object.keys(cfg.nunjucksFilters).indexOf("myFilterName"), -1); +}); + +test("Nested Empty Inner namespace", t => { + eleventyConfig.reset(); + eleventyConfig.namespace("testNs", function() { + eleventyConfig.namespace("", function() { + eleventyConfig.addNunjucksFilter("myFilterName", function() {}); + }); + }); + + let templateCfg = new TemplateConfig( + require("../config.js"), + "./test/stubs/config.js" + ); + let cfg = templateCfg.getConfig(); + t.not(Object.keys(cfg.nunjucksFilters).indexOf("testNsmyFilterName"), -1); +}); + +test("Nested Empty Outer namespace", t => { + eleventyConfig.reset(); + eleventyConfig.namespace("", function() { + eleventyConfig.namespace("testNs", function() { + eleventyConfig.addNunjucksFilter("myFilterName", function() {}); + }); + }); + + let templateCfg = new TemplateConfig( + require("../config.js"), + "./test/stubs/config.js" + ); + let cfg = templateCfg.getConfig(); + t.not(Object.keys(cfg.nunjucksFilters).indexOf("testNsmyFilterName"), -1); +}); + +// important for backwards compatibility with old +// `module.exports = function (eleventyConfig, pluginNamespace) {` +// plugin code +test("Non-string namespaces are ignored", t => { + eleventyConfig.reset(); + eleventyConfig.namespace(["lkdsjflksd"], function() { + eleventyConfig.addNunjucksFilter("myFilterName", function() {}); + }); + + let templateCfg = new TemplateConfig( + require("../config.js"), + "./test/stubs/config.js" + ); + let cfg = templateCfg.getConfig(); + t.not(Object.keys(cfg.nunjucksFilters).indexOf("myFilterName"), -1); +}); + +test(".addPlugin oddity: I donā€™t think pluginNamespace was ever passed in here, but we donā€™t want this to break", t => { + eleventyConfig.reset(); + eleventyConfig.addPlugin(function(eleventyConfig, pluginNamespace) { + eleventyConfig.namespace(pluginNamespace, () => { + eleventyConfig.addNunjucksFilter("myFilterName", function() {}); + }); + }); + + let templateCfg = new TemplateConfig( + require("../config.js"), + "./test/stubs/config.js" + ); + let cfg = templateCfg.getConfig(); + t.not(Object.keys(cfg.nunjucksFilters).indexOf("myFilterName"), -1); +}); + +test("Test url universal filter with custom pathPrefix (no slash)", t => { + let templateCfg = new TemplateConfig( + require("../config.js"), + "./test/stubs/config.js" + ); + templateCfg.setPathPrefix("/testdirectory/"); + let cfg = templateCfg.getConfig(); + t.is(cfg.pathPrefix, "/testdirectory/"); +}); + +test("setTemplateFormats(string)", t => { + eleventyConfig.reset(); + eleventyConfig.setTemplateFormats("ejs,njk, liquid"); + + let templateCfg = new TemplateConfig( + require("../config.js"), + "./test/stubs/config.js" + ); + let cfg = templateCfg.getConfig(); + t.deepEqual(cfg.templateFormats, ["ejs", "njk", "liquid"]); +}); + +test("setTemplateFormats(array)", t => { + eleventyConfig.reset(); + eleventyConfig.setTemplateFormats(["ejs", "njk", "liquid"]); + + let templateCfg = new TemplateConfig( + require("../config.js"), + "./test/stubs/config.js" + ); + let cfg = templateCfg.getConfig(); + t.deepEqual(cfg.templateFormats, ["ejs", "njk", "liquid"]); +}); + +test("setTemplateFormats(array, size 1)", t => { + eleventyConfig.reset(); + eleventyConfig.setTemplateFormats(["liquid"]); + + let templateCfg = new TemplateConfig( + require("../config.js"), + "./test/stubs/config.js" + ); + let cfg = templateCfg.getConfig(); + t.deepEqual(cfg.templateFormats, ["liquid"]); +}); + +test("setTemplateFormats(empty array)", t => { + eleventyConfig.reset(); + eleventyConfig.setTemplateFormats([]); + + let templateCfg = new TemplateConfig( + require("../config.js"), + "./test/stubs/config.js" + ); + let cfg = templateCfg.getConfig(); + t.deepEqual(cfg.templateFormats, []); +}); + +test("libraryOverrides", t => { + let mdLib = md(); + eleventyConfig.setLibrary("md", mdLib); + + let templateCfg = new TemplateConfig( + require("../config.js"), + "./test/stubs/config.js" + ); + let cfg = templateCfg.getConfig(); + t.falsy(cfg.libraryOverrides.ldkja); + t.falsy(cfg.libraryOverrides.njk); + t.truthy(cfg.libraryOverrides.md); + t.deepEqual(mdLib, cfg.libraryOverrides.md); +}); + +test("Properly throws error on missing module #182", t => { + t.throws(function() { + new TemplateConfig( + require("../config.js"), + "./test/stubs/broken-config.js" + ); + }); +}); + +test("Properly throws error when config returns a Promise", t => { + t.throws(function() { + new TemplateConfig( + require("../config.js"), + "./test/stubs/config-promise.js" + ); + }); +}); diff --git a/test/TemplateDataTest.js b/test/TemplateDataTest.js index 5f9a4433f..5892367f1 100644 --- a/test/TemplateDataTest.js +++ b/test/TemplateDataTest.js @@ -1,23 +1,16 @@ import test from "ava"; import TemplateData from "../src/TemplateData"; -import TemplateConfig from "../src/TemplateConfig"; +import templateConfig from "../src/Config"; -let cfg = TemplateConfig.getDefaultConfig(); +const config = templateConfig.getConfig(); test("Create", async t => { let dataObj = new TemplateData("./test/stubs/"); let data = await dataObj.getData(); - t.true(Object.keys(data[cfg.keys.package]).length > 0); + t.true(Object.keys(data[config.keys.package]).length > 0); }); -// test("Create (old method, file name not dir)", async t => { -// let dataObj = new TemplateData("./test/stubs/globalData.json"); -// let data = await dataObj.getData(); - -// t.true(Object.keys(data[cfg.keys.package]).length > 0); -// }); - test("getData()", async t => { let dataObj = new TemplateData("./test/stubs/"); @@ -27,74 +20,317 @@ test("getData()", async t => { t.is(data.globalData.datakey1, "datavalue1", "simple data value"); t.is( data.globalData.datakey2, - "eleventy-cli", - `variables, resolve ${cfg.keys.package} to its value.` + "@11ty/eleventy", + `variables, resolve ${config.keys.package} to its value.` ); t.true( - Object.keys(data[cfg.keys.package]).length > 0, - `package.json imported to data in ${cfg.keys.package}` + Object.keys(data[config.keys.package]).length > 0, + `package.json imported to data in ${config.keys.package}` ); }); test("Data dir does not exist", async t => { - let dataObj = new TemplateData("./test/thisdirectorydoesnotexist"); - - await t.throws(dataObj.getData()); + await t.throwsAsync(async () => { + let dataObj = new TemplateData("./test/thisdirectorydoesnotexist"); + await dataObj.getData(); + }); }); -test("addLocalData()", async t => { +test("Add local data", async t => { let dataObj = new TemplateData("./test/stubs/"); let data = await dataObj.getData(); t.is(data.globalData.datakey1, "datavalue1"); - t.is(data.globalData.datakey2, "eleventy-cli"); + t.is(data.globalData.datakey2, "@11ty/eleventy"); let withLocalData = await dataObj.getLocalData( - "./test/stubs/component/component.json" + "./test/stubs/component/component.njk" ); t.is(withLocalData.globalData.datakey1, "datavalue1"); - t.is(withLocalData.globalData.datakey2, "eleventy-cli"); + t.is(withLocalData.globalData.datakey2, "@11ty/eleventy"); t.is(withLocalData.localdatakey1, "localdatavalue1"); + + // from the js file + t.is(withLocalData.localdatakeyfromjs, "howdydoody"); + t.is(withLocalData.localdatakeyfromjs2, "howdy2"); +}); + +test("Get local data async JS", async t => { + let dataObj = new TemplateData("./test/stubs/"); + + let withLocalData = await dataObj.getLocalData( + "./test/stubs/component-async/component.njk" + ); + + // from the js file + t.is(withLocalData.localdatakeyfromjs, "howdydoody"); +}); + +test("addLocalData() doesnā€™t exist but doesnā€™t fail (template file does exist)", async t => { + let dataObj = new TemplateData("./test/stubs/"); + let data = await dataObj.getData(); + let beforeDataKeyCount = Object.keys(data); + + // template file does exist + let withLocalData = await dataObj.getLocalData( + "./test/stubs/datafiledoesnotexist/template.njk" + ); + t.is(withLocalData.globalData.datakey1, "datavalue1"); + t.is(withLocalData.globalData.datakey2, "@11ty/eleventy"); + t.deepEqual(Object.keys(withLocalData), beforeDataKeyCount); }); -test("addLocalData() doesnā€™t exist but doesnā€™t fail", async t => { +test("addLocalData() doesnā€™t exist but doesnā€™t fail (template file does not exist)", async t => { let dataObj = new TemplateData("./test/stubs/"); let data = await dataObj.getData(); let beforeDataKeyCount = Object.keys(data); let withLocalData = await dataObj.getLocalData( - "./test/stubs/component/thisfiledoesnotexist.json" + "./test/stubs/datafiledoesnotexist/templatedoesnotexist.njk" ); t.is(withLocalData.globalData.datakey1, "datavalue1"); - t.is(withLocalData.globalData.datakey2, "eleventy-cli"); + t.is(withLocalData.globalData.datakey2, "@11ty/eleventy"); t.deepEqual(Object.keys(withLocalData), beforeDataKeyCount); }); test("Global Dir Directory", async t => { - let dataObj = new TemplateData(); + let dataObj = new TemplateData("./"); - t.is(await dataObj.getGlobalDataGlob(), "_data/**/*.json"); + t.deepEqual(await dataObj.getGlobalDataGlob(), ["./_data/**/*.(json|js)"]); }); test("Global Dir Directory with Constructor Path Arg", async t => { let dataObj = new TemplateData("./test/stubs/"); - t.is(await dataObj.getGlobalDataGlob(), "test/stubs/_data/**/*.json"); + t.deepEqual(await dataObj.getGlobalDataGlob(), [ + "./test/stubs/_data/**/*.(json|js)" + ]); }); test("getAllGlobalData() with other data files", async t => { let dataObj = new TemplateData("./test/stubs/"); let data = await dataObj.cacheData(); + let dataFilePaths = await dataObj.getGlobalDataFiles(); + + t.true(dataFilePaths.length > 0); + t.true( + dataFilePaths.filter(path => { + return path.indexOf("./test/stubs/_data/globalData.json") === 0; + }).length > 0 + ); - t.true((await dataObj.getGlobalDataFiles()).length > 0); - t.not(typeof data.testData, "undefined"); + t.truthy(data.globalData); + t.is(data.globalData.datakey1, "datavalue1"); + t.truthy(data.testData); t.deepEqual(data.testData, { testdatakey1: "testdatavalue1" }); - t.deepEqual(data.subdir.testDataSubdir, { subdirkey: "subdirvalue" }); }); + +test("getAllGlobalData() with js object data file", async t => { + let dataObj = new TemplateData("./test/stubs/"); + let data = await dataObj.cacheData(); + let dataFilePaths = await dataObj.getGlobalDataFiles(); + + t.true( + dataFilePaths.filter(path => { + return path.indexOf("./test/stubs/_data/globalData2.js") === 0; + }).length > 0 + ); + + t.truthy(data.globalData2); + t.is(data.globalData2.datakeyfromjs, "howdy"); +}); + +test("getAllGlobalData() with js function data file", async t => { + let dataObj = new TemplateData("./test/stubs/"); + let data = await dataObj.cacheData(); + let dataFilePaths = await dataObj.getGlobalDataFiles(); + + t.true( + dataFilePaths.filter(path => { + return path.indexOf("./test/stubs/_data/globalData2.js") === 0; + }).length > 0 + ); + + t.truthy(data.globalDataFn); + t.is(data.globalDataFn.datakeyfromjsfn, "howdy"); +}); + +test("getDataValue() without a dataTemplateEngine", async t => { + let dataObj = new TemplateData("./test/stubs/"); + dataObj.setDataTemplateEngine(false); + + let data = await dataObj.getDataValue("./test/stubs/_data/testDataEjs.json", { + pkg: { name: "pkgname" } + }); + + t.deepEqual(data, { + datakey1: "datavalue1", + datakey2: "<%= pkg.name %>" + }); +}); + +test("getDataValue() without dataTemplateEngine changed to `ejs`", async t => { + let dataObj = new TemplateData("./test/stubs/"); + dataObj.setDataTemplateEngine("ejs"); + + let data = await dataObj.getDataValue("./test/stubs/_data/testDataEjs.json", { + pkg: { name: "pkgname" } + }); + + t.deepEqual(data, { + datakey1: "datavalue1", + datakey2: "pkgname" + }); +}); + +test("getLocalDataPaths", async t => { + let dataObj = new TemplateData("./test/stubs/"); + let paths = await dataObj.getLocalDataPaths( + "./test/stubs/component/component.liquid" + ); + + t.deepEqual(paths, [ + "./test/stubs/component/component.json", + "./test/stubs/component/component.11tydata.json", + "./test/stubs/component/component.11tydata.js" + ]); +}); + +test("Deeper getLocalDataPaths", async t => { + let dataObj = new TemplateData("./"); + let paths = await dataObj.getLocalDataPaths( + "./test/stubs/component/component.liquid" + ); + + t.deepEqual(paths, [ + "./test/test.json", + "./test/test.11tydata.json", + "./test/test.11tydata.js", + "./test/stubs/stubs.json", + "./test/stubs/stubs.11tydata.json", + "./test/stubs/stubs.11tydata.js", + "./test/stubs/component/component.json", + "./test/stubs/component/component.11tydata.json", + "./test/stubs/component/component.11tydata.js" + ]); +}); + +test("getLocalDataPaths with an 11ty js template", async t => { + let dataObj = new TemplateData("./test/stubs/"); + let paths = await dataObj.getLocalDataPaths( + "./test/stubs/component/component.11ty.js" + ); + + t.deepEqual(paths, [ + "./test/stubs/component/component.json", + "./test/stubs/component/component.11tydata.json", + "./test/stubs/component/component.11tydata.js" + ]); +}); + +test("getLocalDataPaths with inputDir passed in (trailing slash)", async t => { + let dataObj = new TemplateData("./test/stubs/"); + let paths = await dataObj.getLocalDataPaths( + "./test/stubs/component/component.liquid" + ); + + t.deepEqual(paths, [ + "./test/stubs/component/component.json", + "./test/stubs/component/component.11tydata.json", + "./test/stubs/component/component.11tydata.js" + ]); +}); + +test("getLocalDataPaths with inputDir passed in (no trailing slash)", async t => { + let dataObj = new TemplateData("./test/stubs/"); + let paths = await dataObj.getLocalDataPaths( + "./test/stubs/component/component.liquid" + ); + + t.deepEqual(paths, [ + "./test/stubs/component/component.json", + "./test/stubs/component/component.11tydata.json", + "./test/stubs/component/component.11tydata.js" + ]); +}); + +test("getLocalDataPaths with inputDir passed in (no leading slash)", async t => { + let dataObj = new TemplateData("test/stubs"); + let paths = await dataObj.getLocalDataPaths( + "./test/stubs/component/component.liquid" + ); + + t.deepEqual(paths, [ + "./test/stubs/component/component.json", + "./test/stubs/component/component.11tydata.json", + "./test/stubs/component/component.11tydata.js" + ]); +}); + +test("getRawImports", async t => { + let dataObj = new TemplateData("test/stubs"); + let data = dataObj.getRawImports(); + + t.is(data.pkg.name, "@11ty/eleventy"); +}); + +test("getTemplateDataFileGlob", async t => { + let tw = new TemplateData("test/stubs"); + + t.deepEqual(await tw.getTemplateDataFileGlob(), [ + "./test/stubs/**/*.json", + "./test/stubs/**/*.11tydata.js" + ]); +}); + +test("TemplateData.merge", t => { + t.deepEqual( + TemplateData.merge( + { + tags: [1, 2, 3] + }, + { + tags: [4, 5, 6] + } + ), + { tags: [1, 2, 3, 4, 5, 6] } + ); +}); + +test("TemplateData.cleanupData", t => { + t.deepEqual(TemplateData.cleanupData({}), {}); + t.deepEqual(TemplateData.cleanupData({ tags: null }), { tags: [] }); + t.deepEqual(TemplateData.cleanupData({ tags: "" }), { tags: [] }); + t.deepEqual(TemplateData.cleanupData({ tags: [] }), { tags: [] }); + t.deepEqual(TemplateData.cleanupData({ tags: "test" }), { tags: ["test"] }); + t.deepEqual(TemplateData.cleanupData({ tags: ["test1", "test2"] }), { + tags: ["test1", "test2"] + }); +}); + +test("Parent directory for data (Issue #337)", async t => { + let dataObj = new TemplateData("./test/stubs-337/src/"); + dataObj._setConfig({ + dataTemplateEngine: false, + dir: { + input: "./test/stubs-337/src/", + data: "../data/" + } + }); + dataObj.setInputDir("./test/stubs-337/src/"); + + let data = await dataObj.getData(); + + t.deepEqual(data, { + xyz: { + hi: "bye" + } + }); +}); diff --git a/test/TemplateEngineTest.js b/test/TemplateEngineTest.js index bdb2168f7..090820275 100644 --- a/test/TemplateEngineTest.js +++ b/test/TemplateEngineTest.js @@ -9,6 +9,11 @@ test("Unsupported engine", async t => { }); }); +test("Supported engine", async t => { + t.is(new TemplateEngine("ejs").getName(), "ejs"); + t.truthy(TemplateEngine.hasEngine("ejs")); +}); + test("Handlebars Helpers", async t => { let engine = TemplateEngine.getEngine("hbs"); engine.addHelpers({ @@ -20,3 +25,7 @@ test("Handlebars Helpers", async t => { let fn = await engine.compile("

{{uppercase author}}

"); t.is(await fn({ author: "zach" }), "

ZACH

"); }); + +test("getEngineLib", async t => { + t.truthy(TemplateEngine.getEngine("md").getEngineLib()); +}); diff --git a/test/TemplateFileSlugTest.js b/test/TemplateFileSlugTest.js new file mode 100644 index 000000000..56fb8335b --- /dev/null +++ b/test/TemplateFileSlugTest.js @@ -0,0 +1,169 @@ +import test from "ava"; +import TemplateFileSlug from "../src/TemplateFileSlug"; + +test("Easy slug", t => { + let fs = new TemplateFileSlug("./file.html"); + t.is(fs.getSlug(), "file"); + t.is(fs.getFullPathWithoutExtension(), "/file"); +}); + +test("Easy slug with dot", t => { + let fs = new TemplateFileSlug("./file.test.html"); + t.is(fs.getSlug(), "file.test"); + t.is(fs.getFullPathWithoutExtension(), "/file.test"); +}); + +test("Easy slug with dot 11ty.js", t => { + let fs = new TemplateFileSlug("./file.test.11ty.js"); + t.is(fs.getSlug(), "file.test"); + t.is(fs.getFullPathWithoutExtension(), "/file.test"); +}); + +test("Easy slug with date", t => { + let fs = new TemplateFileSlug("./2018-01-01-file.html"); + t.is(fs.getSlug(), "file"); + t.is(fs.getFullPathWithoutExtension(), "/file"); +}); + +test("Easy slug with date and dot in slug", t => { + let fs = new TemplateFileSlug("./2018-01-01-file.test.html"); + t.is(fs.getSlug(), "file.test"); + t.is(fs.getFullPathWithoutExtension(), "/file.test"); +}); + +test("Easy slug, index", t => { + let fs = new TemplateFileSlug("./index.html"); + t.is(fs.getSlug(), ""); + t.is(fs.getFullPathWithoutExtension(), "/index"); +}); + +test("Easy slug with date, index", t => { + let fs = new TemplateFileSlug("./2018-01-01-index.html"); + t.is(fs.getSlug(), ""); + t.is(fs.getFullPathWithoutExtension(), "/index"); +}); + +/* Directories */ + +test("Easy slug with dir", t => { + let fs = new TemplateFileSlug("./test/file.html"); + t.is(fs.getSlug(), "file"); + t.is(fs.getFullPathWithoutExtension(), "/test/file"); +}); + +test("Easy slug with dot with dir", t => { + let fs = new TemplateFileSlug("./test/file.test.html"); + t.is(fs.getSlug(), "file.test"); + t.is(fs.getFullPathWithoutExtension(), "/test/file.test"); +}); + +test("Easy slug with date with dir", t => { + let fs = new TemplateFileSlug("./test/2018-01-01-file.html"); + t.is(fs.getSlug(), "file"); + t.is(fs.getFullPathWithoutExtension(), "/test/file"); +}); + +test("Easy slug with date and dot in slug with dir", t => { + let fs = new TemplateFileSlug("./test/2018-01-01-file.test.html"); + t.is(fs.getSlug(), "file.test"); + t.is(fs.getFullPathWithoutExtension(), "/test/file.test"); +}); + +test("Easy slug, index with dir", t => { + let fs = new TemplateFileSlug("./test/index.html"); + t.is(fs.getSlug(), "test"); + t.is(fs.getFullPathWithoutExtension(), "/test/index"); +}); + +test("Easy slug with date, index with dir", t => { + let fs = new TemplateFileSlug("./test/2018-01-01-index.html"); + t.is(fs.getSlug(), "test"); + t.is(fs.getFullPathWithoutExtension(), "/test/index"); +}); + +/* Pass Input dir */ +test("Easy slug, input dir", t => { + let fs = new TemplateFileSlug("./file.html", "."); + t.is(fs.getSlug(), "file"); + t.is(fs.getFullPathWithoutExtension(), "/file"); +}); + +test("Easy slug with dot, input dir", t => { + let fs = new TemplateFileSlug("./file.test.html", "."); + t.is(fs.getSlug(), "file.test"); + t.is(fs.getFullPathWithoutExtension(), "/file.test"); +}); + +test("Easy slug with date, input dir", t => { + let fs = new TemplateFileSlug("./2018-01-01-file.html", "."); + t.is(fs.getSlug(), "file"); + t.is(fs.getFullPathWithoutExtension(), "/file"); +}); + +test("Easy slug with date and dot in slug, input dir", t => { + let fs = new TemplateFileSlug("./2018-01-01-file.test.html", "."); + t.is(fs.getSlug(), "file.test"); + t.is(fs.getFullPathWithoutExtension(), "/file.test"); +}); + +test("Easy slug, index, input dir", t => { + let fs = new TemplateFileSlug("./index.html", "."); + t.is(fs.getSlug(), ""); + t.is(fs.getFullPathWithoutExtension(), "/index"); +}); + +test("Easy slug with date, index, input dir", t => { + let fs = new TemplateFileSlug("./2018-01-01-index.html", "."); + t.is(fs.getSlug(), ""); + t.is(fs.getFullPathWithoutExtension(), "/index"); +}); + +/* Directories and Input Dir */ + +test("Easy slug with dir and input dir", t => { + let fs = new TemplateFileSlug("./test/file.html", "./test"); + t.is(fs.getSlug(), "file"); + t.is(fs.getFullPathWithoutExtension(), "/file"); +}); + +test("Easy slug with dot with dir and input dir", t => { + let fs = new TemplateFileSlug("./test/file.test.html", "./test"); + t.is(fs.getSlug(), "file.test"); + t.is(fs.getFullPathWithoutExtension(), "/file.test"); +}); + +test("Easy slug with date with dir and input dir", t => { + let fs = new TemplateFileSlug("./test/2018-01-01-file.html", "./test"); + t.is(fs.getSlug(), "file"); + t.is(fs.getFullPathWithoutExtension(), "/file"); +}); + +test("Easy slug with date and dot in slug with dir and input dir", t => { + let fs = new TemplateFileSlug("./test/2018-01-01-file.test.html", "./test"); + t.is(fs.getSlug(), "file.test"); + t.is(fs.getFullPathWithoutExtension(), "/file.test"); +}); + +test("Easy slug, index with dir and input dir", t => { + let fs = new TemplateFileSlug("./test/index.html", "./test"); + t.is(fs.getSlug(), ""); + t.is(fs.getFullPathWithoutExtension(), "/index"); +}); + +test("Easy slug with date, index with dir and input dir", t => { + let fs = new TemplateFileSlug("./test/2018-01-01-index.html", "./test"); + t.is(fs.getSlug(), ""); + t.is(fs.getFullPathWithoutExtension(), "/index"); +}); + +test("Easy slug with multiple dirs", t => { + let fs = new TemplateFileSlug("./dir1/dir2/dir3/file.html", "."); + t.is(fs.getSlug(), "file"); + t.is(fs.getFullPathWithoutExtension(), "/dir1/dir2/dir3/file"); +}); + +test("Easy slug with multiple dirs and an index file", t => { + let fs = new TemplateFileSlug("./dir1/dir2/dir3/index.html", "."); + t.is(fs.getSlug(), "dir3"); + t.is(fs.getFullPathWithoutExtension(), "/dir1/dir2/dir3/index"); +}); diff --git a/test/TemplateGlobTest.js b/test/TemplateGlobTest.js new file mode 100644 index 000000000..df69c40be --- /dev/null +++ b/test/TemplateGlobTest.js @@ -0,0 +1,132 @@ +import test from "ava"; +import fastglob from "fast-glob"; +import TemplatePath from "../src/TemplatePath"; +import TemplateGlob from "../src/TemplateGlob"; + +test("TemplatePath assumptions", t => { + t.is(TemplatePath.normalize("ignoredFolder"), "ignoredFolder"); + t.is(TemplatePath.normalize("./ignoredFolder"), "ignoredFolder"); + t.is(TemplatePath.normalize("./ignoredFolder/"), "ignoredFolder"); +}); + +test("Normalize string argument", t => { + t.deepEqual(TemplateGlob.map("views"), "./views"); + t.deepEqual(TemplateGlob.map("views/"), "./views"); + t.deepEqual(TemplateGlob.map("./views"), "./views"); + t.deepEqual(TemplateGlob.map("./views/"), "./views"); +}); + +test("Normalize with nots", t => { + t.deepEqual(TemplateGlob.map("!views"), "!./views"); + t.deepEqual(TemplateGlob.map("!views/"), "!./views"); + t.deepEqual(TemplateGlob.map("!./views"), "!./views"); + t.deepEqual(TemplateGlob.map("!./views/"), "!./views"); +}); + +test("Normalize with globstar", t => { + t.deepEqual(TemplateGlob.map("!views/**"), "!./views/**"); + t.deepEqual(TemplateGlob.map("!./views/**"), "!./views/**"); +}); + +test("Normalize with globstar and star", t => { + t.deepEqual(TemplateGlob.map("!views/**/*"), "!./views/**/*"); + t.deepEqual(TemplateGlob.map("!./views/**/*"), "!./views/**/*"); +}); + +test("Normalize with globstar and star and file extension", t => { + t.deepEqual(TemplateGlob.map("!views/**/*.json"), "!./views/**/*.json"); + t.deepEqual(TemplateGlob.map("!./views/**/*.json"), "!./views/**/*.json"); +}); + +test("NormalizePath with globstar and star and file extension", t => { + t.deepEqual( + TemplateGlob.normalizePath("views", "/", "**/*.json"), + "./views/**/*.json" + ); + t.deepEqual( + TemplateGlob.normalizePath("./views", "/", "**/*.json"), + "./views/**/*.json" + ); +}); + +test("NormalizePath with globstar and star and file extension (errors)", t => { + t.throws(() => { + TemplateGlob.normalizePath("!views/**/*.json"); + }); + + t.throws(() => { + TemplateGlob.normalizePath("!views", "/", "**/*.json"); + }); + + t.throws(() => { + TemplateGlob.normalizePath("!./views/**/*.json"); + }); + + t.throws(() => { + TemplateGlob.normalizePath("!./views", "/", "**/*.json"); + }); +}); + +test("Normalize array argument", t => { + t.deepEqual(TemplateGlob.map(["views", "content"]), ["./views", "./content"]); + t.deepEqual(TemplateGlob.map("views/"), "./views"); + t.deepEqual(TemplateGlob.map("./views"), "./views"); + t.deepEqual(TemplateGlob.map("./views/"), "./views"); +}); + +test("matuzo project issue with fastglob assumptions", async t => { + let dotslashincludes = await fastglob( + TemplateGlob.map([ + "./test/stubs/globby/**/*.html", + "!./test/stubs/globby/_includes/**/*", + "!./test/stubs/globby/_data/**/*" + ]) + ); + + t.is( + dotslashincludes.filter(function(file) { + return file.indexOf("_includes") > -1; + }).length, + 0 + ); + + let globincludes = await fastglob( + TemplateGlob.map([ + "test/stubs/globby/**/*.html", + "!./test/stubs/globby/_includes/**/*", + "!./test/stubs/globby/_data/**/*" + ]) + ); + t.is( + globincludes.filter(function(file) { + return file.indexOf("_includes") > -1; + }).length, + 0 + ); +}); + +test("fastglob assumptions", async t => { + let glob = await fastglob("test/stubs/ignoredFolder/**"); + t.is(glob.length, 1); + + let glob2 = await fastglob("test/stubs/ignoredFolder/**/*"); + t.is(glob2.length, 1); + + let glob3 = await fastglob([ + "./test/stubs/ignoredFolder/**/*.md", + "!./test/stubs/ignoredFolder/**" + ]); + t.is(glob3.length, 0); + + let glob4 = await fastglob([ + "./test/stubs/ignoredFolder/*.md", + "!./test/stubs/ignoredFolder/**" + ]); + t.is(glob4.length, 0); + + let glob5 = await fastglob([ + "./test/stubs/ignoredFolder/ignored.md", + "!./test/stubs/ignoredFolder/**" + ]); + t.is(glob5.length, 0); +}); diff --git a/test/TemplateLayoutPathResolverTest.js b/test/TemplateLayoutPathResolverTest.js new file mode 100644 index 000000000..09990fae7 --- /dev/null +++ b/test/TemplateLayoutPathResolverTest.js @@ -0,0 +1,189 @@ +import test from "ava"; +import TemplateLayoutPathResolver from "../src/TemplateLayoutPathResolver"; + +test("Layout", t => { + t.is( + new TemplateLayoutPathResolver("default", "./test/stubs").getFileName(), + "default.ejs" + ); +}); + +test("Layout already has extension", t => { + t.is( + new TemplateLayoutPathResolver("default.ejs", "./test/stubs").getFileName(), + "default.ejs" + ); +}); + +test("Layout (uses empty string includes folder)", t => { + let res = new TemplateLayoutPathResolver( + "includesemptystring", + "./test/stubs" + ); + res.config = { + templateFormats: ["ejs"], + dir: { + includes: "" + } + }; + + t.is(res.getFileName(), "includesemptystring.ejs"); +}); + +test("Layout (uses empty string includes folder) already has extension", t => { + let res = new TemplateLayoutPathResolver( + "includesemptystring.ejs", + "./test/stubs" + ); + res.config = { + templateFormats: ["ejs"], + dir: { + includes: "" + } + }; + + t.is(res.getFileName(), "includesemptystring.ejs"); +}); + +test("Layout (uses layouts folder)", t => { + let res = new TemplateLayoutPathResolver("layoutsdefault", "./test/stubs"); + res.config = { + templateFormats: ["ejs"], + dir: { + layouts: "_layouts", + includes: "_includes" + } + }; + + t.is(res.getFileName(), "layoutsdefault.ejs"); +}); + +test("Layout (uses layouts folder) already has extension", t => { + let res = new TemplateLayoutPathResolver( + "layoutsdefault.ejs", + "./test/stubs" + ); + res.config = { + templateFormats: ["ejs"], + dir: { + layouts: "_layouts", + includes: "_includes" + } + }; + + t.is(res.getFileName(), "layoutsdefault.ejs"); +}); + +test("Layout (uses empty string layouts folder)", t => { + let res = new TemplateLayoutPathResolver( + "layoutsemptystring", + "./test/stubs" + ); + res.config = { + templateFormats: ["ejs"], + dir: { + layouts: "", + includes: "_includes" + } + }; + + t.is(res.getFileName(), "layoutsemptystring.ejs"); +}); + +test("Layout (uses empty string layouts folder) already has extension", t => { + let res = new TemplateLayoutPathResolver( + "layoutsemptystring.ejs", + "./test/stubs" + ); + res.config = { + templateFormats: ["ejs"], + dir: { + layouts: "", + includes: "_includes" + } + }; + + t.is(res.getFileName(), "layoutsemptystring.ejs"); +}); + +test("Layout subdir", t => { + t.is( + new TemplateLayoutPathResolver( + "layouts/inasubdir", + "./test/stubs" + ).getFileName(), + "layouts/inasubdir.njk" + ); +}); + +test("Layout subdir already has extension", t => { + t.is( + new TemplateLayoutPathResolver( + "layouts/inasubdir.njk", + "./test/stubs" + ).getFileName(), + "layouts/inasubdir.njk" + ); +}); + +test("Multiple layouts exist with the same file base, pick one", t => { + // pick the first one if multiple exist. + t.is( + new TemplateLayoutPathResolver("multiple", "./test/stubs").getFileName(), + "multiple.ejs" + ); +}); + +test("Multiple layouts exist but we are being explicitā€”layout already has extension", t => { + t.is( + new TemplateLayoutPathResolver( + "multiple.ejs", + "./test/stubs" + ).getFileName(), + "multiple.ejs" + ); + t.is( + new TemplateLayoutPathResolver("multiple.md", "./test/stubs").getFileName(), + "multiple.md" + ); +}); + +test("Layout is aliased to a new location", t => { + let tl = new TemplateLayoutPathResolver("post", "./test/stubs"); + tl.addLayoutAlias("post", "layouts/post.ejs"); + tl.init(); + + t.is(tl.getFileName(), "layouts/post.ejs"); +}); + +test("Global default with empty string alias", t => { + let tl = new TemplateLayoutPathResolver("", "./test/stubs"); + tl.addLayoutAlias("", "layouts/post.ejs"); + tl.init(); + + t.is(tl.getFileName(), "layouts/post.ejs"); +}); + +test("Global default with empty string alias (but no alias exists for this instance)", t => { + let tl = new TemplateLayoutPathResolver("layout.ejs", "./test/stubs"); + tl.addLayoutAlias("", "layouts/post.ejs"); + tl.init(); + + t.throws(() => { + tl.getFileName(); + }); +}); + +test("Layout has no alias and does not exist", async t => { + let tl = new TemplateLayoutPathResolver("shouldnotexist", "./test/stubs"); + tl.addLayoutAlias("post", "layouts/post.ejs"); + tl.init(); + + t.throws(() => { + tl.getFileName(); + }); + + t.throws(() => { + tl.getFullPath(); + }); +}); diff --git a/test/TemplateLayoutTest.js b/test/TemplateLayoutTest.js index b08b3bd79..6a0035f4e 100644 --- a/test/TemplateLayoutTest.js +++ b/test/TemplateLayoutTest.js @@ -1,11 +1,131 @@ import test from "ava"; -import Layout from "../src/TemplateLayout"; +import TemplateLayout from "../src/TemplateLayout"; -test(t => { - t.is(new Layout("default", "./test/stubs").findFileName(), "default.ejs"); +test("Creation", t => { + t.is( + new TemplateLayout("base", "./test/stubs").getInputPath(), + "test/stubs/_includes/base.njk" + ); + + t.throws(() => { + new TemplateLayout("doesnotexist", "./test/stubs").getInputPath(); + }); +}); + +test("Get Front Matter Data", async t => { + let tl = new TemplateLayout("layouts/layout-inherit-a.njk", "./test/stubs"); + t.is(tl.getInputPath(), "test/stubs/_includes/layouts/layout-inherit-a.njk"); + + t.deepEqual(await tl.getData(), { + inherits: "a", + secondinherits: "b", + thirdinherits: "c" + }); + t.deepEqual(await tl.getData(), { + inherits: "a", + secondinherits: "b", + thirdinherits: "c" + }); +}); + +test("Augment data with layoutContent", async t => { + t.deepEqual(TemplateLayout.augmentDataWithContent(null, null), { + content: null, + layoutContent: null, + _layoutContent: null + }); + + t.deepEqual(TemplateLayout.augmentDataWithContent(null, "Test"), { + content: "Test", + layoutContent: "Test", + _layoutContent: "Test" + }); + + t.deepEqual(TemplateLayout.augmentDataWithContent({}, "Test 2"), { + content: "Test 2", + layoutContent: "Test 2", + _layoutContent: "Test 2" + }); + + t.deepEqual( + TemplateLayout.augmentDataWithContent({ content: "Abc" }, "Test 3"), + { + content: "Test 3", + layoutContent: "Test 3", + _layoutContent: "Test 3" + } + ); +}); + +test("Render Layout", async t => { + let tl = new TemplateLayout("layouts/layout-inherit-a.njk", "./test/stubs"); + t.is( + (await tl.render({ + inherits: "a", + secondinherits: "b", + thirdinherits: "c" + })).trim(), + "a b a c" + ); }); -test(t => { - // pick the first one if multiple exist. - t.is(new Layout("multiple", "./test/stubs").findFileName(), "multiple.ejs"); +test("Render Layout (Pass in template content)", async t => { + let tl = new TemplateLayout("layouts/layout-inherit-a.njk", "./test/stubs"); + t.is( + (await tl.render( + { inherits: "a", secondinherits: "b", thirdinherits: "c" }, + "TEMPLATE_CONTENT" + )).trim(), + "TEMPLATE_CONTENT a b a c" + ); +}); + +test("Render Layout (Pass in undefined template content)", async t => { + let tl = new TemplateLayout("layouts/layout-contentdump.njk", "./test/stubs"); + t.is( + await tl.render( + { inherits: "a", secondinherits: "b", thirdinherits: "c" }, + undefined + ), + "this is bad a b a c" + ); +}); + +test("Render Layout (Pass in null template content)", async t => { + let tl = new TemplateLayout("layouts/layout-contentdump.njk", "./test/stubs"); + t.is( + await tl.render( + { inherits: "a", secondinherits: "b", thirdinherits: "c" }, + null + ), + " a b a c" + ); +}); + +test("Render Layout (Pass in empty template content)", async t => { + let tl = new TemplateLayout("layouts/layout-contentdump.njk", "./test/stubs"); + t.is( + await tl.render( + { inherits: "a", secondinherits: "b", thirdinherits: "c" }, + "" + ), + " a b a c" + ); +}); + +test("Cache Duplicates (use full key for cache)", async t => { + // if two different layouts used the same filename but had different inputdirs, make sure templatelayout cache is unique + let tla = new TemplateLayout( + "layout.njk", + "./test/stubs/templateLayoutCacheDuplicates" + ); + t.is((await tla.render({})).trim(), "Hello A"); + + let tlb = new TemplateLayout( + "layout.njk", + "./test/stubs/templateLayoutCacheDuplicates-b" + ); + t.is((await tlb.render({})).trim(), "Hello B"); + + t.is((await tla.render({})).trim(), "Hello A"); }); diff --git a/test/TemplateMapTest.js b/test/TemplateMapTest.js new file mode 100644 index 000000000..b14c0339f --- /dev/null +++ b/test/TemplateMapTest.js @@ -0,0 +1,1109 @@ +import test from "ava"; +import Template from "../src/Template"; +import TemplateMap from "../src/TemplateMap"; +import TemplateCollection from "../src/TemplateCollection"; +import UsingCircularTemplateContentReferenceError from "../src/Errors/UsingCircularTemplateContentReferenceError"; +import normalizeNewLines from "./Util/normalizeNewLines"; + +let tmpl1 = new Template( + "./test/stubs/templateMapCollection/test1.md", + "./test/stubs/", + "./test/stubs/_site" +); +let tmpl2 = new Template( + "./test/stubs/templateMapCollection/test2.md", + "./test/stubs/", + "./test/stubs/_site" +); +let tmpl4 = new Template( + "./test/stubs/templateMapCollection/test4.md", + "./test/stubs/", + "./test/stubs/_site" +); +let tmpl5 = new Template( + "./test/stubs/templateMapCollection/test5.md", + "./test/stubs/", + "./test/stubs/_site" +); + +test("TemplateMap has collections added", async t => { + let tm = new TemplateMap(); + await tm.add(tmpl1); + await tm.add(tmpl2); + await tm.cache(); + + t.is(tm.getMap().length, 2); + t.is(tm.collection.getAll().length, 2); +}); + +test("TemplateMap compared to Collection API", async t => { + let tm = new TemplateMap(); + await tm.add(tmpl1); + await tm.add(tmpl4); + await tm.cache(); + + let map = tm.getMap(); + t.deepEqual(map[0].template, tmpl1); + t.deepEqual(map[0].data.collections.post[0].template, tmpl1); + t.deepEqual(map[1].template, tmpl4); + t.deepEqual(map[1].data.collections.post[1].template, tmpl4); + + let c = new TemplateCollection(); + await c._testAddTemplate(tmpl1); + await c._testAddTemplate(tmpl4); + + let posts = c.getFilteredByTag("post"); + t.is(posts.length, 2); + t.deepEqual(posts[0].template, tmpl1); + t.deepEqual(posts[1].template, tmpl4); +}); + +test("populating the collection twice should clear the previous values (--watch was making it cumulative)", async t => { + let tm = new TemplateMap(); + await tm.add(tmpl1); + await tm.add(tmpl2); + + await tm.cache(); + await tm.cache(); + + t.is(tm.getMap().length, 2); +}); + +test("TemplateMap adds collections data and has templateContent values", async t => { + let tm = new TemplateMap(); + await tm.add(tmpl1); + await tm.add(tmpl2); + + let map = tm.getMap(); + t.falsy(map[0].data.collections); + t.falsy(map[1].data.collections); + + await tm.cache(); + + t.truthy(map[0]._pages[0].templateContent); + t.truthy(map[1]._pages[0].templateContent); + t.truthy(map[0].data.collections); + t.truthy(map[1].data.collections); + t.is(map[0].data.collections.post.length, 1); + t.is(map[0].data.collections.all.length, 2); + t.is(map[1].data.collections.post.length, 1); + t.is(map[1].data.collections.all.length, 2); + + t.is( + await map[0].template._testRenderWithoutLayouts(map[0].data), + map[0]._pages[0].templateContent + ); + t.is( + await map[1].template._testRenderWithoutLayouts(map[1].data), + map[1]._pages[0].templateContent + ); +}); + +test("TemplateMap circular references (map without templateContent)", async t => { + let tm = new TemplateMap(); + await tm.add( + new Template( + "./test/stubs/templateMapCollection/test3.md", + "./test/stubs/", + "./test/stubs/_site" + ) + ); + + let map = tm.getMap(); + t.falsy(map[0].data.collections); + + await tm.cache(); + t.truthy(map[0]._pages[0].templateContent); + t.truthy(map[0].data.collections); + t.is(map[0].data.collections.all.length, 1); + + t.is( + await map[0].template._testRenderWithoutLayouts(map[0].data), + map[0]._pages[0].templateContent + ); +}); + +test("TemplateMap circular references (map.templateContent)", async t => { + let tm = new TemplateMap(); + await tm.add( + new Template( + "./test/stubs/templateMapCollection/templateContent.md", + "./test/stubs/", + "./test/stubs/_site" + ) + ); + + let map = tm.getMap(); + t.falsy(map[0].data.collections); + + await t.throwsAsync( + async () => { + await tm.cache(); + }, + { + instanceOf: UsingCircularTemplateContentReferenceError + } + ); +}); + +test("Issue #115, mixing pagination and collections", async t => { + let tmplFoos = new Template( + "./test/stubs/issue-115/template-foos.liquid", + "./test/stubs/", + "./test/stubs/_site" + ); + let tmplBars = new Template( + "./test/stubs/issue-115/template-bars.liquid", + "./test/stubs/", + "./test/stubs/_site" + ); + let tmplIndex = new Template( + "./test/stubs/issue-115/index.liquid", + "./test/stubs/", + "./test/stubs/_site" + ); + + let tm = new TemplateMap(); + await tm.add(tmplFoos); + await tm.add(tmplBars); + await tm.add(tmplIndex); + await tm.cache(); + + let map = tm.getMap(); + t.is(map.length, 3); + t.deepEqual(map[2].template, tmplIndex); + + let collections = await tm._testGetAllCollectionsData(); + t.is(Object.keys(collections.all).length, 3); + t.is(Object.keys(collections.foos).length, 1); + t.is(Object.keys(collections.bars).length, 1); + t.is(Object.keys((await tm.getCollectionsData()).all).length, 3); + t.is(Object.keys((await tm.getCollectionsData()).foos).length, 1); + t.is(Object.keys((await tm.getCollectionsData()).bars).length, 1); + + t.truthy(map[0].data.collections); + t.truthy(map[1].data.collections); + t.truthy(map[2].data.collections); + t.truthy(Object.keys(map[2].data.collections).length); + + t.is(Object.keys(map[0].data.collections.all).length, 3); + t.is(Object.keys(map[0].data.collections.foos).length, 1); + t.is(Object.keys(map[0].data.collections.bars).length, 1); + + t.is(Object.keys(map[1].data.collections.all).length, 3); + t.is(Object.keys(map[1].data.collections.foos).length, 1); + t.is(Object.keys(map[1].data.collections.bars).length, 1); + + t.is(Object.keys(map[2].data.collections.all).length, 3); + t.is(Object.keys(map[2].data.collections.foos).length, 1); + t.is(Object.keys(map[2].data.collections.bars).length, 1); + + let entry = await map[2].template.getRenderedTemplates(map[2].data); + t.deepEqual( + normalizeNewLines(entry[0].templateContent), + `This page is foos +This page is bars +` + ); +}); + +test("Issue #115 with layout, mixing pagination and collections", async t => { + let tmplFoos = new Template( + "./test/stubs/issue-115/template-foos.liquid", + "./test/stubs/", + "./test/stubs/_site" + ); + let tmplBars = new Template( + "./test/stubs/issue-115/template-bars.liquid", + "./test/stubs/", + "./test/stubs/_site" + ); + let tmplIndex = new Template( + "./test/stubs/issue-115/index-with-layout.liquid", + "./test/stubs/", + "./test/stubs/_site" + ); + + let tm = new TemplateMap(); + await tm.add(tmplFoos); + await tm.add(tmplBars); + await tm.add(tmplIndex); + await tm.cache(); + + let map = tm.getMap(); + t.is(map.length, 3); + t.deepEqual(map[2].template, tmplIndex); + + let collections = await tm._testGetAllCollectionsData(); + t.is(Object.keys(collections.all).length, 3); + t.is(Object.keys(collections.foos).length, 1); + t.is(Object.keys(collections.bars).length, 1); + t.is(Object.keys((await tm.getCollectionsData()).all).length, 3); + t.is(Object.keys((await tm.getCollectionsData()).foos).length, 1); + t.is(Object.keys((await tm.getCollectionsData()).bars).length, 1); + + t.truthy(map[0].data.collections); + t.truthy(map[1].data.collections); + t.truthy(map[2].data.collections); + t.truthy(Object.keys(map[2].data.collections).length); + + t.is(Object.keys(map[0].data.collections.all).length, 3); + t.is(Object.keys(map[0].data.collections.foos).length, 1); + t.is(Object.keys(map[0].data.collections.bars).length, 1); + + t.is(Object.keys(map[1].data.collections.all).length, 3); + t.is(Object.keys(map[1].data.collections.foos).length, 1); + t.is(Object.keys(map[1].data.collections.bars).length, 1); + + t.is(Object.keys(map[2].data.collections.all).length, 3); + t.is(Object.keys(map[2].data.collections.foos).length, 1); + t.is(Object.keys(map[2].data.collections.bars).length, 1); + + let entry = await map[2].template.getRenderedTemplates(map[2].data); + t.deepEqual( + normalizeNewLines(entry[0].templateContent), + `This page is foos +This page is bars +` + ); +}); + +test("TemplateMap adds collections data and has page data values using .cache()", async t => { + let tm = new TemplateMap(); + await tm.add(tmpl1); + await tm.add(tmpl2); + + let map = tm.getMap(); + await tm.cache(); + t.is(map[0].data.page.url, "/templateMapCollection/test1/"); + t.is( + map[0].data.page.outputPath, + "./test/stubs/_site/templateMapCollection/test1/index.html" + ); + t.is( + map[0].data.page.inputPath, + "./test/stubs/templateMapCollection/test1.md" + ); + t.is(map[0].data.page.fileSlug, "test1"); + t.truthy(map[0].data.page.date); +}); + +test("TemplateMap adds collections data and has page data values using .getCollectionsData()", async t => { + let tm = new TemplateMap(); + await tm.add(tmpl1); + await tm.add(tmpl2); + + let collections = await tm.getCollectionsData(); + t.is(collections.all[0].url, "/templateMapCollection/test1/"); + t.is( + collections.all[0].outputPath, + "./test/stubs/_site/templateMapCollection/test1/index.html" + ); + + t.is(collections.all[0].data.page.url, "/templateMapCollection/test1/"); + t.is( + collections.all[0].data.page.outputPath, + "./test/stubs/_site/templateMapCollection/test1/index.html" + ); + t.is( + collections.all[0].data.page.inputPath, + "./test/stubs/templateMapCollection/test1.md" + ); + t.is(collections.all[0].data.page.fileSlug, "test1"); +}); + +test("Url should be available in user config collections API calls", async t => { + let tm = new TemplateMap(); + await tm.add(tmpl1); + await tm.add(tmpl2); + tm.setUserConfigCollections({ + userCollection: function(collection) { + let all = collection.getAll(); + return all; + } + }); + + let collections = await tm.getCollectionsData(); + t.truthy(collections.userCollection); + t.truthy(collections.userCollection.length); + t.is(collections.userCollection[0].url, "/templateMapCollection/test1/"); + t.is( + collections.userCollection[0].outputPath, + "./test/stubs/_site/templateMapCollection/test1/index.html" + ); + + t.is( + collections.userCollection[0].data.page.url, + "/templateMapCollection/test1/" + ); + t.is( + collections.userCollection[0].data.page.outputPath, + "./test/stubs/_site/templateMapCollection/test1/index.html" + ); +}); + +test("Url should be available in user config collections API calls (test in callback)", async t => { + let tm = new TemplateMap(); + tm.setUserConfigCollections({ + userCollection: function(collection) { + let all = collection.getAll(); + t.is(all[0].url, "/templateMapCollection/test1/"); + t.is( + all[0].outputPath, + "./test/stubs/_site/templateMapCollection/test1/index.html" + ); + t.is(all[1].url, "/templateMapCollection/test2/"); + t.is( + all[1].outputPath, + "./test/stubs/_site/templateMapCollection/test2/index.html" + ); + + return all; + } + }); + + await tm.add(tmpl1); + await tm.add(tmpl2); + await tm.cache(); +}); + +test("Should be able to paginate a tag generated collection", async t => { + let tm = new TemplateMap(); + await tm.add(tmpl1); + await tm.add(tmpl2); + + let pagedTmpl = new Template( + "./test/stubs/templateMapCollection/paged-tag.md", + "./test/stubs/", + "./test/stubs/_site" + ); + await tm.add(pagedTmpl); + + let collections = await tm.getCollectionsData(); + t.truthy(collections.dog); + t.truthy(collections.dog.length); +}); + +test("Should be able to paginate a user config collection", async t => { + let tm = new TemplateMap(); + await tm.add(tmpl1); + await tm.add(tmpl2); + + let pagedTmpl = new Template( + "./test/stubs/templateMapCollection/paged-cfg.md", + "./test/stubs/", + "./test/stubs/_site" + ); + await tm.add(pagedTmpl); + + tm.setUserConfigCollections({ + userCollection: function(collection) { + let all = collection.getFilteredByTag("dog"); + return all; + } + }); + + let collections = await tm.getCollectionsData(); + t.truthy(collections.userCollection); + t.truthy(collections.userCollection.length); +}); + +test("Should be able to paginate a user config collection (uses rendered permalink)", async t => { + let tm = new TemplateMap(); + await tm.add(tmpl1); + await tm.add(tmpl2); + + let pagedTmpl = new Template( + "./test/stubs/templateMapCollection/paged-cfg-permalink.md", + "./test/stubs/", + "./test/stubs/_site" + ); + await tm.add(pagedTmpl); + + tm.setUserConfigCollections({ + userCollection: function(collection) { + let all = collection.getFilteredByTag("dog"); + t.is(all[0].url, "/templateMapCollection/test1/"); + t.is( + all[0].outputPath, + "./test/stubs/_site/templateMapCollection/test1/index.html" + ); + return all; + } + }); + + let collections = await tm.getCollectionsData(); + t.truthy(collections.userCollection); + t.truthy(collections.userCollection.length); + + let urls = []; + for (let item of collections.all) { + urls.push(item.url); + } + t.is(urls.indexOf("/test-title/hello/") > -1, true); +}); + +test("Should be able to paginate a user config collection (paged template is also tagged)", async t => { + let tm = new TemplateMap(); + await tm.add(tmpl1); // has dog tag + await tm.add(tmpl2); // does not have dog tag + await tm.add(tmpl4); // has dog tag + + let pagedTmpl = new Template( + "./test/stubs/templateMapCollection/paged-cfg-tagged.md", + "./test/stubs/", + "./test/stubs/_site" + ); + await tm.add(pagedTmpl); + + tm.setUserConfigCollections({ + userCollection: function(collection) { + let all = collection.getFilteredByTag("dog"); + return all; + } + }); + + let collections = await tm.getCollectionsData(); + t.is(collections.dog.length, 2); + + t.truthy(collections.haha); + t.is(collections.haha.length, 1); + t.is(collections.haha[0].url, "/templateMapCollection/paged-cfg-tagged/"); +}); + +test("Should be able to paginate a user config collection (paged template is also tagged, add all pages to collections)", async t => { + let tm = new TemplateMap(); + await tm.add(tmpl1); // has dog tag + await tm.add(tmpl2); // does not have dog tag + await tm.add(tmpl4); // has dog tag + + let pagedTmpl = new Template( + "./test/stubs/templateMapCollection/paged-cfg-tagged-apply-to-all.md", + "./test/stubs/", + "./test/stubs/_site" + ); + await tm.add(pagedTmpl); + + tm.setUserConfigCollections({ + userCollection: function(collection) { + let all = collection.getFilteredByTag("dog"); + return all; + } + }); + + let collections = await tm.getCollectionsData(); + t.is(collections.dog.length, 2); + + t.truthy(collections.haha); + t.is(collections.haha.length, 2); + t.is( + collections.haha[0].url, + "/templateMapCollection/paged-cfg-tagged-apply-to-all/" + ); + t.is( + collections.haha[1].url, + "/templateMapCollection/paged-cfg-tagged-apply-to-all/1/" + ); +}); + +test("Should be able to paginate a user config collection (paged template is also tagged, uses custom rendered permalink)", async t => { + let tm = new TemplateMap(); + await tm.add(tmpl1); // has dog tag + await tm.add(tmpl2); // does not have dog tag + await tm.add(tmpl4); // has dog tag + + let pagedTmpl = new Template( + "./test/stubs/templateMapCollection/paged-cfg-tagged-permalink.md", + "./test/stubs/", + "./test/stubs/_site" + ); + await tm.add(pagedTmpl); + + tm.setUserConfigCollections({ + userCollection: function(collection) { + let all = collection.getFilteredByTag("dog"); + return all; + } + }); + + let collections = await tm.getCollectionsData(); + t.truthy(collections.haha); + t.is(collections.haha.length, 1); + t.is(collections.haha[0].url, "/test-title/goodbye/"); +}); + +test("Should be able to paginate a user config collection (paged template is also tagged, uses custom rendered permalink, add all pages to collections)", async t => { + let tm = new TemplateMap(); + await tm.add(tmpl1); // has dog tag + await tm.add(tmpl2); // does not have dog tag + await tm.add(tmpl4); // has dog tag + + let pagedTmpl = new Template( + "./test/stubs/templateMapCollection/paged-cfg-tagged-permalink-apply-to-all.md", + "./test/stubs/", + "./test/stubs/_site" + ); + await tm.add(pagedTmpl); + + tm.setUserConfigCollections({ + userCollection: function(collection) { + let all = collection.getFilteredByTag("dog"); + return all; + } + }); + + let collections = await tm.getCollectionsData(); + t.truthy(collections.haha); + t.is(collections.haha.length, 2); + t.is(collections.haha[0].url, "/test-title/goodbye/"); + t.is(collections.haha[1].url, "/test-title-4/goodbye/"); +}); + +test("TemplateMap render and templateContent are the same (templateContent doesnā€™t have layout but makes proper use of layout front matter data)", async t => { + let tm = new TemplateMap(); + let tmplLayout = new Template( + "./test/stubs/templateMapCollection/testWithLayout.md", + "./test/stubs/", + "./test/stubs/_site" + ); + + await tm.add(tmplLayout); + + let map = tm.getMap(); + await tm.cache(); + t.is(map[0]._pages[0].templateContent.trim(), "

Inherited

"); + t.is((await map[0].template.render(map[0].data)).trim(), "

Inherited

"); +}); + +test("Should be able to paginate a tag generated collection (and it has templateContent)", async t => { + let tm = new TemplateMap(); + await tm.add(tmpl1); // has dog tag + await tm.add(tmpl2); // does not have dog tag + await tm.add(tmpl4); // has dog tag + + let pagedTmpl = new Template( + "./test/stubs/templateMapCollection/paged-tag-dogs-templateContent.md", + "./test/stubs/", + "./test/stubs/_site" + ); + await tm.add(pagedTmpl); + + await tm.cache(); + + let pagedMapEntry = tm.getMapEntryForInputPath( + "./test/stubs/templateMapCollection/paged-tag-dogs-templateContent.md" + ); + + let templates = await pagedMapEntry.template.getRenderedTemplates( + pagedMapEntry.data + ); + t.is(templates.length, 2); + t.is(templates[0].data.pagination.pageNumber, 0); + t.is(templates[1].data.pagination.pageNumber, 1); + + t.is( + templates[0].templateContent.trim(), + `

Before

+

Test 1

+

After

` + ); + t.is( + templates[1].templateContent.trim(), + `

Before

+

Test 4

+

After

` + ); +}); + +test("Should be able to paginate a tag generated collection when aliased (and it has templateContent)", async t => { + let tm = new TemplateMap(); + await tm.add(tmpl1); // has dog tag + await tm.add(tmpl2); // does not have dog tag + await tm.add(tmpl4); // has dog tag + + let pagedTmpl = new Template( + "./test/stubs/templateMapCollection/paged-tag-dogs-templateContent-alias.md", + "./test/stubs/", + "./test/stubs/_site" + ); + await tm.add(pagedTmpl); + + await tm.cache(); + + let pagedMapEntry = tm.getMapEntryForInputPath( + "./test/stubs/templateMapCollection/paged-tag-dogs-templateContent-alias.md" + ); + + let templates = await pagedMapEntry.template.getRenderedTemplates( + pagedMapEntry.data + ); + + t.is(templates.length, 1); + t.is(templates[0].data.pagination.pageNumber, 0); + t.is( + templates[0].templateContent.trim(), + `

Before

+

Test 1

+

Test 4

+

After

` + ); +}); + +test("Issue #253: Paginated template with a tag should put multiple pages into a collection", async t => { + let tm = new TemplateMap(); + await tm.add(tmpl1); + await tm.add(tmpl2); + await tm.add(tmpl4); + + let pagedTmpl = new Template( + "./test/stubs/tagged-pagination-multiples/test.njk", + "./test/stubs/", + "./test/stubs/_site" + ); + await tm.add(pagedTmpl); + + // TODO test user config collections (no actual tests against this collection yet) + tm.setUserConfigCollections({ + userCollection: function(collection) { + let all = collection.getFilteredByTag("dog"); + return all; + } + }); + + let collections = await tm.getCollectionsData(); + t.is(collections.dog.length, 2); + + t.truthy(collections.haha); + t.is(collections.haha.length, 2); + t.is(collections.haha[0].url, "/tagged-pagination-multiples/test/"); + t.is(collections.haha[1].url, "/tagged-pagination-multiples/test/1/"); +}); + +test("getUserConfigCollectionNames", async t => { + let tm = new TemplateMap(); + + tm.setUserConfigCollections({ + userCollection: function(collection) { + return collection.getAll(); + }, + otherUserCollection: function(collection) { + return collection.getAll(); + } + }); + + t.deepEqual(tm.getUserConfigCollectionNames(), [ + "userCollection", + "otherUserCollection" + ]); +}); + +test("isUserConfigCollectionName", t => { + let tm = new TemplateMap(); + tm.setUserConfigCollections({ + userCollection: function(collection) { + return collection.getAll(); + } + }); + + t.is(tm.isUserConfigCollectionName("userCollection"), true); + t.is(tm.isUserConfigCollectionName("userCollection2"), false); +}); + +test("Dependency Map should have nodes that have no dependencies and no dependents", async t => { + let tm = new TemplateMap(); + await tm.add(tmpl1); + await tm.add(tmpl5); + + await tm.cache(); + + let deps = await tm.getMappedDependencies(); + t.true(deps.filter(dep => dep.indexOf("test5.md") > -1).length > 0); + + let collections = await tm.getCollectionsData(); + t.is(collections.all.length, 2); +}); + +test("Dependency Map should have include orphan user config collections (in the correct order)", async t => { + let tm = new TemplateMap(); + await tm.add(tmpl1); + await tm.add(tmpl5); + + tm.setUserConfigCollections({ + userCollection: function(collection) { + return collection.getAll(); + } + }); + + await tm.cache(); + + let deps = await tm.getMappedDependencies(); + t.true(deps.filter(dep => dep.indexOf("userCollection") > -1).length === 0); + + let delayedDeps = await tm.getDelayedMappedDependencies(); + t.true( + delayedDeps.filter(dep => dep.indexOf("userCollection") > -1).length > 0 + ); + + let collections = await tm.getCollectionsData(); + t.is(collections.all.length, 2); + t.is(collections.userCollection.length, 2); +}); + +test("Dependency graph assumptions", async t => { + const DependencyGraph = require("dependency-graph").DepGraph; + let graph = new DependencyGraph(); + + graph.addNode("all"); + graph.addNode("template-a"); + graph.addNode("template-b"); + graph.addNode("template-c"); + graph.addNode("userCollection"); + graph.addDependency("all", "template-a"); + graph.addDependency("all", "template-b"); + graph.addDependency("all", "template-c"); + graph.addDependency("userCollection", "all"); + t.deepEqual(graph.overallOrder(), [ + "template-a", + "template-b", + "template-c", + "all", + "userCollection" + ]); +}); + +test("Template pages should not have layouts when added to collections", async t => { + let tm = new TemplateMap(); + let tmpl = new Template( + "./test/stubs/collection-layout-wrap.njk", + "./test/stubs/", + "./test/stubs/_site" + ); + await tm.add(tmpl); + t.is(await tmpl.render(), "
Layout Test
"); + + let collections = await tm.getCollectionsData(); + t.is(collections.all.length, 1); + t.is(collections.all[0].templateContent, "Layout Test"); +}); + +test("Paginated template pages should not have layouts when added to collections", async t => { + let tm = new TemplateMap(); + + let pagedTmpl = new Template( + "./test/stubs/tagged-pagination-multiples-layout/test.njk", + "./test/stubs/", + "./test/stubs/_site" + ); + await tm.add(pagedTmpl); + + let collections = await tm.getCollectionsData(); + + t.is(collections.all.length, 3); + t.is(collections.all[0].templateContent, "one"); + t.is(collections.all[1].templateContent, "two"); + t.is(collections.all[2].templateContent, "three"); +}); + +test("Tag pages. Allow pagination over all collections a la `data: collections`", async t => { + let tm = new TemplateMap(); + + let pagedTmpl = new Template( + "./test/stubs/page-target-collections/tagpages.njk", + "./test/stubs/", + "./test/stubs/_site" + ); + await tm.add(pagedTmpl); + await tm.add(tmpl1); + await tm.add(tmpl2); + + let collections = await tm.getCollectionsData(); + t.is(collections.all.length, 3); + + let collectionTagPagesTemplateContents = new Set( + collections.all + .filter(function(entry) { + return entry.inputPath.endsWith("tagpages.njk"); + }) + .map(function(entry) { + return entry.templateContent.trim(); + }) + ); + t.deepEqual(collectionTagPagesTemplateContents, new Set(["post"])); +}); + +test("Tag pages (all pages added to collections). Allow pagination over all collections a la `data: collections`", async t => { + let tm = new TemplateMap(); + + let pagedTmpl = new Template( + "./test/stubs/page-target-collections/tagpagesall.njk", + "./test/stubs/", + "./test/stubs/_site" + ); + await tm.add(pagedTmpl); + await tm.add(tmpl1); + await tm.add(tmpl2); + + let collections = await tm.getCollectionsData(); + t.is(collections.all.length, 5); + + let collectionTagPagesTemplateContents = new Set( + collections.all + .filter(function(entry) { + return entry.inputPath.endsWith("tagpagesall.njk"); + }) + .map(function(entry) { + return entry.templateContent.trim(); + }) + ); + t.deepEqual( + collectionTagPagesTemplateContents, + new Set(["post", "dog", "cat"]) + ); +}); + +test("eleventyExcludeFromCollections", async t => { + let tm = new TemplateMap(); + await tm.add(tmpl1); + + let excludedTmpl = new Template( + "./test/stubs/eleventyExcludeFromCollections.njk", + "./test/stubs/", + "./test/stubs/_site" + ); + await tm.add(excludedTmpl); + + await tm.cache(); + + t.is(tm.getMap().length, 2); + + let collections = await tm.getCollectionsData(); + t.is(collections.all.length, 1); + t.is(collections.post.length, 1); + t.is(collections.dog.length, 1); +}); + +test("Paginate over collections.all", async t => { + let tm = new TemplateMap(); + + let pagedTmpl = new Template( + "./test/stubs/page-target-collections/paginateall.njk", + "./test/stubs/", + "./test/stubs/_site" + ); + await tm.add(pagedTmpl); + await tm.add(tmpl1); + await tm.add(tmpl2); + + let collections = await tm.getCollectionsData(); + t.is(collections.all.length, 4); + t.is( + collections.all.filter(function(entry) { + return entry.inputPath.endsWith("test1.md"); + }).length, + 1 + ); + t.is( + collections.all.filter(function(entry) { + return entry.inputPath.endsWith("test2.md"); + }).length, + 1 + ); + t.is( + collections.all.filter(function(entry) { + return entry.inputPath.endsWith("paginateall.njk"); + }).length, + 2 + ); + + let map = tm.getMap(); + t.is( + map[0].inputPath, + "./test/stubs/page-target-collections/paginateall.njk" + ); + t.is(map[0]._pages.length, 2); + t.is( + map[0]._pages[0].templateContent, + "INPUT PATH:./test/stubs/templateMapCollection/test1.md" + ); + t.is( + map[0]._pages[1].templateContent, + "INPUT PATH:./test/stubs/templateMapCollection/test2.md" + ); + t.is(map[1].inputPath, "./test/stubs/templateMapCollection/test1.md"); + t.is(map[1]._pages[0].templateContent.trim(), "

Test 1

"); + t.is(map[2].inputPath, "./test/stubs/templateMapCollection/test2.md"); + t.is(map[2]._pages[0].templateContent.trim(), "

Test 2

"); +}); + +test("Paginate over collections.all WITH a paginate over collections (tag pages)", async t => { + let tm = new TemplateMap(); + + let pagedTmpl = new Template( + "./test/stubs/page-target-collections/paginateall.njk", + "./test/stubs/", + "./test/stubs/_site" + ); + let tagPagesTmpl = new Template( + "./test/stubs/page-target-collections/tagpagesall.njk", + "./test/stubs/", + "./test/stubs/_site" + ); + await tm.add(pagedTmpl); + await tm.add(tagPagesTmpl); + await tm.add(tmpl1); + await tm.add(tmpl2); + + let collections = await tm.getCollectionsData(); + // 2 individual templates, 3 pages for tagpagesall, 5 pages from paginateall to paginate the 2+3 + t.is(collections.all.length, 10); +}); + +test("Test a transform with a layout (via templateMap)", async t => { + t.plan(7); + let tm = new TemplateMap(); + let tmpl = new Template( + "./test/stubs-475/transform-layout/transform-layout.njk", + "./test/stubs-475/", + "./test/stubs-475/_site" + ); + + tmpl.addLinter(function(content, inputPath, outputPath) { + // should be pre-transform content + t.is(content, "This is content."); + t.true(inputPath.endsWith("transform-layout.njk")); + t.true(outputPath.endsWith("transform-layout/index.html")); + }); + + tmpl.addTransform(function(content, outputPath) { + t.is(content, "This is content."); + t.true(outputPath.endsWith("transform-layout/index.html")); + return "OVERRIDE BY A TRANSFORM"; + }); + + await tm.add(tmpl); + + await tm.cache(); + t.is(tm.getMap().length, 1); + + for (let entry of tm.getMap()) { + for (let page of entry._pages) { + t.is( + await entry.template.renderPageEntry(entry, page), + "OVERRIDE BY A TRANSFORM" + ); + } + } +}); + +test("Async user collection addCollection method", async t => { + let tm = new TemplateMap(); + await tm.add(tmpl1); + tm.setUserConfigCollections({ + userCollection: async function(collection) { + return new Promise((resolve, reject) => { + setTimeout(function() { + resolve(collection.getAll()); + }, 50); + }); + } + }); + + let collections = await tm.getCollectionsData(); + t.is(collections.userCollection[0].url, "/templateMapCollection/test1/"); + + t.is(collections.userCollection[0].data.collections.userCollection.length, 1); +}); + +test("Duplicate permalinks in template map", async t => { + let tmpl1 = new Template( + "./test/stubs/permalink-conflicts/test1.md", + "./test/stubs/", + "./test/stubs/_site" + ); + let tmpl2 = new Template( + "./test/stubs/permalink-conflicts/test2.md", + "./test/stubs/", + "./test/stubs/_site" + ); + + let tm = new TemplateMap(); + await tm.add(tmpl1); + await tm.add(tmpl2); + await t.throwsAsync(async () => { + await tm.cache(); + }); +}); + +test("No duplicate permalinks in template map, using false", async t => { + let tmpl1 = new Template( + "./test/stubs/permalink-conflicts-false/test1.md", + "./test/stubs/", + "./test/stubs/_site" + ); + let tmpl2 = new Template( + "./test/stubs/permalink-conflicts-false/test2.md", + "./test/stubs/", + "./test/stubs/_site" + ); + + let tm = new TemplateMap(); + await tm.add(tmpl1); + await tm.add(tmpl2); + await tm.cache(); + t.true(true); +}); + +test("Duplicate permalinks in template map, no leading slash", async t => { + let tmpl1 = new Template( + "./test/stubs/permalink-conflicts/test1.md", + "./test/stubs/", + "./test/stubs/_site" + ); + let tmpl3 = new Template( + "./test/stubs/permalink-conflicts/test3.md", + "./test/stubs/", + "./test/stubs/_site" + ); + + let tm = new TemplateMap(); + await tm.add(tmpl1); + await tm.add(tmpl3); + + await t.throwsAsync(async () => { + await tm.cache(); + }); +}); + +test("TemplateMap circular references (map.templateContent) using eleventyExcludeFromCollections and collections.all", async t => { + let tm = new TemplateMap(); + await tm.add( + new Template( + "./test/stubs/issue-522/excluded.md", + "./test/stubs/", + "./test/stubs/_site" + ) + ); + + await tm.add( + new Template( + "./test/stubs/issue-522/template.md", + "./test/stubs/", + "./test/stubs/_site" + ) + ); + + let map = tm.getMap(); + t.falsy(map[0].data.collections); + + t.deepEqual(tm.getMappedDependencies(), [ + "./test/stubs/issue-522/template.md", + "___TAG___all", + "./test/stubs/issue-522/excluded.md" + ]); + + await tm.cache(); + t.is(tm.getMap().length, 2); + + let collections = await tm.getCollectionsData(); + t.is(collections.all.length, 1); +}); diff --git a/test/TemplatePassthroughManagerTest.js b/test/TemplatePassthroughManagerTest.js new file mode 100644 index 000000000..fddc7a7ed --- /dev/null +++ b/test/TemplatePassthroughManagerTest.js @@ -0,0 +1,128 @@ +import test from "ava"; +import fs from "fs-extra"; +import TemplatePassthroughManager from "../src/TemplatePassthroughManager"; + +test("Get paths from Config", async t => { + let mgr = new TemplatePassthroughManager(); + mgr.setConfig({ + passthroughFileCopy: true, + passthroughCopies: { + img: true + } + }); + + t.deepEqual(mgr.getConfigPaths(), [{ inputPath: "./img", outputPath: true }]); +}); + +test("Empty config paths when disabled in config", async t => { + let mgr = new TemplatePassthroughManager(); + mgr.setConfig({ + passthroughFileCopy: false, + passthroughCopies: { + img: true + } + }); + + t.deepEqual(mgr.getConfigPaths(), []); +}); + +test("Get glob paths from config", async t => { + let mgr = new TemplatePassthroughManager(); + mgr.setConfig({ + passthroughFileCopy: true, + passthroughCopies: { + "test/stubs/img": true, + "test/stubs/img/**": "./", + "test/stubs/img/*.js": "./" + } + }); + + t.deepEqual(mgr.getConfigPathGlobs(), [ + "./test/stubs/img/**", + "./test/stubs/img/**", + "./test/stubs/img/*.js" + ]); +}); + +test("Get file paths", async t => { + let mgr = new TemplatePassthroughManager(); + mgr.setConfig({ + passthroughFileCopy: true + }); + + t.deepEqual(mgr.getFilePaths(["test.png"]), ["test.png"]); +}); + +test("Get file paths (filter out real templates)", async t => { + let mgr = new TemplatePassthroughManager(); + mgr.setConfig({ + passthroughFileCopy: true + }); + + t.deepEqual(mgr.getFilePaths(["test.njk"]), []); +}); + +test("Get file paths (filter out real templates), multiple", async t => { + let mgr = new TemplatePassthroughManager(); + mgr.setConfig({ + passthroughFileCopy: true + }); + + t.deepEqual(mgr.getFilePaths(["test.njk", "test.png"]), ["test.png"]); +}); + +test("Get file paths with a js file (filter out real templates), multiple", async t => { + let mgr = new TemplatePassthroughManager(); + mgr.setConfig({ + passthroughFileCopy: true + }); + + t.deepEqual(mgr.getFilePaths(["test.njk", "test.js"]), ["test.js"]); +}); + +test("Get file paths when disabled in config", async t => { + let mgr = new TemplatePassthroughManager(); + mgr.setConfig({ + passthroughFileCopy: false + }); + + t.deepEqual(mgr.getFilePaths(["test.png"]), []); +}); + +test("Naughty paths outside of project dir", async t => { + let mgr = new TemplatePassthroughManager(); + mgr.setConfig({ + passthroughFileCopy: true, + passthroughCopies: { + "../static": true, + "../*": "./", + "./test/stubs/template-passthrough2/static/*.css": "./", + "./test/stubs/template-passthrough2/static/*.js": "../../", + "./test/stubs/template-passthrough2/img.jpg": "../../" + } + }); + + await t.throwsAsync(async function() { + for (let path of mgr.getConfigPaths()) { + await mgr.copyPath(path); + } + }); + + const output = [ + "./test/stubs/template-passthrough2/_site/static", + "./test/stubs/template-passthrough2/_site/nope.txt", + "./test/stubs/template-passthrough2/_site/nope/", + "./test/stubs/test.js", + "./test/stubs/img.jpg" + ]; + + let results = await Promise.all( + output.map(function(path) { + return fs.exists(path); + }) + ); + + for (let result of results) { + t.false(result); + } +}); diff --git a/test/TemplatePassthroughTest.js b/test/TemplatePassthroughTest.js new file mode 100644 index 000000000..4678e2619 --- /dev/null +++ b/test/TemplatePassthroughTest.js @@ -0,0 +1,221 @@ +import test from "ava"; +import TemplatePassthrough from "../src/TemplatePassthrough"; + +const getTemplatePassthrough = (path, outputDir, inputDir) => { + if (path instanceof Object) { + return new TemplatePassthrough(path, outputDir, inputDir); + } + return new TemplatePassthrough( + { inputPath: path, outputPath: true }, + outputDir, + inputDir + ); +}; + +test("Constructor", t => { + let pass = getTemplatePassthrough("avatar.png", "_site", "."); + t.truthy(pass); + t.is(pass.getOutputPath(), "_site/avatar.png"); +}); + +test("Constructor Dry Run", t => { + let pass = getTemplatePassthrough("avatar.png", "_site", "."); + pass.setDryRun(true); + t.is(pass.isDryRun, true); +}); + +test("Origin path isnā€™t included in output when targeting a directory", t => { + let pass = getTemplatePassthrough("img", "_site", "_src"); + t.truthy(pass); + t.is(pass.getOutputPath(), "_site/img"); +}); + +test("Origin path isnā€™t included in output when targeting a directory several levels deep", t => { + let pass = getTemplatePassthrough("img", "_site", "_src/subdir"); + t.truthy(pass); + t.is(pass.getOutputPath(), "_site/img"); +}); + +test("Target directoryā€™s subdirectory structure is retained", t => { + let pass = getTemplatePassthrough("subdir/img", "_site", "_src"); + t.truthy(pass); + t.is(pass.getOutputPath(), "_site/subdir/img"); +}); + +test("Origin path isnā€™t included in output when targeting a file", t => { + let pass = getTemplatePassthrough("avatar.png", "_site", "_src"); + t.truthy(pass); + t.is(pass.getOutputPath(), "_site/avatar.png"); +}); + +test("Origin path isnā€™t included in output when targeting a file several levels deep", t => { + let pass = getTemplatePassthrough("avatar.png", "_site", "_src/subdir/img"); + t.truthy(pass); + t.is(pass.getOutputPath(), "_site/avatar.png"); +}); + +test("Full input file path and deep input path", t => { + t.is( + getTemplatePassthrough( + "src/views/avatar.png", + "_site", + "src/views/" + ).getOutputPath(), + "_site/avatar.png" + ); + t.is( + getTemplatePassthrough( + "src/views/avatar.png", + "_site", + "src/views" + ).getOutputPath(), + "_site/avatar.png" + ); + t.is( + getTemplatePassthrough( + "src/views/avatar.png", + "_site/", + "src/views" + ).getOutputPath(), + "_site/avatar.png" + ); + t.is( + getTemplatePassthrough( + "src/views/avatar.png", + "./_site", + "./src/views" + ).getOutputPath(), + "_site/avatar.png" + ); + t.is( + getTemplatePassthrough( + "./src/views/avatar.png", + "./_site/", + "./src/views/" + ).getOutputPath(), + "_site/avatar.png" + ); + t.is( + getTemplatePassthrough( + "./src/views/avatar.png", + "_site", + "src/views/" + ).getOutputPath(), + "_site/avatar.png" + ); +}); + +test(".htaccess", t => { + let pass = getTemplatePassthrough(".htaccess", "_site", "."); + t.truthy(pass); + t.is(pass.getOutputPath(), "_site/.htaccess"); +}); + +test(".htaccess with input dir", t => { + let pass = getTemplatePassthrough(".htaccess", "_site", "_src"); + t.truthy(pass); + t.is(pass.getOutputPath(), "_site/.htaccess"); +}); + +test("getFiles where not glob and file does not exist", async t => { + const inputPath = ".htaccess"; + let pass = getTemplatePassthrough(inputPath, "_site", "_src"); + t.truthy(pass); + const files = await pass.getFiles(inputPath); + t.deepEqual(files, []); +}); + +test("getFiles where not glob and directory does not exist", async t => { + const inputPath = "./test/stubs/template-passthrough/static/not-exists/"; + let pass = getTemplatePassthrough(inputPath, "_site", "_src"); + t.truthy(pass); + const files = await pass.getFiles(inputPath); + t.deepEqual(files, []); +}); + +test("getFiles with glob", async t => { + const inputPath = "./test/stubs/template-passthrough/static/**"; + let pass = getTemplatePassthrough(inputPath, "_site", "_src"); + t.truthy(pass); + const files = await pass.getFiles(inputPath); + t.deepEqual( + files.sort(), + [ + "./test/stubs/template-passthrough/static/test.css", + "./test/stubs/template-passthrough/static/test.js", + "./test/stubs/template-passthrough/static/nested/test-nested.css" + ].sort() + ); +}); +test("getFiles with glob 2", async t => { + const inputPath = "./test/stubs/template-passthrough/static/**/*.js"; + let pass = getTemplatePassthrough( + "./test/stubs/template-passthrough/static/**/*", + "_site", + "_src" + ); + t.truthy(pass); + const files = await pass.getFiles(inputPath); + t.deepEqual(files, ["./test/stubs/template-passthrough/static/test.js"]); +}); + +test("Directory where outputPath is true", async t => { + let pass = getTemplatePassthrough( + { inputPath: "./static", outputPath: true }, + "_site", + "_src" + ); + t.truthy(pass); + t.is(pass.getOutputPath(), "_site/static"); +}); + +test("Nested directory where outputPath is remapped", async t => { + let pass = getTemplatePassthrough( + { inputPath: "./static/nested", outputPath: "./test" }, + "_site", + "_src" + ); + t.truthy(pass); + t.is(pass.getOutputPath(), "_site/test"); +}); + +test("Glob pattern", async t => { + const globResolvedPath = "./test/stubs/template-passthrough/static/test.js"; + let pass = getTemplatePassthrough( + { + inputPath: "./test/stubs/template-passthrough/static/*.js", + outputPath: "./directory/" + }, + "_site", + "_src" + ); + t.truthy(pass); + t.is( + pass.getOutputPathForGlobFile(globResolvedPath), + "_site/directory/test.js" + ); +}); + +test("Output paths match with different templatePassthrough methods", async t => { + let pass1 = getTemplatePassthrough( + { inputPath: "./static/nested", outputPath: "./test" }, + "_site", + "_src" + ); + let pass2 = getTemplatePassthrough("avatar.png", "_site/test", "."); + t.is(pass1.getOutputPathForGlobFile("avatar.png"), pass2.getOutputPath()); +}); + +// ToDo: Currently can't do :( +// test("File renamed", async t => { +// let pass = getTemplatePassthrough( +// { +// inputPath: "./test/stubs/template-passthrough/static/test.js", +// outputPath: "./rename.js" +// }, +// "_site", +// "_src" +// ); +// t.truthy(pass); +// t.is(pass.getOutputPath(), "_site/rename.js"); +// }); diff --git a/test/TemplatePathTest.js b/test/TemplatePathTest.js index ee4d550ec..57dc8d985 100644 --- a/test/TemplatePathTest.js +++ b/test/TemplatePathTest.js @@ -1,47 +1,278 @@ import test from "ava"; -import path from "path"; import TemplatePath from "../src/TemplatePath"; -test("Working dir", t => { - t.is(TemplatePath.getWorkingDir(), path.resolve("./")); - t.is(TemplatePath.getModuleDir(), path.resolve(__dirname, "..")); +test("getDir", t => { + t.is(TemplatePath.getDir("README.md"), "."); + t.is(TemplatePath.getDir("test/stubs/config.js"), "test/stubs"); + t.is(TemplatePath.getDir("./test/stubs/config.js"), "./test/stubs"); + t.is(TemplatePath.getDir("test/stubs/*.md"), "test/stubs"); + t.is(TemplatePath.getDir("test/stubs/**"), "test/stubs"); + t.is(TemplatePath.getDir("test/stubs/!(multiple.md)"), "test/stubs"); }); -test("Normalizer", async t => { - t.is(TemplatePath.normalize("testing", "hello"), "testing/hello"); - t.is(TemplatePath.normalize("testing", "hello/"), "testing/hello"); - t.is(TemplatePath.normalize("./testing", "hello"), "testing/hello"); - t.is(TemplatePath.normalize("./testing", "hello/"), "testing/hello"); +test("getDirFromFilePath", t => { + t.is(TemplatePath.getDirFromFilePath("test/stubs/*.md"), "test/stubs"); + t.is(TemplatePath.getDirFromFilePath("test/stubs/!(x.md)"), "test/stubs"); +}); + +test("getLastPathSegment", t => { + t.is(TemplatePath.getLastPathSegment("./testing/hello"), "hello"); + t.is(TemplatePath.getLastPathSegment("./testing"), "testing"); + t.is(TemplatePath.getLastPathSegment("./testing/"), "testing"); + t.is(TemplatePath.getLastPathSegment("testing/"), "testing"); + t.is(TemplatePath.getLastPathSegment("testing"), "testing"); +}); + +test("getAllDirs", t => { + t.deepEqual(TemplatePath.getAllDirs("."), ["."]); + t.deepEqual(TemplatePath.getAllDirs("./"), ["."]); + t.deepEqual(TemplatePath.getAllDirs("./testing"), ["./testing"]); + t.deepEqual(TemplatePath.getAllDirs("./testing/"), ["./testing"]); + t.deepEqual(TemplatePath.getAllDirs("testing/"), ["testing"]); + t.deepEqual(TemplatePath.getAllDirs("testing"), ["testing"]); + + t.deepEqual(TemplatePath.getAllDirs("./testing/hello"), [ + "./testing/hello", + "./testing" + ]); + + t.deepEqual(TemplatePath.getAllDirs("./src/collections/posts"), [ + "./src/collections/posts", + "./src/collections", + "./src" + ]); + + t.deepEqual( + TemplatePath.getAllDirs("./src/site/content/en/paths/performanceAudits"), + [ + "./src/site/content/en/paths/performanceAudits", + "./src/site/content/en/paths", + "./src/site/content/en", + "./src/site/content", + "./src/site", + "./src" + ] + ); + + t.deepEqual(TemplatePath.getAllDirs("./src/_site/src"), [ + "./src/_site/src", + "./src/_site", + "./src" + ]); + + t.deepEqual(TemplatePath.getAllDirs("./src/_site/src/src/src"), [ + "./src/_site/src/src/src", + "./src/_site/src/src", + "./src/_site/src", + "./src/_site", + "./src" + ]); +}); + +test("normalize", async t => { + t.is(TemplatePath.normalize(""), "."); + t.is(TemplatePath.normalize("."), "."); + t.is(TemplatePath.normalize("/"), "/"); + t.is(TemplatePath.normalize("/testing"), "/testing"); + t.is(TemplatePath.normalize("/testing/"), "/testing"); + + // v0.4.0 changed from `./` to `.` + // normalize removes trailing slashes so it should probably be `.` + t.is(TemplatePath.normalize("./"), "."); + t.is(TemplatePath.normalize("./testing"), "testing"); + + t.is(TemplatePath.normalize("../"), ".."); + t.is(TemplatePath.normalize("../testing"), "../testing"); + t.is(TemplatePath.normalize("./testing/hello"), "testing/hello"); t.is(TemplatePath.normalize("./testing/hello/"), "testing/hello"); + + t.is(TemplatePath.normalize(".htaccess"), ".htaccess"); }); -test("stripLeadingDotSlash", t => { - t.is(TemplatePath.stripLeadingDotSlash("./test/stubs"), "test/stubs"); - t.is(TemplatePath.stripLeadingDotSlash("./dist"), "dist"); - t.is(TemplatePath.stripLeadingDotSlash("../dist"), "../dist"); - t.is(TemplatePath.stripLeadingDotSlash("dist"), "dist"); +test("join", async t => { + t.is(TemplatePath.join("src", "_includes"), "src/_includes"); + t.is(TemplatePath.join("src", "_includes/"), "src/_includes"); + t.is(TemplatePath.join("src", "/_includes"), "src/_includes"); + t.is(TemplatePath.join("src", "./_includes"), "src/_includes"); + t.is(TemplatePath.join("src", "//_includes"), "src/_includes"); + + t.is(TemplatePath.join("./src", "_includes"), "src/_includes"); + t.is(TemplatePath.join("./src", "_includes/"), "src/_includes"); + t.is(TemplatePath.join("./src", "/_includes"), "src/_includes"); + t.is(TemplatePath.join("./src", "./_includes"), "src/_includes"); + t.is(TemplatePath.join("./src", "//_includes"), "src/_includes"); + + t.is(TemplatePath.join("src", "test", "..", "_includes"), "src/_includes"); +}); + +test("normalizeUrlPath", t => { + t.is(TemplatePath.normalizeUrlPath(""), "."); + t.is(TemplatePath.normalizeUrlPath("."), "."); + t.is(TemplatePath.normalizeUrlPath("./"), "./"); + t.is(TemplatePath.normalizeUrlPath(".."), ".."); + t.is(TemplatePath.normalizeUrlPath("../"), "../"); + + t.is(TemplatePath.normalizeUrlPath("/"), "/"); + t.is(TemplatePath.normalizeUrlPath("//"), "/"); + t.is(TemplatePath.normalizeUrlPath("/../"), "/"); + t.is(TemplatePath.normalizeUrlPath("/test"), "/test"); + t.is(TemplatePath.normalizeUrlPath("/test/"), "/test/"); + t.is(TemplatePath.normalizeUrlPath("/test//"), "/test/"); + t.is(TemplatePath.normalizeUrlPath("/test/../"), "/"); + t.is(TemplatePath.normalizeUrlPath("/test/../../"), "/"); +}); + +test("absolutePath", t => { + t.is( + TemplatePath.absolutePath(".eleventy.js") + .split("/") + .pop(), + ".eleventy.js" + ); +}); + +test("absolutePath and relativePath", t => { + t.is( + TemplatePath.relativePath(TemplatePath.absolutePath(".eleventy.js")), + ".eleventy.js" + ); }); test("addLeadingDotSlash", t => { + t.is(TemplatePath.addLeadingDotSlash("."), "./"); + t.is(TemplatePath.addLeadingDotSlash(".."), "../"); t.is(TemplatePath.addLeadingDotSlash("./test/stubs"), "./test/stubs"); t.is(TemplatePath.addLeadingDotSlash("./dist"), "./dist"); t.is(TemplatePath.addLeadingDotSlash("../dist"), "../dist"); t.is(TemplatePath.addLeadingDotSlash("/dist"), "/dist"); t.is(TemplatePath.addLeadingDotSlash("dist"), "./dist"); + t.is(TemplatePath.addLeadingDotSlash(".nyc_output"), "./.nyc_output"); +}); + +test("addLeadingDotSlashArray", t => { + t.deepEqual(TemplatePath.addLeadingDotSlashArray(["."]), ["./"]); + t.deepEqual(TemplatePath.addLeadingDotSlashArray([".."]), ["../"]); + t.deepEqual(TemplatePath.addLeadingDotSlashArray(["./test/stubs"]), [ + "./test/stubs" + ]); + t.deepEqual(TemplatePath.addLeadingDotSlashArray(["./dist"]), ["./dist"]); + t.deepEqual(TemplatePath.addLeadingDotSlashArray(["../dist"]), ["../dist"]); + t.deepEqual(TemplatePath.addLeadingDotSlashArray(["/dist"]), ["/dist"]); + t.deepEqual(TemplatePath.addLeadingDotSlashArray(["dist"]), ["./dist"]); + t.deepEqual(TemplatePath.addLeadingDotSlashArray([".nyc_output"]), [ + "./.nyc_output" + ]); }); -test("stripPathFromDir", t => { +test("stripLeadingDotSlash", t => { + t.is(TemplatePath.stripLeadingDotSlash("./test/stubs"), "test/stubs"); + t.is(TemplatePath.stripLeadingDotSlash("./dist"), "dist"); + t.is(TemplatePath.stripLeadingDotSlash("../dist"), "../dist"); + t.is(TemplatePath.stripLeadingDotSlash("dist"), "dist"); + + t.is(TemplatePath.stripLeadingDotSlash(".htaccess"), ".htaccess"); +}); + +test("startsWithSubPath", t => { + t.false(TemplatePath.startsWithSubPath("./testing/hello", "./lskdjklfjz")); + t.false(TemplatePath.startsWithSubPath("./testing/hello", "lskdjklfjz")); + t.false(TemplatePath.startsWithSubPath("testing/hello", "./lskdjklfjz")); + t.false(TemplatePath.startsWithSubPath("testing/hello", "lskdjklfjz")); + + t.true(TemplatePath.startsWithSubPath("./testing/hello", "./testing")); + t.true(TemplatePath.startsWithSubPath("./testing/hello", "testing")); + t.true(TemplatePath.startsWithSubPath("testing/hello", "./testing")); + t.true(TemplatePath.startsWithSubPath("testing/hello", "testing")); + + t.true( + TemplatePath.startsWithSubPath("testing/hello/subdir/test", "testing") + ); + t.false(TemplatePath.startsWithSubPath("testing/hello/subdir/test", "hello")); + t.false( + TemplatePath.startsWithSubPath("testing/hello/subdir/test", "hello/subdir") + ); + t.true( + TemplatePath.startsWithSubPath( + "testing/hello/subdir/test", + "testing/hello/subdir" + ) + ); + t.true( + TemplatePath.startsWithSubPath( + "testing/hello/subdir/test", + "testing/hello/subdir/test" + ) + ); +}); + +test("stripLeadingSubPath", t => { t.is( - TemplatePath.stripPathFromDir("./testing/hello", "./lskdjklfjz"), + TemplatePath.stripLeadingSubPath("./testing/hello", "./lskdjklfjz"), "testing/hello" ); - t.is(TemplatePath.stripPathFromDir("./test/stubs", "./test"), "stubs"); - t.is(TemplatePath.stripPathFromDir("./testing/hello", "testing"), "hello"); - t.is(TemplatePath.stripPathFromDir("testing/hello", "testing"), "hello"); - t.is(TemplatePath.stripPathFromDir("testing/hello", "./testing"), "hello"); + t.is(TemplatePath.stripLeadingSubPath("./test/stubs", "stubs"), "test/stubs"); + t.is(TemplatePath.stripLeadingSubPath("./test/stubs", "./test"), "stubs"); + t.is(TemplatePath.stripLeadingSubPath("./testing/hello", "testing"), "hello"); + t.is(TemplatePath.stripLeadingSubPath("testing/hello", "testing"), "hello"); + t.is(TemplatePath.stripLeadingSubPath("testing/hello", "./testing"), "hello"); t.is( - TemplatePath.stripPathFromDir("testing/hello/subdir/test", "testing"), + TemplatePath.stripLeadingSubPath("testing/hello/subdir/test", "testing"), "hello/subdir/test" ); + + t.is(TemplatePath.stripLeadingSubPath(".htaccess", "./"), ".htaccess"); + t.is(TemplatePath.stripLeadingSubPath(".htaccess", "."), ".htaccess"); +}); + +test("convertToRecursiveGlob", t => { + t.is(TemplatePath.convertToRecursiveGlob(""), "./**"); + t.is(TemplatePath.convertToRecursiveGlob("."), "./**"); + t.is(TemplatePath.convertToRecursiveGlob("./"), "./**"); + t.is(TemplatePath.convertToRecursiveGlob("test/stubs"), "./test/stubs/**"); + t.is(TemplatePath.convertToRecursiveGlob("test/stubs/"), "./test/stubs/**"); + t.is(TemplatePath.convertToRecursiveGlob("./test/stubs/"), "./test/stubs/**"); + t.is( + TemplatePath.convertToRecursiveGlob("./test/stubs/config.js"), + "./test/stubs/config.js" + ); +}); + +test("getExtension", t => { + t.is(TemplatePath.getExtension(""), ""); + t.is(TemplatePath.getExtension("test/stubs"), ""); + t.is(TemplatePath.getExtension("test/stubs.njk"), "njk"); + t.is(TemplatePath.getExtension("test/stubs.hbs"), "hbs"); +}); + +test("removeExtension", t => { + t.is(TemplatePath.removeExtension(""), ""); + t.is(TemplatePath.removeExtension("", "hbs"), ""); + + t.is(TemplatePath.removeExtension("test/stubs", "hbs"), "test/stubs"); + t.is(TemplatePath.removeExtension("test/stubs.njk"), "test/stubs.njk"); + t.is(TemplatePath.removeExtension("test/stubs.njk", "hbs"), "test/stubs.njk"); + t.is(TemplatePath.removeExtension("test/stubs.hbs", "hbs"), "test/stubs"); + + t.is(TemplatePath.removeExtension("./test/stubs.njk"), "./test/stubs.njk"); + t.is( + TemplatePath.removeExtension("./test/stubs.njk", "hbs"), + "./test/stubs.njk" + ); + t.is(TemplatePath.removeExtension("./test/stubs.hbs", "hbs"), "./test/stubs"); + + t.is(TemplatePath.removeExtension("test/stubs", ".hbs"), "test/stubs"); + t.is( + TemplatePath.removeExtension("test/stubs.njk", ".hbs"), + "test/stubs.njk" + ); + t.is(TemplatePath.removeExtension("test/stubs.hbs", ".hbs"), "test/stubs"); + t.is( + TemplatePath.removeExtension("./test/stubs.njk", ".hbs"), + "./test/stubs.njk" + ); + t.is( + TemplatePath.removeExtension("./test/stubs.hbs", ".hbs"), + "./test/stubs" + ); }); diff --git a/test/TemplatePermalinkNoWriteTest.js b/test/TemplatePermalinkNoWriteTest.js new file mode 100644 index 000000000..ad25b4507 --- /dev/null +++ b/test/TemplatePermalinkNoWriteTest.js @@ -0,0 +1,8 @@ +import test from "ava"; +import TemplatePermalinkNoWrite from "../src/TemplatePermalinkNoWrite"; + +test("Test standard method signature", t => { + let perm = new TemplatePermalinkNoWrite(); + t.is(perm.toHref(), false); + t.is(perm.toString(), false); +}); diff --git a/test/TemplatePermalinkTest.js b/test/TemplatePermalinkTest.js index 02d3ab815..831f99001 100644 --- a/test/TemplatePermalinkTest.js +++ b/test/TemplatePermalinkTest.js @@ -1,5 +1,4 @@ import test from "ava"; -import parsePath from "parse-filepath"; import TemplatePermalink from "../src/TemplatePermalink"; test("Simple straight permalink", t => { @@ -11,6 +10,20 @@ test("Simple straight permalink", t => { new TemplatePermalink("./permalinksubfolder/test.html").toString(), "permalinksubfolder/test.html" ); + + t.is( + new TemplatePermalink("permalinksubfolder/test.html").toHref(), + "/permalinksubfolder/test.html" + ); + t.is( + new TemplatePermalink("./permalinksubfolder/test.html").toHref(), + "/permalinksubfolder/test.html" + ); + t.is(new TemplatePermalink("./testindex.html").toHref(), "/testindex.html"); + t.is( + new TemplatePermalink("./permalinksubfolder/testindex.html").toHref(), + "/permalinksubfolder/testindex.html" + ); }); test("Permalink without filename", t => { @@ -22,6 +35,23 @@ test("Permalink without filename", t => { new TemplatePermalink("./permalinksubfolder/").toString(), "permalinksubfolder/index.html" ); + t.is( + new TemplatePermalink("/permalinksubfolder/").toString(), + "/permalinksubfolder/index.html" + ); + + t.is( + new TemplatePermalink("permalinksubfolder/").toHref(), + "/permalinksubfolder/" + ); + t.is( + new TemplatePermalink("./permalinksubfolder/").toHref(), + "/permalinksubfolder/" + ); + t.is( + new TemplatePermalink("/permalinksubfolder/").toHref(), + "/permalinksubfolder/" + ); }); test("Permalink with pagination subdir", t => { @@ -33,23 +63,39 @@ test("Permalink with pagination subdir", t => { new TemplatePermalink("permalinksubfolder/test.html", "1/").toString(), "permalinksubfolder/1/test.html" ); + + t.is( + new TemplatePermalink("permalinksubfolder/test.html", "0/").toHref(), + "/permalinksubfolder/0/test.html" + ); + t.is( + new TemplatePermalink("permalinksubfolder/test.html", "1/").toHref(), + "/permalinksubfolder/1/test.html" + ); }); test("Permalink generate", t => { let gen = TemplatePermalink.generate; t.is(gen("./", "index").toString(), "index.html"); + t.is(gen("./", "index").toHref(), "/"); t.is(gen(".", "index").toString(), "index.html"); + t.is(gen(".", "index").toHref(), "/"); t.is(gen(".", "test").toString(), "test/index.html"); + t.is(gen(".", "test").toHref(), "/test/"); t.is(gen(".", "test", "0/").toString(), "test/0/index.html"); + t.is(gen(".", "test", "0/").toHref(), "/test/0/"); t.is(gen(".", "test", "1/").toString(), "test/1/index.html"); + t.is(gen(".", "test", "1/").toHref(), "/test/1/"); }); test("Permalink generate with suffix", t => { let gen = TemplatePermalink.generate; t.is(gen(".", "test", null, "-o").toString(), "test/index-o.html"); + t.is(gen(".", "test", null, "-o").toHref(), "/test/index-o.html"); t.is(gen(".", "test", "1/", "-o").toString(), "test/1/index-o.html"); + t.is(gen(".", "test", "1/", "-o").toHref(), "/test/1/index-o.html"); }); test("Permalink generate with subfolders", t => { @@ -67,6 +113,16 @@ test("Permalink generate with subfolders", t => { gen("permalinksubfolder/", "test", "1/", "-o").toString(), "permalinksubfolder/test/1/index-o.html" ); + + t.is(gen("permalinksubfolder/", "index").toHref(), "/permalinksubfolder/"); + t.is( + gen("permalinksubfolder/", "test").toHref(), + "/permalinksubfolder/test/" + ); + t.is( + gen("permalinksubfolder/", "test", "1/", "-o").toHref(), + "/permalinksubfolder/test/1/index-o.html" + ); }); test("Permalink matching folder and filename", t => { @@ -80,4 +136,5 @@ test("Permalink matching folder and filename", t => { t.is(hasDupe("component/", "component"), true); t.is(gen("component/", "component").toString(), "component/index.html"); + t.is(gen("component/", "component").toHref(), "/component/"); }); diff --git a/test/TemplateRenderEJSTest.js b/test/TemplateRenderEJSTest.js new file mode 100644 index 000000000..236b2cd65 --- /dev/null +++ b/test/TemplateRenderEJSTest.js @@ -0,0 +1,110 @@ +import test from "ava"; +import TemplateRender from "../src/TemplateRender"; + +// EJS +test("EJS", t => { + t.is(new TemplateRender("ejs").getEngineName(), "ejs"); + t.is(new TemplateRender("./test/stubs/filename.ejs").getEngineName(), "ejs"); +}); + +test("EJS Render", async t => { + let fn = await new TemplateRender("ejs").getCompiledTemplate( + "

<%= name %>

" + ); + t.is(await fn({ name: "Zach" }), "

Zach

"); +}); + +test("EJS Render Absolute Include, Preprocessor Directive", async t => { + // includes require a full filename passed in + let fn = await new TemplateRender( + "./test/stubs/filename.ejs", + "./test/stubs/" + ).getCompiledTemplate("

<% include /included %>

"); + t.is(await fn(), "

This is an include.

"); +}); + +test("EJS Render Absolute Include, Fxn no Data", async t => { + // includes require a full filename passed in + let fn = await new TemplateRender( + "./test/stubs/filename.ejs", + "./test/stubs/" + ).getCompiledTemplate("

<%- include('/included') %>

"); + t.is(await fn(), "

This is an include.

"); +}); + +test("EJS Render Absolute Include, Fxn with Data", async t => { + // includes require a full filename passed in + let fn = await new TemplateRender( + "./test/stubs/filename.ejs", + "./test/stubs/" + ).getCompiledTemplate( + "

<%- include('/includedvar', { name: 'Bill' }) %>

" + ); + t.is(await fn(), "

This is an Bill.

"); +}); + +test("EJS Render Relative Include (no leading dot-slash for current dir), Preprocessor Directive", async t => { + // includes require a full filename passed in + let fn = await new TemplateRender( + "./test/stubs/relative-ejs/dir/filename.ejs", + "./test/stubs/" + ).getCompiledTemplate("

<% include included %>

"); + t.is(await fn(), "

This is an include.

"); +}); + +test("EJS Render Relative Include Current dir to Subdir, Preprocessor Directive", async t => { + // includes require a full filename passed in + let fn = await new TemplateRender( + "./test/stubs/relative-ejs/filename.ejs", + "./test/stubs/" + ).getCompiledTemplate("

<% include ./dir/included %>

"); + t.is(await fn(), "

This is an include.

"); +}); + +test("EJS Render Relative Include Parent dir to Subdir, Preprocessor Directive", async t => { + // includes require a full filename passed in + let fn = await new TemplateRender( + "./test/stubs/relative-ejs/dir/filename.ejs", + "./test/stubs/" + ).getCompiledTemplate("

<% include ../dir/included %>

"); + t.is(await fn(), "

This is an include.

"); +}); + +test("EJS Render Relative Include, Fxn no Data", async t => { + // includes require a full filename passed in + let fn = await new TemplateRender( + "./test/stubs/filename.ejs", + "./test/stubs/" + ).getCompiledTemplate("

<%- include('_includes/included', {}) %>

"); + t.is(await fn(), "

This is an include.

"); +}); + +test("EJS Render Relative Include current dir to subdir, Fxn no Data", async t => { + // includes require a full filename passed in + let fn = await new TemplateRender( + "./test/stubs/relative-ejs/filename.ejs", + "./test/stubs/" + ).getCompiledTemplate("

<%- include('./dir/included', {}) %>

"); + t.is(await fn(), "

This is an include.

"); +}); + +test("EJS Render Relative Include, Fxn with Data", async t => { + // includes require a full filename passed in + let fn = await new TemplateRender( + "./test/stubs/filename.ejs", + "./test/stubs/" + ).getCompiledTemplate( + "

<%- include('_includes/includedvar', { name: 'Bill' }) %>

" + ); + t.is(await fn(), "

This is an Bill.

"); +}); + +test("EJS Render: with Library Override", async t => { + let tr = new TemplateRender("ejs"); + + let lib = require("ejs"); + tr.engine.setLibrary(lib); + + let fn = await tr.getCompiledTemplate("

<%= name %>

"); + t.is(await fn({ name: "Zach" }), "

Zach

"); +}); diff --git a/test/TemplateRenderHTMLTest.js b/test/TemplateRenderHTMLTest.js new file mode 100644 index 000000000..ac735db20 --- /dev/null +++ b/test/TemplateRenderHTMLTest.js @@ -0,0 +1,51 @@ +import test from "ava"; +import TemplateRender from "../src/TemplateRender"; + +// HTML +test("HTML", t => { + t.is(new TemplateRender("html").getEngineName(), "html"); +}); + +test("HTML Render", async t => { + let fn = await new TemplateRender("html").getCompiledTemplate( + "

Paragraph

" + ); + t.is(await fn(), "

Paragraph

"); + t.is(await fn({}), "

Paragraph

"); +}); + +test("HTML Render: Parses HTML using liquid engine (default, with data)", async t => { + let fn = await new TemplateRender("html").getCompiledTemplate( + "

{{title}}

" + ); + t.is((await fn({ title: "My Title" })).trim(), "

My Title

"); +}); + +test("HTML Render: Parses HTML using ejs engine", async t => { + let tr = new TemplateRender("html"); + tr.setHtmlEngine("ejs"); + let fn = await tr.getCompiledTemplate("

<%=title %>

"); + t.is((await fn({ title: "My Title" })).trim(), "

My Title

"); +}); + +test("HTML Render: Set HTML engine to false, donā€™t parse", async t => { + let tr = new TemplateRender("html"); + tr.setHtmlEngine(false); + let fn = await tr.getCompiledTemplate("

{{title}}

"); + t.is((await fn()).trim(), "

{{title}}

"); +}); + +test("HTML Render: Pass in an override (ejs)", async t => { + let tr = new TemplateRender("html"); + tr.setHtmlEngine("ejs"); + let fn = await tr.getCompiledTemplate("

<%= title %>

"); + t.is((await fn({ title: "My Title" })).trim(), "

My Title

"); +}); + +test("HTML Render: Pass in an override (liquid)", async t => { + let tr = new TemplateRender("html"); + tr.setHtmlEngine("liquid"); + let fn = await tr.getCompiledTemplate("

{{title}}

"); + + t.is((await fn({ title: "My Title" })).trim(), "

My Title

"); +}); diff --git a/test/TemplateRenderHamlTest.js b/test/TemplateRenderHamlTest.js new file mode 100644 index 000000000..309026c99 --- /dev/null +++ b/test/TemplateRenderHamlTest.js @@ -0,0 +1,22 @@ +import test from "ava"; +import TemplateRender from "../src/TemplateRender"; + +// Haml +test("Haml", t => { + t.is(new TemplateRender("haml").getEngineName(), "haml"); +}); + +test("Haml Render", async t => { + let fn = await new TemplateRender("haml").getCompiledTemplate("%p= name"); + t.is((await fn({ name: "Zach" })).trim(), "

Zach

"); +}); + +test("Haml Render: with Library Override", async t => { + let tr = new TemplateRender("haml"); + + let lib = require("hamljs"); + tr.engine.setLibrary(lib); + + let fn = await tr.getCompiledTemplate("%p= name"); + t.is((await fn({ name: "Zach" })).trim(), "

Zach

"); +}); diff --git a/test/TemplateRenderHandlebarsTest.js b/test/TemplateRenderHandlebarsTest.js new file mode 100644 index 000000000..261b468e6 --- /dev/null +++ b/test/TemplateRenderHandlebarsTest.js @@ -0,0 +1,252 @@ +import test from "ava"; +import TemplateRender from "../src/TemplateRender"; + +// Handlebars +test("Handlebars", t => { + t.is(new TemplateRender("hbs").getEngineName(), "hbs"); +}); + +test("Handlebars Render", async t => { + let fn = await new TemplateRender("hbs").getCompiledTemplate( + "

{{name}}

" + ); + t.is(await fn({ name: "Zach" }), "

Zach

"); +}); + +test("Handlebars Render Unescaped Output (no HTML)", async t => { + let fn = await new TemplateRender("hbs").getCompiledTemplate( + "

{{{name}}}

" + ); + t.is(await fn({ name: "Zach" }), "

Zach

"); +}); + +test("Handlebars Render Escaped Output", async t => { + let fn = await new TemplateRender("hbs").getCompiledTemplate( + "

{{name}}

" + ); + t.is(await fn({ name: "Zach" }), "

<b>Zach</b>

"); +}); + +test("Handlebars Render Unescaped Output (HTML)", async t => { + let fn = await new TemplateRender("hbs").getCompiledTemplate( + "

{{{name}}}

" + ); + t.is(await fn({ name: "Zach" }), "

Zach

"); +}); + +test("Handlebars Render Partial", async t => { + let fn = await new TemplateRender("hbs", "./test/stubs/").getCompiledTemplate( + "

{{> included}}

" + ); + t.is(await fn(), "

This is an include.

"); +}); + +test.skip("Handlebars Render Partial (Relative)", async t => { + let fn = await new TemplateRender( + "./test/stubs/does_not_exist_and_thats_ok.hbs", + "./test/stubs/" + ).getCompiledTemplate("

{{> ./included}}

"); + + // not supported yet. + t.is(await fn(), "

This is an includdde.

"); +}); + +test("Handlebars Render Partial (Subdirectory)", async t => { + let fn = await new TemplateRender("hbs", "./test/stubs/").getCompiledTemplate( + "

{{> subfolder/included}}

" + ); + t.is(await fn(), "

This is an include.

"); +}); + +test("Handlebars Render Partial with variable", async t => { + let fn = await new TemplateRender("hbs", "./test/stubs/").getCompiledTemplate( + "

{{> includedvar}}

" + ); + t.is(await fn({ name: "Zach" }), "

This is a Zach.

"); +}); + +test("Handlebars Render: with Library Override", async t => { + let tr = new TemplateRender("hbs"); + + let lib = require("handlebars"); + tr.engine.setLibrary(lib); + + let fn = await tr.getCompiledTemplate("

{{name}}

"); + t.is(await fn({ name: "Zach" }), "

Zach

"); +}); + +test("Handlebars Render Helper", async t => { + let tr = new TemplateRender("hbs"); + tr.engine.addHelpers({ + helpername: function() { + return "Zach"; + } + }); + + let fn = await tr.getCompiledTemplate( + "

This is a {{helpername}} {{name}}.

" + ); + t.is(await fn({ name: "Zach" }), "

This is a Zach Zach.

"); +}); + +test("Handlebars Render Helper (uses argument)", async t => { + let tr = new TemplateRender("hbs"); + tr.engine.addHelpers({ + helpername2: function(name) { + return "Zach"; + } + }); + + let fn = await tr.getCompiledTemplate( + "

This is a {{helpername2 name}}.

" + ); + t.is(await fn({ name: "Zach" }), "

This is a Zach.

"); +}); + +test("Handlebars Render Shortcode", async t => { + let tr = new TemplateRender("hbs"); + tr.engine.addShortcodes({ + shortcodename: function(name) { + return name.toUpperCase(); + } + }); + + let fn = await tr.getCompiledTemplate( + "

This is a {{shortcodename name}}.

" + ); + t.is(await fn({ name: "Howdy" }), "

This is a HOWDY.

"); +}); + +test("Handlebars Render HTML in Shortcode (Issue #460)", async t => { + let tr = new TemplateRender("hbs"); + tr.engine.addShortcodes({ + shortcodenamehtml: function(name) { + return `${name.toUpperCase()}`; + } + }); + + let fn = await tr.getCompiledTemplate( + "

This is a {{{shortcodenamehtml name}}}.

" + ); + t.is(await fn({ name: "Howdy" }), "

This is a HOWDY.

"); +}); + +test("Handlebars Render Shortcode (Multiple args)", async t => { + let tr = new TemplateRender("hbs"); + tr.engine.addShortcodes({ + shortcodename2: function(name, name2) { + return name.toUpperCase() + name2.toUpperCase(); + } + }); + + let fn = await tr.getCompiledTemplate( + "

This is a {{shortcodename2 name name2}}.

" + ); + t.is( + await fn({ name: "Howdy", name2: "Zach" }), + "

This is a HOWDYZACH.

" + ); +}); + +test("Handlebars Render Paired Shortcode", async t => { + let tr = new TemplateRender("hbs"); + tr.engine.addPairedShortcodes({ + shortcodename3: function(content, name, options) { + return (content + name).toUpperCase(); + } + }); + + let fn = await tr.getCompiledTemplate( + "

This is a {{#shortcodename3 name}}Testing{{/shortcodename3}}.

" + ); + t.is(await fn({ name: "Howdy" }), "

This is a TESTINGHOWDY.

"); +}); + +test("Handlebars Render Paired Shortcode (HTML)", async t => { + let tr = new TemplateRender("hbs"); + tr.engine.addPairedShortcodes({ + shortcodename3html: function(content, name, options) { + return `${(content + name).toUpperCase()}`; + } + }); + + let fn = await tr.getCompiledTemplate( + "

This is a {{#shortcodename3html name}}Testing{{/shortcodename3html}}.

" + ); + t.is( + await fn({ name: "Howdy" }), + "

This is a TESTINGHOWDY.

" + ); +}); + +test("Handlebars Render Paired Shortcode (Spaces)", async t => { + let tr = new TemplateRender("hbs"); + tr.engine.addPairedShortcodes({ + shortcodename4: function(content, name, options) { + return (content + name).toUpperCase(); + } + }); + + let fn = await tr.getCompiledTemplate( + "

This is a {{# shortcodename4 name }}Testing{{/ shortcodename4 }}.

" + ); + t.is(await fn({ name: "Howdy" }), "

This is a TESTINGHOWDY.

"); +}); + +test("Handlebars Render Paired Shortcode with a Nested Single Shortcode", async t => { + let tr = new TemplateRender("hbs"); + tr.engine.addShortcodes({ + shortcodechild: function(txt, options) { + return txt; + } + }); + + tr.engine.addPairedShortcodes({ + shortcodeparent: function(content, name, name2, options) { + return (content + name + name2).toUpperCase(); + } + }); + + let fn = await tr.getCompiledTemplate( + "

This is a {{# shortcodeparent name name2 }}{{shortcodechild 'CHILD CONTENT'}}{{/ shortcodeparent }}.

" + ); + t.is( + await fn({ name: "Howdy", name2: "Zach" }), + "

This is a CHILD CONTENTHOWDYZACH.

" + ); +}); + +test("Handlebars Render Raw Output (Issue #436)", async t => { + let tr = new TemplateRender("hbs"); + tr.engine.addHelpers({ + "raw-helper": function(options) { + return options.fn(); + } + }); + + let fn = await tr.getCompiledTemplate( + "{{{{raw-helper}}}}{{bar}}{{{{/raw-helper}}}}" + ); + t.is(await fn({ name: "Zach" }), "{{bar}}"); +}); + +test("Handlebars Render Raw Output (Issue #436 with if statement)", async t => { + let tr = new TemplateRender("hbs"); + tr.engine.addHelpers({ + "raw-helper": function(options) { + return options.fn(); + } + }); + + let fn = await tr.getCompiledTemplate( + `{{{{raw-helper}}}}{{#if ready}} +

Ready

+{{/if}}{{{{/raw-helper}}}}` + ); + t.is( + await fn({ name: "Zach" }), + `{{#if ready}} +

Ready

+{{/if}}` + ); +}); diff --git a/test/TemplateRenderJSTLTest.js b/test/TemplateRenderJSTLTest.js new file mode 100644 index 000000000..4027ce09d --- /dev/null +++ b/test/TemplateRenderJSTLTest.js @@ -0,0 +1,41 @@ +import test from "ava"; +import TemplateRender from "../src/TemplateRender"; + +// ES6 +test("ES6 Template Literal", t => { + t.is(new TemplateRender("jstl").getEngineName(), "jstl"); +}); + +test("ES6 Template Literal Render (Backticks)", async t => { + // pass in a string here, we donā€™t want to compile the template in the test :O + let fn = await new TemplateRender("jstl").getCompiledTemplate( + "`

${name.toUpperCase()}

`" + ); + t.is(await fn({ name: "Tim" }), "

TIM

"); +}); + +test("ES6 Template Literal Render (No backticks)", async t => { + // pass in a string here, we donā€™t want to compile the template in the test :O + let fn = await new TemplateRender("jstl").getCompiledTemplate( + "

${name.toUpperCase()}

" + ); + t.is(await fn({ name: "Tim" }), "

TIM

"); +}); + +test("ES6 Template Literal with newlines", async t => { + // pass in a string here, we donā€™t want to compile the template in the test :O + let fn = await new TemplateRender("jstl").getCompiledTemplate( + "Test\n\nMarkdown Syntax ${name}\n" + ); + t.is(await fn({ name: "Tim" }), "Test\n\nMarkdown Syntax Tim\n"); +}); + +test("ES6 Template Literal with markdown", async t => { + // pass in a string here, we donā€™t want to compile the template in the test :O + let fn = await new TemplateRender("jstl").getCompiledTemplate( + "Test\n```\nMarkdown Syntax ${name}\n```" + ); + + // TODO this has an extra newline at the end because the input string ends in a `! + t.is(await fn({ name: "Tim" }), "Test\n```\nMarkdown Syntax Tim\n```\n"); +}); diff --git a/test/TemplateRenderJavaScriptTest.js b/test/TemplateRenderJavaScriptTest.js new file mode 100644 index 000000000..80e78a690 --- /dev/null +++ b/test/TemplateRenderJavaScriptTest.js @@ -0,0 +1,221 @@ +import test from "ava"; +import TemplateRender from "../src/TemplateRender"; + +test("JS", t => { + t.is(new TemplateRender("11ty.js").getEngineName(), "11ty.js"); + t.is( + new TemplateRender("./test/stubs/filename.11ty.js").getEngineName(), + "11ty.js" + ); +}); + +test("JS Render a string (no data)", async t => { + let fn = await new TemplateRender( + "./test/stubs/string.11ty.js" + ).getCompiledTemplate(); + t.is(await fn({ name: "Bill" }), "

Zach

"); +}); + +test("JS Render a promise (no data)", async t => { + let fn = await new TemplateRender( + "./test/stubs/promise.11ty.js" + ).getCompiledTemplate(); + t.is(await fn({ name: "Bill" }), "

Zach

"); +}); + +test("JS Render a buffer (no data)", async t => { + let fn = await new TemplateRender( + "./test/stubs/buffer.11ty.js" + ).getCompiledTemplate(); + t.is(await fn({ name: "Bill" }), "

tƩst

"); +}); + +test("JS Render a function", async t => { + let fn = await new TemplateRender( + "./test/stubs/function.11ty.js" + ).getCompiledTemplate(); + t.is(await fn({ name: "Zach" }), "

Zach

"); + t.is(await fn({ name: "Bill" }), "

Bill

"); +}); + +test("JS Render a function (arrow syntax)", async t => { + let fn = await new TemplateRender( + "./test/stubs/function-arrow.11ty.js" + ).getCompiledTemplate(); + t.is(await fn({ name: "Zach" }), "

Zach

"); + t.is(await fn({ name: "Bill" }), "

Bill

"); +}); + +test("JS Render a function, returns a Buffer", async t => { + let fn = await new TemplateRender( + "./test/stubs/function-buffer.11ty.js" + ).getCompiledTemplate(); + t.is(await fn({ name: "tƩst" }), "

tƩst

"); + t.is(await fn({ name: "Zach" }), "

Zach

"); + t.is(await fn({ name: "Bill" }), "

Bill

"); +}); + +test("JS Render a function (Markdown)", async t => { + let tr = new TemplateRender("./test/stubs/function-markdown.11ty.js"); + tr.setEngineOverride("11ty.js,md"); + let fn = await tr.getCompiledTemplate(); + t.is((await fn({ name: "Zach" })).trim(), "

Zach

"); + t.is((await fn({ name: "Bill" })).trim(), "

Bill

"); +}); + +test("JS Render a function (Collections)", async t => { + let tr = new TemplateRender("./test/stubs/use-collection.11ty.js"); + let fn = await tr.getCompiledTemplate(); + t.is( + (await fn({ + collections: { + post: [ + { + data: { + title: "Testing" + } + }, + { + data: { + title: "Testing2" + } + } + ] + } + })).trim(), + `
  • Testing
  • Testing2
` + ); +}); + +test("JS Render an async function", async t => { + let fn = await new TemplateRender( + "./test/stubs/function-async.11ty.js" + ).getCompiledTemplate(); + t.is(await fn({ name: "Zach" }), "

Zach

"); + t.is(await fn({ name: "Bill" }), "

Bill

"); +}); + +test("JS Render with a Class", async t => { + let fn = await new TemplateRender( + "./test/stubs/class.11ty.js" + ).getCompiledTemplate(); + t.is(await fn({ name: "Zach" }), "

ZachBillTed

"); + t.is(await fn({ name: "Bill" }), "

BillBillTed

"); +}); + +test("JS Render with a Class, returns a buffer", async t => { + let fn = await new TemplateRender( + "./test/stubs/class-buffer.11ty.js" + ).getCompiledTemplate(); + t.is(await fn({ name: "ZƔch" }), "

ZƔchBillTed

"); + t.is(await fn({ name: "Zach" }), "

ZachBillTed

"); + t.is(await fn({ name: "Bill" }), "

BillBillTed

"); +}); + +test("JS Render with a Class, async render", async t => { + let fn = await new TemplateRender( + "./test/stubs/class-async.11ty.js" + ).getCompiledTemplate(); + t.is(await fn({ name: "Zach" }), "

Zach

"); + t.is(await fn({ name: "Bill" }), "

Bill

"); +}); + +test("JS Render using Vue", async t => { + let fn = await new TemplateRender( + "./test/stubs/vue.11ty.js" + ).getCompiledTemplate(); + t.is( + await fn({ name: "Zach" }), + '

Hello Zach, this is a Vue template.

' + ); + t.is( + await fn({ name: "Bill" }), + '

Hello Bill, this is a Vue template.

' + ); +}); + +test("JS Render using Vue (with a layout)", async t => { + let fn = await new TemplateRender( + "./test/stubs/vue-layout.11ty.js" + ).getCompiledTemplate(); + t.is( + await fn({ name: "Zach" }), + ` +Test +

Hello Zach, this is a Vue template.

` + ); +}); + +test("JS Render using ViperHTML", async t => { + let fn = await new TemplateRender( + "./test/stubs/viperhtml.11ty.js" + ).getCompiledTemplate(); + t.is( + await fn({ name: "Zach", html: "Hi" }), + `
+ This is a viper template, Zach + Hi +
` + ); +}); + +test("JS Render with a function", async t => { + let tr = new TemplateRender("./test/stubs/function-filter.11ty.js"); + tr.config = { + javascriptFunctions: { + upper: function(val) { + return new String(val).toUpperCase(); + } + } + }; + + let fn = await tr.getCompiledTemplate(); + t.is(await fn({ name: "Zach" }), "

ZACHT9000

"); + t.is(await fn({ name: "Bill" }), "

BILLT9000

"); +}); + +test("JS Render with a function prototype", async t => { + let tr = new TemplateRender("./test/stubs/function-prototype.11ty.js"); + tr.config = { + javascriptFunctions: { + upper: function(val) { + return new String(val).toUpperCase(); + } + } + }; + + let fn = await tr.getCompiledTemplate(); + t.is(await fn({ name: "Zach" }), "

ZACHBillT9001

"); + t.is(await fn({ name: "Bill" }), "

BILLBillT9001

"); +}); + +test("JS Class Render with a function", async t => { + let tr = new TemplateRender("./test/stubs/class-filter.11ty.js"); + tr.config = { + javascriptFunctions: { + upper: function(val) { + return new String(val).toUpperCase(); + } + } + }; + + let fn = await tr.getCompiledTemplate(); + t.is(await fn({ name: "Zach" }), "

ZACHBillTed

"); + t.is(await fn({ name: "Bill" }), "

BILLBillTed

"); +}); + +test("JS Class Async Render with a function", async t => { + let tr = new TemplateRender("./test/stubs/class-async-filter.11ty.js"); + tr.config = { + javascriptFunctions: { + upper: function(val) { + return new String(val).toUpperCase(); + } + } + }; + + let fn = await tr.getCompiledTemplate(); + // Overrides all names to Ted + t.is(await fn({ name: "Zach" }), "

ZACHBillTed

"); + t.is(await fn({ name: "Bill" }), "

BILLBillTed

"); +}); diff --git a/test/TemplateRenderLiquidTest.js b/test/TemplateRenderLiquidTest.js new file mode 100644 index 000000000..0bed58f5c --- /dev/null +++ b/test/TemplateRenderLiquidTest.js @@ -0,0 +1,736 @@ +import test from "ava"; +import TemplateRender from "../src/TemplateRender"; + +// Liquid +test("Liquid", t => { + t.is(new TemplateRender("liquid").getEngineName(), "liquid"); +}); + +test("Liquid Render (with Helper)", async t => { + let fn = await new TemplateRender("liquid").getCompiledTemplate( + "

{{name | capitalize}}

" + ); + t.is(await fn({ name: "tim" }), "

Tim

"); +}); + +test("Liquid Render Include", async t => { + t.is(new TemplateRender("liquid", "./test/stubs/").getEngineName(), "liquid"); + + let fn = await new TemplateRender( + "liquid", + "./test/stubs/" + ).getCompiledTemplate("

{% include included %}

"); + t.is(await fn(), "

This is an include.

"); +}); + +test("Liquid Render Relative Include", async t => { + t.is(new TemplateRender("liquid", "./test/stubs/").getEngineName(), "liquid"); + + let fn = await new TemplateRender( + "liquid", + "./test/stubs/" + ).getCompiledTemplate("

{% include ./included %}

"); + t.is(await fn(), "

This is an include.

"); +}); + +test("Liquid Render Relative (current dir) Include", async t => { + let fn = await new TemplateRender( + "./test/stubs/relative-liquid/does_not_exist_and_thats_ok.liquid", + "./test/stubs/" + ).getCompiledTemplate("

{% include ./dir/included %}

"); + t.is(await fn(), "

TIME IS RELATIVE.

"); +}); + +test("Liquid Render Relative (parent dir) Include", async t => { + let fn = await new TemplateRender( + "./test/stubs/relative-liquid/dir/does_not_exist_and_thats_ok.liquid", + "./test/stubs/" + ).getCompiledTemplate("

{% include ../dir/included %}

"); + t.is(await fn(), "

TIME IS RELATIVE.

"); +}); + +test.skip("Liquid Render Relative (relative include should ignore _includes dir) Include", async t => { + let tr = new TemplateRender( + "./test/stubs/does_not_exist_and_thats_ok.liquid", + "./test/stubs/" + ); + + let fn = await tr.getCompiledTemplate(`

{% include ./included %}

`); + + // This is currently wrong, it uses _includes/included.liquid instead of ./included.liquid + // Not changing the above to ../stubs/included works fine because thatā€™s not an ambiguous reference. + t.is(await fn(), "

This is not in the includes dir.

"); +}); + +test("Liquid Render Include with Liquid Suffix", async t => { + t.is(new TemplateRender("liquid", "./test/stubs/").getEngineName(), "liquid"); + + let fn = await new TemplateRender( + "liquid", + "./test/stubs/" + ).getCompiledTemplate("

{% include included.liquid %}

"); + t.is(await fn(), "

This is an include.

"); +}); + +test("Liquid Render Include with HTML Suffix", async t => { + t.is(new TemplateRender("liquid", "./test/stubs/").getEngineName(), "liquid"); + + let fn = await new TemplateRender( + "liquid", + "./test/stubs/" + ).getCompiledTemplate("

{% include included.html %}

"); + t.is(await fn(), "

This is an include.

"); +}); + +test("Liquid Render Include with HTML Suffix and Data Pass in", async t => { + t.is(new TemplateRender("liquid", "./test/stubs/").getEngineName(), "liquid"); + + let fn = await new TemplateRender( + "liquid", + "./test/stubs/" + ).getCompiledTemplate( + "{% include included-data.html, myVariable: 'myValue' %}" + ); + t.is((await fn()).trim(), "This is an include. myValue"); +}); + +test("Liquid Custom Filter", async t => { + let tr = new TemplateRender("liquid", "./test/stubs/"); + tr.engine.addFilter("prefixWithZach", function(val) { + return "Zach" + val; + }); + + t.is(await tr.render("{{ 'test' | prefixWithZach }}", {}), "Zachtest"); +}); + +test("Liquid Custom Tag prefixWithZach", async t => { + let tr = new TemplateRender("liquid", "./test/stubs/"); + tr.engine.addTag("prefixWithZach", function(liquidEngine) { + return { + parse: function(tagToken, remainTokens) { + this.str = tagToken.args; // name + }, + render: function(scope, hash) { + var str = liquidEngine.evalValue(this.str, scope); // 'alice' + return Promise.resolve("Zach" + str); // 'Alice' + } + }; + }); + + t.is( + await tr.render("{% prefixWithZach name %}", { name: "test" }), + "Zachtest" + ); +}); + +test("Liquid Custom Tag postfixWithZach", async t => { + let tr = new TemplateRender("liquid", "./test/stubs/"); + tr.engine.addTag("postfixWithZach", function(liquidEngine) { + return { + parse: function(tagToken, remainTokens) { + this.str = tagToken.args; + }, + render: function(scope, hash) { + var str = liquidEngine.evalValue(this.str, scope); + return Promise.resolve(str + "Zach"); + } + }; + }); + + t.is( + await tr.render("{% postfixWithZach name %}", { name: "test" }), + "testZach" + ); +}); + +test("Liquid Custom Tag Unquoted String", async t => { + let tr = new TemplateRender("liquid", "./test/stubs/"); + tr.engine.addTag("testUnquotedStringTag", function(liquidEngine) { + return { + parse: function(tagToken, remainTokens) { + this.str = tagToken.args; + }, + render: function(scope, hash) { + return Promise.resolve(this.str + "Zach"); + } + }; + }); + + t.is( + await tr.render( + "{% testUnquotedStringTag _posts/2016-07-26-name-of-post.md %}", + { name: "test" } + ), + "_posts/2016-07-26-name-of-post.mdZach" + ); +}); + +test("Liquid addTag errors", async t => { + let tr = new TemplateRender("liquid", "./test/stubs/"); + t.throws(() => { + tr.engine.addTag("badSecondArgument", {}); + }); +}); + +test("Liquid addTags", async t => { + let tr = new TemplateRender("liquid", "./test/stubs/"); + tr.engine.addCustomTags({ + postfixWithZach: function(liquidEngine) { + return { + parse: function(tagToken, remainTokens) { + this.str = tagToken.args; + }, + render: function(scope, hash) { + var str = liquidEngine.evalValue(this.str, scope); + return Promise.resolve(str + "Zach"); + } + }; + } + }); + + t.is( + await tr.render("{% postfixWithZach name %}", { name: "test" }), + "testZach" + ); +}); + +test("Liquid Shortcode", async t => { + let tr = new TemplateRender("liquid", "./test/stubs/"); + tr.engine.addShortcode("postfixWithZach", function(str) { + return str + "Zach"; + }); + + t.is( + await tr.render("{% postfixWithZach name %}", { name: "test" }), + "testZach" + ); +}); + +test("Liquid Shortcode Safe Output", async t => { + let tr = new TemplateRender("liquid", "./test/stubs/"); + tr.engine.addShortcode("postfixWithZach", function(str) { + return `${str}`; + }); + + t.is( + await tr.render("{% postfixWithZach name %}", { name: "test" }), + "test" + ); +}); + +test("Liquid Paired Shortcode", async t => { + let tr = new TemplateRender("liquid", "./test/stubs/"); + tr.engine.addPairedShortcode("postfixWithZach", function(content, str) { + return str + content + "Zach"; + }); + + t.is( + await tr.render( + "{% postfixWithZach name %}Content{% endpostfixWithZach %}", + { name: "test" } + ), + "testContentZach" + ); +}); + +test("Liquid Render Include Subfolder", async t => { + let fn = await new TemplateRender( + "liquid", + "./test/stubs/" + ).getCompiledTemplate(`

{% include subfolder/included.liquid %}

`); + t.is(await fn(), "

This is an include.

"); +}); + +test("Liquid Render Include Subfolder HTML", async t => { + let fn = await new TemplateRender( + "liquid", + "./test/stubs/" + ).getCompiledTemplate(`

{% include subfolder/included.html %}

`); + t.is(await fn(), "

This is an include.

"); +}); + +test("Liquid Render Include Subfolder No file extension", async t => { + let fn = await new TemplateRender( + "liquid", + "./test/stubs/" + ).getCompiledTemplate(`

{% include subfolder/included %}

`); + t.is(await fn(), "

This is an include.

"); +}); + +// Skipped tests pending https://github.com/harttle/liquidjs/issues/61 +// Resolution: weā€™re going to leave this skipped as LiquidJS will require dynamicPartials +// to be on for quoted includes! +test.skip("Liquid Render Include Subfolder Single quotes", async t => { + let fn = await new TemplateRender( + "liquid", + "./test/stubs/" + ).getCompiledTemplate(`

{% include 'subfolder/included.liquid' %}

`); + t.is(await fn(), "

This is an include.

"); +}); + +test.skip("Liquid Render Include Subfolder Double quotes", async t => { + let fn = await new TemplateRender( + "liquid", + "./test/stubs/" + ).getCompiledTemplate(`

{% include "subfolder/included.liquid" %}

`); + t.is(await fn(), "

This is an include.

"); +}); + +test.skip("Liquid Render Include Subfolder Single quotes HTML", async t => { + let fn = await new TemplateRender( + "liquid", + "./test/stubs/" + ).getCompiledTemplate(`

{% include 'subfolder/included.html' %}

`); + t.is(await fn(), "

This is an include.

"); +}); + +test.skip("Liquid Render Include Subfolder Double quotes HTML", async t => { + let fn = await new TemplateRender( + "liquid", + "./test/stubs/" + ).getCompiledTemplate(`

{% include "subfolder/included.html" %}

`); + t.is(await fn(), "

This is an include.

"); +}); + +test.skip("Liquid Render Include Subfolder Single quotes No file extension", async t => { + let fn = await new TemplateRender( + "liquid", + "./test/stubs/" + ).getCompiledTemplate(`

{% include 'subfolder/included' %}

`); + t.is(await fn(), "

This is an include.

"); +}); + +test.skip("Liquid Render Include Subfolder Double quotes No file extension", async t => { + let fn = await new TemplateRender( + "liquid", + "./test/stubs/" + ).getCompiledTemplate(`

{% include "subfolder/included" %}

`); + t.is(await fn(), "

This is an include.

"); +}); +/* End skipped tests */ + +test("Liquid Options Overrides", async t => { + let tr = new TemplateRender("liquid", "./test/stubs/"); + tr.engine.setLiquidOptions({ dynamicPartials: true }); + + let options = tr.engine.getLiquidOptions(); + t.is(options.dynamicPartials, true); +}); + +test("Liquid Render Include Subfolder Single quotes no extension dynamicPartials true", async t => { + let tr = new TemplateRender("liquid", "./test/stubs/"); + tr.engine.setLiquidOptions({ dynamicPartials: true }); + + let fn = await tr.getCompiledTemplate( + `

{% include 'subfolder/included' %}

` + ); + t.is(await fn(), "

This is an include.

"); +}); + +test("Liquid Render Include Subfolder Single quotes (relative include current dir) dynamicPartials true", async t => { + let tr = new TemplateRender( + "./test/stubs/does_not_exist_and_thats_ok.liquid", + "./test/stubs/" + ); + tr.engine.setLiquidOptions({ dynamicPartials: true }); + + let fn = await tr.getCompiledTemplate( + `

{% include './relative-liquid/dir/included' %}

` + ); + t.is(await fn(), "

TIME IS RELATIVE.

"); +}); + +test("Liquid Render Include Subfolder Single quotes (relative include parent dir) dynamicPartials true", async t => { + let tr = new TemplateRender( + "./test/stubs/subfolder/does_not_exist_and_thats_ok.liquid", + "./test/stubs/" + ); + tr.engine.setLiquidOptions({ dynamicPartials: true }); + + let fn = await tr.getCompiledTemplate( + `

{% include '../relative-liquid/dir/included' %}

` + ); + t.is(await fn(), "

TIME IS RELATIVE.

"); +}); + +test("Liquid Render Include Subfolder Double quotes no extension dynamicPartials true", async t => { + let tr = new TemplateRender("liquid", "./test/stubs/"); + tr.engine.setLiquidOptions({ dynamicPartials: true }); + + let fn = await tr.getCompiledTemplate( + `

{% include "subfolder/included" %}

` + ); + t.is(await fn(), "

This is an include.

"); +}); + +test("Liquid Render Include Subfolder Single quotes dynamicPartials true", async t => { + let tr = new TemplateRender("liquid", "./test/stubs/"); + tr.engine.setLiquidOptions({ dynamicPartials: true }); + + let fn = await tr.getCompiledTemplate( + `

{% include 'subfolder/included.liquid' %}

` + ); + t.is(await fn(), "

This is an include.

"); +}); + +test("Liquid Render Include Subfolder Double quotes dynamicPartials true", async t => { + let tr = new TemplateRender("liquid", "./test/stubs/"); + tr.engine.setLiquidOptions({ dynamicPartials: true }); + + let fn = await tr.getCompiledTemplate( + `

{% include "subfolder/included.liquid" %}

` + ); + t.is(await fn(), "

This is an include.

"); +}); + +test("Liquid Render Include Subfolder Single quotes HTML dynamicPartials true", async t => { + let tr = new TemplateRender("liquid", "./test/stubs/"); + tr.engine.setLiquidOptions({ dynamicPartials: true }); + + let fn = await tr.getCompiledTemplate( + `

{% include 'subfolder/included.html' %}

` + ); + t.is(await fn(), "

This is an include.

"); +}); + +test("Liquid Render Include Subfolder Double quotes HTML dynamicPartials true", async t => { + let tr = new TemplateRender("liquid", "./test/stubs/"); + tr.engine.setLiquidOptions({ dynamicPartials: true }); + + let fn = await tr.getCompiledTemplate( + `

{% include "subfolder/included.html" %}

` + ); + t.is(await fn(), "

This is an include.

"); +}); + +test("Liquid Render Include Subfolder Single quotes HTML dynamicPartials true, data passed in", async t => { + let tr = new TemplateRender("liquid", "./test/stubs/"); + tr.engine.setLiquidOptions({ dynamicPartials: true }); + + let fn = await tr.getCompiledTemplate( + `

{% include 'subfolder/included.html', myVariable: 'myValue' %}

` + ); + t.is(await fn(), "

This is an include.

"); +}); + +test("Liquid Render Include Subfolder Double quotes HTML dynamicPartials true, data passed in", async t => { + let tr = new TemplateRender("liquid", "./test/stubs/"); + tr.engine.setLiquidOptions({ dynamicPartials: true }); + + let fn = await tr.getCompiledTemplate( + `

{% include "subfolder/included.html", myVariable: "myValue" %}

` + ); + t.is(await fn(), "

This is an include.

"); +}); + +test("Liquid Render: with Library Override", async t => { + let tr = new TemplateRender("liquid"); + + let lib = require("liquidjs")(); + tr.engine.setLibrary(lib); + + let fn = await tr.getCompiledTemplate("

{{name | capitalize}}

"); + t.is(await fn({ name: "tim" }), "

Tim

"); +}); + +test("Liquid Paired Shortcode with Tag Inside", async t => { + let tr = new TemplateRender("liquid", "./test/stubs/"); + tr.engine.addPairedShortcode("postfixWithZach", function(content, str) { + return str + content + "Zach"; + }); + + t.is( + await tr.render( + "{% postfixWithZach name %}Content{% if tester %}If{% endif %}{% endpostfixWithZach %}", + { name: "test", tester: true } + ), + "testContentIfZach" + ); +}); + +test("Liquid Nested Paired Shortcode", async t => { + let tr = new TemplateRender("liquid", "./test/stubs/"); + tr.engine.addPairedShortcode("postfixWithZach", function(content, str) { + return str + content + "Zach"; + }); + + t.is( + await tr.render( + "{% postfixWithZach name %}Content{% postfixWithZach name2 %}Content{% endpostfixWithZach %}{% endpostfixWithZach %}", + { name: "test", name2: "test2" } + ), + "testContenttest2ContentZachZach" + ); +}); + +test("Liquid Shortcode Multiple Args", async t => { + let tr = new TemplateRender("liquid", "./test/stubs/"); + tr.engine.addShortcode("postfixWithZach", function(str, str2) { + return str + str2 + "Zach"; + }); + + t.is( + await tr.render("{% postfixWithZach name other %}", { + name: "test", + other: "howdy" + }), + "testhowdyZach" + ); +}); + +test.skip("Liquid Include Scope Leak", async t => { + t.is(new TemplateRender("liquid", "./test/stubs/").getEngineName(), "liquid"); + + let fn = await new TemplateRender( + "liquid", + "./test/stubs/" + ).getCompiledTemplate("

{% include scopeleak %}{{ test }}

"); + t.is(await fn({ test: 1 }), "

21

"); +}); + +// TODO this will change in 1.0 +test("Liquid Missing Filter Issue #183 (no strict_filters)", async t => { + let tr = new TemplateRender("liquid", "./test/stubs/"); + + try { + await tr.render("{{ 'test' | prefixWithZach }}", {}); + t.pass("Did not error."); + } catch (e) { + t.fail("Threw an error."); + } +}); + +test("Liquid Missing Filter Issue #183", async t => { + let tr = new TemplateRender("liquid", "./test/stubs/"); + tr.engine.setLiquidOptions({ strict_filters: true }); + + try { + await tr.render("{{ 'test' | prefixWithZach }}", {}); + t.fail("Did not error."); + } catch (e) { + t.pass("Threw an error."); + } +}); + +test("Issue 258: Liquid Render Date", async t => { + let fn = await new TemplateRender("liquid").getCompiledTemplate( + "

{{ myDate }}

" + ); + let dateStr = await fn({ myDate: new Date(Date.UTC(2016, 0, 1, 0, 0, 0)) }); + t.is(dateStr.substr(0, 3), "

"); + t.is(dateStr.substr(-4), "

"); + t.not(dateStr.substr(2, 1), '"'); +}); + +test("Issue 347: Liquid addTags with space in argument", async t => { + let tr = new TemplateRender("liquid", "./test/stubs/"); + tr.engine.addCustomTags({ + issue347CustomTag: function(liquidEngine) { + return { + parse: function(tagToken, remainTokens) { + this.str = tagToken.args; + }, + render: function(scope, hash) { + var str = liquidEngine.evalValue(this.str, scope); + return Promise.resolve(str + "Zach"); + } + }; + } + }); + + t.is( + await tr.render("{% issue347CustomTag 'te st' %}", { + name: "slkdjflksdjf" + }), + "te stZach" + ); +}); + +test("Issue 347: Liquid Shortcode, string argument", async t => { + let tr = new TemplateRender("liquid", "./test/stubs/"); + tr.engine.addShortcode("issue347", function(str) { + return str + "Zach"; + }); + + t.is( + await tr.render("{% issue347 'test' %}", { name: "alkdsjfkslja" }), + "testZach" + ); +}); + +test("Issue 347: Liquid Shortcode string argument with space, double quotes", async t => { + let tr = new TemplateRender("liquid", "./test/stubs/"); + tr.engine.addShortcode("issue347b", function(str) { + return str + "Zach"; + }); + + t.is( + await tr.render('{% issue347b "test 2" "test 3" %}', { + name: "alkdsjfkslja" + }), + "test 2Zach" + ); +}); + +test("Issue 347: Liquid Shortcode string argument with space, single quotes", async t => { + let tr = new TemplateRender("liquid", "./test/stubs/"); + tr.engine.addShortcode("issue347", function(str) { + return str + "Zach"; + }); + + t.is( + await tr.render("{% issue347 'test 2' %}", { name: "alkdsjfkslja" }), + "test 2Zach" + ); +}); + +test("Issue 347: Liquid Shortcode string argument with space, combination of quotes", async t => { + let tr = new TemplateRender("liquid", "./test/stubs/"); + tr.engine.addShortcode("issue347", function(str, str2) { + return str + str2 + "Zach"; + }); + + t.is( + await tr.render("{% issue347 'test 2' \"test 3\" %}", { + name: "alkdsjfkslja" + }), + "test 2test 3Zach" + ); +}); + +test("Issue 347: Liquid Shortcode multiple arguments, comma separated", async t => { + let tr = new TemplateRender("liquid", "./test/stubs/"); + tr.engine.addShortcode("issue347", function(str, str2) { + return str + str2 + "Zach"; + }); + + t.is( + await tr.render("{% issue347 'test 2', \"test 3\" %}", { + name: "alkdsjfkslja" + }), + "test 2test 3Zach" + ); +}); + +test("Issue 347: Liquid Shortcode multiple arguments, comma separated, one is an integer", async t => { + let tr = new TemplateRender("liquid", "./test/stubs/"); + tr.engine.addShortcode("issue347", function(str, str2) { + return str + str2 + "Zach"; + }); + + t.is( + await tr.render("{% issue347 'test 2', 3 %}", { name: "alkdsjfkslja" }), + "test 23Zach" + ); +}); + +test("Issue 347: Liquid Shortcode multiple arguments, comma separated, one is a float", async t => { + let tr = new TemplateRender("liquid", "./test/stubs/"); + tr.engine.addShortcode("issue347", function(str, str2) { + return str + str2 + "Zach"; + }); + + t.is( + await tr.render("{% issue347 'test 2', 3.23 %}", { name: "alkdsjfkslja" }), + "test 23.23Zach" + ); +}); + +test("Issue 347: Liquid Shortcode boolean argument", async t => { + let tr = new TemplateRender("liquid", "./test/stubs/"); + tr.engine.addShortcode("issue347", function(bool) { + return bool ? "Zach" : "Not Zach"; + }); + + t.is( + await tr.render("{% issue347 true %}", { name: "alkdsjfkslja" }), + "Zach" + ); + t.is( + await tr.render("{% issue347 false %}", { name: "alkdsjfkslja" }), + "Not Zach" + ); +}); + +test("Issue 347: Liquid Paired Shortcode with Spaces", async t => { + let tr = new TemplateRender("liquid", "./test/stubs/"); + tr.engine.addPairedShortcode("postfixWithZach", function( + content, + str1, + num, + str2 + ) { + return str1 + num + str2 + content + "Zach"; + }); + + t.is( + await tr.render( + "{% postfixWithZach 'My Name', 1234, \"Other\" %}Content{% endpostfixWithZach %}", + { name: "test" } + ), + "My Name1234OtherContentZach" + ); +}); + +test("Liquid Render with dash variable Issue #567", async t => { + let tr = new TemplateRender("liquid"); + + let fn = await tr.getCompiledTemplate("

{{ my-global-name }}

"); + t.is(await fn({ "my-global-name": "Zach" }), "

Zach

"); +}); + +test("Issue 600: Liquid Shortcode argument page.url", async t => { + let tr = new TemplateRender("liquid", "./test/stubs/"); + tr.engine.addShortcode("issue600", function(str) { + return str + "Zach"; + }); + + t.is( + await tr.render("{% issue600 page.url %}", { + page: { url: "alkdsjfkslja" } + }), + "alkdsjfksljaZach" + ); +}); + +test("Issue 600: Liquid Shortcode argument with dashes", async t => { + let tr = new TemplateRender("liquid", "./test/stubs/"); + tr.engine.addShortcode("issue600b", function(str) { + return str + "Zach"; + }); + + t.is( + await tr.render("{% issue600b page-url %}", { + "page-url": "alkdsjfkslja" + }), + "alkdsjfksljaZach" + ); +}); + +test("Issue 600: Liquid Shortcode argument with underscores", async t => { + let tr = new TemplateRender("liquid", "./test/stubs/"); + tr.engine.addShortcode("issue600c", function(str) { + return str + "Zach"; + }); + + t.is( + await tr.render("{% issue600c page_url %}", { + page_url: "alkdsjfkslja" + }), + "alkdsjfksljaZach" + ); +}); + +test.skip("Issue 611: Run a function", async t => { + // This works in Nunjucks + let tr = new TemplateRender("liquid", "./test/stubs/"); + + t.is( + await tr.render("{{ test() }}", { + test: function() { + return "alkdsjfksljaZach"; + } + }), + "alkdsjfksljaZach" + ); +}); diff --git a/test/TemplateRenderMarkdownTest.js b/test/TemplateRenderMarkdownTest.js new file mode 100644 index 000000000..cca7dbbe6 --- /dev/null +++ b/test/TemplateRenderMarkdownTest.js @@ -0,0 +1,333 @@ +import test from "ava"; +import TemplateRender from "../src/TemplateRender"; +import md from "markdown-it"; +import mdEmoji from "markdown-it-emoji"; +import UserConfig from "../src/UserConfig"; +import eleventySyntaxHighlightPlugin from "@11ty/eleventy-plugin-syntaxhighlight"; + +// Markdown +test("Markdown", t => { + t.is(new TemplateRender("md").getEngineName(), "md"); +}); + +test("Markdown Render: Parses base markdown, no data", async t => { + let fn = await new TemplateRender("md").getCompiledTemplate("# My Title"); + t.is((await fn()).trim(), "

My Title

"); +}); + +test("Markdown Render: Markdown should work with HTML too", async t => { + let fn = await new TemplateRender("md").getCompiledTemplate( + "

My Title

" + ); + t.is((await fn()).trim(), "

My Title

"); +}); + +test("Markdown Render: Parses markdown using liquid engine (default, with data)", async t => { + let fn = await new TemplateRender("md").getCompiledTemplate("# {{title}}"); + t.is((await fn({ title: "My Title" })).trim(), "

My Title

"); +}); + +test("Markdown Render: Parses markdown using ejs engine", async t => { + let tr = new TemplateRender("md"); + tr.setMarkdownEngine("ejs"); + let fn = await tr.getCompiledTemplate("<%=title %>"); + t.is((await fn({ title: "My Title" })).trim(), "

My Title

"); +}); + +test("Markdown Render: Ignore markdown, use only preprocess engine (useful for variable resolution in permalinks)", async t => { + let tr = new TemplateRender("md"); + tr.setUseMarkdown(false); + let fn = await tr.getCompiledTemplate("{{title}}"); + t.is((await fn({ title: "My Title" })).trim(), "My Title"); +}); + +test("Markdown Render: Skip markdown and preprocess engine (issue #466)", async t => { + let tr = new TemplateRender("md"); + tr.setMarkdownEngine(false); + tr.setUseMarkdown(false); + let fn = await tr.getCompiledTemplate("404.html"); + t.is((await fn({ title: "My Title" })).trim(), "404.html"); +}); + +test("Markdown Render: Set markdown engine to false, donā€™t parse", async t => { + let tr = new TemplateRender("md"); + tr.setMarkdownEngine(false); + let fn = await tr.getCompiledTemplate("# {{title}}"); + t.is((await fn()).trim(), "

{{title}}

"); +}); + +test("Markdown Render: Set markdown engine to false, donā€™t parse (test with HTML input)", async t => { + let tr = new TemplateRender("md"); + tr.setMarkdownEngine(false); + let fn = await tr.getCompiledTemplate("

{{title}}

"); + + t.is((await fn()).trim(), "

{{title}}

"); +}); + +test("Markdown Render: Pass in engine override (ejs)", async t => { + let tr = new TemplateRender("md"); + tr.setMarkdownEngine("ejs"); + let fn = await tr.getCompiledTemplate("# <%= title %>"); + t.is((await fn({ title: "My Title" })).trim(), "

My Title

"); +}); + +test("Markdown Render: Pass in an override (liquid)", async t => { + let tr = new TemplateRender("md"); + tr.setMarkdownEngine("liquid"); + let fn = await tr.getCompiledTemplate("# {{title}}"); + + t.is((await fn({ title: "My Title" })).trim(), "

My Title

"); +}); + +test("Markdown Render: Strikethrough", async t => { + let fn = await new TemplateRender("md").getCompiledTemplate("~~No~~"); + t.is((await fn()).trim(), "

No

"); +}); + +test("Markdown Render: Strikethrough in a Header", async t => { + let fn = await new TemplateRender("md").getCompiledTemplate("# ~~No~~"); + t.is((await fn()).trim(), "

No

"); +}); + +test("Markdown Render: with Library Override", async t => { + let tr = new TemplateRender("md"); + + let mdLib = md(); + tr.engine.setLibrary(mdLib); + t.is(mdLib.render(":)").trim(), "

:)

"); + + let fn = await tr.getCompiledTemplate(":)"); + t.is((await fn()).trim(), "

:)

"); +}); + +test("Markdown Render: with Library Override and a Plugin", async t => { + let tr = new TemplateRender("md"); + + let mdLib = md().use(mdEmoji); + tr.engine.setLibrary(mdLib); + t.is(mdLib.render(":)").trim(), "

šŸ˜ƒ

"); + + let fn = await tr.getCompiledTemplate(":)"); + t.is((await fn()).trim(), "

šŸ˜ƒ

"); +}); + +test("Markdown Render: use a custom highlighter", async t => { + let tr = new TemplateRender("md"); + + let mdLib = md(); + mdLib.set({ + highlight: function(str, lang) { + return "This is overrrrrrride"; + } + }); + tr.engine.setLibrary(mdLib); + + let fn = await tr.getCompiledTemplate(`\`\`\` +This is some code. +\`\`\``); + t.is((await fn()).trim(), "
This is overrrrrrride
"); +}); + +test("Markdown Render: use prism highlighter (no language)", async t => { + let tr = new TemplateRender("md"); + let userConfig = new UserConfig(); + userConfig.addPlugin(eleventySyntaxHighlightPlugin); + + let markdownHighlight = userConfig.getMergingConfigObject() + .markdownHighlighter; + + let mdLib = md(); + mdLib.set({ + highlight: markdownHighlight + }); + tr.engine.setLibrary(mdLib); + + let fn = await tr.getCompiledTemplate(`\`\`\` +This is some code. +\`\`\``); + t.is( + (await fn()).trim(), + `
This is some code.
+
` + ); +}); + +test("Markdown Render: use prism highlighter", async t => { + let tr = new TemplateRender("md"); + let userConfig = new UserConfig(); + userConfig.addPlugin(eleventySyntaxHighlightPlugin); + + let markdownHighlight = userConfig.getMergingConfigObject() + .markdownHighlighter; + + let mdLib = md(); + mdLib.set({ + highlight: markdownHighlight + }); + tr.engine.setLibrary(mdLib); + + let fn = await tr.getCompiledTemplate(`\`\`\` js +var key = "value"; +\`\`\``); + t.is( + (await fn()).trim(), + `
var key = "value";
` + ); +}); + +test("Markdown Render: use prism highlighter (no space before language)", async t => { + let tr = new TemplateRender("md"); + let userConfig = new UserConfig(); + userConfig.addPlugin(eleventySyntaxHighlightPlugin); + + let markdownHighlight = userConfig.getMergingConfigObject() + .markdownHighlighter; + + let mdLib = md(); + mdLib.set({ + highlight: markdownHighlight + }); + tr.engine.setLibrary(mdLib); + + let fn = await tr.getCompiledTemplate(`\`\`\`js +var key = "value"; +\`\`\``); + t.is( + (await fn()).trim(), + `
var key = "value";
` + ); +}); + +test("Markdown Render: use prism highlighter, line highlighting", async t => { + let tr = new TemplateRender("md"); + let userConfig = new UserConfig(); + userConfig.addPlugin(eleventySyntaxHighlightPlugin); + + let markdownHighlight = userConfig.getMergingConfigObject() + .markdownHighlighter; + + let mdLib = md(); + mdLib.set({ + highlight: markdownHighlight + }); + tr.engine.setLibrary(mdLib); + + let fn = await tr.getCompiledTemplate(`\`\`\`js/0 +var key = "value"; +\`\`\``); + t.is( + (await fn()).trim(), + `
var key = "value";
` + ); +}); + +test("Markdown Render: use prism highlighter, line highlighting with fallback `text` language.", async t => { + let tr = new TemplateRender("md"); + let userConfig = new UserConfig(); + userConfig.addPlugin(eleventySyntaxHighlightPlugin); + + let markdownHighlight = userConfig.getMergingConfigObject() + .markdownHighlighter; + + let mdLib = md(); + mdLib.set({ + highlight: markdownHighlight + }); + tr.engine.setLibrary(mdLib); + + let fn = await tr.getCompiledTemplate(`\`\`\` text/0 +var key = "value"; +\`\`\``); + t.is( + (await fn()).trim(), + `
var key = "value";
` + ); +}); + +test("Markdown Render: use Markdown inside of a Liquid shortcode (Issue #536)", async t => { + let tr = new TemplateRender("md"); + + let cls = require("../src/Engines/Liquid"); + let liquidEngine = new cls("liquid", tr.getIncludesDir()); + liquidEngine.addShortcode("testShortcode", function() { + return "## My Other Title"; + }); + tr.setMarkdownEngine(liquidEngine); + + let fn = await tr.getCompiledTemplate(`# {{title}} +{% testShortcode %}`); + t.is( + (await fn({ + title: "My Title", + otherTitle: "My Other Title" + })).trim(), + `

My Title

+

My Other Title

` + ); +}); + +test("Markdown Render: use Markdown inside of a Nunjucks shortcode (Issue #536)", async t => { + let tr = new TemplateRender("md"); + + let cls = require("../src/Engines/Nunjucks"); + let nunjucksEngine = new cls("njk", tr.getIncludesDir()); + nunjucksEngine.addShortcode("testShortcode", function() { + return "## My Other Title"; + }); + tr.setMarkdownEngine(nunjucksEngine); + + let fn = await tr.getCompiledTemplate(`# {{title}} +{% testShortcode %}`); + t.is( + (await fn({ + title: "My Title", + otherTitle: "My Other Title" + })).trim(), + `

My Title

+

My Other Title

` + ); +}); + +test("Markdown Render: use Markdown inside of a Liquid paired shortcode (Issue #536)", async t => { + let tr = new TemplateRender("md"); + + let cls = require("../src/Engines/Liquid"); + let liquidEngine = new cls("liquid", tr.getIncludesDir()); + liquidEngine.addPairedShortcode("testShortcode", function(content) { + return content; + }); + tr.setMarkdownEngine(liquidEngine); + + let fn = await tr.getCompiledTemplate(`# {{title}} +{% testShortcode %}## My Other Title{% endtestShortcode %}`); + t.is( + (await fn({ + title: "My Title", + otherTitle: "My Other Title" + })).trim(), + `

My Title

+

My Other Title

` + ); +}); + +test("Markdown Render: use Markdown inside of a Nunjucks paired shortcode (Issue #536)", async t => { + let tr = new TemplateRender("md"); + + let cls = require("../src/Engines/Nunjucks"); + let nunjucksEngine = new cls("njk", tr.getIncludesDir()); + nunjucksEngine.addPairedShortcode("testShortcode", function(content) { + return content; + }); + tr.setMarkdownEngine(nunjucksEngine); + + let fn = await tr.getCompiledTemplate(`# {{title}} +{% testShortcode %}## My Other Title{% endtestShortcode %}`); + t.is( + (await fn({ + title: "My Title", + otherTitle: "My Other Title" + })).trim(), + `

My Title

+

My Other Title

` + ); +}); diff --git a/test/TemplateRenderMustacheTest.js b/test/TemplateRenderMustacheTest.js new file mode 100644 index 000000000..802bdd0f5 --- /dev/null +++ b/test/TemplateRenderMustacheTest.js @@ -0,0 +1,80 @@ +import test from "ava"; +import TemplateRender from "../src/TemplateRender"; + +// Mustache +test("Mustache", async t => { + t.is(new TemplateRender("mustache").getEngineName(), "mustache"); +}); + +test("Mustache Render", async t => { + let fn = await new TemplateRender("mustache").getCompiledTemplate( + "

{{name}}

" + ); + t.is(await fn({ name: "Zach" }), "

Zach

"); +}); + +test("Mustache Render Partial (raw text content)", async t => { + let fn = await new TemplateRender( + "mustache", + "./test/stubs/" + ).getCompiledTemplate("

{{> included}}

"); + t.is(await fn(), "

This is an include.

"); +}); + +test.skip("Mustache Render Partial (relative path, raw text content)", async t => { + let fn = await new TemplateRender( + "./test/stubs/does_not_exist_and_thats_ok.mustache", + "./test/stubs/" + ).getCompiledTemplate("

{{> ./includedrelative}}

"); + t.is(await fn(), "

This is an includdde.

"); +}); + +test("Mustache Render Partial (uses a variable in content)", async t => { + let fn = await new TemplateRender( + "mustache", + "./test/stubs/" + ).getCompiledTemplate("

{{> includedvar}}

"); + t.is(await fn({ name: "Zach" }), "

This is a Zach.

"); +}); + +test("Mustache Render Partial (Subdirectory)", async t => { + let fn = await new TemplateRender( + "mustache", + "./test/stubs/" + ).getCompiledTemplate("

{{> subfolder/included}}

"); + t.is(await fn({ name: "Zach" }), "

This is an include.

"); +}); + +test("Mustache Render: with Library Override", async t => { + let tr = new TemplateRender("mustache"); + + let lib = require("mustache"); + tr.engine.setLibrary(lib); + + let fn = await tr.getCompiledTemplate("

{{name}}

"); + t.is(await fn({ name: "Zach" }), "

Zach

"); +}); + +test("Mustache Render Unescaped Output (no HTML)", async t => { + let fn = await new TemplateRender("mustache").getCompiledTemplate( + "

{{{name}}}

" + ); + t.is(await fn({ name: "Zach" }), "

Zach

"); +}); + +test("Mustache Render Escaped Output", async t => { + let fn = await new TemplateRender("mustache").getCompiledTemplate( + "

{{name}}

" + ); + t.is( + await fn({ name: "Zach" }), + "

<b>Zach</b>

" + ); +}); + +test("Mustache Render Unescaped Output (HTML)", async t => { + let fn = await new TemplateRender("mustache").getCompiledTemplate( + "

{{{name}}}

" + ); + t.is(await fn({ name: "Zach" }), "

Zach

"); +}); diff --git a/test/TemplateRenderNunjucksTest.js b/test/TemplateRenderNunjucksTest.js new file mode 100644 index 000000000..11a499038 --- /dev/null +++ b/test/TemplateRenderNunjucksTest.js @@ -0,0 +1,433 @@ +import test from "ava"; +import TemplateRender from "../src/TemplateRender"; + +// Nunjucks +test("Nunjucks", t => { + t.is(new TemplateRender("njk").getEngineName(), "njk"); +}); + +test("Nunjucks Render", async t => { + let fn = await new TemplateRender("njk").getCompiledTemplate( + "

{{ name }}

" + ); + t.is(await fn({ name: "Zach" }), "

Zach

"); +}); + +test("Nunjucks Render Extends", async t => { + let fn = await new TemplateRender("njk", "test/stubs").getCompiledTemplate( + "{% extends 'base.njk' %}{% block content %}This is a child.{% endblock %}" + ); + t.is(await fn(), "

This is a child.

"); +}); + +test("Nunjucks Render Relative Extends", async t => { + let fn = await new TemplateRender( + "./test/stubs/njk-relative/dir/does_not_exist_and_thats_ok.njk", + "test/stubs" + ).getCompiledTemplate( + "{% extends '../dir/base.njk' %}{% block content %}This is a child.{% endblock %}" + ); + t.is(await fn(), "

This is a child.

"); +}); + +test("Nunjucks Render Include", async t => { + let fn = await new TemplateRender("njk", "test/stubs").getCompiledTemplate( + "

{% include 'included.njk' %}

" + ); + t.is(await fn(), "

This is an include.

"); +}); + +test("Nunjucks Render Include (different extension)", async t => { + let fn = await new TemplateRender("njk", "test/stubs").getCompiledTemplate( + "

{% include 'included.nunj' %}

" + ); + t.is(await fn(), "

Nunjabusiness

"); +}); + +test("Nunjucks Render Include (different extension, subdir)", async t => { + let fn = await new TemplateRender("njk", "test/stubs").getCompiledTemplate( + "

{% include 'subfolder/included.nunj' %}

" + ); + t.is(await fn(), "

Nunjabusiness2

"); +}); + +test("Nunjucks Render Relative Include Issue #190", async t => { + let tr = new TemplateRender( + "./test/stubs/njk-relative/does_not_exist_and_thats_ok.njk", + "./test/stubs" + ); + let fn = await tr.getCompiledTemplate( + "

{% include './dir/included.njk' %}

" + ); + t.is(await fn(), "

HELLO FROM THE OTHER SIDE.

"); +}); + +test("Nunjucks Render Relative Include (using ..) Issue #190", async t => { + let tr = new TemplateRender( + "./test/stubs/njk-relative/dir/does_not_exist_and_thats_ok.njk", + "./test/stubs" + ); + let fn = await tr.getCompiledTemplate( + "

{% include '../dir/included.njk' %}

" + ); + t.is(await fn(), "

HELLO FROM THE OTHER SIDE.

"); +}); + +test("Nunjucks Render Relative Include (using current dir) Issue #190", async t => { + let tr = new TemplateRender( + "./test/stubs/njk-relative/dir/does_not_exist_and_thats_ok.njk", + "./test/stubs" + ); + let fn = await tr.getCompiledTemplate( + "

{% include './included.njk' %}

" + ); + t.is(await fn(), "

HELLO FROM THE OTHER SIDE.

"); + + // This fails because ./ doesnā€™t look in _includes (this is good) + // let fn = await tr.getCompiledTemplate( + // "

{% include './included-relative.njk' %}

" + // ); + // t.is(await fn(), "

akdlsjafkljdskl

"); +}); + +test("Nunjucks Render Relative Include (ambiguous path, file exists in _includes and in current dir) Issue #190", async t => { + let tr = new TemplateRender( + "./test/stubs/njk-relative/dir/does_not_exist_and_thats_ok.njk", + "./test/stubs" + ); + let fn = await tr.getCompiledTemplate( + // should prefer to use _includes first + // more specifically, this will not use the current dir at all. + "

{% include 'included.njk' %}

" + ); + t.is(await fn(), "

This is an include.

"); + + // This fails, a leading dot is required for a relative include + // let tr2 = new TemplateRender("./test/stubs/njk-relative/dir/does_not_exist_and_thats_ok.njk", "./test/stubs"); + // let fn2 = await tr.getCompiledTemplate( + // "

{% include 'unique-include-123.njk' %}

" + // ); + // t.is(await fn2(), "

HELLO FROM THE OTHER SIDE.

"); +}); + +test("Nunjucks Render Include a JS file (Issue 398)", async t => { + let tr = new TemplateRender("njk", "test/stubs"); + let engine = tr.engine; + engine.addFilters({ + jsmin: function(str) { + return str; + } + }); + let fn = await tr.getCompiledTemplate( + "{% set ga %}{% include 'test.js' %}{% endset %}{{ ga | safe | jsmin }}" + ); + t.is((await fn()).trim(), `/* THIS IS A COMMENT */ alert("Issue #398");`); +}); + +test("Nunjucks Render Include Subfolder", async t => { + let fn = await new TemplateRender("njk", "test/stubs").getCompiledTemplate( + "

{% include 'subfolder/included.html' %}

" + ); + t.is(await fn(), "

This is an include.

"); +}); + +test("Nunjucks Render Include Double Quotes", async t => { + let fn = await new TemplateRender("njk", "test/stubs").getCompiledTemplate( + `

{% include "included.njk" %}

` + ); + t.is(await fn(), "

This is an include.

"); +}); + +test("Nunjucks Render Include Subfolder Double Quotes", async t => { + let fn = await new TemplateRender("njk", "test/stubs").getCompiledTemplate( + `

{% include "subfolder/included.html" %}

` + ); + t.is(await fn(), "

This is an include.

"); +}); + +test("Nunjucks Render Imports", async t => { + let fn = await new TemplateRender("njk", "test/stubs").getCompiledTemplate( + "{% import 'imports.njk' as forms %}
{{ forms.label('Name') }}
" + ); + t.is(await fn(), "
"); +}); + +test("Nunjucks Render Relative Imports", async t => { + let fn = await new TemplateRender( + "./test/stubs/njk-relative/dir/does_not_exist_and_thats_ok.njk", + "test/stubs" + ).getCompiledTemplate( + "{% import '../dir/imports.njk' as forms %}
{{ forms.label('Name') }}
" + ); + t.is(await fn(), "
"); +}); + +test("Nunjucks Render Imports From", async t => { + let fn = await new TemplateRender("njk", "test/stubs").getCompiledTemplate( + "{% from 'imports.njk' import label %}
{{ label('Name') }}
" + ); + t.is(await fn(), "
"); +}); + +test("Nunjucks getEngineLib", async t => { + let tr = new TemplateRender("njk", "./test/stubs/"); + t.truthy(tr.engine.getEngineLib()); +}); + +test("Nunjucks Render: with Library Override", async t => { + let tr = new TemplateRender("njk"); + + let lib = require("nunjucks"); + let env = new lib.Environment( + new lib.FileSystemLoader("./test/stubs/_includes/") + ); + tr.engine.setLibrary(env); + + let fn = await tr.getCompiledTemplate("

{{ name }}

"); + t.is(await fn({ name: "Zach" }), "

Zach

"); +}); + +test("Nunjucks Render with getGlobals Issue #567", async t => { + let tr = new TemplateRender("njk"); + let env = tr.engine.getEngineLib(); + env.addGlobal("getGlobals", function() { + return this.getVariables(); + }); + + let fn = await tr.getCompiledTemplate( + "

{{ getGlobals()['my-global-name'] }}

" + ); + t.is(await fn({ "my-global-name": "Zach" }), "

Zach

"); +}); + +test("Nunjucks Render with getVarByName Filter Issue #567", async t => { + let tr = new TemplateRender("njk"); + let env = tr.engine.getEngineLib(); + env.addFilter("getVarByName", function(varName) { + return this.getVariables()[varName]; + }); + + let fn = await tr.getCompiledTemplate( + "

{{ 'my-global-name' | getVarByName }}

" + ); + t.is(await fn({ "my-global-name": "Zach" }), "

Zach

"); +}); + +test("Nunjucks Shortcode without args", async t => { + let tr = new TemplateRender("njk", "./test/stubs/"); + tr.engine.addShortcode("postfixWithZach", function() { + return "Zach"; + }); + + t.is(await tr.render("{% postfixWithZach %}", {}), "Zach"); +}); + +test("Nunjucks Shortcode", async t => { + let tr = new TemplateRender("njk", "./test/stubs/"); + tr.engine.addShortcode("postfixWithZach", function(str) { + return str + "Zach"; + }); + + t.is( + await tr.render("{% postfixWithZach name %}", { name: "test" }), + "testZach" + ); +}); + +test("Nunjucks Shortcode Safe Output", async t => { + let tr = new TemplateRender("njk", "./test/stubs/"); + tr.engine.addShortcode("postfixWithZach", function(str) { + return `${str}`; + }); + + t.is( + await tr.render("{% postfixWithZach name %}", { name: "test" }), + "test" + ); +}); + +test("Nunjucks Paired Shortcode", async t => { + let tr = new TemplateRender("njk", "./test/stubs/"); + tr.engine.addPairedShortcode("postfixWithZach", function(content, str) { + return str + content + "Zach"; + }); + + t.is( + await tr.render( + "{% postfixWithZach name %}Content{% endpostfixWithZach %}", + { name: "test" } + ), + "testContentZach" + ); +}); + +test("Nunjucks Paired Shortcode without args", async t => { + let tr = new TemplateRender("njk", "./test/stubs/"); + tr.engine.addPairedShortcode("postfixWithZach", function(content) { + return content + "Zach"; + }); + + t.is( + await tr.render("{% postfixWithZach %}Content{% endpostfixWithZach %}", {}), + "ContentZach" + ); +}); + +test("Nunjucks Paired Shortcode with Tag Inside", async t => { + let tr = new TemplateRender("njk", "./test/stubs/"); + tr.engine.addPairedShortcode("postfixWithZach", function(content, str) { + return str + content + "Zach"; + }); + + t.is( + await tr.render( + "{% postfixWithZach name %}Content{% if tester %}If{% endif %}{% endpostfixWithZach %}", + { name: "test", tester: true } + ), + "testContentIfZach" + ); +}); + +test("Nunjucks Nested Paired Shortcode", async t => { + let tr = new TemplateRender("njk", "./test/stubs/"); + tr.engine.addPairedShortcode("postfixWithZach", function(content, str) { + return str + content + "Zach"; + }); + + t.is( + await tr.render( + "{% postfixWithZach name %}Content{% postfixWithZach name2 %}Content{% endpostfixWithZach %}{% endpostfixWithZach %}", + { name: "test", name2: "test2" } + ), + "testContenttest2ContentZachZach" + ); +}); + +test("Nunjucks Shortcode Multiple Args", async t => { + let tr = new TemplateRender("njk", "./test/stubs/"); + tr.engine.addShortcode("postfixWithZach", function(str, str2) { + return str + str2 + "Zach"; + }); + + t.is( + await tr.render("{% postfixWithZach name, other %}", { + name: "test", + other: "howdy" + }), + "testhowdyZach" + ); +}); + +test("Nunjucks Shortcode Named Args", async t => { + let tr = new TemplateRender("njk", "./test/stubs/"); + tr.engine.addShortcode("postfixWithZach", function(arg) { + return arg.arg1 + arg.arg2 + "Zach"; + }); + + t.is( + await tr.render("{% postfixWithZach arg1=name, arg2=other %}", { + name: "test", + other: "howdy" + }), + "testhowdyZach" + ); +}); + +test("Nunjucks Shortcode Named Args (Reverse Order)", async t => { + let tr = new TemplateRender("njk", "./test/stubs/"); + tr.engine.addShortcode("postfixWithZach", function(arg) { + return arg.arg1 + arg.arg2 + "Zach"; + }); + + t.is( + await tr.render("{% postfixWithZach arg2=other, arg1=name %}", { + name: "test", + other: "howdy" + }), + "testhowdyZach" + ); +}); + +test("Nunjucks Shortcode Named Args (JS notation)", async t => { + let tr = new TemplateRender("njk", "./test/stubs/"); + tr.engine.addShortcode("postfixWithZach", function(arg) { + return arg.arg1 + arg.arg2 + "Zach"; + }); + + t.is( + await tr.render("{% postfixWithZach { arg1: name, arg2: other } %}", { + name: "test", + other: "howdy" + }), + "testhowdyZach" + ); +}); + +test("Nunjucks Test if statements on arrays (Issue #524)", async t => { + let tr = new TemplateRender("njk", "./test/stubs/"); + + t.is( + await tr.render("{% if 'first' in tags %}Success.{% endif %}", { + tags: ["first", "second"] + }), + "Success." + ); + + t.is( + await tr.render("{% if 'sdfsdfs' in tags %}{% else %}Success.{% endif %}", { + tags: ["first", "second"] + }), + "Success." + ); + + t.is( + await tr.render( + "{% if false %}{% elseif 'first' in tags %}Success.{% endif %}", + { + tags: ["first", "second"] + } + ), + "Success." + ); + + t.is( + await tr.render("{% if tags.includes('first') %}Success.{% endif %}", { + tags: ["first", "second"] + }), + "Success." + ); + + t.is( + await tr.render( + "{% if tags.includes('dsds') %}{% else %}Success.{% endif %}", + { + tags: ["first", "second"] + } + ), + "Success." + ); + + t.is( + await tr.render( + "{% if false %}{% elseif tags.includes('first') %}Success.{% endif %}", + { + tags: ["first", "second"] + } + ), + "Success." + ); +}); + +test("Issue 611: Run a function", async t => { + // This does not work in Liquid + let tr = new TemplateRender("njk", "./test/stubs/"); + + t.is( + await tr.render("{{ test() }}", { + test: function() { + return "alkdsjfksljaZach"; + } + }), + "alkdsjfksljaZach" + ); +}); diff --git a/test/TemplateRenderPugTest.js b/test/TemplateRenderPugTest.js new file mode 100644 index 000000000..5ec9e87ec --- /dev/null +++ b/test/TemplateRenderPugTest.js @@ -0,0 +1,148 @@ +import test from "ava"; +import TemplateRender from "../src/TemplateRender"; + +// Pug +test("Pug", t => { + t.is(new TemplateRender("pug").getEngineName(), "pug"); +}); + +test("Pug Render", async t => { + let fn = await new TemplateRender("pug").getCompiledTemplate("p= name"); + t.is(await fn({ name: "Zach" }), "

Zach

"); +}); + +test("Pug Render Include (Absolute)", async t => { + let fn = await new TemplateRender("pug", "./test/stubs/") + .getCompiledTemplate(`p + include /included.pug`); + t.is(await fn({ name: "Zach" }), "

This is an include.

"); +}); + +test("Pug Render Include with Data", async t => { + let fn = await new TemplateRender("pug", "./test/stubs/") + .getCompiledTemplate(`p + include /includedvar.pug`); + t.is(await fn({ name: "Zach" }), "

This is Zach.

"); +}); + +test("Pug Render Include with Data, inline var overrides data", async t => { + let fn = await new TemplateRender("pug", "./test/stubs/") + .getCompiledTemplate(` +- var name = "Bill"; +p + include /includedvar.pug`); + t.is(await fn({ name: "Zach" }), "

This is Bill.

"); +}); + +test("Pug Render Extends (Layouts)", async t => { + let fn = await new TemplateRender("pug", "./test/stubs/") + .getCompiledTemplate(`extends /layout.pug +block content + h1= name`); + t.is(await fn({ name: "Zach" }), "

Zach

"); +}); + +test("Pug Render Extends (Relative, Layouts)", async t => { + let fn = await new TemplateRender( + "./test/stubs/does_not_exist_and_thats_ok.pug", + "./test/stubs/" + ).getCompiledTemplate(`extends ./layout-relative.pug +block content + h1= name`); + t.is(await fn({ name: "Zach" }), "

Zach

"); +}); + +test("Pug Render Include (Relative)", async t => { + let fn = await new TemplateRender("pug", "./test/stubs/") + .getCompiledTemplate(`p + include _includes/included.pug`); + t.is(await fn({ name: "Zach" }), "

This is an include.

"); +}); + +test("Pug Render Include (Relative, again)", async t => { + let fn = await new TemplateRender( + "./test/stubs/does_not_exist_and_thats_ok.pug", + "./test/stubs/" + ).getCompiledTemplate(`p + include included.pug`); + t.is( + await fn({ name: "Zach" }), + "

This is a relative include.

" + ); +}); + +test("Pug Render Include (Relative, dot slash)", async t => { + let fn = await new TemplateRender( + "./test/stubs/does_not_exist_and_thats_ok.pug", + "./test/stubs/" + ).getCompiledTemplate(`p + include ./included.pug`); + t.is( + await fn({ name: "Zach" }), + "

This is a relative include.

" + ); +}); + +test("Pug Render Include (Relative, dot dot slash)", async t => { + let fn = await new TemplateRender( + "./test/stubs/dir/does_not_exist_and_thats_ok.pug", + "./test/stubs/" + ).getCompiledTemplate(`p + include ../included.pug`); + t.is( + await fn({ name: "Zach" }), + "

This is a relative include.

" + ); +}); + +test("Pug Options Overrides", async t => { + let tr = new TemplateRender("pug", "./test/stubs/"); + tr.engine.setPugOptions({ testoption: "testoverride" }); + + let options = tr.engine.getPugOptions(); + t.is(options.testoption, "testoverride"); +}); + +test("Pug getEngineLib", async t => { + let tr = new TemplateRender("pug", "./test/stubs/"); + t.truthy(tr.engine.getEngineLib()); +}); + +test("Pug Render: with Library Override", async t => { + let tr = new TemplateRender("pug"); + + let lib = require("pug"); + tr.engine.setLibrary(lib); + + let fn = await tr.getCompiledTemplate("p= name"); + t.is(await fn({ name: "Zach" }), "

Zach

"); +}); + +test("Pug Filter", async t => { + let tr = new TemplateRender("pug", "./test/stubs/"); + tr.engine.setPugOptions({ + filters: { + makeUppercase: function(text, options) { + return text.toUpperCase(); + } + } + }); + + let fn = await tr.getCompiledTemplate(`p + :makeUppercase() + Zach +`); + t.is(await fn({ name: "Test" }), "

ZACH

"); +}); + +test("Pug Render with Function", async t => { + let fn = await new TemplateRender("pug").getCompiledTemplate("p= name()"); + t.is( + await fn({ + name: function() { + return "Zach2"; + } + }), + "

Zach2

" + ); +}); diff --git a/test/TemplateRenderTest.js b/test/TemplateRenderTest.js index d8f02911d..16317d8dd 100644 --- a/test/TemplateRenderTest.js +++ b/test/TemplateRenderTest.js @@ -2,410 +2,66 @@ import test from "ava"; import TemplateRender from "../src/TemplateRender"; import path from "path"; -test(t => { +test("Basic", t => { // Path is unnecessary but supported - t.truthy(new TemplateRender("default.ejs").parsed); + t.is(TemplateRender.cleanupEngineName("default.ejs"), "ejs"); + t.true(TemplateRender.hasEngine("default.ejs")); t.is(new TemplateRender("default.ejs").getEngineName(), "ejs"); // Better - t.truthy(new TemplateRender("ejs").parsed); + t.is(TemplateRender.cleanupEngineName("ejs"), "ejs"); + t.is(TemplateRender.cleanupEngineName("EjS"), "ejs"); + t.true(TemplateRender.hasEngine("EjS")); + t.true(TemplateRender.hasEngine("ejs")); t.is(new TemplateRender("ejs").getEngineName(), "ejs"); + + t.falsy(TemplateRender.cleanupEngineName("sldkjfkldsj")); + t.false(TemplateRender.hasEngine("sldkjfkldsj")); }); -test("Input Dir", async t => { +test("Includes Dir", async t => { t.is( - new TemplateRender("ejs", "./test/stubs").getInputDir(), + new TemplateRender("ejs", "./test/stubs").getIncludesDir(), "test/stubs/_includes" ); }); -// HTML -test("HTML", t => { - t.is(new TemplateRender("html").getEngineName(), "html"); -}); - -test("HTML Render", async t => { - let fn = await new TemplateRender("html").getCompiledTemplate( - "

Paragraph

" - ); - t.is(await fn(), "

Paragraph

"); - t.is(await fn({}), "

Paragraph

"); -}); - -test("HTML Render: Parses markdown using liquid engine (default, with data)", async t => { - let fn = await new TemplateRender("html").getCompiledTemplate( - "

{{title}}

" - ); - t.is((await fn({ title: "My Title" })).trim(), "

My Title

"); -}); - -test("HTML Render: Parses markdown using ejs engine", async t => { - let fn = await new TemplateRender("html").getCompiledTemplate( - "

<%=title %>

", - { - parseHtmlWith: "ejs" - } - ); - t.is((await fn({ title: "My Title" })).trim(), "

My Title

"); -}); - -test("HTML Render: Set markdown engine to false, donā€™t parse", async t => { - let fn = await new TemplateRender("html").getCompiledTemplate( - "

{{title}}

", - { - parseHtmlWith: false - } - ); - t.is((await fn()).trim(), "

{{title}}

"); -}); - -test("HTML Render: Change the default engine", async t => { - let tr = new TemplateRender("html"); - tr.setDefaultHtmlEngine("ejs"); - - let fn = await tr.getCompiledTemplate("

<%= title %>

"); - t.is((await fn({ title: "My Title" })).trim(), "

My Title

"); -}); - -test("HTML Render: Change the default engine and pass in an override", async t => { - let tr = new TemplateRender("html"); - tr.setDefaultHtmlEngine("njk"); - - let fn = await tr.getCompiledTemplate("

{{title}}

", { - parseHtmlWith: "liquid" - }); - - t.is((await fn({ title: "My Title" })).trim(), "

My Title

"); -}); - -// EJS -test("EJS", t => { - t.is(new TemplateRender("ejs").getEngineName(), "ejs"); -}); - -test("EJS Render", async t => { - let fn = await new TemplateRender("ejs").getCompiledTemplate( - "

<%= name %>

" - ); - t.is(await fn({ name: "Zach" }), "

Zach

"); -}); - -test("EJS Render Include Preprocessor Directive", async t => { - t.is(path.resolve(undefined, "/included"), "/included"); - - let fn = await new TemplateRender("ejs", "./test/stubs/").getCompiledTemplate( - "

<% include /included %>

" - ); - t.is(await fn(), "

This is an include.

"); -}); - -test("EJS Render Include, New Style no Data", async t => { - let fn = await new TemplateRender("ejs", "./test/stubs/").getCompiledTemplate( - "

<%- include('/included') %>

" - ); - t.is(await fn(), "

This is an include.

"); -}); - -test("EJS Render Include, New Style", async t => { - let fn = await new TemplateRender("ejs", "./test/stubs/").getCompiledTemplate( - "

<%- include('/included', {}) %>

" - ); - t.is(await fn(), "

This is an include.

"); -}); - -test("EJS Render Include, New Style with Data", async t => { - let fn = await new TemplateRender("ejs", "./test/stubs/").getCompiledTemplate( - "

<%- include('/includedvar', { name: 'Bill' }) %>

" - ); - t.is(await fn(), "

This is an Bill.

"); -}); - -// test("EJS Render Include Preprocessor Directive Relative", async t => { - -// let fn = await new TemplateRender("ejs", "./test/stubs/").getCompiledTemplate( -// "

<% include included %>

" -// ); -// t.is(await fn(), "

This is an include.

"); -// }); - -// test("EJS Render Include, Relative Path New Style", async t => { -// let fn = await new TemplateRender("ejs", "./test/stubs/").getCompiledTemplate( -// "

<%- include('stubs/includedrelative', {}) %>

" -// ); - -// t.is(await fn(), "

This is a relative include.

"); -// }); - -// Markdown -test("Markdown", t => { - t.is(new TemplateRender("md").getEngineName(), "md"); -}); - -test("Markdown Render: Parses base markdown, no data", async t => { - let fn = await new TemplateRender("md").getCompiledTemplate("# My Title"); - t.is((await fn()).trim(), "

My Title

"); -}); - -test("Markdown Render: Parses markdown using liquid engine (default, with data)", async t => { - let fn = await new TemplateRender("md").getCompiledTemplate("# {{title}}"); - t.is((await fn({ title: "My Title" })).trim(), "

My Title

"); -}); - -test("Markdown Render: Parses markdown using ejs engine", async t => { - let fn = await new TemplateRender("md").getCompiledTemplate("<%=title %>", { - parseMarkdownWith: "ejs" +test("Invalid override", async t => { + let tr = new TemplateRender("ejs", "./test/stubs"); + t.throws(() => { + tr.setEngineOverride("lslkdjf"); }); - t.is((await fn({ title: "My Title" })).trim(), "

My Title

"); }); -test("Markdown Render: Ignore markdown, use only preprocess engine (useful for variable resolution in permalinks)", async t => { - let fn = await new TemplateRender("md").getCompiledTemplate("{{title}}", { - bypassMarkdown: true +test("Valid Override", async t => { + let tr = new TemplateRender("ejs", "./test/stubs"); + tr.setEngineOverride("njk"); + t.is(tr.getEngineName(), "njk"); + t.truthy(tr.isEngine("njk")); +}); + +test("Parse Overrides to get Prioritized Engine List", async t => { + t.deepEqual(TemplateRender.parseEngineOverrides(""), []); + t.deepEqual(TemplateRender.parseEngineOverrides(null), []); + t.deepEqual(TemplateRender.parseEngineOverrides(undefined), []); + t.deepEqual(TemplateRender.parseEngineOverrides(false), []); + t.deepEqual(TemplateRender.parseEngineOverrides("html"), []); + t.deepEqual(TemplateRender.parseEngineOverrides("html,html"), []); + t.deepEqual(TemplateRender.parseEngineOverrides("html,md,md"), ["md"]); + t.deepEqual(TemplateRender.parseEngineOverrides("ejs,md"), ["md", "ejs"]); + t.deepEqual(TemplateRender.parseEngineOverrides("ejs"), ["ejs"]); + t.deepEqual(TemplateRender.parseEngineOverrides("njk"), ["njk"]); + t.deepEqual(TemplateRender.parseEngineOverrides("ejs,html"), ["ejs"]); + t.deepEqual(TemplateRender.parseEngineOverrides("ejs,md,html"), [ + "md", + "ejs" + ]); + t.deepEqual(TemplateRender.parseEngineOverrides("njk,njk"), ["njk"]); + + t.throws(function() { + TemplateRender.parseEngineOverrides("njk,ejs"); }); - t.is((await fn({ title: "My Title" })).trim(), "My Title"); -}); - -test("Markdown Render: Set markdown engine to false, donā€™t parse", async t => { - let fn = await new TemplateRender("md").getCompiledTemplate("# {{title}}", { - parseMarkdownWith: false + t.throws(function() { + TemplateRender.parseEngineOverrides("ejs,njk,html"); }); - t.is((await fn()).trim(), "

{{title}}

"); -}); - -test("Markdown Render: Change the default engine", async t => { - let tr = new TemplateRender("md"); - tr.setDefaultMarkdownEngine("ejs"); - - let fn = await tr.getCompiledTemplate("# <%= title %>"); - t.is((await fn({ title: "My Title" })).trim(), "

My Title

"); -}); - -test("Markdown Render: Change the default engine and pass in an override", async t => { - let tr = new TemplateRender("md"); - tr.setDefaultMarkdownEngine("njk"); - - let fn = await tr.getCompiledTemplate("# {{title}}", { - parseMarkdownWith: "liquid" - }); - - t.is((await fn({ title: "My Title" })).trim(), "

My Title

"); -}); - -// Handlebars -test("Handlebars", t => { - t.is(new TemplateRender("hbs").getEngineName(), "hbs"); -}); - -test("Handlebars Render", async t => { - let fn = await new TemplateRender("hbs").getCompiledTemplate( - "

{{name}}

" - ); - t.is(await fn({ name: "Zach" }), "

Zach

"); -}); - -test("Handlebars Render Partial", async t => { - let fn = await new TemplateRender("hbs", "./test/stubs/").getCompiledTemplate( - "

{{> included}}

" - ); - t.is(await fn(), "

This is an include.

"); -}); - -test("Handlebars Render Partial", async t => { - let fn = await new TemplateRender("hbs", "./test/stubs/").getCompiledTemplate( - "

{{> includedvar}}

" - ); - t.is(await fn({ name: "Zach" }), "

This is a Zach.

"); -}); - -// Mustache -test("Mustache", async t => { - t.is(new TemplateRender("mustache").getEngineName(), "mustache"); -}); - -test("Mustache Render", async t => { - let fn = await new TemplateRender("mustache").getCompiledTemplate( - "

{{name}}

" - ); - t.is(await fn({ name: "Zach" }), "

Zach

"); -}); - -test("Mustache Render Partial", async t => { - let fn = await new TemplateRender( - "mustache", - "./test/stubs/" - ).getCompiledTemplate("

{{> included}}

"); - t.is(await fn(), "

This is an include.

"); -}); - -test("Mustache Render Partial", async t => { - let fn = await new TemplateRender( - "mustache", - "./test/stubs/" - ).getCompiledTemplate("

{{> includedvar}}

"); - t.is(await fn({ name: "Zach" }), "

This is a Zach.

"); -}); - -// Haml -test("Haml", t => { - t.is(new TemplateRender("haml").getEngineName(), "haml"); -}); - -test("Haml Render", async t => { - let fn = await new TemplateRender("haml").getCompiledTemplate("%p= name"); - t.is((await fn({ name: "Zach" })).trim(), "

Zach

"); -}); - -// Pug -test("Pug", t => { - t.is(new TemplateRender("pug").getEngineName(), "pug"); -}); - -test("Pug Render", async t => { - let fn = await new TemplateRender("pug").getCompiledTemplate("p= name"); - t.is(await fn({ name: "Zach" }), "

Zach

"); -}); - -test("Pug Render Include", async t => { - let fn = await new TemplateRender("pug", "./test/stubs/") - .getCompiledTemplate(`p - include /included.pug`); - t.is(await fn({ name: "Zach" }), "

This is an include.

"); -}); - -test("Pug Render Include with Data", async t => { - let fn = await new TemplateRender("pug", "./test/stubs/") - .getCompiledTemplate(`p - include /includedvar.pug`); - t.is(await fn({ name: "Zach" }), "

This is Zach.

"); -}); - -test("Pug Render Include with Data, inline var overrides data", async t => { - let fn = await new TemplateRender("pug", "./test/stubs/") - .getCompiledTemplate(` -- var name = "Bill"; -p - include /includedvar.pug`); - t.is(await fn({ name: "Zach" }), "

This is Bill.

"); -}); - -test("Pug Render Extends (Layouts)", async t => { - let fn = await new TemplateRender("pug", "./test/stubs/") - .getCompiledTemplate(`extends /layout.pug -block content - h1= name`); - t.is(await fn({ name: "Zach" }), "

Zach

"); -}); - -// Nunjucks -test("Nunjucks", t => { - t.is(new TemplateRender("njk").getEngineName(), "njk"); -}); - -test("Nunjucks Render", async t => { - let fn = await new TemplateRender("njk").getCompiledTemplate( - "

{{ name }}

" - ); - t.is(await fn({ name: "Zach" }), "

Zach

"); -}); - -test("Nunjucks Render Extends", async t => { - let fn = await new TemplateRender("njk", "test/stubs").getCompiledTemplate( - "{% extends 'base.njk' %}{% block content %}This is a child.{% endblock %}" - ); - t.is(await fn(), "

This is a child.

"); -}); - -test("Nunjucks Render Include", async t => { - let fn = await new TemplateRender("njk", "test/stubs").getCompiledTemplate( - "

{% include 'included.njk' %}

" - ); - t.is(await fn(), "

This is an include.

"); -}); - -test("Nunjucks Render Imports", async t => { - let fn = await new TemplateRender("njk", "test/stubs").getCompiledTemplate( - "{% import 'imports.njk' as forms %}
{{ forms.label('Name') }}
" - ); - t.is(await fn(), "
"); -}); - -test("Nunjucks Render Imports From", async t => { - let fn = await new TemplateRender("njk", "test/stubs").getCompiledTemplate( - "{% from 'imports.njk' import label %}
{{ label('Name') }}
" - ); - t.is(await fn(), "
"); -}); - -// Liquid -test("Liquid", t => { - t.is(new TemplateRender("liquid").getEngineName(), "liquid"); -}); - -test("Liquid Render (with Helper)", async t => { - let fn = await new TemplateRender("liquid").getCompiledTemplate( - "

{{name | capitalize}}

" - ); - t.is(await fn({ name: "tim" }), "

Tim

"); -}); - -test("Liquid Render Include", async t => { - t.is(new TemplateRender("liquid", "./test/stubs/").getEngineName(), "liquid"); - - let fn = await new TemplateRender( - "liquid", - "./test/stubs/" - ).getCompiledTemplate("

{% include included %}

"); - t.is(await fn(), "

This is an include.

"); -}); - -test("Liquid Render Include with Liquid Suffix", async t => { - t.is(new TemplateRender("liquid", "./test/stubs/").getEngineName(), "liquid"); - - let fn = await new TemplateRender( - "liquid", - "./test/stubs/" - ).getCompiledTemplate("

{% include included.liquid %}

"); - t.is(await fn(), "

This is an include.

"); -}); - -test("Liquid Render Include with HTML Suffix", async t => { - t.is(new TemplateRender("liquid", "./test/stubs/").getEngineName(), "liquid"); - - let fn = await new TemplateRender( - "liquid", - "./test/stubs/" - ).getCompiledTemplate("

{% include included.html %}

"); - t.is(await fn(), "

This is an include.

"); -}); - -// This is an upstream limitation of the Liquid implementation -// test("Liquid Render Include No Quotes", async t => { -// t.is(new TemplateRender("liquid", "./test/stubs/").getEngineName(), "liquid"); - -// let fn = await new TemplateRender( -// "liquid", -// "./test/stubs/" -// ).getCompiledTemplate("

{% include included.liquid %}

"); -// t.is(await fn(), "

This is an include.

"); -// }); - -// ES6 -test("ES6 Template Literal", t => { - t.is(new TemplateRender("jstl").getEngineName(), "jstl"); -}); - -test("ES6 Template Literal Render", async t => { - // pass in a string here, we donā€™t want to compile the template in the test :O - let fn = await new TemplateRender("jstl").getCompiledTemplate( - "`

${name.toUpperCase()}

`" - ); - t.is(await fn({ name: "Tim" }), "

TIM

"); -}); - -test("ES6 Template Literal Render", async t => { - // pass in a string here, we donā€™t want to compile the template in the test :O - let fn = await new TemplateRender("jstl").getCompiledTemplate( - "

${name.toUpperCase()}

" - ); - t.is(await fn({ name: "Tim" }), "

TIM

"); }); diff --git a/test/TemplateTest-JavaScript.js b/test/TemplateTest-JavaScript.js new file mode 100644 index 000000000..5fabb8284 --- /dev/null +++ b/test/TemplateTest-JavaScript.js @@ -0,0 +1,302 @@ +import test from "ava"; +import Template from "../src/Template"; +import semver from "semver"; + +test("JavaScript template type (function)", async t => { + let tmpl = new Template( + "./test/stubs/function.11ty.js", + "./test/stubs/", + "./dist" + ); + + t.is(await tmpl.getOutputPath(), "./dist/function/index.html"); + let data = await tmpl.getData(); + data.name = "Zach"; + + let pages = await tmpl.getRenderedTemplates(data); + t.is(pages[0].templateContent.trim(), "

Zach

"); +}); + +test("JavaScript template type (class with data getter)", async t => { + let tmpl = new Template( + "./test/stubs/class-data.11ty.js", + "./test/stubs/", + "./dist" + ); + + t.is(await tmpl.getOutputPath(), "./dist/class-data/index.html"); + let data = await tmpl.getData(); + + let pages = await tmpl.getRenderedTemplates(data); + t.is(pages[0].templateContent.trim(), "

Ted

"); +}); + +test("JavaScript template type (class with data method)", async t => { + let tmpl = new Template( + "./test/stubs/class-data-fn.11ty.js", + "./test/stubs/", + "./dist" + ); + + t.is(await tmpl.getOutputPath(), "./dist/class-data-fn/index.html"); + let data = await tmpl.getData(); + + let pages = await tmpl.getRenderedTemplates(data); + t.is(pages[0].templateContent.trim(), "

Ted

"); +}); + +if (semver.gte(process.version, "12.4.0")) { + test("JavaScript template type (class fields)", async t => { + let tmpl = new Template( + "./test/stubs/classfields-data.11ty.js", + "./test/stubs/", + "./dist" + ); + + t.is(await tmpl.getOutputPath(), "./dist/classfields-data/index.html"); + let data = await tmpl.getData(); + + let pages = await tmpl.getRenderedTemplates(data); + t.is(pages[0].templateContent.trim(), "

Ted

"); + }); +} + +test("JavaScript template type (class with shorthand data method)", async t => { + let tmpl = new Template( + "./test/stubs/class-data-fn-shorthand.11ty.js", + "./test/stubs/", + "./dist" + ); + + t.is(await tmpl.getOutputPath(), "./dist/class-data-fn-shorthand/index.html"); + let data = await tmpl.getData(); + + let pages = await tmpl.getRenderedTemplates(data); + t.is(pages[0].templateContent.trim(), "

Ted

"); +}); + +test("JavaScript template type (class with async data method)", async t => { + let tmpl = new Template( + "./test/stubs/class-async-data-fn.11ty.js", + "./test/stubs/", + "./dist" + ); + + t.is(await tmpl.getOutputPath(), "./dist/class-async-data-fn/index.html"); + let data = await tmpl.getData(); + + let pages = await tmpl.getRenderedTemplates(data); + t.is(pages[0].templateContent.trim(), "

Ted

"); +}); + +test("JavaScript template type (class with data getter and a javascriptFunction)", async t => { + let tmpl = new Template( + "./test/stubs/class-data-filter.11ty.js", + "./test/stubs/", + "./dist" + ); + tmpl.templateRender.config = { + javascriptFunctions: { + upper: function(val) { + return new String(val).toUpperCase(); + } + } + }; + + t.is(await tmpl.getOutputPath(), "./dist/class-data-filter/index.html"); + let data = await tmpl.getData(); + let pages = await tmpl.getRenderedTemplates(data); + t.is(pages[0].templateContent.trim(), "

TED

"); +}); + +test("JavaScript template type (class with data method and a javascriptFunction)", async t => { + let tmpl = new Template( + "./test/stubs/class-data-fn-filter.11ty.js", + "./test/stubs/", + "./dist" + ); + tmpl.templateRender.config = { + javascriptFunctions: { + upper: function(val) { + return new String(val).toUpperCase(); + } + } + }; + + t.is(await tmpl.getOutputPath(), "./dist/class-data-fn-filter/index.html"); + let data = await tmpl.getData(); + let pages = await tmpl.getRenderedTemplates(data); + t.is(pages[0].templateContent.trim(), "

TED

"); +}); + +test("JavaScript template type (class with data permalink)", async t => { + let tmpl = new Template( + "./test/stubs/class-data-permalink.11ty.js", + "./test/stubs/", + "./dist" + ); + + t.is(await tmpl.getOutputPath(), "./dist/my-permalink/index.html"); +}); + +test("JavaScript template type (class with data permalink using a buffer)", async t => { + let tmpl = new Template( + "./test/stubs/class-data-permalink-buffer.11ty.js", + "./test/stubs/", + "./dist" + ); + + t.is(await tmpl.getOutputPath(), "./dist/my-permalink/index.html"); +}); + +test("JavaScript template type (class with data permalink function)", async t => { + let tmpl = new Template( + "./test/stubs/class-data-permalink-fn.11ty.js", + "./test/stubs/", + "./dist" + ); + + t.is(await tmpl.getOutputPath(), "./dist/my-permalink/value1/index.html"); +}); + +test("JavaScript template type (class with data permalink function using a buffer)", async t => { + let tmpl = new Template( + "./test/stubs/class-data-permalink-fn-buffer.11ty.js", + "./test/stubs/", + "./dist" + ); + + t.is(await tmpl.getOutputPath(), "./dist/my-permalink/value1/index.html"); +}); + +test("JavaScript template type (class with data permalink async function)", async t => { + let tmpl = new Template( + "./test/stubs/class-data-permalink-async-fn.11ty.js", + "./test/stubs/", + "./dist" + ); + + t.is(await tmpl.getOutputPath(), "./dist/my-permalink/value1/index.html"); +}); + +test("JavaScript template type (class with data permalink function using a filter)", async t => { + let tmpl = new Template( + "./test/stubs/class-data-permalink-fn-filter.11ty.js", + "./test/stubs/", + "./dist" + ); + + t.is( + await tmpl.getOutputPath(), + "./dist/my-permalink/my-super-cool-title/index.html" + ); +}); + +test("JavaScript template type (class with renderData)", async t => { + let tmpl = new Template( + "./test/stubs/class-data-renderdata.11ty.js", + "./test/stubs/", + "./dist" + ); + + let data = await tmpl.getRenderedData(); + let pages = await tmpl.getRenderedTemplates(data); + t.is( + pages[0].templateContent.trim(), + "

StringTesthowdy Zach, meet Thanos

" + ); +}); + +test("JavaScript template type (should use the same class instance for data and render)", async t => { + let tmpl = new Template( + "./test/stubs/oneinstance.11ty.js", + "./test/stubs/", + "./dist" + ); + + let data = await tmpl.getData(); + let pages = await tmpl.getRenderedTemplates(data); + // the template renders the random number created in the class constructor + // the data returns the random number created in the class constructor + // if they are different, the class is not reused. + t.is(pages[0].templateContent.trim(), `

Ted${data.rand}

`); +}); + +test("JavaScript template type (multiple exports)", async t => { + let tmpl = new Template( + "./test/stubs/multipleexports.11ty.js", + "./test/stubs/", + "./dist" + ); + + let data = await tmpl.getData(); + let pages = await tmpl.getRenderedTemplates(data); + t.is(pages[0].templateContent.trim(), "

Ted

"); +}); + +test("JavaScript template type (multiple exports, promises)", async t => { + let tmpl = new Template( + "./test/stubs/multipleexports-promises.11ty.js", + "./test/stubs/", + "./dist" + ); + + let data = await tmpl.getData(); + t.is(data.name, "Ted"); + + let pages = await tmpl.getRenderedTemplates(data); + t.is(pages[0].templateContent.trim(), "

Ted

"); +}); + +test("JavaScript template type (object)", async t => { + let tmpl = new Template( + "./test/stubs/object.11ty.js", + "./test/stubs/", + "./dist" + ); + + let data = await tmpl.getData(); + t.is(data.name, "Ted"); + + let pages = await tmpl.getRenderedTemplates(data); + t.is(pages[0].templateContent.trim(), "

Ted

"); +}); + +test("JavaScript template type (object, no render method)", async t => { + let tmpl = new Template( + "./test/stubs/object-norender.11ty.js", + "./test/stubs/", + "./dist" + ); + + let data = await tmpl.getData(); + t.is(data.name, "Ted"); + + let pages = await tmpl.getRenderedTemplates(data); + t.is(pages[0].templateContent.trim(), ""); +}); + +test("JavaScript template type (class, no render method)", async t => { + let tmpl = new Template( + "./test/stubs/class-norender.11ty.js", + "./test/stubs/", + "./dist" + ); + + let data = await tmpl.getData(); + t.is(data.name, "Ted"); + + let pages = await tmpl.getRenderedTemplates(data); + t.is(pages[0].templateContent.trim(), ""); +}); +test("JavaScript template type (data returns a string)", async t => { + let tmpl = new Template( + "./test/stubs/exports-flatdata.11ty.js", + "./test/stubs/", + "./dist" + ); + + await t.throwsAsync(async () => { + await tmpl.getData(); + }); +}); diff --git a/test/TemplateTest.js b/test/TemplateTest.js index d34f84dae..f8a6ea2bb 100644 --- a/test/TemplateTest.js +++ b/test/TemplateTest.js @@ -1,11 +1,14 @@ import test from "ava"; +import fs from "fs-extra"; +import pretty from "pretty"; import TemplateData from "../src/TemplateData"; -import TemplateConfig from "../src/TemplateConfig"; import Template from "../src/Template"; -import pretty from "pretty"; -import normalize from "normalize-path"; +import EleventyErrorUtil from "../src/EleventyErrorUtil"; +import TemplateContentPrematureUseError from "../src/Errors/TemplateContentPrematureUseError"; +import templateConfig from "../src/Config"; +import normalizeNewLines from "./Util/normalizeNewLines"; -let cfg = TemplateConfig.getDefaultConfig(); +const config = templateConfig.getConfig(); function cleanHtml(str) { return pretty(str, { ocd: true }); @@ -82,7 +85,7 @@ test("HTML files output to the same as the input directory have a file suffix ad t.is(await tmpl.getOutputPath(), "./test/stubs/index-o.html"); }); -test("HTML files output to the same as the input directory have a file suffix added (only if index, this _is_ index).", async t => { +test("HTML files output to the same as the input directory have a file suffix added (only if index, this _is_ index, subfolder).", async t => { let tmpl = new Template( "./test/stubs/subfolder/index.html", "./test/stubs", @@ -91,14 +94,62 @@ test("HTML files output to the same as the input directory have a file suffix ad t.is(await tmpl.getOutputPath(), "./test/stubs/subfolder/index-o.html"); }); -test("Test raw front matter from template", t => { +test("Test raw front matter from template (yaml)", async t => { + // https://github.com/jonschlinkert/gray-matter/blob/master/examples/yaml.js let tmpl = new Template( "./test/stubs/templateFrontMatter.ejs", "./test/stubs/", "./dist" ); - t.truthy(tmpl.inputContent, "template exists and can be opened."); - t.is(tmpl.frontMatter.data.key1, "value1"); + t.truthy(await tmpl.getInputContent(), "template exists and can be opened."); + + t.is((await tmpl.getFrontMatter()).data.key1, "value1"); + t.is((await tmpl.getFrontMatter()).data.key3, "value3"); + + let data = await tmpl.getData(); + t.is(data.key1, "value1"); + t.is(data.key3, "value3"); + + let pages = await tmpl.getRenderedTemplates(data); + t.is(pages[0].templateContent.trim(), "c:value1:value2:value3"); +}); + +test("Test raw front matter from template (json)", async t => { + // https://github.com/jonschlinkert/gray-matter/blob/master/examples/json.js + let tmpl = new Template( + "./test/stubs/templateFrontMatterJson.ejs", + "./test/stubs/", + "./dist" + ); + + t.is((await tmpl.getFrontMatter()).data.key1, "value1"); + t.is((await tmpl.getFrontMatter()).data.key3, "value3"); + + let data = await tmpl.getData(); + t.is(data.key1, "value1"); + t.is(data.key3, "value3"); + + let pages = await tmpl.getRenderedTemplates(data); + t.is(pages[0].templateContent.trim(), "c:value1:value2:value3"); +}); + +test("Test raw front matter from template (js)", async t => { + // https://github.com/jonschlinkert/gray-matter/blob/master/examples/javascript.js + let tmpl = new Template( + "./test/stubs/templateFrontMatterJs.ejs", + "./test/stubs/", + "./dist" + ); + + t.is((await tmpl.getFrontMatter()).data.key1, "value1"); + t.is((await tmpl.getFrontMatter()).data.key3, "value3"); + + let data = await tmpl.getData(); + t.is(data.key1, "value1"); + t.is(data.key3, "value3"); + + let pages = await tmpl.getRenderedTemplates(data); + t.is(pages[0].templateContent.trim(), "c:value1:VALUE2:value3"); }); test("Test that getData() works", async t => { @@ -111,67 +162,140 @@ test("Test that getData() works", async t => { t.is(data.key1, "value1"); t.is(data.key3, "value3"); +}); + +test("One Layout (using new content var)", async t => { + let dataObj = new TemplateData("./test/stubs/"); + let tmpl = new Template( + "./test/stubs/templateWithLayout.ejs", + "./test/stubs/", + "dist", + dataObj + ); + + t.is((await tmpl.getFrontMatter()).data[config.keys.layout], "defaultLayout"); + + let data = await tmpl.getData(); + t.is(data[config.keys.layout], "defaultLayout"); - let mergedFrontMatter = await tmpl.getAllLayoutFrontMatterData( - tmpl, - tmpl.getFrontMatterData() + t.is( + normalizeNewLines(cleanHtml(await tmpl.renderLayout(tmpl, data))), + `
+

Hello.

+
` ); - t.is(mergedFrontMatter.key1, "value1"); - t.is(mergedFrontMatter.key3, "value3"); + t.is(data.keymain, "valuemain"); + t.is(data.keylayout, "valuelayout"); }); -test("More advanced getData()", async t => { +test("One Layout (using layoutContent)", async t => { let dataObj = new TemplateData("./test/stubs/"); let tmpl = new Template( - "./test/stubs/templateFrontMatter.ejs", + "./test/stubs/templateWithLayoutContent.ejs", "./test/stubs/", "dist", dataObj ); - let data = await tmpl.getData({ - key1: "value1override", - key2: "value2" - }); - t.is(data[cfg.keys.package].name, "eleventy-cli"); t.is( - data.key1, - "value1override", - "local data argument overrides front matter" + (await tmpl.getFrontMatter()).data[config.keys.layout], + "defaultLayoutLayoutContent" + ); + + let data = await tmpl.getData(); + t.is(data[config.keys.layout], "defaultLayoutLayoutContent"); + + t.is( + normalizeNewLines(cleanHtml(await tmpl.renderLayout(tmpl, data))), + `
+

Hello.

+
` ); - t.is(data.key2, "value2", "local data argument, no front matter"); - t.is(data.key3, "value3", "front matter only"); + + t.is(data.keymain, "valuemain"); + t.is(data.keylayout, "valuelayout"); }); -test("One Layout", async t => { +test("One Layout (layouts disabled)", async t => { let dataObj = new TemplateData("./test/stubs/"); let tmpl = new Template( - "./test/stubs/templateWithLayout.ejs", + "./test/stubs/templateWithLayoutContent.ejs", + "./test/stubs/", + "dist", + dataObj + ); + + tmpl.setWrapWithLayouts(false); + + t.is( + (await tmpl.getFrontMatter()).data[config.keys.layout], + "defaultLayoutLayoutContent" + ); + + let data = await tmpl.getData(); + t.is(data[config.keys.layout], "defaultLayoutLayoutContent"); + + t.is(cleanHtml(await tmpl.render(data)), "

Hello.

"); + + t.is(data.keymain, "valuemain"); + t.is(data.keylayout, "valuelayout"); +}); + +test("One Layout (_layoutContent deprecated but supported)", async t => { + let dataObj = new TemplateData("./test/stubs/"); + let tmpl = new Template( + "./test/stubs/templateWithLayoutBackCompat.ejs", "./test/stubs/", "dist", dataObj ); - t.is(tmpl.frontMatter.data[cfg.keys.layout], "defaultLayout"); + t.is( + (await tmpl.getFrontMatter()).data[config.keys.layout], + "defaultLayout_layoutContent" + ); let data = await tmpl.getData(); - t.is(data[cfg.keys.layout], "defaultLayout"); + t.is(data[config.keys.layout], "defaultLayout_layoutContent"); t.is( - cleanHtml(await tmpl.renderLayout(tmpl, data)), + normalizeNewLines(cleanHtml(await tmpl.renderLayout(tmpl, data))), `

Hello.

` ); - let mergedFrontMatter = await tmpl.getAllLayoutFrontMatterData( - tmpl, - tmpl.getFrontMatterData() + t.is(data.keymain, "valuemain"); + t.is(data.keylayout, "valuelayout"); +}); + +test("One Layout (liquid test)", async t => { + let dataObj = new TemplateData("./test/stubs/"); + let tmpl = new Template( + "./test/stubs/templateWithLayout.liquid", + "./test/stubs/", + "dist", + dataObj + ); + + t.is( + (await tmpl.getFrontMatter()).data[config.keys.layout], + "layoutLiquid.liquid" + ); + + let data = await tmpl.getData(); + t.is(data[config.keys.layout], "layoutLiquid.liquid"); + + t.is( + normalizeNewLines(cleanHtml(await tmpl.renderLayout(tmpl, data))), + `
+

Hello.

+
` ); - t.is(mergedFrontMatter.keymain, "valuemain"); - t.is(mergedFrontMatter.keylayout, "valuelayout"); + t.is(data.keymain, "valuemain"); + t.is(data.keylayout, "valuelayout"); }); test("Two Layouts", async t => { @@ -183,14 +307,14 @@ test("Two Layouts", async t => { dataObj ); - t.is(tmpl.frontMatter.data[cfg.keys.layout], "layout-a"); + t.is((await tmpl.getFrontMatter()).data[config.keys.layout], "layout-a"); let data = await tmpl.getData(); - t.is(data[cfg.keys.layout], "layout-a"); + t.is(data[config.keys.layout], "layout-a"); t.is(data.key1, "value1"); t.is( - cleanHtml(await tmpl.renderLayout(tmpl, data)), + normalizeNewLines(cleanHtml(await tmpl.renderLayout(tmpl, data))), `

value2-a

@@ -198,12 +322,7 @@ test("Two Layouts", async t => {
` ); - let mergedFrontMatter = await tmpl.getAllLayoutFrontMatterData( - tmpl, - tmpl.getFrontMatterData() - ); - - t.is(mergedFrontMatter.daysPosted, 152); + t.is(data.daysPosted, 152); }); test("Liquid template", async t => { @@ -225,7 +344,7 @@ test("Liquid template with include", async t => { "dist" ); - t.is((await tmpl.render()).trim(), `

This is an include.

`); + t.is((await tmpl.render()).trim(), "

This is an include.

"); }); test("ES6 Template Literal (No Backticks)", async t => { @@ -261,8 +380,56 @@ test("Permalink output directory", async t => { t.is(await tmpl.getOutputPath(), "./dist/permalinksubfolder/index.html"); }); +test("Permalink output directory from layout", async t => { + let tmpl = new Template( + "./test/stubs/permalink-in-layout.ejs", + "./test/stubs/", + "./dist" + ); + t.is(await tmpl.getOutputPath(), "./dist/hello/index.html"); +}); + +test("Permalink output directory from layout (fileslug)", async t => { + let tmpl = new Template( + "./test/stubs/permalink-in-layout-fileslug.ejs", + "./test/stubs/", + "./dist" + ); + t.is( + await tmpl.getOutputPath(), + "./dist/test/permalink-in-layout-fileslug/index.html" + ); +}); + +test("Layout from template-data-file that has a permalink (fileslug) Issue #121", async t => { + let dataObj = new TemplateData("./test/stubs/"); + let tmpl = new Template( + "./test/stubs/permalink-data-layout/test.njk", + "./test/stubs/", + "./dist", + dataObj + ); + + let data = await tmpl.getData(); + let renderedTmpl = (await tmpl.getRenderedTemplates(data))[0]; + t.is(renderedTmpl.templateContent, "Wrapper:Test 1:test"); + t.is(await tmpl.getOutputPath(), "./dist/test/index.html"); +}); + +test("Fileslug in an 11ty.js template Issue #588", async t => { + let tmpl = new Template( + "./test/stubs/fileslug.11ty.js", + "./test/stubs/", + "./dist" + ); + + let data = await tmpl.getData(); + let renderedTmpl = (await tmpl.getRenderedTemplates(data))[0]; + t.is(renderedTmpl.templateContent, "

fileslug

"); +}); + test("Local template data file import (without a global data json)", async t => { - let dataObj = new TemplateData(); + let dataObj = new TemplateData("./test/stubs/"); await dataObj.cacheData(); let tmpl = new Template( @@ -273,11 +440,106 @@ test("Local template data file import (without a global data json)", async t => ); let data = await tmpl.getData(); - t.is(tmpl.getLocalDataPath(), "./test/stubs/component/component.json"); + t.deepEqual(await dataObj.getLocalDataPaths(tmpl.getInputPath()), [ + "./test/stubs/component/component.json", + "./test/stubs/component/component.11tydata.json", + "./test/stubs/component/component.11tydata.js" + ]); t.is(data.localdatakey1, "localdatavalue1"); t.is(await tmpl.render(), "localdatavalue1"); }); +test("Local template data file import (two subdirectories deep)", async t => { + let dataObj = new TemplateData("./test/stubs/"); + await dataObj.cacheData(); + + let tmpl = new Template( + "./test/stubs/firstdir/seconddir/component.njk", + "./test/stubs/", + "./dist", + dataObj + ); + + t.deepEqual(await dataObj.getLocalDataPaths(tmpl.getInputPath()), [ + "./test/stubs/firstdir/firstdir.json", + "./test/stubs/firstdir/firstdir.11tydata.json", + "./test/stubs/firstdir/firstdir.11tydata.js", + "./test/stubs/firstdir/seconddir/seconddir.json", + "./test/stubs/firstdir/seconddir/seconddir.11tydata.json", + "./test/stubs/firstdir/seconddir/seconddir.11tydata.js", + "./test/stubs/firstdir/seconddir/component.json", + "./test/stubs/firstdir/seconddir/component.11tydata.json", + "./test/stubs/firstdir/seconddir/component.11tydata.js" + ]); +}); + +test("Posts inherits local JSON, layouts", async t => { + let dataObj = new TemplateData("./test/stubs/"); + await dataObj.cacheData(); + + let tmpl = new Template( + "./test/stubs/posts/post1.njk", + "./test/stubs/", + "./dist", + dataObj + ); + + let localDataPaths = await dataObj.getLocalDataPaths(tmpl.getInputPath()); + t.deepEqual(localDataPaths, [ + "./test/stubs/posts/posts.json", + "./test/stubs/posts/posts.11tydata.json", + "./test/stubs/posts/posts.11tydata.js", + "./test/stubs/posts/post1.json", + "./test/stubs/posts/post1.11tydata.json", + "./test/stubs/posts/post1.11tydata.js" + ]); + + let localData = await dataObj.getLocalData(tmpl.getInputPath()); + t.is(localData.layout, "mylocallayout.njk"); + t.truthy(localData.pkg); + + let data = await tmpl.getData(); + t.is(localData.layout, "mylocallayout.njk"); + + t.is( + normalizeNewLines((await tmpl.render(data)).trim()), + `
Post1 +
` + ); +}); + +test("Template and folder name are the same, make sure data imports work ok", async t => { + let dataObj = new TemplateData("./test/stubs/"); + await dataObj.cacheData(); + + let tmpl = new Template( + "./test/stubs/posts/posts.njk", + "./test/stubs/", + "./dist", + dataObj + ); + + let localDataPaths = await dataObj.getLocalDataPaths(tmpl.getInputPath()); + t.deepEqual(localDataPaths, [ + "./test/stubs/posts/posts.json", + "./test/stubs/posts/posts.11tydata.json", + "./test/stubs/posts/posts.11tydata.js" + ]); + + let localData = await dataObj.getLocalData(tmpl.getInputPath()); + t.is(localData.layout, "mylocallayout.njk"); + t.truthy(localData.pkg); + + let data = await tmpl.getData(); + t.is(localData.layout, "mylocallayout.njk"); + + t.is( + normalizeNewLines((await tmpl.render(data)).trim()), + `
Posts +
` + ); +}); + test("Clone the template", async t => { let tmpl = new Template( "./test/stubs/default.ejs", @@ -301,6 +563,38 @@ test("Permalink with variables!", async t => { t.is(await tmpl.getOutputPath(), "./dist/subdir/slug-candidate/index.html"); }); +test("Permalink with dates!", async t => { + let tmpl = new Template( + "./test/stubs/permalinkdate.liquid", + "./test/stubs/", + "./dist" + ); + + t.is(await tmpl.getOutputPath(), "./dist/2016/01/01/index.html"); +}); + +test("Permalink with dates on file name regex!", async t => { + let tmpl = new Template( + "./test/stubs/2016-02-01-permalinkdate.liquid", + "./test/stubs/", + "./dist" + ); + + t.is(await tmpl.getOutputPath(), "./dist/2016/02/01/index.html"); +}); + +test("Reuse permalink in directory specific data file", async t => { + let dataObj = new TemplateData("./test/stubs/"); + let tmpl = new Template( + "./test/stubs/reuse-permalink/test1.liquid", + "./test/stubs/", + "./dist", + dataObj + ); + + t.is(await tmpl.getOutputPath(), "./dist/2016/01/01/index.html"); +}); + test("mapDataAsRenderedTemplates", async t => { let tmpl = new Template( "./test/stubs/default.ejs", @@ -352,3 +646,1280 @@ test("mapDataAsRenderedTemplates", async t => { } ); }); + +test("renderData", async t => { + let tmpl = new Template( + "./test/stubs/renderData/renderData.njk", + "./test/stubs/", + "./dist" + ); + + t.is((await tmpl.render()).trim(), "hi:value2-value1.css"); +}); + +test("renderData markdown (issue #40)", async t => { + let tmpl = new Template( + "./test/stubs/renderData/renderData.md", + "./test/stubs/", + "./dist" + ); + + t.is((await tmpl.render()).trim(), "value2-value1.css"); +}); + +test("getMappedDate (empty, assume created)", async t => { + let tmpl = new Template( + "./test/stubs/dates/file1.md", + "./test/stubs/", + "./dist" + ); + let data = await tmpl.getRenderedData(); + let date = await tmpl.getMappedDate(data); + + t.true(date instanceof Date); + t.truthy(date.getTime()); +}); + +test("getMappedDate (explicit date, yaml String)", async t => { + let tmpl = new Template( + "./test/stubs/dates/file2.md", + "./test/stubs/", + "./dist" + ); + let data = await tmpl.getRenderedData(); + let date = await tmpl.getMappedDate(data); + + t.true(date instanceof Date); + t.truthy(date.getTime()); +}); + +test("getMappedDate (explicit date, yaml Date)", async t => { + let tmpl = new Template( + "./test/stubs/dates/file2b.md", + "./test/stubs/", + "./dist" + ); + let data = await tmpl.getRenderedData(); + let date = await tmpl.getMappedDate(data); + + t.true(date instanceof Date); + t.truthy(date.getTime()); +}); + +test("getMappedDate (explicit date, yaml Date and string should be the same)", async t => { + let tmplA = new Template( + "./test/stubs/dates/file2.md", + "./test/stubs/", + "./dist" + ); + let dataA = await tmplA.getRenderedData(); + let stringDate = await tmplA.getMappedDate(dataA); + + let tmplB = new Template( + "./test/stubs/dates/file2b.md", + "./test/stubs/", + "./dist" + ); + let dataB = await tmplB.getRenderedData(); + let yamlDate = await tmplB.getMappedDate(dataB); + + t.truthy(stringDate); + t.truthy(yamlDate); + t.deepEqual(stringDate, yamlDate); +}); + +test("getMappedDate (modified date)", async t => { + let tmpl = new Template( + "./test/stubs/dates/file3.md", + "./test/stubs/", + "./dist" + ); + let data = await tmpl.getRenderedData(); + let date = await tmpl.getMappedDate(data); + + t.true(date instanceof Date); + t.truthy(date.getTime()); +}); + +test("getMappedDate (created date)", async t => { + let tmpl = new Template( + "./test/stubs/dates/file4.md", + "./test/stubs/", + "./dist" + ); + let data = await tmpl.getRenderedData(); + let date = await tmpl.getMappedDate(data); + + t.true(date instanceof Date); + t.truthy(date.getTime()); +}); + +test("getMappedDate (falls back to filename date)", async t => { + let tmpl = new Template( + "./test/stubs/dates/2018-01-01-file5.md", + "./test/stubs/", + "./dist" + ); + let data = await tmpl.getRenderedData(); + let date = await tmpl.getMappedDate(data); + + t.true(date instanceof Date); + t.truthy(date.getTime()); +}); + +test("getRenderedData() has all the page variables", async t => { + let tmpl = new Template( + "./test/stubs/template.ejs", + "./test/stubs/", + "./dist" + ); + let data = await tmpl.getRenderedData(); + + t.truthy(data.page.url); + t.is(data.page.url, "/template/"); + t.is(data.page.fileSlug, "template"); + t.is(data.page.filePathStem, "/template"); + t.truthy(data.page.date.getTime()); + t.is(data.page.inputPath, "./test/stubs/template.ejs"); + t.is(data.page.outputPath, "./dist/template/index.html"); +}); + +test("Issue #603: page.date Liquid", async t => { + let tmpl = new Template( + "./test/stubs/pagedate.liquid", + "./test/stubs/", + "./dist" + ); + let data = await tmpl.getData(); + + t.truthy(data.page.date); + t.truthy(data.page.date.toUTCString()); + + let pages = await tmpl.getRenderedTemplates(data); + t.is(pages[0].templateContent.trim(), data.page.date.toString()); +}); + +test("Issue #603: page.date Nunjucks", async t => { + let tmpl = new Template( + "./test/stubs/pagedate.njk", + "./test/stubs/", + "./dist" + ); + let data = await tmpl.getData(); + + t.truthy(data.page.date); + t.truthy(data.page.date.toUTCString()); + + let pages = await tmpl.getRenderedTemplates(data); + t.is(pages[0].templateContent.trim(), data.page.date.toString()); +}); + +test("Issue #603: page.date.toUTCString() Nunjucks", async t => { + // Note this is not supported in Liquid + let tmpl = new Template( + "./test/stubs/pagedateutc.njk", + "./test/stubs/", + "./dist" + ); + let data = await tmpl.getData(); + + t.truthy(data.page.date); + t.truthy(data.page.date.toUTCString()); + + let pages = await tmpl.getRenderedTemplates(data); + t.is(pages[0].templateContent.trim(), data.page.date.toUTCString()); +}); + +test("getTemplates() data has all the root variables", async t => { + let tmpl = new Template( + "./test/stubs/template.ejs", + "./test/stubs/", + "./dist" + ); + let data = await tmpl.getData(); + let templates = await tmpl.getTemplates(data); + + t.is(templates[0].url, "/template/"); + t.is(templates[0].fileSlug, "template"); + t.is(templates[0].filePathStem, "/template"); + t.truthy(templates[0].date.getTime()); + t.is(templates[0].inputPath, "./test/stubs/template.ejs"); + t.is(templates[0].outputPath, "./dist/template/index.html"); +}); + +test("getTemplates() data has all the page variables", async t => { + let tmpl = new Template( + "./test/stubs/template.ejs", + "./test/stubs/", + "./dist" + ); + let data = await tmpl.getData(); + let templates = await tmpl.getTemplates(data); + + t.is(templates[0].data.page.url, "/template/"); + t.is(templates[0].data.page.fileSlug, "template"); + t.is(templates[0].filePathStem, "/template"); + t.truthy(templates[0].data.page.date.getTime()); + t.is(templates[0].data.page.inputPath, "./test/stubs/template.ejs"); + t.is(templates[0].data.page.outputPath, "./dist/template/index.html"); +}); + +test("getRenderedTemplates() data has all the page variables", async t => { + let tmpl = new Template( + "./test/stubs/template.ejs", + "./test/stubs/", + "./dist" + ); + let data = await tmpl.getData(); + + let templates = await tmpl.getRenderedTemplates(data); + t.is(templates[0].data.page.url, "/template/"); + t.is(templates[0].data.page.fileSlug, "template"); + t.is(templates[0].filePathStem, "/template"); + t.truthy(templates[0].data.page.date.getTime()); + t.is(templates[0].data.page.inputPath, "./test/stubs/template.ejs"); + t.is(templates[0].data.page.outputPath, "./dist/template/index.html"); +}); + +test("getRenderedData() has good slug (empty, index)", async t => { + let tmpl = new Template("./test/stubs/index.ejs", "./test/stubs/", "./dist"); + let data = await tmpl.getRenderedData(); + t.is(data.page.fileSlug, ""); + t.is(data.page.filePathStem, "/index"); +}); + +test("getRenderedData() has good slug", async t => { + let tmpl = new Template( + "./test/stubs/includer.liquid", + "./test/stubs/", + "./dist" + ); + let data = await tmpl.getRenderedData(); + t.is(data.page.fileSlug, "includer"); + t.is(data.page.filePathStem, "/includer"); +}); + +test("Override base templating engine from .liquid to ejs", async t => { + let tmpl = new Template( + "./test/stubs/overrides/test-ejs.liquid", + "./test/stubs/", + "./dist" + ); + + t.is((await tmpl.render()).trim(), "My Title"); +}); + +test("Override base templating engine from markdown to 11ty.js, then markdown", async t => { + let tmpl = new Template( + "./test/stubs/test-override-js-markdown.11ty.js", + "./test/stubs/", + "./dist" + ); + + t.is((await tmpl.render()).trim(), "

This is markdown

"); +}); + +test("Override base templating engine from .liquid to md", async t => { + let tmpl = new Template( + "./test/stubs/overrides/test-md.liquid", + "./test/stubs/", + "./dist" + ); + + t.is((await tmpl.render()).trim(), "

My Title

"); +}); + +test("Override base templating engine from .liquid to ejs,md", async t => { + let tmpl = new Template( + "./test/stubs/overrides/test-multiple.md", + "./test/stubs/", + "./dist" + ); + + t.is((await tmpl.render()).trim(), "

My Title

"); +}); + +test("Override base templating engine from .njk to ejs,md", async t => { + let tmpl = new Template( + "./test/stubs/overrides/test-multiple2.njk", + "./test/stubs/", + "./dist" + ); + + t.is((await tmpl.render()).trim(), "

My Title

"); +}); + +test("Override base templating engine from .html to ejs", async t => { + let tmpl = new Template( + "./test/stubs/overrides/test.html", + "./test/stubs/", + "./dist" + ); + + t.is((await tmpl.render()).trim(), "

My Title

"); +}); + +test("Override base templating engine from .html to (nothing)", async t => { + let tmpl = new Template( + "./test/stubs/overrides/test-empty.html", + "./test/stubs/", + "./dist" + ); + + t.is((await tmpl.render()).trim(), "

<%= title %>

"); +}); + +test("Override base templating engine should error with bad string", async t => { + let tmpl = new Template( + "./test/stubs/overrides/test-error.njk", + "./test/stubs/", + "./dist" + ); + + await t.throwsAsync(async () => { + await tmpl.render(); + }); +}); + +test("Override base templating engine (bypasses markdown)", async t => { + let tmpl = new Template( + "./test/stubs/overrides/test-bypass.md", + "./test/stubs/", + "./dist" + ); + + t.is((await tmpl.render()).trim(), "# My Title"); +}); + +test("Override base templating engine to (nothing)", async t => { + let tmpl = new Template( + "./test/stubs/overrides/test-empty.md", + "./test/stubs/", + "./dist" + ); + + // not parsed + t.is((await tmpl.render()).trim(), "# <%= title %>"); +}); + +test("Override base templating engine from .ejs to njk", async t => { + let tmpl = new Template( + "./test/stubs/overrides/test.ejs", + "./test/stubs/", + "./dist" + ); + + t.is((await tmpl.render()).trim(), "My Title"); +}); + +test("Override base templating engine from .njk to ejs (with a layout that uses njk)", async t => { + let tmpl = new Template( + "./test/stubs/overrides/layout.njk", + "./test/stubs/", + "./dist" + ); + + t.is( + (await tmpl.render()).trim(), + '

My Title

' + ); +}); + +test("Override base templating engine from .njk to nothing (with a layout that uses njk)", async t => { + let tmpl = new Template( + "./test/stubs/overrides/layoutfalse.njk", + "./test/stubs/", + "./dist" + ); + + t.is( + (await tmpl.render()).trim(), + `

<%= title %>

` + ); +}); + +test("Using a markdown source file (with a layout that uses njk), markdown shouldnā€™t render in layout file", async t => { + let tmpl = new Template( + "./test/stubs/overrides/test.md", + "./test/stubs/", + "./dist" + ); + + t.is( + normalizeNewLines((await tmpl.render()).trim()), + `# Layout header + +

My Title

+
` + ); +}); + +test("Override base templating engine from .md to ejs,md (with a layout that uses njk), markdown shouldnā€™t render in layout file", async t => { + let tmpl = new Template( + "./test/stubs/overrides/test2.md", + "./test/stubs/", + "./dist" + ); + + t.is( + normalizeNewLines((await tmpl.render()).trim()), + `# Layout header + +

My Title

+
` + ); +}); + +test("renderContent on a markdown file, permalink should not render markdown", async t => { + let tmpl = new Template( + "./test/stubs/permalink-markdown.md", + "./test/stubs/", + "./dist" + ); + + t.is( + await tmpl.renderContent("/news/my-test-file/index.html", {}, true), + "/news/my-test-file/index.html" + ); + + t.is(await tmpl.getOutputLink(), "/news/my-test-file/index.html"); +}); + +test("renderContent on a markdown file, permalink should not render markdown (with variable)", async t => { + let tmpl = new Template( + "./test/stubs/permalink-markdown-var.md", + "./test/stubs/", + "./dist" + ); + + t.is( + await tmpl.renderContent( + "/news/{{ slug }}/index.html", + { slug: "my-title" }, + true + ), + "/news/my-title/index.html" + ); + + t.is(await tmpl.getOutputLink(), "/news/my-title/index.html"); +}); + +test("renderContent on a markdown file, permalink should not render markdown (has override)", async t => { + let tmpl = new Template( + "./test/stubs/permalink-markdown-override.md", + "./test/stubs/", + "./dist" + ); + + t.is( + await tmpl.renderContent("/news/my-test-file/index.html", {}, true), + "/news/my-test-file/index.html" + ); + + t.is(await tmpl.getOutputLink(), "/news/my-test-file/index.html"); +}); + +/* Transforms */ +test("Test a transform", async t => { + t.plan(2); + + let tmpl = new Template( + "./test/stubs/template.ejs", + "./test/stubs/", + "./test/stubs/_site" + ); + + tmpl.addTransform(function(content, outputPath) { + t.true(outputPath.endsWith(".html")); + return "OVERRIDE BY A TRANSFORM"; + }); + + let renders = await tmpl._testCompleteRender(); + t.is(renders[0], "OVERRIDE BY A TRANSFORM"); +}); + +test("Test a transform with pages", async t => { + t.plan(5); + + let tmpl = new Template( + "./test/stubs/transform-pages/template.njk", + "./test/stubs/", + "./test/stubs/_site" + ); + + tmpl.addTransform(function(content, outputPath) { + // should run twice, one for each page + t.true(content.length > 0); + t.true(outputPath.endsWith(".html")); + return "OVERRIDE BY A TRANSFORM"; + }); + + let renders = await tmpl._testCompleteRender(); + t.is(renders[0], "OVERRIDE BY A TRANSFORM"); +}); + +test("Test a transform with a layout", async t => { + t.plan(3); + + let tmpl = new Template( + "./test/stubs-475/transform-layout/transform-layout.njk", + "./test/stubs-475/", + "./test/stubs-475/_site" + ); + + tmpl.addTransform(function(content, outputPath) { + t.is(content, "This is content."); + t.true(outputPath.endsWith(".html")); + return "OVERRIDE BY A TRANSFORM"; + }); + + let renders = await tmpl._testCompleteRender(); + t.is(renders[0], "OVERRIDE BY A TRANSFORM"); +}); + +test("Test a single asynchronous transform", async t => { + t.plan(2); + + let tmpl = new Template( + "./test/stubs/template.ejs", + "./test/stubs/", + "./test/stubs/_site" + ); + + tmpl.addTransform(async function(content, outputPath) { + t.true(outputPath.endsWith("template/index.html")); + + return new Promise((resolve, reject) => { + setTimeout(function(str, outputPath) { + resolve("OVERRIDE BY A TRANSFORM"); + }, 50); + }); + }); + + let renders = await tmpl._testCompleteRender(); + t.is(renders[0], "OVERRIDE BY A TRANSFORM"); +}); + +test("Test multiple asynchronous transforms", async t => { + t.plan(3); + + let tmpl = new Template( + "./test/stubs/template.ejs", + "./test/stubs/", + "./test/stubs/_site" + ); + + tmpl.addTransform(async function(content, outputPath) { + t.true(outputPath.endsWith("template/index.html")); + + return new Promise((resolve, reject) => { + setTimeout(function(str, outputPath) { + resolve("lowercase transform"); + }, 50); + }); + }); + + // uppercase + tmpl.addTransform(async function(str, outputPath) { + t.true(outputPath.endsWith("template/index.html")); + + return new Promise((resolve, reject) => { + setTimeout(function() { + resolve(str.toUpperCase()); + }, 50); + }); + }); + + let renders = await tmpl._testCompleteRender(); + t.is(renders[0], "LOWERCASE TRANSFORM"); +}); + +test("Test a linter", async t => { + t.plan(4); + + let tmpl = new Template( + "./test/stubs/transform-pages/template.njk", + "./test/stubs/", + "./test/stubs/_site" + ); + + tmpl.addLinter(function(str, inputPath, outputPath) { + t.true(inputPath.endsWith("template.njk")); + t.true(outputPath.endsWith("index.html")); + }); + + await tmpl._testCompleteRender(); +}); + +test("permalink: false", async t => { + let tmpl = new Template( + "./test/stubs/permalink-false/test.md", + "./test/stubs/", + "./test/stubs/_site" + ); + + t.is(await tmpl.getOutputLink(), false); + t.is(await tmpl.getOutputHref(), false); + + let data = await tmpl.getData(); + await tmpl.write(false, data); + + // Input file exists (sanity check for paths) + t.is(fs.existsSync("./test/stubs/permalink-false/"), true); + t.is(fs.existsSync("./test/stubs/permalink-false/test.md"), true); + + // Output does not exist + t.is(fs.existsSync("./test/stubs/_site/permalink-false/"), false); + t.is(fs.existsSync("./test/stubs/_site/permalink-false/test/"), false); + t.is( + fs.existsSync("./test/stubs/_site/permalink-false/test/index.html"), + false + ); +}); + +test("Disable dynamic permalinks", async t => { + let tmpl = new Template( + "./test/stubs/dynamic-permalink/test.njk", + "./test/stubs/", + "./test/stubs/_site" + ); + + t.is(await tmpl.getOutputLink(), "/{{justastring}}/index.html"); + t.is(await tmpl.getOutputHref(), "/{{justastring}}/"); +}); + +test("Front Matter Tags (Single)", async t => { + let tmpl = new Template( + "./test/stubs/templatetest-frontmatter/single.njk", + "./test/stubs/", + "dist" + ); + let frontmatter = await tmpl.getFrontMatterData(); + t.deepEqual(frontmatter.tags, ["single-tag"]); + + let fulldata = await tmpl.getData(); + t.deepEqual(fulldata.tags, ["single-tag"]); + + let pages = await tmpl.getRenderedTemplates(fulldata); + t.is(pages[0].templateContent.trim(), "Has single-tag"); +}); + +test("Front Matter Tags (Multiple)", async t => { + let tmpl = new Template( + "./test/stubs/templatetest-frontmatter/multiple.njk", + "./test/stubs/", + "dist" + ); + let frontmatter = await tmpl.getFrontMatterData(); + t.deepEqual(frontmatter.tags, ["multi-tag", "multi-tag-2"]); + + let fulldata = await tmpl.getData(); + t.deepEqual(fulldata.tags, ["multi-tag", "multi-tag-2"]); + + let pages = await tmpl.getRenderedTemplates(fulldata); + t.is(pages[0].templateContent.trim(), "Has multi-tag-2"); +}); + +test("Front matter date with quotes (liquid), issue #258", async t => { + let tmpl = new Template( + "./test/stubs/frontmatter-date/test.liquid", + "./test/stubs/", + "dist" + ); + + let data = await tmpl.getData(); + t.is(data.mydate.toISOString(), "2009-04-15T11:34:34.000Z"); + + let pages = await tmpl.getRenderedTemplates(data); + t.is(pages[0].templateContent.trim(), "2009-04-15"); +}); + +test("Front matter date with quotes (njk), issue #258", async t => { + let tmpl = new Template( + "./test/stubs/frontmatter-date/test.njk", + "./test/stubs/", + "dist" + ); + + let data = await tmpl.getData(); + t.is(data.mydate.toISOString(), "2009-04-15T00:34:34.000Z"); + + let pages = await tmpl.getRenderedTemplates(data); + t.is(pages[0].templateContent.trim(), "2009-04-15T00:34:34.000Z"); +}); + +test("Data Cascade (Deep merge)", async t => { + let newConfig = Object.assign({}, config); + newConfig.dataDeepMerge = true; + + let dataObj = new TemplateData("./test/"); + dataObj._setConfig(newConfig); + await dataObj.cacheData(); + + let tmpl = new Template( + "./test/stubs/data-cascade/template.njk", + "./test/stubs/", + "./dist", + dataObj + ); + tmpl.config = newConfig; + + let data = await tmpl.getData(); + t.deepEqual(Object.keys(data).sort(), [ + "datafile", + "frontmatter", + "page", + "parent", + "pkg", + "tags" + ]); + + t.deepEqual(Object.keys(data.parent).sort(), [ + "child", + "datafile", + "frontmatter" + ]); + + t.is(data.parent.child, -2); +}); + +test("Data Cascade (Shallow merge)", async t => { + let dataObj = new TemplateData("./test/"); + await dataObj.cacheData(); + + let tmpl = new Template( + "./test/stubs/data-cascade/template.njk", + "./test/stubs/", + "./dist", + dataObj + ); + + let data = await tmpl.getData(); + t.deepEqual(Object.keys(data).sort(), [ + "datafile", + "frontmatter", + "page", + "parent", + "pkg", + "tags" + ]); + + t.deepEqual(Object.keys(data.parent).sort(), ["child", "frontmatter"]); + + t.is(data.parent.child, -2); +}); + +test("Data Cascade Tag Merge (Deep merge)", async t => { + let newConfig = Object.assign({}, config); + newConfig.dataDeepMerge = true; + + let dataObj = new TemplateData("./test/stubs/"); + dataObj._setConfig(newConfig); + await dataObj.cacheData(); + + let tmpl = new Template( + "./test/stubs/data-cascade/template.njk", + "./test/stubs/", + "./dist", + dataObj + ); + tmpl.config = newConfig; + + let data = await tmpl.getData(); + t.deepEqual(data.tags.sort(), ["tagA", "tagB", "tagC", "tagD"]); +}); + +test("Data Cascade Tag Merge (Shallow merge)", async t => { + let dataObj = new TemplateData("./test/stubs/"); + await dataObj.cacheData(); + + let tmpl = new Template( + "./test/stubs/data-cascade/template.njk", + "./test/stubs/", + "./dist", + dataObj + ); + + let data = await tmpl.getData(); + t.deepEqual(data.tags.sort(), ["tagA", "tagB"]); +}); + +test('Local data inherits tags string ([tags] vs "tags") Shallow Merge', async t => { + let dataObj = new TemplateData("./test/stubs/"); + await dataObj.cacheData(); + + let tmpl = new Template( + "./test/stubs/local-data-tags/component.njk", + "./test/stubs/", + "./dist", + dataObj + ); + + let data = await tmpl.getData(); + t.deepEqual(data.tags.sort(), ["tag1", "tag2"]); +}); + +test('Local data inherits tags string ([tags] vs "tags") Deep Merge', async t => { + let newConfig = Object.assign({}, config); + newConfig.dataDeepMerge = true; + + let dataObj = new TemplateData("./test/stubs/"); + dataObj._setConfig(newConfig); + await dataObj.cacheData(); + + let tmpl = new Template( + "./test/stubs/local-data-tags/component.njk", + "./test/stubs/", + "./dist", + dataObj + ); + tmpl.config = newConfig; + + let data = await tmpl.getData(); + t.deepEqual(data.tags.sort(), ["tag1", "tag2", "tag3"]); +}); + +test("Throws a Premature Template Content Error (njk)", async t => { + let tmpl = new Template( + "./test/stubs/prematureTemplateContent/test.njk", + "./test/stubs/", + "./test/stubs/_site" + ); + + let data = await tmpl.getData(); + let mapEntries = await tmpl.getTemplates(data); + let error = t.throws(() => { + mapEntries[0].templateContent; + }); + t.is(EleventyErrorUtil.isPrematureTemplateContentError(error), true); +}); + +test("Throws a Premature Template Content Error from rendering (njk)", async t => { + let tmpl = new Template( + "./test/stubs/prematureTemplateContent/test.njk", + "./test/stubs/", + "./test/stubs/_site" + ); + + let mapEntries = await tmpl.getTemplateMapEntries(); + let pageEntries = await tmpl.getTemplates({ + page: {}, + sample: { + get templateContent() { + throw new TemplateContentPrematureUseError( + "Tried to use templateContent too early (test.njk)" + ); + } + } + }); + let error = await t.throwsAsync(async () => { + await tmpl.renderPageEntry(mapEntries[0], pageEntries[0]); + }); + t.is(EleventyErrorUtil.isPrematureTemplateContentError(error), true); +}); + +test("Throws a Premature Template Content Error (liquid)", async t => { + let tmpl = new Template( + "./test/stubs/prematureTemplateContent/test.liquid", + "./test/stubs/", + "./test/stubs/_site" + ); + + let data = await tmpl.getData(); + let mapEntries = await tmpl.getTemplates(data); + let error = t.throws(() => { + mapEntries[0].templateContent; + }); + t.is(EleventyErrorUtil.isPrematureTemplateContentError(error), true); +}); + +test("Throws a Premature Template Content Error (11ty.js)", async t => { + let tmpl = new Template( + "./test/stubs/prematureTemplateContent/test.11ty.js", + "./test/stubs/", + "./test/stubs/_site" + ); + + let data = await tmpl.getData(); + let mapEntries = await tmpl.getTemplates(data); + let error = t.throws(() => { + mapEntries[0].templateContent; + }); + t.is(EleventyErrorUtil.isPrematureTemplateContentError(error), true); +}); + +test("Throws a Premature Template Content Error (pug)", async t => { + let tmpl = new Template( + "./test/stubs/prematureTemplateContent/test.pug", + "./test/stubs/", + "./test/stubs/_site" + ); + + let data = await tmpl.getData(); + let mapEntries = await tmpl.getTemplates(data); + let error = t.throws(() => { + mapEntries[0].templateContent; + }); + t.is(EleventyErrorUtil.isPrematureTemplateContentError(error), true); +}); + +test("Throws a Premature Template Content Error from rendering (pug)", async t => { + let tmpl = new Template( + "./test/stubs/prematureTemplateContent/test.pug", + "./test/stubs/", + "./test/stubs/_site" + ); + + let mapEntries = await tmpl.getTemplateMapEntries(); + let pageEntries = await tmpl.getTemplates({ + page: {}, + sample: { + get templateContent() { + throw new TemplateContentPrematureUseError( + "Tried to use templateContent too early (test.pug)" + ); + } + } + }); + let error = await t.throwsAsync(async () => { + await tmpl.renderPageEntry(mapEntries[0], pageEntries[0]); + }); + t.is(EleventyErrorUtil.isPrematureTemplateContentError(error), true); +}); + +test("Throws a Premature Template Content Error (md)", async t => { + let tmpl = new Template( + "./test/stubs/prematureTemplateContent/test.md", + "./test/stubs/", + "./test/stubs/_site" + ); + + let data = await tmpl.getData(); + let mapEntries = await tmpl.getTemplates(data); + let error = t.throws(() => { + mapEntries[0].templateContent; + }); + t.is(EleventyErrorUtil.isPrematureTemplateContentError(error), true); +}); + +test("Throws a Premature Template Content Error from rendering (md)", async t => { + let tmpl = new Template( + "./test/stubs/prematureTemplateContent/test.md", + "./test/stubs/", + "./test/stubs/_site" + ); + + let mapEntries = await tmpl.getTemplateMapEntries(); + let pageEntries = await tmpl.getTemplates({ + page: {}, + sample: { + get templateContent() { + throw new TemplateContentPrematureUseError( + "Tried to use templateContent too early (test.md)" + ); + } + } + }); + let error = await t.throwsAsync(async () => { + await tmpl.renderPageEntry(mapEntries[0], pageEntries[0]); + }); + t.is(EleventyErrorUtil.isPrematureTemplateContentError(error), true); +}); + +test("Throws a Premature Template Content Error (hbs)", async t => { + let tmpl = new Template( + "./test/stubs/prematureTemplateContent/test.hbs", + "./test/stubs/", + "./test/stubs/_site" + ); + + let data = await tmpl.getData(); + let mapEntries = await tmpl.getTemplates(data); + let error = t.throws(() => { + mapEntries[0].templateContent; + }); + t.is(EleventyErrorUtil.isPrematureTemplateContentError(error), true); +}); + +test("Throws a Premature Template Content Error from rendering (hbs)", async t => { + let tmpl = new Template( + "./test/stubs/prematureTemplateContent/test.hbs", + "./test/stubs/", + "./test/stubs/_site" + ); + + let mapEntries = await tmpl.getTemplateMapEntries(); + let pageEntries = await tmpl.getTemplates({ + page: {}, + sample: { + get templateContent() { + throw new TemplateContentPrematureUseError( + "Tried to use templateContent too early (test.hbs)" + ); + } + } + }); + let error = await t.throwsAsync(async () => { + await tmpl.renderPageEntry(mapEntries[0], pageEntries[0]); + }); + t.is(EleventyErrorUtil.isPrematureTemplateContentError(error), true); +}); + +test("Throws a Premature Template Content Error (mustache)", async t => { + let tmpl = new Template( + "./test/stubs/prematureTemplateContent/test.mustache", + "./test/stubs/", + "./test/stubs/_site" + ); + + let data = await tmpl.getData(); + let mapEntries = await tmpl.getTemplates(data); + let error = t.throws(() => { + mapEntries[0].templateContent; + }); + t.is(EleventyErrorUtil.isPrematureTemplateContentError(error), true); +}); + +test("Throws a Premature Template Content Error (ejs)", async t => { + let tmpl = new Template( + "./test/stubs/prematureTemplateContent/test.ejs", + "./test/stubs/", + "./test/stubs/_site" + ); + + let data = await tmpl.getData(); + let mapEntries = await tmpl.getTemplates(data); + let error = t.throws(() => { + mapEntries[0].templateContent; + }); + t.is(EleventyErrorUtil.isPrematureTemplateContentError(error), true); +}); + +test("Throws a Premature Template Content Error (haml)", async t => { + let tmpl = new Template( + "./test/stubs/prematureTemplateContent/test.haml", + "./test/stubs/", + "./test/stubs/_site" + ); + + let data = await tmpl.getData(); + let mapEntries = await tmpl.getTemplates(data); + let error = t.throws(() => { + mapEntries[0].templateContent; + }); + t.is(EleventyErrorUtil.isPrematureTemplateContentError(error), true); +}); + +test.skip("Issue 413 weird date format", async t => { + let tmpl = new Template( + "./test/stubs-413/date-frontmatter.md", + "./test/stubs-413/", + "./dist" + ); + + let data = await tmpl.getData(); + t.is(data.page.date, ""); +}); + +test("Custom Front Matter Parsing Options", async t => { + let newConfig = Object.assign({}, config); + newConfig.frontMatterParsingOptions = { + excerpt: true + }; + + let tmpl = new Template( + "./test/stubs/custom-frontmatter/template.njk", + "./test/stubs/", + "./dist" + ); + tmpl.config = newConfig; + + let frontmatter = await tmpl.getFrontMatter(); + t.is(frontmatter.data.front, "hello"); + t.is(frontmatter.data.page.excerpt.trim(), "This is an excerpt."); + + t.is(frontmatter.excerpt.trim(), "This is an excerpt."); + t.is( + normalizeNewLines(frontmatter.content.trim()), + `This is an excerpt. +This is content.` + ); + + let fulldata = await tmpl.getData(); + t.is(fulldata.page.excerpt.trim(), "This is an excerpt."); +}); + +test("Custom Front Matter Parsing Options (using alias)", async t => { + let newConfig = Object.assign({}, config); + newConfig.frontMatterParsingOptions = { + excerpt: true, + excerpt_alias: "my_excerpt" + }; + + let tmpl = new Template( + "./test/stubs/custom-frontmatter/template.njk", + "./test/stubs/", + "./dist" + ); + tmpl.config = newConfig; + + let frontmatter = await tmpl.getFrontMatter(); + t.is(frontmatter.data.front, "hello"); + t.is(frontmatter.data.my_excerpt.trim(), "This is an excerpt."); + t.is( + normalizeNewLines(frontmatter.content.trim()), + `This is an excerpt. +This is content.` + ); + + let fulldata = await tmpl.getData(); + t.is(fulldata.my_excerpt.trim(), "This is an excerpt."); +}); + +test("Custom Front Matter Parsing Options (no newline before excerpt separator)", async t => { + let newConfig = Object.assign({}, config); + newConfig.frontMatterParsingOptions = { + excerpt: true + }; + + let tmpl = new Template( + "./test/stubs/custom-frontmatter/template-newline1.njk", + "./test/stubs/", + "./dist" + ); + tmpl.config = newConfig; + + let frontmatter = await tmpl.getFrontMatter(); + t.is(frontmatter.data.front, "hello"); + t.is(frontmatter.data.page.excerpt.trim(), "This is an excerpt."); + + t.is(frontmatter.excerpt.trim(), "This is an excerpt."); + t.is( + normalizeNewLines(frontmatter.content.trim()), + `This is an excerpt. +This is content.` + ); + + let fulldata = await tmpl.getData(); + t.is(fulldata.page.excerpt.trim(), "This is an excerpt."); +}); + +test("Custom Front Matter Parsing Options (no newline after excerpt separator)", async t => { + let newConfig = Object.assign({}, config); + newConfig.frontMatterParsingOptions = { + excerpt: true + }; + + let tmpl = new Template( + "./test/stubs/custom-frontmatter/template-newline3.njk", + "./test/stubs/", + "./dist" + ); + tmpl.config = newConfig; + + let frontmatter = await tmpl.getFrontMatter(); + t.is( + normalizeNewLines(frontmatter.content.trim()), + `This is an excerpt. +This is content.` + ); +}); + +test("Custom Front Matter Parsing Options (no newlines before or after excerpt separator)", async t => { + let newConfig = Object.assign({}, config); + newConfig.frontMatterParsingOptions = { + excerpt: true + }; + + let tmpl = new Template( + "./test/stubs/custom-frontmatter/template-newline2.njk", + "./test/stubs/", + "./dist" + ); + tmpl.config = newConfig; + + let frontmatter = await tmpl.getFrontMatter(); + t.is(frontmatter.content.trim(), "This is an excerpt.This is content."); +}); + +test("Custom Front Matter Parsing Options (html comment separator)", async t => { + let newConfig = Object.assign({}, config); + newConfig.frontMatterParsingOptions = { + excerpt: true, + excerpt_separator: "" + }; + + let tmpl = new Template( + "./test/stubs/custom-frontmatter/template-excerpt-comment.njk", + "./test/stubs/", + "./dist" + ); + tmpl.config = newConfig; + + let frontmatter = await tmpl.getFrontMatter(); + t.is(frontmatter.data.front, "hello"); + t.is(frontmatter.data.page.excerpt.trim(), "This is an excerpt."); + + t.is(frontmatter.excerpt.trim(), "This is an excerpt."); + t.is( + normalizeNewLines(frontmatter.content.trim()), + `This is an excerpt. +This is content.` + ); +}); + +test.skip("Custom Front Matter Parsing Options (using TOML)", async t => { + // Depends on https://github.com/jonschlinkert/gray-matter/issues/92 for Windows + let newConfig = Object.assign({}, config); + let toml = require("toml"); + + newConfig.frontMatterParsingOptions = { + engines: { + toml: toml.parse.bind(toml) + } + }; + + let tmpl = new Template( + "./test/stubs/custom-frontmatter/template-toml.njk", + "./test/stubs/", + "./dist" + ); + tmpl.config = newConfig; + + let frontmatter = await tmpl.getFrontMatter(); + t.deepEqual(frontmatter.data, { + front: "hello" + }); + t.is(frontmatter.content.trim(), "This is content."); + + let fulldata = await tmpl.getData(); + t.is(fulldata.front, "hello"); +}); + +test("global variable with dashes Issue #567 (liquid)", async t => { + let tmpl = new Template( + "./test/stubs/global-dash-variable.liquid", + "./test/stubs/", + "./dist" + ); + + let data = await tmpl.getData(); + t.is(data["is-it-tasty"], "Yes"); + + let pages = await tmpl.getRenderedTemplates(data); + t.is(pages[0].templateContent.trim(), "Yes"); +}); + +// test("Issue #446: Layout has a permalink with a different template language than content", async t => { +// let tmpl = new Template( +// "./test/stubs/layout-permalink-difflang/test.md", +// "./test/stubs/layout-permalink-difflang/", +// "dist" +// ); + +// let data = await tmpl.getData(); +// let pages = await tmpl.getRenderedTemplates(data); + +// t.is(data.permalink, "/{{ page.fileSlug }}/"); +// t.is(data.page.url, "/test/"); +// }); diff --git a/test/TemplateWriterTest.js b/test/TemplateWriterTest.js index b70cfc77f..bdce43126 100644 --- a/test/TemplateWriterTest.js +++ b/test/TemplateWriterTest.js @@ -1,63 +1,666 @@ -import fs from "fs-extra"; import test from "ava"; -import globby from "globby"; +import fs from "fs-extra"; +import rimraf from "rimraf"; +import fastglob from "fast-glob"; +import parsePath from "parse-filepath"; +import EleventyFiles from "../src/EleventyFiles"; +import EleventyExtensionMap from "../src/EleventyExtensionMap"; import TemplateWriter from "../src/TemplateWriter"; +// Not sure why but this import up `ava` and _createTemplate šŸ‘€ +// import Template from "../src/Template"; +import eleventyConfig from "../src/EleventyConfig"; +import normalizeNewLines from "./Util/normalizeNewLines"; -test("Mutually exclusive Input and Output dirs", async t => { +// TODO make sure if output is a subdir of input dir that they donā€™t conflict. +test("Output is a subdir of input", async t => { let tw = new TemplateWriter( "./test/stubs/writeTest", - "./test/stubs/_writeTestSite", + "./test/stubs/writeTest/_writeTestSite" + ); + let evf = new EleventyFiles( + "./test/stubs/writeTest", + "./test/stubs/writeTest/_writeTestSite", ["ejs", "md"] ); + evf.init(); - let files = await globby(tw.files); - t.is(tw.rawFiles.length, 2); + let files = await fastglob(evf.getFileGlobs()); + t.is(evf.getRawFiles().length, 2); t.true(files.length > 0); - t.is(files[0], "./test/stubs/writeTest/test.md"); + + let tmpl = tw._createTemplate(files[0]); + t.is(tmpl.inputDir, "./test/stubs/writeTest"); + t.is( + await tmpl.getOutputPath(), + "./test/stubs/writeTest/_writeTestSite/test/index.html" + ); }); -// TODO make sure if output is a subdir of input dir that they donā€™t conflict. -test("Output is a subdir of input", async t => { +test("_createTemplateMap", async t => { let tw = new TemplateWriter( "./test/stubs/writeTest", - "./test/stubs/writeTest/_writeTestSite", + "./test/stubs/_writeTestSite", ["ejs", "md"] ); - let files = await globby(tw.files); - t.is(tw.rawFiles.length, 2); - t.true(files.length > 0); - let tmpl = tw._getTemplate(files[0]); - t.is(tmpl.inputDir, "./test/stubs/writeTest"); + let paths = await tw._getAllPaths(); + t.true(paths.length > 0); + t.is(paths[0], "./test/stubs/writeTest/test.md"); + + let templateMap = await tw._createTemplateMap(paths); + let map = templateMap.getMap(); + t.true(map.length > 0); + t.truthy(map[0].template); + t.truthy(map[0].data); +}); + +test("_createTemplateMap (no leading dot slash)", async t => { + let tw = new TemplateWriter( + "test/stubs/writeTest", + "test/stubs/_writeTestSite", + ["ejs", "md"] + ); + + let paths = await tw._getAllPaths(); + t.true(paths.length > 0); + t.is(paths[0], "./test/stubs/writeTest/test.md"); +}); + +test("getCollectionsData", async t => { + let tw = new TemplateWriter("./test/stubs/collection", "./test/stubs/_site", [ + "md" + ]); + + let paths = await tw._getAllPaths(); + let templateMap = await tw._createTemplateMap(paths); + let collectionsData = await templateMap.getCollectionsData(); + t.is(collectionsData.post.length, 2); + t.is(collectionsData.cat.length, 2); + t.is(collectionsData.dog.length, 1); +}); + +// TODO remove this (used by other test things) +test("_testGetAllTags", async t => { + let tw = new TemplateWriter("./test/stubs/collection", "./test/stubs/_site", [ + "md" + ]); + + let paths = await tw._getAllPaths(); + let templateMap = await tw._createTemplateMap(paths); + let tags = templateMap._testGetAllTags(); + + t.deepEqual(tags.sort(), ["cat", "dog", "post"].sort()); +}); + +test("Collection of files sorted by date", async t => { + let tw = new TemplateWriter("./test/stubs/dates", "./test/stubs/_site", [ + "md" + ]); + + let paths = await tw._getAllPaths(); + let templateMap = await tw._createTemplateMap(paths); + let collectionsData = await templateMap.getCollectionsData(); + t.is(collectionsData.dateTestTag.length, 6); +}); + +test("_getCollectionsData with custom collection (ascending)", async t => { + let tw = new TemplateWriter( + "./test/stubs/collection2", + "./test/stubs/_site", + ["md"] + ); + + /* Careful here, eleventyConfig is a global */ + eleventyConfig.addCollection("customPostsAsc", function(collection) { + return collection.getFilteredByTag("post").sort(function(a, b) { + return a.date - b.date; + }); + }); + + let paths = await tw._getAllPaths(); + let templateMap = await tw._createTemplateMap(paths); + let collectionsData = await templateMap.getCollectionsData(); + t.is(collectionsData.customPostsAsc.length, 2); + t.is(parsePath(collectionsData.customPostsAsc[0].inputPath).base, "test1.md"); + t.is(parsePath(collectionsData.customPostsAsc[1].inputPath).base, "test2.md"); +}); + +test("_getCollectionsData with custom collection (descending)", async t => { + let tw = new TemplateWriter( + "./test/stubs/collection2", + "./test/stubs/_site", + ["md"] + ); + + /* Careful here, eleventyConfig is a global */ + eleventyConfig.addCollection("customPosts", function(collection) { + return collection.getFilteredByTag("post").sort(function(a, b) { + return b.date - a.date; + }); + }); + + let paths = await tw._getAllPaths(); + let templateMap = await tw._createTemplateMap(paths); + let collectionsData = await templateMap.getCollectionsData(); + t.is(collectionsData.customPosts.length, 2); + t.is(parsePath(collectionsData.customPosts[0].inputPath).base, "test2.md"); + t.is(parsePath(collectionsData.customPosts[1].inputPath).base, "test1.md"); +}); + +test("_getCollectionsData with custom collection (filter only to markdown input)", async t => { + let tw = new TemplateWriter( + "./test/stubs/collection2", + "./test/stubs/_site", + ["md"] + ); + + /* Careful here, eleventyConfig is a global */ + eleventyConfig.addCollection("onlyMarkdown", function(collection) { + return collection.getAllSorted().filter(function(item) { + let extension = item.inputPath.split(".").pop(); + return extension === "md"; + }); + }); + + let paths = await tw._getAllPaths(); + let templateMap = await tw._createTemplateMap(paths); + let collectionsData = await templateMap.getCollectionsData(); + t.is(collectionsData.onlyMarkdown.length, 2); + t.is(parsePath(collectionsData.onlyMarkdown[0].inputPath).base, "test1.md"); + t.is(parsePath(collectionsData.onlyMarkdown[1].inputPath).base, "test2.md"); +}); + +test("Pagination with a Collection", async t => { + let tw = new TemplateWriter( + "./test/stubs/paged/collection", + "./test/stubs/_site", + ["njk"] + ); + + let paths = await tw._getAllPaths(); + let templateMap = await tw._createTemplateMap(paths); + + let collectionsData = await templateMap.getCollectionsData(); + t.is(collectionsData.tag1.length, 3); + t.is(collectionsData.pagingtag.length, 1); + + let mapEntry = templateMap.getMapEntryForInputPath( + "./test/stubs/paged/collection/main.njk" + ); + t.truthy(mapEntry); + t.is(mapEntry.inputPath, "./test/stubs/paged/collection/main.njk"); + t.is(mapEntry._pages.length, 2); + t.is(mapEntry._pages[0].outputPath, "./test/stubs/_site/main/index.html"); + t.is(mapEntry._pages[1].outputPath, "./test/stubs/_site/main/1/index.html"); + t.is( - await tmpl.getOutputPath(), - "./test/stubs/writeTest/_writeTestSite/test/index.html" + mapEntry._pages[0].templateContent.trim(), + "
  1. /test1/
  2. /test2/
" ); + t.is(mapEntry._pages[1].templateContent.trim(), "
  1. /test3/
"); +}); + +test("Pagination with a Collection from another Paged Template", async t => { + let tw = new TemplateWriter( + "./test/stubs/paged/cfg-collection-tag-cfg-collection", + "./test/stubs/_site", + ["njk"] + ); + + let paths = await tw._getAllPaths(); + let templateMap = await tw._createTemplateMap(paths); + + let collectionsData = await templateMap.getCollectionsData(); + t.is(collectionsData.tag1.length, 3); + t.is(collectionsData.pagingtag.length, 2); - // donā€™t write because this messes with avaā€™s watch - // fs.removeSync( "./test/stubs/writeTest/_site" ); - // await tw.write(); - // t.true( fs.existsSync( "./test/stubs/writeTest/_site/test.html" ) ); + let map1 = templateMap.getMapEntryForInputPath( + "./test/stubs/paged/cfg-collection-tag-cfg-collection/paged-main.njk" + ); + t.is( + map1._pages[0].templateContent.trim(), + "
  1. /test1/
  2. /test2/
" + ); + t.is(map1._pages[1].templateContent.trim(), "
  1. /test3/
"); + + let map2 = templateMap.getMapEntryForInputPath( + "./test/stubs/paged/cfg-collection-tag-cfg-collection/paged-downstream.njk" + ); + t.is(map2._pages[0].templateContent.trim(), "
  1. /paged-main/
"); + t.is( + map2._pages[1].templateContent.trim(), + "
  1. /paged-main/1/
" + ); }); -test(".eleventyignore ignores parsing", t => { - let ignores = new TemplateWriter.getFileIgnores("./test/stubs"); - t.is(ignores[0], "!./test/stubs/ignoredFolder/**"); - t.is(ignores[1], "!./test/stubs/ignoredFolder/ignored.md"); +test("Pagination with a Collection (apply all pages to collections)", async t => { + let tw = new TemplateWriter( + "./test/stubs/paged/collection-apply-to-all", + "./test/stubs/_site", + ["njk"] + ); + + let paths = await tw._getAllPaths(); + let templateMap = await tw._createTemplateMap(paths); + + let collectionsData = await templateMap.getCollectionsData(); + t.is(collectionsData.tag1.length, 3); + t.is(collectionsData.pagingtag.length, 2); + + let mapEntry = templateMap.getMapEntryForInputPath( + "./test/stubs/paged/collection-apply-to-all/main.njk" + ); + t.truthy(mapEntry); + t.is( + mapEntry.inputPath, + "./test/stubs/paged/collection-apply-to-all/main.njk" + ); + + let mainTmpl = tw._createTemplate( + "./test/stubs/paged/collection-apply-to-all/main.njk" + ); + let outputPath = await mainTmpl.getOutputPath(); + t.is(outputPath, "./test/stubs/_site/main/index.html"); + t.is(mapEntry.outputPath, "./test/stubs/_site/main/index.html"); + + let templates = await mapEntry.template.getRenderedTemplates(mapEntry.data); + t.is(templates.length, 2); + t.is( + await templates[0].template.getOutputPath(), + "./test/stubs/_site/main/index.html" + ); + t.is(templates[0].outputPath, "./test/stubs/_site/main/index.html"); + t.is( + await templates[1].template.getOutputPath(), + "./test/stubs/_site/main/1/index.html" + ); + t.is(templates[1].outputPath, "./test/stubs/_site/main/1/index.html"); + + // test content + t.is( + templates[0].templateContent.trim(), + "
  1. /test1/
  2. /test2/
" + ); + t.is(templates[1].templateContent.trim(), "
  1. /test3/
"); }); -test(".eleventyignore files", async t => { - let tw = new TemplateWriter("test/stubs", "test/stubs/_site", ["ejs", "md"]); - let ignoredFiles = await globby("test/stubs/ignoredFolder/*.md"); - t.is(ignoredFiles.length, 1); +test("Use a collection inside of a template", async t => { + let tw = new TemplateWriter( + "./test/stubs/collection-template", + "./test/stubs/collection-template/_site", + ["ejs"] + ); - let files = await globby(tw.files); - t.true(files.length > 0); + let paths = await tw._getAllPaths(); + let templateMap = await tw._createTemplateMap(paths); + let collectionsData = await templateMap.getCollectionsData(); + t.is(collectionsData.dog.length, 1); + + let mapEntry = templateMap.getMapEntryForInputPath( + "./test/stubs/collection-template/template.ejs" + ); + t.truthy(mapEntry); + t.is(mapEntry.inputPath, "./test/stubs/collection-template/template.ejs"); + + let mainTmpl = tw._createTemplate( + "./test/stubs/collection-template/template.ejs" + ); + let outputPath = await mainTmpl.getOutputPath(); + t.is( + outputPath, + "./test/stubs/collection-template/_site/template/index.html" + ); + + let templates = await mapEntry.template.getRenderedTemplates(mapEntry.data); + + // test content t.is( - files.filter(file => { - return file.indexOf("./test/stubs/ignoredFolder") > -1; - }).length, - 0 + normalizeNewLines(templates[0].templateContent.trim()), + `Layout + +Template + +All 2 templates +Template 1 dog` + ); +}); + +test("Use a collection inside of a layout", async t => { + let tw = new TemplateWriter( + "./test/stubs/collection-layout", + "./test/stubs/collection-layout/_site", + ["ejs"] ); + + let paths = await tw._getAllPaths(); + let templateMap = await tw._createTemplateMap(paths); + + let collectionsData = await templateMap.getCollectionsData(); + t.is(collectionsData.dog.length, 1); + + let mapEntry = templateMap.getMapEntryForInputPath( + "./test/stubs/collection-layout/template.ejs" + ); + t.truthy(mapEntry); + t.is(mapEntry.inputPath, "./test/stubs/collection-layout/template.ejs"); + + let mainTmpl = tw._createTemplate( + "./test/stubs/collection-layout/template.ejs" + ); + let outputPath = await mainTmpl.getOutputPath(); + t.is(outputPath, "./test/stubs/collection-layout/_site/template/index.html"); + + let templates = await mapEntry.template.getRenderedTemplates(mapEntry.data); + + // test content + t.is( + normalizeNewLines(templates[0].templateContent.trim()), + `Layout + +Template + +All 2 templates +Layout 1 dog` + ); +}); + +test("Glob Watcher Files with Passthroughs", t => { + let tw = new TemplateWriter("test/stubs", "test/stubs/_site", ["njk", "png"]); + t.deepEqual(tw.getFileManager().getPassthroughPaths(), []); +}); + +test("Pagination and TemplateContent", async t => { + let tw = new TemplateWriter( + "./test/stubs/pagination-templatecontent", + "./test/stubs/pagination-templatecontent/_site", + ["njk", "md"] + ); + + tw.setVerboseOutput(false); + await tw.write(); + + let content = fs.readFileSync( + "./test/stubs/pagination-templatecontent/_site/index.html", + "utf-8" + ); + t.is( + content.trim(), + `

Post 1

+

Post 2

` + ); + + rimraf.sync("./test/stubs/pagination-templatecontent/_site/"); +}); + +test("Custom collection returns array", async t => { + let tw = new TemplateWriter( + "./test/stubs/collection2", + "./test/stubs/_site", + ["md"] + ); + + /* Careful here, eleventyConfig is a global */ + eleventyConfig.addCollection("returnAllInputPaths", function(collection) { + return collection.getAllSorted().map(function(item) { + return item.inputPath; + }); + }); + + let paths = await tw._getAllPaths(); + let templateMap = await tw._createTemplateMap(paths); + let collectionsData = await templateMap.getCollectionsData(); + t.is(collectionsData.returnAllInputPaths.length, 2); + t.is(parsePath(collectionsData.returnAllInputPaths[0]).base, "test1.md"); + t.is(parsePath(collectionsData.returnAllInputPaths[1]).base, "test2.md"); +}); + +test("Custom collection returns a string", async t => { + let tw = new TemplateWriter( + "./test/stubs/collection2", + "./test/stubs/_site", + ["md"] + ); + + /* Careful here, eleventyConfig is a global */ + eleventyConfig.addCollection("returnATestString", function(collection) { + return "test"; + }); + + let paths = await tw._getAllPaths(); + let templateMap = await tw._createTemplateMap(paths); + let collectionsData = await templateMap.getCollectionsData(); + t.is(collectionsData.returnATestString, "test"); +}); + +test("Custom collection returns an object", async t => { + let tw = new TemplateWriter( + "./test/stubs/collection2", + "./test/stubs/_site", + ["md"] + ); + + /* Careful here, eleventyConfig is a global */ + eleventyConfig.addCollection("returnATestObject", function() { + return { test: "value" }; + }); + + let paths = await tw._getAllPaths(); + let templateMap = await tw._createTemplateMap(paths); + let collectionsData = await templateMap.getCollectionsData(); + t.deepEqual(collectionsData.returnATestObject, { test: "value" }); +}); + +test("fileSlug should exist in a collection", async t => { + let tw = new TemplateWriter( + "./test/stubs/collection-slug", + "./test/stubs/collection-slug/_site", + ["njk"] + ); + + let paths = await tw._getAllPaths(); + let templateMap = await tw._createTemplateMap(paths); + + let collectionsData = await templateMap.getCollectionsData(); + t.is(collectionsData.dog.length, 1); + + let mapEntry = templateMap.getMapEntryForInputPath( + "./test/stubs/collection-slug/template.njk" + ); + t.truthy(mapEntry); + t.is(mapEntry.inputPath, "./test/stubs/collection-slug/template.njk"); + + let templates = await mapEntry.template.getRenderedTemplates(mapEntry.data); + t.is(templates[0].templateContent.trim(), "fileSlug:/dog1/:dog1"); +}); + +// TODO +test.skip("renderData should exist and be resolved in a collection (Issue #289)", async t => { + let tw = new TemplateWriter( + "./test/stubs/collection-renderdata", + "./test/stubs/collection-renderdata/_site", + ["njk"] + ); + + let paths = await tw._getAllPaths(); + let templateMap = await tw._createTemplateMap(paths); + + let collectionsData = await templateMap.getCollectionsData(); + t.is(collectionsData.dog.length, 1); + + let mapEntry = templateMap.getMapEntryForInputPath( + "./test/stubs/collection-renderdata/template.njk" + ); + t.truthy(mapEntry); + t.is(mapEntry.inputPath, "./test/stubs/collection-renderdata/template.njk"); + + let templates = await mapEntry.template.getRenderedTemplates(mapEntry.data); + t.is(templates[0].templateContent.trim(), "Test Title"); +}); + +test("Write Test 11ty.js", async t => { + let tw = new TemplateWriter( + "./test/stubs/writeTestJS", + "./test/stubs/_writeTestJSSite" + ); + let evf = new EleventyFiles( + "./test/stubs/writeTestJS", + "./test/stubs/_writeTestJSSite", + ["11ty.js"] + ); + evf.init(); + + let files = await fastglob(evf.getFileGlobs()); + t.deepEqual(evf.getRawFiles(), ["./test/stubs/writeTestJS/**/*.11ty.js"]); + t.deepEqual(files, ["./test/stubs/writeTestJS/test.11ty.js"]); + + let tmpl = tw._createTemplate(files[0]); + t.is( + await tmpl.getOutputPath(), + "./test/stubs/_writeTestJSSite/test/index.html" + ); +}); + +test.skip("Markdown with alias", async t => { + let map = new EleventyExtensionMap(["md"]); + map.config = { + templateExtensionAliases: { + markdown: "md" + } + }; + + let evf = new EleventyFiles( + "./test/stubs/writeTestMarkdown", + "./test/stubs/_writeTestMarkdownSite", + ["md"] + ); + evf._setExtensionMap(map); + evf.init(); + + let files = await fastglob(evf.getFileGlobs()); + t.deepEqual(evf.getRawFiles(), [ + "./test/stubs/writeTestMarkdown/**/*.md", + "./test/stubs/writeTestMarkdown/**/*.markdown" + ]); + t.true(files.indexOf("./test/stubs/writeTestMarkdown/sample.md") > -1); + t.true(files.indexOf("./test/stubs/writeTestMarkdown/sample2.markdown") > -1); + + let tw = new TemplateWriter( + "./test/stubs/writeTestMarkdown", + "./test/stubs/_writeTestMarkdownSite" + ); + tw.setEleventyFiles(evf); + + let tmpl = tw._createTemplate(files[0]); + tmpl._setExtensionMap(map); + t.is( + await tmpl.getOutputPath(), + "./test/stubs/_writeTestMarkdownSite/sample/index.html" + ); + + let tmpl2 = tw._createTemplate(files[1]); + tmpl2._setExtensionMap(map); + t.is( + await tmpl2.getOutputPath(), + "./test/stubs/_writeTestMarkdownSite/sample2/index.html" + ); +}); + +test.skip("JavaScript with alias", async t => { + let map = new EleventyExtensionMap(["11ty.js"]); + map.config = { + templateExtensionAliases: { + js: "11ty.js" + } + }; + + let evf = new EleventyFiles( + "./test/stubs/writeTestJS", + "./test/stubs/_writeTestJSSite", + ["11ty.js"] + ); + evf._setExtensionMap(map); + evf.init(); + + let files = await fastglob(evf.getFileGlobs()); + t.deepEqual( + evf.getRawFiles().sort(), + [ + "./test/stubs/writeTestJS/**/*.11ty.js", + "./test/stubs/writeTestJS/**/*.js" + ].sort() + ); + t.deepEqual( + files.sort(), + [ + "./test/stubs/writeTestJS/sample.js", + "./test/stubs/writeTestJS/test.11ty.js" + ].sort() + ); + + let tw = new TemplateWriter( + "./test/stubs/writeTestJS", + "./test/stubs/_writeTestJSSite" + ); + tw.setEleventyFiles(evf); + + let tmpl = tw._createTemplate(files[0]); + tmpl._setExtensionMap(map); + t.is( + await tmpl.getOutputPath(), + "./test/stubs/_writeTestJSSite/sample/index.html" + ); + + let tmpl2 = tw._createTemplate(files[1]); + tmpl2._setExtensionMap(map); + t.is( + await tmpl2.getOutputPath(), + "./test/stubs/_writeTestJSSite/test/index.html" + ); +}); + +test("Passthrough file output", async t => { + let tw = new TemplateWriter( + "./test/stubs/template-passthrough/", + "./test/stubs/template-passthrough/_site", + ["njk", "md"] + ); + + const mgr = tw.getFileManager().getPassthroughManager(); + mgr.setConfig({ + passthroughFileCopy: true, + passthroughCopies: { + "./test/stubs/template-passthrough/static": true, + "./test/stubs/template-passthrough/static/": "./", + "./test/stubs/template-passthrough/static/**/*": "./all/", + "./test/stubs/template-passthrough/static/**/*.js": "./js/" + } + }); + + await tw.write(); + + const output = [ + "./test/stubs/template-passthrough/_site/static/nested/test-nested.css", + "./test/stubs/template-passthrough/_site/all/test.js", + "./test/stubs/template-passthrough/_site/all/test.css", + "./test/stubs/template-passthrough/_site/all/test-nested.css", + "./test/stubs/template-passthrough/_site/js/", + "./test/stubs/template-passthrough/_site/js/test.js", + "./test/stubs/template-passthrough/_site/nested/", + "./test/stubs/template-passthrough/_site/nested/test-nested.css", + "./test/stubs/template-passthrough/_site/test.css", + "./test/stubs/template-passthrough/_site/test.js" + ]; + + let results = await Promise.all( + output.map(function(path) { + return fs.exists(path); + }) + ); + + for (let result of results) { + t.true(result); + } + + rimraf.sync("./test/stubs/template-passthrough/_site/"); }); diff --git a/test/TestUtilityTest.js b/test/TestUtilityTest.js new file mode 100644 index 000000000..e8c4428cc --- /dev/null +++ b/test/TestUtilityTest.js @@ -0,0 +1,10 @@ +import test from "ava"; +import normalizeNewLines from "./Util/normalizeNewLines"; + +test("normalizeNewLines", t => { + t.is(normalizeNewLines("\n"), "\n"); + t.is(normalizeNewLines("\r\n"), "\n"); + t.is(normalizeNewLines("\r\n\n"), "\n\n"); + t.is(normalizeNewLines("\r\n\r\n"), "\n\n"); + t.is(normalizeNewLines("a\r\nhello\r\nhi"), "a\nhello\nhi"); +}); diff --git a/test/UrlTest.js b/test/UrlTest.js new file mode 100644 index 000000000..a81985c9b --- /dev/null +++ b/test/UrlTest.js @@ -0,0 +1,253 @@ +import test from "ava"; +import url from "../src/Filters/Url.js"; + +test("Test url filter without passing in pathPrefix", t => { + let projectConfig = require("../src/Config").getConfig(); + t.is(projectConfig.pathPrefix, "/"); + + t.is(url("test"), "test"); + t.is(url("/test"), "/test"); +}); + +test("Test url filter with passthrough urls", t => { + // via https://gist.github.com/mxpv/034933deeebb26b62f14 + t.is(url("http://foo.com/blah_blah", ""), "http://foo.com/blah_blah"); + t.is(url("http://foo.com/blah_blah/", ""), "http://foo.com/blah_blah/"); + t.is( + url("http://foo.com/blah_blah_(wikipedia)", ""), + "http://foo.com/blah_blah_(wikipedia)" + ); + t.is( + url("http://foo.com/blah_blah_(wikipedia)_(again)", ""), + "http://foo.com/blah_blah_(wikipedia)_(again)" + ); + t.is( + url("http://www.example.com/wpstyle/?p=364", ""), + "http://www.example.com/wpstyle/?p=364" + ); + t.is( + url("https://www.example.com/foo/?bar=baz&inga=42&quux", ""), + "https://www.example.com/foo/?bar=baz&inga=42&quux" + ); + t.is( + url("http://userid:password@example.com:8080", ""), + "http://userid:password@example.com:8080" + ); + t.is( + url("http://userid:password@example.com:8080/", ""), + "http://userid:password@example.com:8080/" + ); + t.is(url("http://userid@example.com", ""), "http://userid@example.com"); + t.is(url("http://userid@example.com/", ""), "http://userid@example.com/"); + t.is( + url("http://userid@example.com:8080", ""), + "http://userid@example.com:8080" + ); + t.is( + url("http://userid@example.com:8080/", ""), + "http://userid@example.com:8080/" + ); + t.is( + url("http://userid:password@example.com", ""), + "http://userid:password@example.com" + ); + t.is( + url("http://userid:password@example.com/", ""), + "http://userid:password@example.com/" + ); + t.is(url("http://142.42.1.1/", ""), "http://142.42.1.1/"); + t.is(url("http://142.42.1.1:8080/", ""), "http://142.42.1.1:8080/"); + t.is( + url("http://foo.com/blah_(wikipedia)#cite-1", ""), + "http://foo.com/blah_(wikipedia)#cite-1" + ); + t.is( + url("http://foo.com/blah_(wikipedia)_blah#cite-1", ""), + "http://foo.com/blah_(wikipedia)_blah#cite-1" + ); + t.is( + url("http://foo.com/(something)?after=parens", ""), + "http://foo.com/(something)?after=parens" + ); + t.is( + url("http://code.google.com/events/#&product=browser", ""), + "http://code.google.com/events/#&product=browser" + ); + t.is(url("http://j.mp", ""), "http://j.mp"); + t.is(url("ftp://foo.bar/baz", ""), "ftp://foo.bar/baz"); + t.is( + url("http://foo.bar/?q=Test%20URL-encoded%20stuff", ""), + "http://foo.bar/?q=Test%20URL-encoded%20stuff" + ); + t.is( + url("http://-.~_!$&'()*+,;=:%40:80%2f::::::@example.com", ""), + "http://-.~_!$&'()*+,;=:%40:80%2f::::::@example.com" + ); + t.is(url("http://1337.net", ""), "http://1337.net"); + t.is(url("http://a.b-c.de", ""), "http://a.b-c.de"); + t.is(url("http://223.255.255.254", ""), "http://223.255.255.254"); + + // these tests were failing without the http/https bypassā€”upstream issues with valid-url + t.is(url("http://āœŖdf.ws/123", ""), "http://āœŖdf.ws/123"); + t.is(url("http://āž”.ws/äع", ""), "http://āž”.ws/äع"); + t.is(url("http://āŒ˜.ws", ""), "http://āŒ˜.ws"); + t.is(url("http://āŒ˜.ws/", ""), "http://āŒ˜.ws/"); + t.is( + url("http://foo.com/unicode_(āœŖ)_in_parens", ""), + "http://foo.com/unicode_(āœŖ)_in_parens" + ); + t.is(url("http://ā˜ŗ.damowmow.com/", ""), "http://ā˜ŗ.damowmow.com/"); + t.is(url("http://Ł…Ų«Ų§Ł„.Ų„Ų®ŲŖŲØŲ§Ų±", ""), "http://Ł…Ų«Ų§Ł„.Ų„Ų®ŲŖŲØŲ§Ų±"); + t.is(url("http://例子.굋čƕ", ""), "http://例子.굋čƕ"); + t.is(url("http://ą¤‰ą¤¦ą¤¾ą¤¹ą¤°ą¤£.ą¤Ŗą¤°ą„€ą¤•ą„ą¤·ą¤¾", ""), "http://ą¤‰ą¤¦ą¤¾ą¤¹ą¤°ą¤£.ą¤Ŗą¤°ą„€ą¤•ą„ą¤·ą¤¾"); +}); + +test("Test url filter", t => { + t.is(url("/", "/"), "/"); + t.is(url("//", "/"), "/"); + t.is(url(undefined, "/"), "."); + t.is(url("", "/"), "."); + + // leave . and .. alone + t.is(url(".", "/"), "."); + t.is(url("./", "/"), "./"); + t.is(url("..", "/"), ".."); + t.is(url("../", "/"), "../"); + + t.is(url("test", "/"), "test"); + t.is(url("/test", "/"), "/test"); + t.is(url("//test", "/"), "/test"); + t.is(url("./test", "/"), "test"); + t.is(url("../test", "/"), "../test"); + + t.is(url("test/", "/"), "test/"); + t.is(url("/test/", "/"), "/test/"); + t.is(url("//test/", "/"), "/test/"); + t.is(url("./test/", "/"), "test/"); + t.is(url("../test/", "/"), "../test/"); +}); + +test("Test url filter with custom pathPrefix (empty, gets overwritten by root config `/`)", t => { + t.is(url("/", ""), "/"); + t.is(url("//", ""), "/"); + t.is(url(undefined, ""), "."); + t.is(url("", ""), "."); + + // leave . and .. alone + t.is(url(".", ""), "."); + t.is(url("./", ""), "./"); + t.is(url("..", ""), ".."); + t.is(url("../", ""), "../"); + + t.is(url("test", ""), "test"); + t.is(url("/test", ""), "/test"); + t.is(url("//test", ""), "/test"); + t.is(url("./test", ""), "test"); + t.is(url("../test", ""), "../test"); + + t.is(url("test/", ""), "test/"); + t.is(url("/test/", ""), "/test/"); + t.is(url("//test/", ""), "/test/"); + t.is(url("./test/", ""), "test/"); + t.is(url("../test/", ""), "../test/"); +}); + +test("Test url filter with custom pathPrefix (leading slash)", t => { + t.is(url("/", "/testdir"), "/testdir/"); + t.is(url("//", "/testdir"), "/testdir/"); + t.is(url(undefined, "/testdir"), "."); + t.is(url("", "/testdir"), "."); + + // leave . and .. alone + t.is(url(".", "/testdir"), "."); + t.is(url("./", "/testdir"), "./"); + t.is(url("..", "/testdir"), ".."); + t.is(url("../", "/testdir"), "../"); + + t.is(url("test", "/testdir"), "test"); + t.is(url("/test", "/testdir"), "/testdir/test"); + t.is(url("//test", "/testdir"), "/testdir/test"); + t.is(url("./test", "/testdir"), "test"); + t.is(url("../test", "/testdir"), "../test"); + + t.is(url("test/", "/testdir"), "test/"); + t.is(url("/test/", "/testdir"), "/testdir/test/"); + t.is(url("//test/", "/testdir"), "/testdir/test/"); + t.is(url("./test/", "/testdir"), "test/"); + t.is(url("../test/", "/testdir"), "../test/"); +}); + +test("Test url filter with custom pathPrefix (double slash)", t => { + t.is(url("/", "/testdir/"), "/testdir/"); + t.is(url("//", "/testdir/"), "/testdir/"); + t.is(url(undefined, "/testdir/"), "."); + t.is(url("", "/testdir/"), "."); + + // leave . and .. alone + t.is(url(".", "/testdir/"), "."); + t.is(url("./", "/testdir/"), "./"); + t.is(url("..", "/testdir/"), ".."); + t.is(url("../", "/testdir/"), "../"); + + t.is(url("test", "/testdir/"), "test"); + t.is(url("/test", "/testdir/"), "/testdir/test"); + t.is(url("//test", "/testdir/"), "/testdir/test"); + t.is(url("./test", "/testdir/"), "test"); + t.is(url("../test", "/testdir/"), "../test"); + + t.is(url("test/", "/testdir/"), "test/"); + t.is(url("/test/", "/testdir/"), "/testdir/test/"); + t.is(url("//test/", "/testdir/"), "/testdir/test/"); + t.is(url("./test/", "/testdir/"), "test/"); + t.is(url("../test/", "/testdir/"), "../test/"); +}); + +test("Test url filter with custom pathPrefix (trailing slash)", t => { + t.is(url("/", "testdir/"), "/testdir/"); + t.is(url("//", "testdir/"), "/testdir/"); + t.is(url(undefined, "testdir/"), "."); + t.is(url("", "testdir/"), "."); + + // leave . and .. alone + t.is(url(".", "testdir/"), "."); + t.is(url("./", "testdir/"), "./"); + t.is(url("..", "testdir/"), ".."); + t.is(url("../", "testdir/"), "../"); + + t.is(url("test", "testdir/"), "test"); + t.is(url("/test", "testdir/"), "/testdir/test"); + t.is(url("//test", "testdir/"), "/testdir/test"); + t.is(url("./test", "testdir/"), "test"); + t.is(url("../test", "testdir/"), "../test"); + + t.is(url("test/", "testdir/"), "test/"); + t.is(url("/test/", "testdir/"), "/testdir/test/"); + t.is(url("//test/", "testdir/"), "/testdir/test/"); + t.is(url("./test/", "testdir/"), "test/"); + t.is(url("../test/", "testdir/"), "../test/"); +}); + +test("Test url filter with custom pathPrefix (no slash)", t => { + t.is(url("/", "testdir"), "/testdir/"); + t.is(url("//", "testdir"), "/testdir/"); + t.is(url(undefined, "testdir"), "."); + t.is(url("", "testdir"), "."); + + // leave . and .. alone + t.is(url(".", "testdir"), "."); + t.is(url("./", "testdir"), "./"); + t.is(url("..", "testdir"), ".."); + t.is(url("../", "testdir"), "../"); + + t.is(url("test", "testdir"), "test"); + t.is(url("/test", "testdir"), "/testdir/test"); + t.is(url("//test", "testdir"), "/testdir/test"); + t.is(url("./test", "testdir"), "test"); + t.is(url("../test", "testdir"), "../test"); + + t.is(url("test/", "testdir"), "test/"); + t.is(url("/test/", "testdir"), "/testdir/test/"); + t.is(url("//test/", "testdir"), "/testdir/test/"); + t.is(url("./test/", "testdir"), "test/"); + t.is(url("../test/", "testdir"), "../test/"); +}); diff --git a/test/Util/normalizeNewLines.js b/test/Util/normalizeNewLines.js new file mode 100644 index 000000000..613b6ac58 --- /dev/null +++ b/test/Util/normalizeNewLines.js @@ -0,0 +1,5 @@ +function normalizeNewLines(str) { + return str.replace(/\r\n/g, "\n"); +} + +module.exports = normalizeNewLines; diff --git a/test/stubs-337/data/xyz.json b/test/stubs-337/data/xyz.json new file mode 100644 index 000000000..71c7ab338 --- /dev/null +++ b/test/stubs-337/data/xyz.json @@ -0,0 +1,3 @@ +{ + "hi": "bye" +} \ No newline at end of file diff --git a/test/stubs/multiple.ejs b/test/stubs-337/src/empty.md similarity index 100% rename from test/stubs/multiple.ejs rename to test/stubs-337/src/empty.md diff --git a/test/stubs-403/.eleventyignore b/test/stubs-403/.eleventyignore new file mode 100644 index 000000000..b30e837cf --- /dev/null +++ b/test/stubs-403/.eleventyignore @@ -0,0 +1 @@ +./_includes/** \ No newline at end of file diff --git a/test/stubs/multiple.md b/test/stubs-403/_includes/include.liquid similarity index 100% rename from test/stubs/multiple.md rename to test/stubs-403/_includes/include.liquid diff --git a/test/stubs-403/template.liquid b/test/stubs-403/template.liquid new file mode 100644 index 000000000..e69de29bb diff --git a/test/stubs-413/date-frontmatter.md b/test/stubs-413/date-frontmatter.md new file mode 100644 index 000000000..d4902556e --- /dev/null +++ b/test/stubs-413/date-frontmatter.md @@ -0,0 +1,6 @@ +--- +subtitle: New doc page +date: 2019-03-13 20:18:42 +0000 +tags: + - docs +--- diff --git a/test/stubs-475/_includes/layout.njk b/test/stubs-475/_includes/layout.njk new file mode 100644 index 000000000..2701558ed --- /dev/null +++ b/test/stubs-475/_includes/layout.njk @@ -0,0 +1 @@ +{{ content | safe }} \ No newline at end of file diff --git a/test/stubs-475/transform-layout/transform-layout.njk b/test/stubs-475/transform-layout/transform-layout.njk new file mode 100644 index 000000000..57105f5fe --- /dev/null +++ b/test/stubs-475/transform-layout/transform-layout.njk @@ -0,0 +1,4 @@ +--- +layout: layout.njk +--- +This is content. \ No newline at end of file diff --git a/test/stubs/.eleventyignore b/test/stubs/.eleventyignore index 68f56bd71..de22e21c5 100644 --- a/test/stubs/.eleventyignore +++ b/test/stubs/.eleventyignore @@ -1,2 +1,3 @@ ignoredFolder -./ignoredFolder/ignored.md \ No newline at end of file +./ignoredFolder/ignored.md +# This is a comment diff --git a/test/stubs/2016-02-01-permalinkdate.liquid b/test/stubs/2016-02-01-permalinkdate.liquid new file mode 100644 index 000000000..98cfeaca6 --- /dev/null +++ b/test/stubs/2016-02-01-permalinkdate.liquid @@ -0,0 +1,5 @@ +--- +title: Date Permalink +permalink: "/{{ page.date | date: '%Y/%m/%d' }}/index.html" +--- +Date Permalinks \ No newline at end of file diff --git a/test/stubs/_data/globalData2.js b/test/stubs/_data/globalData2.js new file mode 100644 index 000000000..16a705853 --- /dev/null +++ b/test/stubs/_data/globalData2.js @@ -0,0 +1,3 @@ +module.exports = { + datakeyfromjs: "howdy" +}; diff --git a/test/stubs/_data/globalDataFn.js b/test/stubs/_data/globalDataFn.js new file mode 100644 index 000000000..2a27b03c9 --- /dev/null +++ b/test/stubs/_data/globalDataFn.js @@ -0,0 +1,7 @@ +const dep1 = require("../deps/dep1"); + +module.exports = function() { + return { + datakeyfromjsfn: "howdy" + }; +}; diff --git a/test/stubs/_data/testDataEjs.json b/test/stubs/_data/testDataEjs.json new file mode 100644 index 000000000..badaef2f0 --- /dev/null +++ b/test/stubs/_data/testDataEjs.json @@ -0,0 +1,4 @@ +{ + "datakey1": "datavalue1", + "datakey2": "<%= pkg.name %>" +} diff --git a/test/stubs/_includes/default.ejs b/test/stubs/_includes/default.ejs new file mode 100644 index 000000000..e69de29bb diff --git a/test/stubs/_includes/defaultLayout.ejs b/test/stubs/_includes/defaultLayout.ejs index 9028eaf0b..91a6e84e1 100644 --- a/test/stubs/_includes/defaultLayout.ejs +++ b/test/stubs/_includes/defaultLayout.ejs @@ -6,5 +6,5 @@ yearsPosted: 0.4 ---
- <%- _layoutContent %> -
\ No newline at end of file + <%- content %> +
diff --git a/test/stubs/_includes/defaultLayoutLayoutContent.ejs b/test/stubs/_includes/defaultLayoutLayoutContent.ejs new file mode 100644 index 000000000..5beea2c22 --- /dev/null +++ b/test/stubs/_includes/defaultLayoutLayoutContent.ejs @@ -0,0 +1,10 @@ +--- +keylayout: valuelayout +postRank: 4 +daysPosted: 152 +yearsPosted: 0.4 +--- + +
+ <%- layoutContent %> +
\ No newline at end of file diff --git a/test/stubs/_includes/defaultLayout_layoutContent.ejs b/test/stubs/_includes/defaultLayout_layoutContent.ejs new file mode 100644 index 000000000..9028eaf0b --- /dev/null +++ b/test/stubs/_includes/defaultLayout_layoutContent.ejs @@ -0,0 +1,10 @@ +--- +keylayout: valuelayout +postRank: 4 +daysPosted: 152 +yearsPosted: 0.4 +--- + +
+ <%- _layoutContent %> +
\ No newline at end of file diff --git a/test/stubs/_includes/included-data.html b/test/stubs/_includes/included-data.html new file mode 100644 index 000000000..dd9e5fe56 --- /dev/null +++ b/test/stubs/_includes/included-data.html @@ -0,0 +1 @@ +This is an include. {{ myVariable }} diff --git a/test/stubs/_includes/included-relative.njk b/test/stubs/_includes/included-relative.njk new file mode 100644 index 000000000..471de0c43 --- /dev/null +++ b/test/stubs/_includes/included-relative.njk @@ -0,0 +1 @@ +akdlsjafkljdskl \ No newline at end of file diff --git a/test/stubs/_includes/included.nunj b/test/stubs/_includes/included.nunj new file mode 100644 index 000000000..6b8ba63a8 --- /dev/null +++ b/test/stubs/_includes/included.nunj @@ -0,0 +1 @@ +Nunjabusiness \ No newline at end of file diff --git a/test/stubs/_includes/layout-a.ejs b/test/stubs/_includes/layout-a.ejs index 6037a4314..49ff36f00 100644 --- a/test/stubs/_includes/layout-a.ejs +++ b/test/stubs/_includes/layout-a.ejs @@ -5,5 +5,5 @@ upstream: value2-a ---
- <%- _layoutContent %> -
\ No newline at end of file + <%- content %> + diff --git a/test/stubs/_includes/layout-b.ejs b/test/stubs/_includes/layout-b.ejs index cde382708..05c6bb6d0 100644 --- a/test/stubs/_includes/layout-b.ejs +++ b/test/stubs/_includes/layout-b.ejs @@ -5,5 +5,5 @@ daysPosted: 154 ---
- <%- _layoutContent %> -
\ No newline at end of file + <%- content %> + diff --git a/test/stubs/_includes/layoutLiquid.liquid b/test/stubs/_includes/layoutLiquid.liquid new file mode 100644 index 000000000..5ffaf5280 --- /dev/null +++ b/test/stubs/_includes/layoutLiquid.liquid @@ -0,0 +1,7 @@ +--- +keylayout: valuelayout +--- + +
+ {{ content }} +
diff --git a/test/stubs/_includes/layouts/div-wrapper-layout.njk b/test/stubs/_includes/layouts/div-wrapper-layout.njk new file mode 100644 index 000000000..f5e54988e --- /dev/null +++ b/test/stubs/_includes/layouts/div-wrapper-layout.njk @@ -0,0 +1 @@ +
{{ content }}
\ No newline at end of file diff --git a/test/stubs/_includes/layouts/engineOverrides.njk b/test/stubs/_includes/layouts/engineOverrides.njk new file mode 100644 index 000000000..4e97bde80 --- /dev/null +++ b/test/stubs/_includes/layouts/engineOverrides.njk @@ -0,0 +1,4 @@ +--- +layoutkey: layoutvalue +--- +
{{ content | safe }}
diff --git a/test/stubs/_includes/layouts/engineOverridesMd.njk b/test/stubs/_includes/layouts/engineOverridesMd.njk new file mode 100644 index 000000000..1420f0fb4 --- /dev/null +++ b/test/stubs/_includes/layouts/engineOverridesMd.njk @@ -0,0 +1,6 @@ +--- +layoutkey: layoutvalue +--- +# Layout header + +
{{ content | safe }}
\ No newline at end of file diff --git a/test/stubs/_includes/layouts/inasubdir.njk b/test/stubs/_includes/layouts/inasubdir.njk new file mode 100644 index 000000000..e69de29bb diff --git a/test/stubs/_includes/layouts/issue-115.liquid b/test/stubs/_includes/layouts/issue-115.liquid new file mode 100644 index 000000000..1dff615c8 --- /dev/null +++ b/test/stubs/_includes/layouts/issue-115.liquid @@ -0,0 +1,6 @@ +{% for foo in pagination.items -%} +{{ foo.data.title }} +{% endfor -%} +{% for bar in collections.bars -%} +{{ bar.data.title }} +{% endfor -%} \ No newline at end of file diff --git a/test/stubs/_includes/layouts/layout-contentdump.njk b/test/stubs/_includes/layouts/layout-contentdump.njk new file mode 100644 index 000000000..872316c32 --- /dev/null +++ b/test/stubs/_includes/layouts/layout-contentdump.njk @@ -0,0 +1,5 @@ +--- +inherits: a +layout: layouts/layout-inherit-b.njk +--- +{{content | default("this is bad")}} {{inherits}} \ No newline at end of file diff --git a/test/stubs/_includes/layouts/layout-inherit-a.njk b/test/stubs/_includes/layouts/layout-inherit-a.njk new file mode 100644 index 000000000..39dc984a3 --- /dev/null +++ b/test/stubs/_includes/layouts/layout-inherit-a.njk @@ -0,0 +1,5 @@ +--- +inherits: a +layout: layouts/layout-inherit-b.njk +--- +{{content}} {{inherits}} \ No newline at end of file diff --git a/test/stubs/_includes/layouts/layout-inherit-b.njk b/test/stubs/_includes/layouts/layout-inherit-b.njk new file mode 100644 index 000000000..5d0f11126 --- /dev/null +++ b/test/stubs/_includes/layouts/layout-inherit-b.njk @@ -0,0 +1,6 @@ +--- +inherits: b +secondinherits: b +layout: layouts/layout-inherit-c.njk +--- +{{ content | safe }} {{secondinherits}} \ No newline at end of file diff --git a/test/stubs/_includes/layouts/layout-inherit-c.njk b/test/stubs/_includes/layouts/layout-inherit-c.njk new file mode 100644 index 000000000..bc49f0cbe --- /dev/null +++ b/test/stubs/_includes/layouts/layout-inherit-c.njk @@ -0,0 +1,6 @@ +--- +inherits: c +secondinherits: c +thirdinherits: c +--- +{{ content | safe }} {{inherits}} {{thirdinherits}} \ No newline at end of file diff --git a/test/stubs/_includes/layouts/post.ejs b/test/stubs/_includes/layouts/post.ejs new file mode 100644 index 000000000..e69de29bb diff --git a/test/stubs/_includes/layouts/templateMapCollection.njk b/test/stubs/_includes/layouts/templateMapCollection.njk new file mode 100644 index 000000000..1f23655c3 --- /dev/null +++ b/test/stubs/_includes/layouts/templateMapCollection.njk @@ -0,0 +1,5 @@ +--- +upstream: Inherited +--- + +{{ content | safe }} \ No newline at end of file diff --git a/test/stubs/_includes/layouts/usescollection.ejs b/test/stubs/_includes/layouts/usescollection.ejs new file mode 100644 index 000000000..e69de29bb diff --git a/test/stubs/_includes/multiple.ejs b/test/stubs/_includes/multiple.ejs new file mode 100644 index 000000000..e69de29bb diff --git a/test/stubs/_includes/multiple.md b/test/stubs/_includes/multiple.md new file mode 100644 index 000000000..e69de29bb diff --git a/test/stubs/_includes/mylocallayout.njk b/test/stubs/_includes/mylocallayout.njk new file mode 100644 index 000000000..8c2049d62 --- /dev/null +++ b/test/stubs/_includes/mylocallayout.njk @@ -0,0 +1 @@ +
{{ content | safe }}
diff --git a/test/stubs/_includes/permalink-data-layout.njk b/test/stubs/_includes/permalink-data-layout.njk new file mode 100644 index 000000000..f2254d70f --- /dev/null +++ b/test/stubs/_includes/permalink-data-layout.njk @@ -0,0 +1,4 @@ +--- +permalink: "{{ page.fileSlug }}/index.html" +--- +Wrapper:{{ layoutContent | safe }} \ No newline at end of file diff --git a/test/stubs/_includes/permalink-in-layout/layout-fileslug.ejs b/test/stubs/_includes/permalink-in-layout/layout-fileslug.ejs new file mode 100644 index 000000000..0b7698c32 --- /dev/null +++ b/test/stubs/_includes/permalink-in-layout/layout-fileslug.ejs @@ -0,0 +1,4 @@ +--- +permalink: test/<%= page.fileSlug %>/ +--- +<%- content %> \ No newline at end of file diff --git a/test/stubs/_includes/permalink-in-layout/layout.ejs b/test/stubs/_includes/permalink-in-layout/layout.ejs new file mode 100644 index 000000000..e6d64bf9a --- /dev/null +++ b/test/stubs/_includes/permalink-in-layout/layout.ejs @@ -0,0 +1,4 @@ +--- +permalink: hello/index.html +--- +<%- content %> \ No newline at end of file diff --git a/test/stubs/_includes/scopeleak.liquid b/test/stubs/_includes/scopeleak.liquid new file mode 100644 index 000000000..5b27a2a7e --- /dev/null +++ b/test/stubs/_includes/scopeleak.liquid @@ -0,0 +1 @@ +{% assign test = 2 %}{{ test }} \ No newline at end of file diff --git a/test/stubs/_includes/subfolder/included.hbs b/test/stubs/_includes/subfolder/included.hbs new file mode 100644 index 000000000..6b622104c --- /dev/null +++ b/test/stubs/_includes/subfolder/included.hbs @@ -0,0 +1 @@ +This is an include. \ No newline at end of file diff --git a/test/stubs/_includes/subfolder/included.html b/test/stubs/_includes/subfolder/included.html new file mode 100644 index 000000000..6b622104c --- /dev/null +++ b/test/stubs/_includes/subfolder/included.html @@ -0,0 +1 @@ +This is an include. \ No newline at end of file diff --git a/test/stubs/_includes/subfolder/included.liquid b/test/stubs/_includes/subfolder/included.liquid new file mode 100644 index 000000000..6b622104c --- /dev/null +++ b/test/stubs/_includes/subfolder/included.liquid @@ -0,0 +1 @@ +This is an include. \ No newline at end of file diff --git a/test/stubs/_includes/subfolder/included.mustache b/test/stubs/_includes/subfolder/included.mustache new file mode 100644 index 000000000..6b622104c --- /dev/null +++ b/test/stubs/_includes/subfolder/included.mustache @@ -0,0 +1 @@ +This is an include. \ No newline at end of file diff --git a/test/stubs/_includes/subfolder/included.nunj b/test/stubs/_includes/subfolder/included.nunj new file mode 100644 index 000000000..5d1709b47 --- /dev/null +++ b/test/stubs/_includes/subfolder/included.nunj @@ -0,0 +1 @@ +Nunjabusiness2 \ No newline at end of file diff --git a/test/stubs/_includes/test.js b/test/stubs/_includes/test.js new file mode 100644 index 000000000..c740b7c0d --- /dev/null +++ b/test/stubs/_includes/test.js @@ -0,0 +1 @@ +/* THIS IS A COMMENT */ alert("Issue #398"); diff --git a/test/stubs/_layouts/layoutsdefault.ejs b/test/stubs/_layouts/layoutsdefault.ejs new file mode 100644 index 000000000..e69de29bb diff --git a/test/stubs/broken-config.js b/test/stubs/broken-config.js new file mode 100644 index 000000000..8c4d54ab5 --- /dev/null +++ b/test/stubs/broken-config.js @@ -0,0 +1,9 @@ +const missingModule = require("this-is-a-module-that-does-not-exist"); + +module.exports = function(eleventyConfig) { + eleventyConfig.addFilter("cssmin", function(code) { + return missingModule(code); + }); + + return {}; +}; diff --git a/test/stubs/buffer.11ty.js b/test/stubs/buffer.11ty.js new file mode 100644 index 000000000..358e0f5b6 --- /dev/null +++ b/test/stubs/buffer.11ty.js @@ -0,0 +1 @@ +module.exports = Buffer.from("

tƩst

"); diff --git a/test/stubs/class-async-data-fn.11ty.js b/test/stubs/class-async-data-fn.11ty.js new file mode 100644 index 000000000..d9cde418b --- /dev/null +++ b/test/stubs/class-async-data-fn.11ty.js @@ -0,0 +1,17 @@ +class Test { + async data() { + return new Promise((resolve, reject) => { + setTimeout(function() { + resolve({ + name: "Ted" + }); + }, 50); + }); + } + + render({ name }) { + return `

${name}

`; + } +} + +module.exports = Test; diff --git a/test/stubs/class-async-filter.11ty.js b/test/stubs/class-async-filter.11ty.js new file mode 100644 index 000000000..03f4d1694 --- /dev/null +++ b/test/stubs/class-async-filter.11ty.js @@ -0,0 +1,17 @@ +class Test { + static returnsTed() { + return "Ted"; + } + + returnsBill() { + return "Bill"; + } + + async render({ name }) { + return Promise.resolve( + `

${this.upper(name)}${this.returnsBill()}${Test.returnsTed()}

` + ); + } +} + +module.exports = Test; diff --git a/test/stubs/class-async.11ty.js b/test/stubs/class-async.11ty.js new file mode 100644 index 000000000..1524ff3ae --- /dev/null +++ b/test/stubs/class-async.11ty.js @@ -0,0 +1,7 @@ +class Test { + async render({ name }) { + return Promise.resolve(`

${name}

`); + } +} + +module.exports = Test; diff --git a/test/stubs/class-buffer.11ty.js b/test/stubs/class-buffer.11ty.js new file mode 100644 index 000000000..cae985d63 --- /dev/null +++ b/test/stubs/class-buffer.11ty.js @@ -0,0 +1,17 @@ +class Test { + returnsBill() { + return "Bill"; + } + + static returnsTed() { + return "Ted"; + } + + render({ name }) { + return Buffer.from( + `

${name}${this.returnsBill()}${Test.returnsTed()}

` + ); + } +} + +module.exports = Test; diff --git a/test/stubs/class-data-filter.11ty.js b/test/stubs/class-data-filter.11ty.js new file mode 100644 index 000000000..6a747c5c2 --- /dev/null +++ b/test/stubs/class-data-filter.11ty.js @@ -0,0 +1,13 @@ +class Test { + get data() { + return { + name: "Ted" + }; + } + + render({ name }) { + return `

${this.upper(name)}

`; + } +} + +module.exports = Test; diff --git a/test/stubs/class-data-fn-filter.11ty.js b/test/stubs/class-data-fn-filter.11ty.js new file mode 100644 index 000000000..cc4bf4d7d --- /dev/null +++ b/test/stubs/class-data-fn-filter.11ty.js @@ -0,0 +1,13 @@ +class Test { + data() { + return { + name: "Ted" + }; + } + + render({ name }) { + return `

${this.upper(name)}

`; + } +} + +module.exports = Test; diff --git a/test/stubs/class-data-fn-shorthand.11ty.js b/test/stubs/class-data-fn-shorthand.11ty.js new file mode 100644 index 000000000..e041478bf --- /dev/null +++ b/test/stubs/class-data-fn-shorthand.11ty.js @@ -0,0 +1,30 @@ +function Test() {} + +// this doesnā€™t return an object?? šŸ¤” +// Test.prototype.data = () => { name: "Ted" }; +Test.prototype.data = () => { + return { name: "Ted" }; +}; +Test.prototype.render = ({ name }) => `

${name}

`; + +/* +Test.prototype.data = function() { + return { name: "Ted" }; +}; +Test.prototype.render = function(data) { + return `

${data.name}

`; +} +*/ + +/* +// this isnā€™t valid syntax?? šŸ¤” +class Test { + data() => { + name: "Ted" + }; + + render({ name }) => `

${name}

`; +} +*/ + +module.exports = Test; diff --git a/test/stubs/class-data-fn.11ty.js b/test/stubs/class-data-fn.11ty.js new file mode 100644 index 000000000..adbe5c422 --- /dev/null +++ b/test/stubs/class-data-fn.11ty.js @@ -0,0 +1,13 @@ +class Test { + data() { + return { + name: "Ted" + }; + } + + render({ name }) { + return `

${name}

`; + } +} + +module.exports = Test; diff --git a/test/stubs/class-data-permalink-async-fn.11ty.js b/test/stubs/class-data-permalink-async-fn.11ty.js new file mode 100644 index 000000000..17a3d3467 --- /dev/null +++ b/test/stubs/class-data-permalink-async-fn.11ty.js @@ -0,0 +1,20 @@ +class Test { + get data() { + return { + key: "value1", + permalink: async function(data) { + return new Promise((resolve, reject) => { + setTimeout(function() { + resolve(`/my-permalink/${data.key}/`); + }, 100); + }); + } + }; + } + + render({ name }) { + return `

${name}

`; + } +} + +module.exports = Test; diff --git a/test/stubs/class-data-permalink-buffer.11ty.js b/test/stubs/class-data-permalink-buffer.11ty.js new file mode 100644 index 000000000..b4a58e67a --- /dev/null +++ b/test/stubs/class-data-permalink-buffer.11ty.js @@ -0,0 +1,13 @@ +class Test { + get data() { + return { + permalink: Buffer.from("/my-permalink/") + }; + } + + render({ name }) { + return `

${name}

`; + } +} + +module.exports = Test; diff --git a/test/stubs/class-data-permalink-fn-buffer.11ty.js b/test/stubs/class-data-permalink-fn-buffer.11ty.js new file mode 100644 index 000000000..98fbbbfc1 --- /dev/null +++ b/test/stubs/class-data-permalink-fn-buffer.11ty.js @@ -0,0 +1,14 @@ +class Test { + get data() { + return { + key: "value1", + permalink: data => Buffer.from(`/my-permalink/${data.key}/`) + }; + } + + render({ name }) { + return `

${name}

`; + } +} + +module.exports = Test; diff --git a/test/stubs/class-data-permalink-fn-filter.11ty.js b/test/stubs/class-data-permalink-fn-filter.11ty.js new file mode 100644 index 000000000..7d4b4bc55 --- /dev/null +++ b/test/stubs/class-data-permalink-fn-filter.11ty.js @@ -0,0 +1,16 @@ +class Test { + get data() { + return { + title: "My Super Cool Title", + permalink: function({ title }) { + return `/my-permalink/${this.slug(title)}/`; + } + }; + } + + render({ name }) { + return `

${name}

`; + } +} + +module.exports = Test; diff --git a/test/stubs/class-data-permalink-fn.11ty.js b/test/stubs/class-data-permalink-fn.11ty.js new file mode 100644 index 000000000..f92ac4785 --- /dev/null +++ b/test/stubs/class-data-permalink-fn.11ty.js @@ -0,0 +1,14 @@ +class Test { + get data() { + return { + key: "value1", + permalink: data => `/my-permalink/${data.key}/` + }; + } + + render({ name }) { + return `

${name}

`; + } +} + +module.exports = Test; diff --git a/test/stubs/class-data-permalink.11ty.js b/test/stubs/class-data-permalink.11ty.js new file mode 100644 index 000000000..c1005dbcc --- /dev/null +++ b/test/stubs/class-data-permalink.11ty.js @@ -0,0 +1,13 @@ +class Test { + get data() { + return { + permalink: "/my-permalink/" + }; + } + + render({ name }) { + return `

${name}

`; + } +} + +module.exports = Test; diff --git a/test/stubs/class-data-renderdata.11ty.js b/test/stubs/class-data-renderdata.11ty.js new file mode 100644 index 000000000..570770017 --- /dev/null +++ b/test/stubs/class-data-renderdata.11ty.js @@ -0,0 +1,24 @@ +class Test { + data() { + return { + name: "Zach", + otherFn: function() { + return "Thanos"; + }, + renderData: { + str: `StringTest`, + test: function({ name }) { + return `howdy ${name}`; + } + } + }; + } + + render(data) { + return `

${data.renderData.str}${ + data.renderData.test + }, meet ${data.otherFn()}

`; + } +} + +module.exports = Test; diff --git a/test/stubs/class-data.11ty.js b/test/stubs/class-data.11ty.js new file mode 100644 index 000000000..d7f5f0d73 --- /dev/null +++ b/test/stubs/class-data.11ty.js @@ -0,0 +1,13 @@ +class Test { + get data() { + return { + name: "Ted" + }; + } + + render({ name }) { + return `

${name}

`; + } +} + +module.exports = Test; diff --git a/test/stubs/class-filter.11ty.js b/test/stubs/class-filter.11ty.js new file mode 100644 index 000000000..78467e4c7 --- /dev/null +++ b/test/stubs/class-filter.11ty.js @@ -0,0 +1,17 @@ +class Test { + static returnsTed() { + return "Ted"; + } + + returnsBill() { + return "Bill"; + } + + render({ name }) { + return `

${this.upper( + name + )}${this.returnsBill()}${Test.returnsTed()}

`; + } +} + +module.exports = Test; diff --git a/test/stubs/class-norender.11ty.js b/test/stubs/class-norender.11ty.js new file mode 100644 index 000000000..a6962bb44 --- /dev/null +++ b/test/stubs/class-norender.11ty.js @@ -0,0 +1,9 @@ +class Test { + data() { + return { + name: "Ted" + }; + } +} + +module.exports = Test; diff --git a/test/stubs/class-with-dep-upstream.js b/test/stubs/class-with-dep-upstream.js new file mode 100644 index 000000000..0a55f349a --- /dev/null +++ b/test/stubs/class-with-dep-upstream.js @@ -0,0 +1 @@ +module.exports = function() {}; diff --git a/test/stubs/class-with-dep.11ty.js b/test/stubs/class-with-dep.11ty.js new file mode 100644 index 000000000..2942a8a6d --- /dev/null +++ b/test/stubs/class-with-dep.11ty.js @@ -0,0 +1,17 @@ +const Dep = require("./class-with-dep-upstream.js"); + +class Test { + returnsBill() { + return "Bill"; + } + + static returnsTed() { + return "Ted"; + } + + render({ name }) { + return `

${name}${this.returnsBill()}${Test.returnsTed()}

`; + } +} + +module.exports = Test; diff --git a/test/stubs/class.11ty.js b/test/stubs/class.11ty.js new file mode 100644 index 000000000..dca544087 --- /dev/null +++ b/test/stubs/class.11ty.js @@ -0,0 +1,15 @@ +class Test { + returnsBill() { + return "Bill"; + } + + static returnsTed() { + return "Ted"; + } + + render({ name }) { + return `

${name}${this.returnsBill()}${Test.returnsTed()}

`; + } +} + +module.exports = Test; diff --git a/test/stubs/classfields-data.11ty.js b/test/stubs/classfields-data.11ty.js new file mode 100644 index 000000000..78f93f73c --- /dev/null +++ b/test/stubs/classfields-data.11ty.js @@ -0,0 +1,11 @@ +class Test { + data = { + name: "Ted" + }; + + render({ name }) { + return `

${name}

`; + } +} + +module.exports = Test; diff --git a/test/stubs/collection-layout-wrap.njk b/test/stubs/collection-layout-wrap.njk new file mode 100644 index 000000000..50c27ec5e --- /dev/null +++ b/test/stubs/collection-layout-wrap.njk @@ -0,0 +1,5 @@ +--- +title: Layout Test +layout: layouts/div-wrapper-layout.njk +--- +{{ title }} \ No newline at end of file diff --git a/test/stubs/collection-layout/_includes/layout.ejs b/test/stubs/collection-layout/_includes/layout.ejs new file mode 100644 index 000000000..80c8f97ce --- /dev/null +++ b/test/stubs/collection-layout/_includes/layout.ejs @@ -0,0 +1,6 @@ +Layout + +<%- content %> + +All <%= collections.all.length %> templates +Layout <%= collections.dog.length %> dog \ No newline at end of file diff --git a/test/stubs/collection-layout/dog1.ejs b/test/stubs/collection-layout/dog1.ejs new file mode 100644 index 000000000..b66839343 --- /dev/null +++ b/test/stubs/collection-layout/dog1.ejs @@ -0,0 +1,4 @@ +--- +tags: + - dog +--- \ No newline at end of file diff --git a/test/stubs/collection-layout/template.ejs b/test/stubs/collection-layout/template.ejs new file mode 100644 index 000000000..9e8ba1f38 --- /dev/null +++ b/test/stubs/collection-layout/template.ejs @@ -0,0 +1,4 @@ +--- +layout: layout.ejs +--- +Template \ No newline at end of file diff --git a/test/stubs/collection-renderdata/dog.njk b/test/stubs/collection-renderdata/dog.njk new file mode 100644 index 000000000..58c75c8be --- /dev/null +++ b/test/stubs/collection-renderdata/dog.njk @@ -0,0 +1,7 @@ +--- +key1: value1 +renderData: + key2: value2-{{ key1 }}.css +tags: + - dog +--- \ No newline at end of file diff --git a/test/stubs/collection-renderdata/template.njk b/test/stubs/collection-renderdata/template.njk new file mode 100644 index 000000000..3196f7ff4 --- /dev/null +++ b/test/stubs/collection-renderdata/template.njk @@ -0,0 +1 @@ +{% for post in collections.dog %}{{ post.data.renderData.key2 }}{% endfor %} \ No newline at end of file diff --git a/test/stubs/collection-slug/dog1.njk b/test/stubs/collection-slug/dog1.njk new file mode 100644 index 000000000..b66839343 --- /dev/null +++ b/test/stubs/collection-slug/dog1.njk @@ -0,0 +1,4 @@ +--- +tags: + - dog +--- \ No newline at end of file diff --git a/test/stubs/collection-slug/template.njk b/test/stubs/collection-slug/template.njk new file mode 100644 index 000000000..222e76962 --- /dev/null +++ b/test/stubs/collection-slug/template.njk @@ -0,0 +1 @@ +fileSlug:{% for post in collections.dog %}{{ post.url }}:{{ post.fileSlug }}{% endfor %} \ No newline at end of file diff --git a/test/stubs/collection-template/_includes/layout.ejs b/test/stubs/collection-template/_includes/layout.ejs new file mode 100644 index 000000000..ef9a6ab92 --- /dev/null +++ b/test/stubs/collection-template/_includes/layout.ejs @@ -0,0 +1,3 @@ +Layout + +<%- content %> \ No newline at end of file diff --git a/test/stubs/collection-template/dog1.ejs b/test/stubs/collection-template/dog1.ejs new file mode 100644 index 000000000..b66839343 --- /dev/null +++ b/test/stubs/collection-template/dog1.ejs @@ -0,0 +1,4 @@ +--- +tags: + - dog +--- \ No newline at end of file diff --git a/test/stubs/collection-template/template.ejs b/test/stubs/collection-template/template.ejs new file mode 100644 index 000000000..cdb900434 --- /dev/null +++ b/test/stubs/collection-template/template.ejs @@ -0,0 +1,7 @@ +--- +layout: layout.ejs +--- +Template + +All <%= collections.all.length %> templates +Template <%= collections.dog.length %> dog \ No newline at end of file diff --git a/test/stubs/collection/test1.md b/test/stubs/collection/test1.md new file mode 100644 index 000000000..f8f5da2c5 --- /dev/null +++ b/test/stubs/collection/test1.md @@ -0,0 +1,8 @@ +--- +title: Test Title +tags: + - post + - dog +--- + +# Test 1 diff --git a/test/stubs/collection/test2.md b/test/stubs/collection/test2.md new file mode 100644 index 000000000..53aed0eda --- /dev/null +++ b/test/stubs/collection/test2.md @@ -0,0 +1,5 @@ +--- +tags: cat +--- + +# Test 2 diff --git a/test/stubs/collection/test3.md b/test/stubs/collection/test3.md new file mode 100644 index 000000000..551d56c59 --- /dev/null +++ b/test/stubs/collection/test3.md @@ -0,0 +1,7 @@ +--- +tags: + - post + - cat +--- + +# Test 3 diff --git a/test/stubs/collection/test4.md b/test/stubs/collection/test4.md new file mode 100644 index 000000000..decff8532 --- /dev/null +++ b/test/stubs/collection/test4.md @@ -0,0 +1,5 @@ +--- +date: 1983-01-01 +--- + +# Test 3 diff --git a/test/stubs/collection/test5.md b/test/stubs/collection/test5.md new file mode 100644 index 000000000..7d3ee6905 --- /dev/null +++ b/test/stubs/collection/test5.md @@ -0,0 +1,5 @@ +--- +date: 2020-01-01 +--- + +# Test 3 diff --git a/test/stubs/collection/test6.html b/test/stubs/collection/test6.html new file mode 100644 index 000000000..6811c3a0a --- /dev/null +++ b/test/stubs/collection/test6.html @@ -0,0 +1 @@ +# Test 6 diff --git a/test/stubs/collection/test7.njk b/test/stubs/collection/test7.njk new file mode 100644 index 000000000..5a0ac4e9a --- /dev/null +++ b/test/stubs/collection/test7.njk @@ -0,0 +1 @@ +# Test 7 \ No newline at end of file diff --git a/test/stubs/collection2/test1.md b/test/stubs/collection2/test1.md new file mode 100644 index 000000000..dcd36bd40 --- /dev/null +++ b/test/stubs/collection2/test1.md @@ -0,0 +1,9 @@ +--- +title: Test Title +tags: + - post + - dog +date: 2009-01-01 +--- + +# Test 1 diff --git a/test/stubs/collection2/test2.md b/test/stubs/collection2/test2.md new file mode 100644 index 000000000..a40261708 --- /dev/null +++ b/test/stubs/collection2/test2.md @@ -0,0 +1,8 @@ +--- +tags: + - post + - cat +date: 2010-01-01 +--- + +# Test 2 diff --git a/test/stubs/component-async/component.11tydata.js b/test/stubs/component-async/component.11tydata.js new file mode 100644 index 000000000..cc248eaf1 --- /dev/null +++ b/test/stubs/component-async/component.11tydata.js @@ -0,0 +1,9 @@ +module.exports = async function() { + return new Promise(resolve => { + setTimeout(function() { + resolve({ + localdatakeyfromjs: "howdydoody" + }); + }, 1); + }); +}; diff --git a/test/stubs/component-async/component.njk b/test/stubs/component-async/component.njk new file mode 100644 index 000000000..e52e6bb8b --- /dev/null +++ b/test/stubs/component-async/component.njk @@ -0,0 +1 @@ +{{localdatakey1}} \ No newline at end of file diff --git a/test/stubs/component/component.11tydata.js b/test/stubs/component/component.11tydata.js new file mode 100644 index 000000000..88f198b73 --- /dev/null +++ b/test/stubs/component/component.11tydata.js @@ -0,0 +1,5 @@ +const dep2 = require("../deps/dep2"); + +module.exports = { + localdatakeyfromjs: "howdydoody" +}; diff --git a/test/stubs/component/component.11tydata.json b/test/stubs/component/component.11tydata.json new file mode 100644 index 000000000..d0d637365 --- /dev/null +++ b/test/stubs/component/component.11tydata.json @@ -0,0 +1,4 @@ +{ + "localdatakeyfromjs": "this_is_overridden", + "localdatakeyfromjs2": "howdy2" +} \ No newline at end of file diff --git a/test/stubs/component/component.json b/test/stubs/component/component.json index 856c116de..68bc91710 100644 --- a/test/stubs/component/component.json +++ b/test/stubs/component/component.json @@ -1,3 +1,4 @@ { - "localdatakey1": "localdatavalue1" + "localdatakey1": "localdatavalue1", + "localdatakeyfromjs": "this_is_also_overridden" } diff --git a/test/stubs/config-deps-upstream.js b/test/stubs/config-deps-upstream.js new file mode 100644 index 000000000..0a55f349a --- /dev/null +++ b/test/stubs/config-deps-upstream.js @@ -0,0 +1 @@ +module.exports = function() {}; diff --git a/test/stubs/config-deps.js b/test/stubs/config-deps.js new file mode 100644 index 000000000..dd3ac6fd6 --- /dev/null +++ b/test/stubs/config-deps.js @@ -0,0 +1,6 @@ +const pretty = require("pretty"); +const Dep = require("./config-deps-upstream"); + +module.exports = function(config) { + return {}; +}; diff --git a/test/stubs/config-promise.js b/test/stubs/config-promise.js new file mode 100644 index 000000000..57ea37262 --- /dev/null +++ b/test/stubs/config-promise.js @@ -0,0 +1,5 @@ +module.exports = async function() { + return { + layouts: "promise" + }; +}; diff --git a/test/stubs/config.js b/test/stubs/config.js index b1324ddf9..142863b40 100644 --- a/test/stubs/config.js +++ b/test/stubs/config.js @@ -1,20 +1,37 @@ const pretty = require("pretty"); -module.exports = { - markdownTemplateEngine: "ejs", - templateFormats: ["md", "njk"], - keys: { - package: "pkg2" - }, - filters: { - prettyHtml: function(str, outputPath) { - // todo check if HTML output before transforming - return pretty(str, { ocd: true }); - } - }, - nunjucksFilters: { - testing: str => { - return str; +module.exports = function(config) { + /* { + template, + inputPath, + outputPath, + url, + data, + date + } */ + + return { + markdownTemplateEngine: "ejs", + templateFormats: ["md", "njk"], + + pathPrefix: "/testdir", + + keys: { + package: "pkg2" + }, + filters: { + prettyHtml: function(str, outputPath) { + if (outputPath.split(".").pop() === "html") { + return pretty(str, { ocd: true }); + } else { + return str; + } + } + }, + nunjucksFilters: { + testing: str => { + return str; + } } - } + }; }; diff --git a/test/stubs/custom-frontmatter/template-excerpt-comment.njk b/test/stubs/custom-frontmatter/template-excerpt-comment.njk new file mode 100644 index 000000000..b910f64c7 --- /dev/null +++ b/test/stubs/custom-frontmatter/template-excerpt-comment.njk @@ -0,0 +1,6 @@ +--- +front: hello +--- +This is an excerpt. + +This is content. \ No newline at end of file diff --git a/test/stubs/custom-frontmatter/template-newline1.njk b/test/stubs/custom-frontmatter/template-newline1.njk new file mode 100644 index 000000000..d1dc9859b --- /dev/null +++ b/test/stubs/custom-frontmatter/template-newline1.njk @@ -0,0 +1,5 @@ +--- +front: hello +--- +This is an excerpt.--- +This is content. \ No newline at end of file diff --git a/test/stubs/custom-frontmatter/template-newline2.njk b/test/stubs/custom-frontmatter/template-newline2.njk new file mode 100644 index 000000000..35bf7684a --- /dev/null +++ b/test/stubs/custom-frontmatter/template-newline2.njk @@ -0,0 +1,4 @@ +--- +front: hello +--- +This is an excerpt.---This is content. \ No newline at end of file diff --git a/test/stubs/custom-frontmatter/template-newline3.njk b/test/stubs/custom-frontmatter/template-newline3.njk new file mode 100644 index 000000000..2e56abe47 --- /dev/null +++ b/test/stubs/custom-frontmatter/template-newline3.njk @@ -0,0 +1,5 @@ +--- +front: hello +--- +This is an excerpt. +---This is content. \ No newline at end of file diff --git a/test/stubs/custom-frontmatter/template-nonewline.njk b/test/stubs/custom-frontmatter/template-nonewline.njk new file mode 100644 index 000000000..2e56abe47 --- /dev/null +++ b/test/stubs/custom-frontmatter/template-nonewline.njk @@ -0,0 +1,5 @@ +--- +front: hello +--- +This is an excerpt. +---This is content. \ No newline at end of file diff --git a/test/stubs/custom-frontmatter/template-toml.njk b/test/stubs/custom-frontmatter/template-toml.njk new file mode 100644 index 000000000..21a1d3281 --- /dev/null +++ b/test/stubs/custom-frontmatter/template-toml.njk @@ -0,0 +1,4 @@ +---toml +front = "hello" +--- +This is content. \ No newline at end of file diff --git a/test/stubs/custom-frontmatter/template.njk b/test/stubs/custom-frontmatter/template.njk new file mode 100644 index 000000000..e83013a2d --- /dev/null +++ b/test/stubs/custom-frontmatter/template.njk @@ -0,0 +1,6 @@ +--- +front: hello +--- +This is an excerpt. +--- +This is content. \ No newline at end of file diff --git a/test/stubs/data-cascade/template.11tydata.js b/test/stubs/data-cascade/template.11tydata.js new file mode 100644 index 000000000..c28b80b76 --- /dev/null +++ b/test/stubs/data-cascade/template.11tydata.js @@ -0,0 +1,8 @@ +module.exports = { + parent: { + child: 2, + datafile: true + }, + datafile: true, + tags: ["tagC", "tagD"] +}; diff --git a/test/stubs/data-cascade/template.njk b/test/stubs/data-cascade/template.njk new file mode 100644 index 000000000..4af8e9fa2 --- /dev/null +++ b/test/stubs/data-cascade/template.njk @@ -0,0 +1,9 @@ +--- +parent: + child: -2 + frontmatter: true +frontmatter: true +tags: + - tagA + - tagB +--- \ No newline at end of file diff --git a/test/stubs/datafiledoesnotexist/template.njk b/test/stubs/datafiledoesnotexist/template.njk new file mode 100644 index 000000000..e69de29bb diff --git a/test/stubs/dates/2018-01-01-file5.md b/test/stubs/dates/2018-01-01-file5.md new file mode 100644 index 000000000..188979947 --- /dev/null +++ b/test/stubs/dates/2018-01-01-file5.md @@ -0,0 +1,3 @@ +--- +tags: dateTestTag +--- diff --git a/test/stubs/dates/file1.md b/test/stubs/dates/file1.md new file mode 100644 index 000000000..22b80955b --- /dev/null +++ b/test/stubs/dates/file1.md @@ -0,0 +1,5 @@ +--- +tags: dateTestTag +--- + +Assume file created time. diff --git a/test/stubs/dates/file2.md b/test/stubs/dates/file2.md new file mode 100644 index 000000000..f41d018d9 --- /dev/null +++ b/test/stubs/dates/file2.md @@ -0,0 +1,4 @@ +--- +tags: dateTestTag +date: "2016-01-01" +--- diff --git a/test/stubs/dates/file2b.md b/test/stubs/dates/file2b.md new file mode 100644 index 000000000..d62d2e87b --- /dev/null +++ b/test/stubs/dates/file2b.md @@ -0,0 +1,4 @@ +--- +tags: dateTestTag +date: 2016-01-01 +--- diff --git a/test/stubs/dates/file3.md b/test/stubs/dates/file3.md new file mode 100644 index 000000000..12ec11f14 --- /dev/null +++ b/test/stubs/dates/file3.md @@ -0,0 +1,4 @@ +--- +tags: dateTestTag +date: Last Modified +--- diff --git a/test/stubs/dates/file4.md b/test/stubs/dates/file4.md new file mode 100644 index 000000000..fa924c055 --- /dev/null +++ b/test/stubs/dates/file4.md @@ -0,0 +1,4 @@ +--- +tags: dateTestTag +date: Created +--- diff --git a/test/stubs/dependencies/dep1.js b/test/stubs/dependencies/dep1.js new file mode 100644 index 000000000..0a55f349a --- /dev/null +++ b/test/stubs/dependencies/dep1.js @@ -0,0 +1 @@ +module.exports = function() {}; diff --git a/test/stubs/dependencies/dep2.js b/test/stubs/dependencies/dep2.js new file mode 100644 index 000000000..0a55f349a --- /dev/null +++ b/test/stubs/dependencies/dep2.js @@ -0,0 +1 @@ +module.exports = function() {}; diff --git a/test/stubs/dependencies/two-deps.11ty.js b/test/stubs/dependencies/two-deps.11ty.js new file mode 100644 index 000000000..5328fa26c --- /dev/null +++ b/test/stubs/dependencies/two-deps.11ty.js @@ -0,0 +1,2 @@ +const dep1 = require("./dep1"); +const dep2 = require("./dep2"); diff --git a/test/stubs/deps/dep1.js b/test/stubs/deps/dep1.js new file mode 100644 index 000000000..0a55f349a --- /dev/null +++ b/test/stubs/deps/dep1.js @@ -0,0 +1 @@ +module.exports = function() {}; diff --git a/test/stubs/deps/dep2.js b/test/stubs/deps/dep2.js new file mode 100644 index 000000000..0a55f349a --- /dev/null +++ b/test/stubs/deps/dep2.js @@ -0,0 +1 @@ +module.exports = function() {}; diff --git a/test/stubs/dynamic-permalink/test.njk b/test/stubs/dynamic-permalink/test.njk new file mode 100644 index 000000000..614a57103 --- /dev/null +++ b/test/stubs/dynamic-permalink/test.njk @@ -0,0 +1,4 @@ +--- +permalink: "/{{justastring}}/" +dynamicPermalink: false +--- \ No newline at end of file diff --git a/test/stubs/eleventyExcludeFromCollections.njk b/test/stubs/eleventyExcludeFromCollections.njk new file mode 100644 index 000000000..be2afde30 --- /dev/null +++ b/test/stubs/eleventyExcludeFromCollections.njk @@ -0,0 +1,8 @@ +--- +title: Paged Test +eleventyExcludeFromCollections: true +tags: + - post + - dog +--- +{{ title }} \ No newline at end of file diff --git a/test/stubs/exitCode/failure.njk b/test/stubs/exitCode/failure.njk new file mode 100644 index 000000000..617ee6df8 --- /dev/null +++ b/test/stubs/exitCode/failure.njk @@ -0,0 +1 @@ +{{ test() }} \ No newline at end of file diff --git a/test/stubs/exports-flatdata.11ty.js b/test/stubs/exports-flatdata.11ty.js new file mode 100644 index 000000000..fd0c7d253 --- /dev/null +++ b/test/stubs/exports-flatdata.11ty.js @@ -0,0 +1,6 @@ +// This is invalid, data must be an object +exports.data = "Ted"; + +exports.render = function(name) { + return `

${JSON.stringify(name)}

`; +}; diff --git a/test/stubs/fileslug.11ty.js b/test/stubs/fileslug.11ty.js new file mode 100644 index 000000000..6ea439875 --- /dev/null +++ b/test/stubs/fileslug.11ty.js @@ -0,0 +1,3 @@ +module.exports = function(data) { + return `

${data.page.fileSlug}

`; +}; diff --git a/test/stubs/firstdir/seconddir/component.njk b/test/stubs/firstdir/seconddir/component.njk new file mode 100644 index 000000000..e69de29bb diff --git a/test/stubs/frontmatter-date/test.liquid b/test/stubs/frontmatter-date/test.liquid new file mode 100644 index 000000000..87273b6b3 --- /dev/null +++ b/test/stubs/frontmatter-date/test.liquid @@ -0,0 +1,4 @@ +--- +mydate: 2009-04-15T12:34:34+01:00 +--- +{{ mydate | date: "%Y-%m-%d" }} \ No newline at end of file diff --git a/test/stubs/frontmatter-date/test.njk b/test/stubs/frontmatter-date/test.njk new file mode 100644 index 000000000..37a41b992 --- /dev/null +++ b/test/stubs/frontmatter-date/test.njk @@ -0,0 +1,4 @@ +--- +mydate: 2009-04-15T01:34:34+01:00 +--- +{{ mydate.toISOString() }} \ No newline at end of file diff --git a/test/stubs/function-arrow.11ty.js b/test/stubs/function-arrow.11ty.js new file mode 100644 index 000000000..02a6a2c4a --- /dev/null +++ b/test/stubs/function-arrow.11ty.js @@ -0,0 +1 @@ +module.exports = ({ name }) => `

${name}

`; diff --git a/test/stubs/function-async.11ty.js b/test/stubs/function-async.11ty.js new file mode 100644 index 000000000..5581a999f --- /dev/null +++ b/test/stubs/function-async.11ty.js @@ -0,0 +1,7 @@ +module.exports = async function(data) { + return new Promise((resolve, reject) => { + setTimeout(function() { + resolve(`

${data.name}

`); + }, 100); + }); +}; diff --git a/test/stubs/function-buffer.11ty.js b/test/stubs/function-buffer.11ty.js new file mode 100644 index 000000000..ba5c891ef --- /dev/null +++ b/test/stubs/function-buffer.11ty.js @@ -0,0 +1,3 @@ +module.exports = function(data) { + return Buffer.from(`

${data.name}

`); +}; diff --git a/test/stubs/function-filter.11ty.js b/test/stubs/function-filter.11ty.js new file mode 100644 index 000000000..e9db5bf42 --- /dev/null +++ b/test/stubs/function-filter.11ty.js @@ -0,0 +1,9 @@ +function myFunction({ name }) { + return `

${this.upper(name)}${myFunction.staticMethod()}

`; +} + +myFunction.staticMethod = function() { + return "T9000"; +}; + +module.exports = myFunction; diff --git a/test/stubs/function-markdown.11ty.js b/test/stubs/function-markdown.11ty.js new file mode 100644 index 000000000..e9db22752 --- /dev/null +++ b/test/stubs/function-markdown.11ty.js @@ -0,0 +1,3 @@ +module.exports = function(data) { + return `# ${data.name}`; +}; diff --git a/test/stubs/function-prototype.11ty.js b/test/stubs/function-prototype.11ty.js new file mode 100644 index 000000000..c9304a4db --- /dev/null +++ b/test/stubs/function-prototype.11ty.js @@ -0,0 +1,17 @@ +function myFunction() {} + +myFunction.prototype.render = function({ name }) { + return `

${this.upper( + name + )}${this.returnsBill()}${myFunction.staticMethod()}

`; +}; + +myFunction.prototype.returnsBill = function() { + return "Bill"; +}; + +myFunction.staticMethod = function() { + return "T9001"; +}; + +module.exports = myFunction; diff --git a/test/stubs/function.11ty.js b/test/stubs/function.11ty.js new file mode 100644 index 000000000..f0cb12ca5 --- /dev/null +++ b/test/stubs/function.11ty.js @@ -0,0 +1,3 @@ +module.exports = function(data) { + return `

${data.name}

`; +}; diff --git a/test/stubs/glob-pages/about.md b/test/stubs/glob-pages/about.md new file mode 100644 index 000000000..8f3542437 --- /dev/null +++ b/test/stubs/glob-pages/about.md @@ -0,0 +1,5 @@ +--- +title: "About" +--- + +About diff --git a/test/stubs/glob-pages/contact.md b/test/stubs/glob-pages/contact.md new file mode 100644 index 000000000..c45882275 --- /dev/null +++ b/test/stubs/glob-pages/contact.md @@ -0,0 +1,5 @@ +--- +title: "Contact" +--- + +Contact diff --git a/test/stubs/glob-pages/home.md b/test/stubs/glob-pages/home.md new file mode 100644 index 000000000..277823f4b --- /dev/null +++ b/test/stubs/glob-pages/home.md @@ -0,0 +1,5 @@ +--- +title: "Home" +--- + +Home diff --git a/test/stubs/global-dash-variable.liquid b/test/stubs/global-dash-variable.liquid new file mode 100644 index 000000000..c7c17fa3a --- /dev/null +++ b/test/stubs/global-dash-variable.liquid @@ -0,0 +1,4 @@ +--- +is-it-tasty: Yes +--- +{{ is-it-tasty }} \ No newline at end of file diff --git a/test/stubs/globby/_includes/include.html b/test/stubs/globby/_includes/include.html new file mode 100644 index 000000000..e69de29bb diff --git a/test/stubs/globby/test.html b/test/stubs/globby/test.html new file mode 100644 index 000000000..e69de29bb diff --git a/test/stubs/ignore1/ignoredFolder/ignored.md b/test/stubs/ignore1/ignoredFolder/ignored.md new file mode 100644 index 000000000..16ab01333 --- /dev/null +++ b/test/stubs/ignore1/ignoredFolder/ignored.md @@ -0,0 +1 @@ +# This should be ignored diff --git a/test/stubs/ignore2/.gitignore b/test/stubs/ignore2/.gitignore new file mode 100644 index 000000000..036fc82c7 --- /dev/null +++ b/test/stubs/ignore2/.gitignore @@ -0,0 +1 @@ +thisshouldnotexist12345 \ No newline at end of file diff --git a/test/stubs/ignore2/ignoredFolder/ignored.md b/test/stubs/ignore2/ignoredFolder/ignored.md new file mode 100644 index 000000000..16ab01333 --- /dev/null +++ b/test/stubs/ignore2/ignoredFolder/ignored.md @@ -0,0 +1 @@ +# This should be ignored diff --git a/test/stubs/ignore3/.eleventyignore b/test/stubs/ignore3/.eleventyignore new file mode 100644 index 000000000..de22e21c5 --- /dev/null +++ b/test/stubs/ignore3/.eleventyignore @@ -0,0 +1,3 @@ +ignoredFolder +./ignoredFolder/ignored.md +# This is a comment diff --git a/test/stubs/ignore3/ignoredFolder/ignored.md b/test/stubs/ignore3/ignoredFolder/ignored.md new file mode 100644 index 000000000..16ab01333 --- /dev/null +++ b/test/stubs/ignore3/ignoredFolder/ignored.md @@ -0,0 +1 @@ +# This should be ignored diff --git a/test/stubs/ignore4/.eleventyignore b/test/stubs/ignore4/.eleventyignore new file mode 100644 index 000000000..de22e21c5 --- /dev/null +++ b/test/stubs/ignore4/.eleventyignore @@ -0,0 +1,3 @@ +ignoredFolder +./ignoredFolder/ignored.md +# This is a comment diff --git a/test/stubs/ignore4/.gitignore b/test/stubs/ignore4/.gitignore new file mode 100644 index 000000000..036fc82c7 --- /dev/null +++ b/test/stubs/ignore4/.gitignore @@ -0,0 +1 @@ +thisshouldnotexist12345 \ No newline at end of file diff --git a/test/stubs/ignore4/ignoredFolder/ignored.md b/test/stubs/ignore4/ignoredFolder/ignored.md new file mode 100644 index 000000000..16ab01333 --- /dev/null +++ b/test/stubs/ignore4/ignoredFolder/ignored.md @@ -0,0 +1 @@ +# This should be ignored diff --git a/test/stubs/ignore5/.gitignore b/test/stubs/ignore5/.gitignore new file mode 100644 index 000000000..e69de29bb diff --git a/test/stubs/ignore5/ignoredFolder/ignored.md b/test/stubs/ignore5/ignoredFolder/ignored.md new file mode 100644 index 000000000..16ab01333 --- /dev/null +++ b/test/stubs/ignore5/ignoredFolder/ignored.md @@ -0,0 +1 @@ +# This should be ignored diff --git a/test/stubs/ignore6/.eleventyignore b/test/stubs/ignore6/.eleventyignore new file mode 100644 index 000000000..de22e21c5 --- /dev/null +++ b/test/stubs/ignore6/.eleventyignore @@ -0,0 +1,3 @@ +ignoredFolder +./ignoredFolder/ignored.md +# This is a comment diff --git a/test/stubs/ignore6/.gitignore b/test/stubs/ignore6/.gitignore new file mode 100644 index 000000000..e69de29bb diff --git a/test/stubs/ignore6/ignoredFolder/ignored.md b/test/stubs/ignore6/ignoredFolder/ignored.md new file mode 100644 index 000000000..16ab01333 --- /dev/null +++ b/test/stubs/ignore6/ignoredFolder/ignored.md @@ -0,0 +1 @@ +# This should be ignored diff --git a/test/stubs/ignore7/.gitignore b/test/stubs/ignore7/.gitignore new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/test/stubs/ignore7/.gitignore @@ -0,0 +1 @@ + diff --git a/test/stubs/ignore7/ignoredFolder/ignored.md b/test/stubs/ignore7/ignoredFolder/ignored.md new file mode 100644 index 000000000..16ab01333 --- /dev/null +++ b/test/stubs/ignore7/ignoredFolder/ignored.md @@ -0,0 +1 @@ +# This should be ignored diff --git a/test/stubs/ignore8/.eleventyignore b/test/stubs/ignore8/.eleventyignore new file mode 100644 index 000000000..de22e21c5 --- /dev/null +++ b/test/stubs/ignore8/.eleventyignore @@ -0,0 +1,3 @@ +ignoredFolder +./ignoredFolder/ignored.md +# This is a comment diff --git a/test/stubs/ignore8/.gitignore b/test/stubs/ignore8/.gitignore new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/test/stubs/ignore8/.gitignore @@ -0,0 +1 @@ + diff --git a/test/stubs/ignore8/ignoredFolder/ignored.md b/test/stubs/ignore8/ignoredFolder/ignored.md new file mode 100644 index 000000000..16ab01333 --- /dev/null +++ b/test/stubs/ignore8/ignoredFolder/ignored.md @@ -0,0 +1 @@ +# This should be ignored diff --git a/test/stubs/ignorelocalroot/.eleventyignore b/test/stubs/ignorelocalroot/.eleventyignore new file mode 100644 index 000000000..7545a50d7 --- /dev/null +++ b/test/stubs/ignorelocalroot/.eleventyignore @@ -0,0 +1 @@ +test.md \ No newline at end of file diff --git a/test/stubs/img/stub.md b/test/stubs/img/stub.md new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/test/stubs/img/stub.md @@ -0,0 +1 @@ + diff --git a/test/stubs/included.hbs b/test/stubs/included.hbs new file mode 100644 index 000000000..28f1f1852 --- /dev/null +++ b/test/stubs/included.hbs @@ -0,0 +1 @@ +This is an includdde. \ No newline at end of file diff --git a/test/stubs/included.liquid b/test/stubs/included.liquid new file mode 100644 index 000000000..29a495d22 --- /dev/null +++ b/test/stubs/included.liquid @@ -0,0 +1 @@ +This is not in the includes dir. \ No newline at end of file diff --git a/test/stubs/included.pug b/test/stubs/included.pug new file mode 100644 index 000000000..c4fd5a685 --- /dev/null +++ b/test/stubs/included.pug @@ -0,0 +1 @@ +span This is a relative include. \ No newline at end of file diff --git a/test/stubs/includedrelative.mustache b/test/stubs/includedrelative.mustache new file mode 100644 index 000000000..28f1f1852 --- /dev/null +++ b/test/stubs/includedrelative.mustache @@ -0,0 +1 @@ +This is an includdde. \ No newline at end of file diff --git a/test/stubs/includesemptystring.ejs b/test/stubs/includesemptystring.ejs new file mode 100644 index 000000000..e69de29bb diff --git a/test/stubs/index.ejs b/test/stubs/index.ejs new file mode 100644 index 000000000..e69de29bb diff --git a/test/stubs/issue-115/index-with-layout.liquid b/test/stubs/issue-115/index-with-layout.liquid new file mode 100644 index 000000000..8f0ce382b --- /dev/null +++ b/test/stubs/issue-115/index-with-layout.liquid @@ -0,0 +1,7 @@ +--- +layout: layouts/issue-115.liquid +title: My index page +pagination: + data: collections.foos + size: 12 +--- diff --git a/test/stubs/issue-115/index.liquid b/test/stubs/issue-115/index.liquid new file mode 100644 index 000000000..077d1c66d --- /dev/null +++ b/test/stubs/issue-115/index.liquid @@ -0,0 +1,12 @@ +--- +title: My index page +pagination: + data: collections.foos + size: 12 +--- +{% for foo in pagination.items -%} +{{ foo.data.title }} +{% endfor -%} +{% for bar in collections.bars -%} +{{ bar.data.title }} +{% endfor -%} \ No newline at end of file diff --git a/test/stubs/issue-115/template-bars.liquid b/test/stubs/issue-115/template-bars.liquid new file mode 100644 index 000000000..87f1ce825 --- /dev/null +++ b/test/stubs/issue-115/template-bars.liquid @@ -0,0 +1,6 @@ +--- +title: This page is bars +tags: + - bars +--- +Bars \ No newline at end of file diff --git a/test/stubs/issue-115/template-foos.liquid b/test/stubs/issue-115/template-foos.liquid new file mode 100644 index 000000000..a52f13f43 --- /dev/null +++ b/test/stubs/issue-115/template-foos.liquid @@ -0,0 +1,6 @@ +--- +title: This page is foos +tags: + - foos +--- +Foos. \ No newline at end of file diff --git a/test/stubs/issue-135/template.json b/test/stubs/issue-135/template.json new file mode 100644 index 000000000..9623fba33 --- /dev/null +++ b/test/stubs/issue-135/template.json @@ -0,0 +1,16 @@ +{ + "articles": + [ + { + "title": "Do you even paginate bro?", + "author": "Bill Ted", + "publish_date": "2018-06-22", + "tags": [ + "post" + ], + "image": "/url/thing", + "teaser": "Teaser copy", + "body": "

Raw HTML

" + } + ] +} diff --git a/test/stubs/issue-135/template.njk b/test/stubs/issue-135/template.njk new file mode 100644 index 000000000..3380d4340 --- /dev/null +++ b/test/stubs/issue-135/template.njk @@ -0,0 +1,8 @@ +--- +pagination: + data: articles + size: 1 + alias: article +permalink: blog/{{ article.title | slug }}/index.html +--- +{{ article.body | safe }} \ No newline at end of file diff --git a/test/stubs/issue-522/excluded.md b/test/stubs/issue-522/excluded.md new file mode 100644 index 000000000..a9bad7a5d --- /dev/null +++ b/test/stubs/issue-522/excluded.md @@ -0,0 +1,7 @@ +--- +eleventyExcludeFromCollections: true +--- + +# Test + +{{ collections.all[0].templateContent }} diff --git a/test/stubs/issue-522/template.md b/test/stubs/issue-522/template.md new file mode 100644 index 000000000..8ae056963 --- /dev/null +++ b/test/stubs/issue-522/template.md @@ -0,0 +1 @@ +# Test diff --git a/test/stubs/issue-95/cat.md b/test/stubs/issue-95/cat.md new file mode 100644 index 000000000..21e6dcc7e --- /dev/null +++ b/test/stubs/issue-95/cat.md @@ -0,0 +1,6 @@ +--- +tags: + - cat +--- + +# Test 8 diff --git a/test/stubs/issue-95/notacat.md b/test/stubs/issue-95/notacat.md new file mode 100644 index 000000000..e67ac877d --- /dev/null +++ b/test/stubs/issue-95/notacat.md @@ -0,0 +1,5 @@ +--- +tags: notacat +--- + +# Test 8 diff --git a/test/stubs/layout-permalink-difflang/_includes/test.njk b/test/stubs/layout-permalink-difflang/_includes/test.njk new file mode 100644 index 000000000..f6a0fa97a --- /dev/null +++ b/test/stubs/layout-permalink-difflang/_includes/test.njk @@ -0,0 +1,4 @@ +--- +permalink: "/{{ page.fileSlug }}/" +--- +{{ content }} \ No newline at end of file diff --git a/test/stubs/layout-permalink-difflang/test.md b/test/stubs/layout-permalink-difflang/test.md new file mode 100644 index 000000000..ceba37acd --- /dev/null +++ b/test/stubs/layout-permalink-difflang/test.md @@ -0,0 +1,6 @@ +--- +layout: test.njk +templateEngineOverride: md +--- + +# Title diff --git a/test/stubs/layout-relative.pug b/test/stubs/layout-relative.pug new file mode 100644 index 000000000..dc1969a8e --- /dev/null +++ b/test/stubs/layout-relative.pug @@ -0,0 +1,3 @@ +html + body + block content \ No newline at end of file diff --git a/test/stubs/layoutsemptystring.ejs b/test/stubs/layoutsemptystring.ejs new file mode 100644 index 000000000..e69de29bb diff --git a/test/stubs/local-data-tags/component.11tydata.js b/test/stubs/local-data-tags/component.11tydata.js new file mode 100644 index 000000000..69e9bfcc3 --- /dev/null +++ b/test/stubs/local-data-tags/component.11tydata.js @@ -0,0 +1,3 @@ +module.exports = { + tags: "tag3" +}; diff --git a/test/stubs/local-data-tags/component.njk b/test/stubs/local-data-tags/component.njk new file mode 100644 index 000000000..332cd748d --- /dev/null +++ b/test/stubs/local-data-tags/component.njk @@ -0,0 +1,6 @@ +--- +tags: + - tag1 + - tag2 +--- +{{localdatakey1}} \ No newline at end of file diff --git a/test/stubs/multiple-ignores/.eleventyignore b/test/stubs/multiple-ignores/.eleventyignore new file mode 100644 index 000000000..c29c21d84 --- /dev/null +++ b/test/stubs/multiple-ignores/.eleventyignore @@ -0,0 +1,3 @@ +ignoredFolder +./ignoredFolder/ignored.md +# This is a comment \ No newline at end of file diff --git a/test/stubs/multiple-ignores/ignoredFolder/ignored.md b/test/stubs/multiple-ignores/ignoredFolder/ignored.md new file mode 100644 index 000000000..e69de29bb diff --git a/test/stubs/multiple-ignores/subfolder/.eleventyignore b/test/stubs/multiple-ignores/subfolder/.eleventyignore new file mode 100644 index 000000000..30ddd6187 --- /dev/null +++ b/test/stubs/multiple-ignores/subfolder/.eleventyignore @@ -0,0 +1,3 @@ +ignoredFolder2 +./ignoredFolder2/ignored2.md +# This is a comment \ No newline at end of file diff --git a/test/stubs/multiple-ignores/subfolder/ignoredFolder2/ignored2.md b/test/stubs/multiple-ignores/subfolder/ignoredFolder2/ignored2.md new file mode 100644 index 000000000..e69de29bb diff --git a/test/stubs/multipleexports-promises.11ty.js b/test/stubs/multipleexports-promises.11ty.js new file mode 100644 index 000000000..995f7f4ad --- /dev/null +++ b/test/stubs/multipleexports-promises.11ty.js @@ -0,0 +1,15 @@ +exports.data = async function() { + return new Promise((resolve, reject) => { + setTimeout(function() { + resolve({ name: "Ted" }); + }, 100); + }); +}; + +exports.render = async function({ name }) { + return new Promise((resolve, reject) => { + setTimeout(function() { + resolve(`

${name}

`); + }, 100); + }); +}; diff --git a/test/stubs/multipleexports.11ty.js b/test/stubs/multipleexports.11ty.js new file mode 100644 index 000000000..28be6e7a8 --- /dev/null +++ b/test/stubs/multipleexports.11ty.js @@ -0,0 +1,7 @@ +exports.data = { + name: "Ted" +}; + +exports.render = function({ name }) { + return `

${name}

`; +}; diff --git a/test/stubs/njk-relative/dir/base.njk b/test/stubs/njk-relative/dir/base.njk new file mode 100644 index 000000000..f7fd25115 --- /dev/null +++ b/test/stubs/njk-relative/dir/base.njk @@ -0,0 +1 @@ +

{% block content %}This is a parent.{% endblock %}

\ No newline at end of file diff --git a/test/stubs/njk-relative/dir/imports.njk b/test/stubs/njk-relative/dir/imports.njk new file mode 100644 index 000000000..3039f1812 --- /dev/null +++ b/test/stubs/njk-relative/dir/imports.njk @@ -0,0 +1 @@ +{% macro label(text) %}{% endmacro %} \ No newline at end of file diff --git a/test/stubs/njk-relative/dir/included.njk b/test/stubs/njk-relative/dir/included.njk new file mode 100644 index 000000000..7a2836763 --- /dev/null +++ b/test/stubs/njk-relative/dir/included.njk @@ -0,0 +1 @@ +HELLO FROM THE OTHER SIDE. \ No newline at end of file diff --git a/test/stubs/njk-relative/dir/unique-include-123.njk b/test/stubs/njk-relative/dir/unique-include-123.njk new file mode 100644 index 000000000..7a2836763 --- /dev/null +++ b/test/stubs/njk-relative/dir/unique-include-123.njk @@ -0,0 +1 @@ +HELLO FROM THE OTHER SIDE. \ No newline at end of file diff --git a/test/stubs/object-norender.11ty.js b/test/stubs/object-norender.11ty.js new file mode 100644 index 000000000..b9a29f697 --- /dev/null +++ b/test/stubs/object-norender.11ty.js @@ -0,0 +1,5 @@ +module.exports = { + data: { + name: "Ted" + } +}; diff --git a/test/stubs/object.11ty.js b/test/stubs/object.11ty.js new file mode 100644 index 000000000..75ff39494 --- /dev/null +++ b/test/stubs/object.11ty.js @@ -0,0 +1,8 @@ +module.exports = { + data: { + name: "Ted" + }, + render: function({ name }) { + return `

${name}

`; + } +}; diff --git a/test/stubs/oneinstance.11ty.js b/test/stubs/oneinstance.11ty.js new file mode 100644 index 000000000..19c403d38 --- /dev/null +++ b/test/stubs/oneinstance.11ty.js @@ -0,0 +1,18 @@ +class Test { + constructor() { + this.rand = Math.random(); + } + + get data() { + return { + name: "Ted", + rand: this.rand + }; + } + + render({ name }) { + return `

${name}${this.rand}

`; + } +} + +module.exports = Test; diff --git a/test/stubs/overrides/layout.njk b/test/stubs/overrides/layout.njk new file mode 100644 index 000000000..b2ef6a93a --- /dev/null +++ b/test/stubs/overrides/layout.njk @@ -0,0 +1,6 @@ +--- +templateEngineOverride: ejs +title: My Title +layout: layouts/engineOverrides.njk +--- +

<%= title %>

\ No newline at end of file diff --git a/test/stubs/overrides/layoutfalse.njk b/test/stubs/overrides/layoutfalse.njk new file mode 100644 index 000000000..2fde43332 --- /dev/null +++ b/test/stubs/overrides/layoutfalse.njk @@ -0,0 +1,6 @@ +--- +templateEngineOverride: false +title: My Title +layout: layouts/engineOverrides.njk +--- +

<%= title %>

\ No newline at end of file diff --git a/test/stubs/overrides/test-bypass.md b/test/stubs/overrides/test-bypass.md new file mode 100644 index 000000000..9f09d4bf6 --- /dev/null +++ b/test/stubs/overrides/test-bypass.md @@ -0,0 +1,6 @@ +--- +templateEngineOverride: ejs +title: My Title +--- + +# <%= title %> diff --git a/test/stubs/overrides/test-ejs.liquid b/test/stubs/overrides/test-ejs.liquid new file mode 100644 index 000000000..badd12bce --- /dev/null +++ b/test/stubs/overrides/test-ejs.liquid @@ -0,0 +1,5 @@ +--- +templateEngineOverride: ejs +title: My Title +--- +<%= title %> diff --git a/test/stubs/overrides/test-empty.html b/test/stubs/overrides/test-empty.html new file mode 100644 index 000000000..8951af3c3 --- /dev/null +++ b/test/stubs/overrides/test-empty.html @@ -0,0 +1,5 @@ +--- +templateEngineOverride: +title: My Title +--- +

<%= title %>

diff --git a/test/stubs/overrides/test-empty.md b/test/stubs/overrides/test-empty.md new file mode 100644 index 000000000..521d1ad6f --- /dev/null +++ b/test/stubs/overrides/test-empty.md @@ -0,0 +1,6 @@ +--- +templateEngineOverride: +title: My Title +--- + +# <%= title %> diff --git a/test/stubs/overrides/test-error.njk b/test/stubs/overrides/test-error.njk new file mode 100644 index 000000000..7cf0cab4e --- /dev/null +++ b/test/stubs/overrides/test-error.njk @@ -0,0 +1,5 @@ +--- +templateEngineOverride: ejs,njk +title: My Title +--- +# <%= title %> diff --git a/test/stubs/overrides/test-md.liquid b/test/stubs/overrides/test-md.liquid new file mode 100644 index 000000000..286e2fdea --- /dev/null +++ b/test/stubs/overrides/test-md.liquid @@ -0,0 +1,4 @@ +--- +templateEngineOverride: md +--- +# My Title diff --git a/test/stubs/overrides/test-multiple.md b/test/stubs/overrides/test-multiple.md new file mode 100644 index 000000000..097f5c481 --- /dev/null +++ b/test/stubs/overrides/test-multiple.md @@ -0,0 +1,6 @@ +--- +templateEngineOverride: ejs,md +title: My Title +--- + +# <%= title %> diff --git a/test/stubs/overrides/test-multiple2.njk b/test/stubs/overrides/test-multiple2.njk new file mode 100644 index 000000000..c59324a3c --- /dev/null +++ b/test/stubs/overrides/test-multiple2.njk @@ -0,0 +1,5 @@ +--- +templateEngineOverride: ejs,md +title: My Title +--- +# <%= title %> diff --git a/test/stubs/overrides/test.ejs b/test/stubs/overrides/test.ejs new file mode 100644 index 000000000..7170e4147 --- /dev/null +++ b/test/stubs/overrides/test.ejs @@ -0,0 +1,5 @@ +--- +templateEngineOverride: njk +title: My Title +--- +{{ title }} diff --git a/test/stubs/overrides/test.html b/test/stubs/overrides/test.html new file mode 100644 index 000000000..9e1a5db22 --- /dev/null +++ b/test/stubs/overrides/test.html @@ -0,0 +1,5 @@ +--- +templateEngineOverride: ejs +title: My Title +--- +

<%= title %>

diff --git a/test/stubs/overrides/test.md b/test/stubs/overrides/test.md new file mode 100644 index 000000000..d5b2df8db --- /dev/null +++ b/test/stubs/overrides/test.md @@ -0,0 +1,6 @@ +--- +title: My Title +layout: layouts/engineOverridesMd.njk +--- + +# {{ title }} diff --git a/test/stubs/overrides/test2.md b/test/stubs/overrides/test2.md new file mode 100644 index 000000000..53f857eb9 --- /dev/null +++ b/test/stubs/overrides/test2.md @@ -0,0 +1,7 @@ +--- +title: My Title +templateEngineOverride: ejs,md +layout: layouts/engineOverridesMd.njk +--- + +# <%= title %> diff --git a/test/stubs/page-target-collections/paginateall.njk b/test/stubs/page-target-collections/paginateall.njk new file mode 100644 index 000000000..77e9c1e30 --- /dev/null +++ b/test/stubs/page-target-collections/paginateall.njk @@ -0,0 +1,10 @@ +--- +pagination: + data: collections.all + size: 1 + alias: entry + filter: + - all + addAllPagesToCollections: true +--- +INPUT PATH:{{ entry.inputPath }} \ No newline at end of file diff --git a/test/stubs/page-target-collections/tagpages.njk b/test/stubs/page-target-collections/tagpages.njk new file mode 100644 index 000000000..b419fea70 --- /dev/null +++ b/test/stubs/page-target-collections/tagpages.njk @@ -0,0 +1,10 @@ +--- +pagination: + data: collections + size: 1 + alias: tag + filter: + - all +--- + +{{ tag }} \ No newline at end of file diff --git a/test/stubs/page-target-collections/tagpagesall.njk b/test/stubs/page-target-collections/tagpagesall.njk new file mode 100644 index 000000000..68d7557d4 --- /dev/null +++ b/test/stubs/page-target-collections/tagpagesall.njk @@ -0,0 +1,11 @@ +--- +pagination: + data: collections + size: 1 + alias: tag + filter: + - all + addAllPagesToCollections: true +--- + +{{ tag }} \ No newline at end of file diff --git a/test/stubs/paged/cfg-collection-tag-cfg-collection/consumer.njk b/test/stubs/paged/cfg-collection-tag-cfg-collection/consumer.njk new file mode 100644 index 000000000..7988bf094 --- /dev/null +++ b/test/stubs/paged/cfg-collection-tag-cfg-collection/consumer.njk @@ -0,0 +1 @@ +{% for item in collections.pagingtag %}{{ item.templateContent | safe }}{% endfor %} diff --git a/test/stubs/paged/cfg-collection-tag-cfg-collection/paged-downstream.njk b/test/stubs/paged/cfg-collection-tag-cfg-collection/paged-downstream.njk new file mode 100644 index 000000000..67f56adde --- /dev/null +++ b/test/stubs/paged/cfg-collection-tag-cfg-collection/paged-downstream.njk @@ -0,0 +1,6 @@ +--- +pagination: + data: collections.pagingtag + size: 1 +--- +
    {% for item in pagination.items %}
  1. {{ item.url }}
  2. {% endfor %}
\ No newline at end of file diff --git a/test/stubs/paged/cfg-collection-tag-cfg-collection/paged-main.njk b/test/stubs/paged/cfg-collection-tag-cfg-collection/paged-main.njk new file mode 100644 index 000000000..0d148f988 --- /dev/null +++ b/test/stubs/paged/cfg-collection-tag-cfg-collection/paged-main.njk @@ -0,0 +1,9 @@ +--- +pagination: + data: collections.tag1 + size: 2 + addAllPagesToCollections: true +tags: + - pagingtag +--- +
    {% for item in pagination.items %}
  1. {{ item.url }}
  2. {% endfor %}
\ No newline at end of file diff --git a/test/stubs/paged/cfg-collection-tag-cfg-collection/test1.njk b/test/stubs/paged/cfg-collection-tag-cfg-collection/test1.njk new file mode 100644 index 000000000..14eb845e2 --- /dev/null +++ b/test/stubs/paged/cfg-collection-tag-cfg-collection/test1.njk @@ -0,0 +1,6 @@ +--- +title: Testing 1 +tags: + - tag1 +--- +{{ title }} \ No newline at end of file diff --git a/test/stubs/paged/cfg-collection-tag-cfg-collection/test2.njk b/test/stubs/paged/cfg-collection-tag-cfg-collection/test2.njk new file mode 100644 index 000000000..ab5f04b26 --- /dev/null +++ b/test/stubs/paged/cfg-collection-tag-cfg-collection/test2.njk @@ -0,0 +1,6 @@ +--- +title: Testing 2 +tags: + - tag1 +--- +{{ title }} \ No newline at end of file diff --git a/test/stubs/paged/cfg-collection-tag-cfg-collection/test3.njk b/test/stubs/paged/cfg-collection-tag-cfg-collection/test3.njk new file mode 100644 index 000000000..753551bb2 --- /dev/null +++ b/test/stubs/paged/cfg-collection-tag-cfg-collection/test3.njk @@ -0,0 +1,6 @@ +--- +title: Testing 3 +tags: + - tag1 +--- +{{ title }} \ No newline at end of file diff --git a/test/stubs/paged/collection-apply-to-all/consumer.njk b/test/stubs/paged/collection-apply-to-all/consumer.njk new file mode 100644 index 000000000..7988bf094 --- /dev/null +++ b/test/stubs/paged/collection-apply-to-all/consumer.njk @@ -0,0 +1 @@ +{% for item in collections.pagingtag %}{{ item.templateContent | safe }}{% endfor %} diff --git a/test/stubs/paged/collection-apply-to-all/main.njk b/test/stubs/paged/collection-apply-to-all/main.njk new file mode 100644 index 000000000..0d148f988 --- /dev/null +++ b/test/stubs/paged/collection-apply-to-all/main.njk @@ -0,0 +1,9 @@ +--- +pagination: + data: collections.tag1 + size: 2 + addAllPagesToCollections: true +tags: + - pagingtag +--- +
    {% for item in pagination.items %}
  1. {{ item.url }}
  2. {% endfor %}
\ No newline at end of file diff --git a/test/stubs/paged/collection-apply-to-all/test1.njk b/test/stubs/paged/collection-apply-to-all/test1.njk new file mode 100644 index 000000000..14eb845e2 --- /dev/null +++ b/test/stubs/paged/collection-apply-to-all/test1.njk @@ -0,0 +1,6 @@ +--- +title: Testing 1 +tags: + - tag1 +--- +{{ title }} \ No newline at end of file diff --git a/test/stubs/paged/collection-apply-to-all/test2.njk b/test/stubs/paged/collection-apply-to-all/test2.njk new file mode 100644 index 000000000..ab5f04b26 --- /dev/null +++ b/test/stubs/paged/collection-apply-to-all/test2.njk @@ -0,0 +1,6 @@ +--- +title: Testing 2 +tags: + - tag1 +--- +{{ title }} \ No newline at end of file diff --git a/test/stubs/paged/collection-apply-to-all/test3.njk b/test/stubs/paged/collection-apply-to-all/test3.njk new file mode 100644 index 000000000..753551bb2 --- /dev/null +++ b/test/stubs/paged/collection-apply-to-all/test3.njk @@ -0,0 +1,6 @@ +--- +title: Testing 3 +tags: + - tag1 +--- +{{ title }} \ No newline at end of file diff --git a/test/stubs/paged/collection/consumer.njk b/test/stubs/paged/collection/consumer.njk new file mode 100644 index 000000000..7988bf094 --- /dev/null +++ b/test/stubs/paged/collection/consumer.njk @@ -0,0 +1 @@ +{% for item in collections.pagingtag %}{{ item.templateContent | safe }}{% endfor %} diff --git a/test/stubs/paged/collection/main.njk b/test/stubs/paged/collection/main.njk new file mode 100644 index 000000000..f3230551a --- /dev/null +++ b/test/stubs/paged/collection/main.njk @@ -0,0 +1,8 @@ +--- +pagination: + data: collections.tag1 + size: 2 +tags: + - pagingtag +--- +
    {% for item in pagination.items %}
  1. {{ item.url }}
  2. {% endfor %}
diff --git a/test/stubs/paged/collection/test1.njk b/test/stubs/paged/collection/test1.njk new file mode 100644 index 000000000..14eb845e2 --- /dev/null +++ b/test/stubs/paged/collection/test1.njk @@ -0,0 +1,6 @@ +--- +title: Testing 1 +tags: + - tag1 +--- +{{ title }} \ No newline at end of file diff --git a/test/stubs/paged/collection/test2.njk b/test/stubs/paged/collection/test2.njk new file mode 100644 index 000000000..ab5f04b26 --- /dev/null +++ b/test/stubs/paged/collection/test2.njk @@ -0,0 +1,6 @@ +--- +title: Testing 2 +tags: + - tag1 +--- +{{ title }} \ No newline at end of file diff --git a/test/stubs/paged/collection/test3.njk b/test/stubs/paged/collection/test3.njk new file mode 100644 index 000000000..753551bb2 --- /dev/null +++ b/test/stubs/paged/collection/test3.njk @@ -0,0 +1,6 @@ +--- +title: Testing 3 +tags: + - tag1 +--- +{{ title }} \ No newline at end of file diff --git a/test/stubs/paged/pagedinlinedata-reverse.njk b/test/stubs/paged/pagedinlinedata-reverse.njk new file mode 100644 index 000000000..1641566a1 --- /dev/null +++ b/test/stubs/paged/pagedinlinedata-reverse.njk @@ -0,0 +1,16 @@ +--- +pagination: + data: testdata + size: 4 + reverse: true +testdata: + - item1 + - item2 + - item3 + - item4 + - item5 + - item6 + - item7 + - item8 +--- +
    {% for item in pagination.items %}
  1. {{ item }}
  2. {% endfor %}
\ No newline at end of file diff --git a/test/stubs/paged/pagedobject.njk b/test/stubs/paged/pagedobject.njk new file mode 100644 index 000000000..dcdecd240 --- /dev/null +++ b/test/stubs/paged/pagedobject.njk @@ -0,0 +1,16 @@ +--- +pagination: + data: testdata + size: 4 +testdata: + item1: itemvalue1 + item2: itemvalue2 + item3: itemvalue3 + item4: itemvalue4 + item5: itemvalue5 + item6: itemvalue6 + item7: itemvalue7 + item8: itemvalue8 + item9: itemvalue9 +--- +
    {% for item in pagination.items %}
  1. {{ item }}
  2. {% endfor %}
diff --git a/test/stubs/paged/pagedobjectfilterarray.njk b/test/stubs/paged/pagedobjectfilterarray.njk new file mode 100644 index 000000000..1e166f4b4 --- /dev/null +++ b/test/stubs/paged/pagedobjectfilterarray.njk @@ -0,0 +1,18 @@ +--- +pagination: + data: testdata + size: 4 + filter: + - item4 +testdata: + item1: itemvalue1 + item2: itemvalue2 + item3: itemvalue3 + item4: itemvalue4 + item5: itemvalue5 + item6: itemvalue6 + item7: itemvalue7 + item8: itemvalue8 + item9: itemvalue9 +--- +
    {% for item in pagination.items %}
  1. {{ item }}
  2. {% endfor %}
diff --git a/test/stubs/paged/pagedobjectfilterstring.njk b/test/stubs/paged/pagedobjectfilterstring.njk new file mode 100644 index 000000000..0c279e6c7 --- /dev/null +++ b/test/stubs/paged/pagedobjectfilterstring.njk @@ -0,0 +1,17 @@ +--- +pagination: + data: testdata + size: 4 + filter: item4 +testdata: + item1: itemvalue1 + item2: itemvalue2 + item3: itemvalue3 + item4: itemvalue4 + item5: itemvalue5 + item6: itemvalue6 + item7: itemvalue7 + item8: itemvalue8 + item9: itemvalue9 +--- +
    {% for item in pagination.items %}
  1. {{ item }}
  2. {% endfor %}
diff --git a/test/stubs/paged/pagedobjectvalues.njk b/test/stubs/paged/pagedobjectvalues.njk new file mode 100644 index 000000000..aa2444180 --- /dev/null +++ b/test/stubs/paged/pagedobjectvalues.njk @@ -0,0 +1,17 @@ +--- +pagination: + data: testdata + size: 4 + resolve: values +testdata: + item1: itemvalue1 + item2: itemvalue2 + item3: itemvalue3 + item4: itemvalue4 + item5: itemvalue5 + item6: itemvalue6 + item7: itemvalue7 + item8: itemvalue8 + item9: itemvalue9 +--- +
    {% for item in pagination.items %}
  1. {{ item }}
  2. {% endfor %}
diff --git a/test/stubs/paged/pagedpermalinkif.liquid b/test/stubs/paged/pagedpermalinkif.liquid new file mode 100644 index 000000000..16be77b7c --- /dev/null +++ b/test/stubs/paged/pagedpermalinkif.liquid @@ -0,0 +1,12 @@ +--- +pagination: + data: items + size: 2 +items: + - item1 + - item2 + - item3 + - item4 +permalink: paged/{% if pagination.pageNumber > 0 %}page-{{ pagination.pageNumber }}/{% endif %}index.html +--- +
    {% for item in pagination.items %}
  1. {{ item }}
  2. {% endfor %}
diff --git a/test/stubs/paged/pagedpermalinkif.njk b/test/stubs/paged/pagedpermalinkif.njk new file mode 100644 index 000000000..16be77b7c --- /dev/null +++ b/test/stubs/paged/pagedpermalinkif.njk @@ -0,0 +1,12 @@ +--- +pagination: + data: items + size: 2 +items: + - item1 + - item2 + - item3 + - item4 +permalink: paged/{% if pagination.pageNumber > 0 %}page-{{ pagination.pageNumber }}/{% endif %}index.html +--- +
    {% for item in pagination.items %}
  1. {{ item }}
  2. {% endfor %}
diff --git a/test/stubs/pagedate.liquid b/test/stubs/pagedate.liquid new file mode 100644 index 000000000..868d82cc3 --- /dev/null +++ b/test/stubs/pagedate.liquid @@ -0,0 +1 @@ +{{ page.date }} \ No newline at end of file diff --git a/test/stubs/pagedate.njk b/test/stubs/pagedate.njk new file mode 100644 index 000000000..868d82cc3 --- /dev/null +++ b/test/stubs/pagedate.njk @@ -0,0 +1 @@ +{{ page.date }} \ No newline at end of file diff --git a/test/stubs/pagedateutc.njk b/test/stubs/pagedateutc.njk new file mode 100644 index 000000000..df9e8906b --- /dev/null +++ b/test/stubs/pagedateutc.njk @@ -0,0 +1 @@ +{{ page.date.toUTCString() }} \ No newline at end of file diff --git a/test/stubs/pagination-templatecontent/index.njk b/test/stubs/pagination-templatecontent/index.njk new file mode 100644 index 000000000..3b946a880 --- /dev/null +++ b/test/stubs/pagination-templatecontent/index.njk @@ -0,0 +1,9 @@ +--- +pagination: + data: collections.post + size: 5 + alias: posts +--- +{%- for post in posts -%} +{{ post.templateContent | safe }} +{%- endfor -%} \ No newline at end of file diff --git a/test/stubs/pagination-templatecontent/post-1.md b/test/stubs/pagination-templatecontent/post-1.md new file mode 100644 index 000000000..10314c323 --- /dev/null +++ b/test/stubs/pagination-templatecontent/post-1.md @@ -0,0 +1,6 @@ +--- +tags: + - post +--- + +# Post 1 diff --git a/test/stubs/pagination-templatecontent/post-2.md b/test/stubs/pagination-templatecontent/post-2.md new file mode 100644 index 000000000..93a35204c --- /dev/null +++ b/test/stubs/pagination-templatecontent/post-2.md @@ -0,0 +1,6 @@ +--- +tags: + - post +--- + +# Post 2 diff --git a/test/stubs/permalink-conflicts-false/test1.md b/test/stubs/permalink-conflicts-false/test1.md new file mode 100644 index 000000000..cc43508dc --- /dev/null +++ b/test/stubs/permalink-conflicts-false/test1.md @@ -0,0 +1,9 @@ +--- +title: Test Title +tags: + - post + - dog +permalink: false +--- + +# Test 1 diff --git a/test/stubs/permalink-conflicts-false/test2.md b/test/stubs/permalink-conflicts-false/test2.md new file mode 100644 index 000000000..17ce60abe --- /dev/null +++ b/test/stubs/permalink-conflicts-false/test2.md @@ -0,0 +1,9 @@ +--- +title: Test Title +tags: + - post + - dog +permalink: false +--- + +# Test 2 diff --git a/test/stubs/permalink-conflicts/test1.md b/test/stubs/permalink-conflicts/test1.md new file mode 100644 index 000000000..f790f3056 --- /dev/null +++ b/test/stubs/permalink-conflicts/test1.md @@ -0,0 +1,9 @@ +--- +title: Test Title +tags: + - post + - dog +permalink: /permalink-conflicts/ +--- + +# Test 1 diff --git a/test/stubs/permalink-conflicts/test2.md b/test/stubs/permalink-conflicts/test2.md new file mode 100644 index 000000000..e7a243128 --- /dev/null +++ b/test/stubs/permalink-conflicts/test2.md @@ -0,0 +1,9 @@ +--- +title: Test Title +tags: + - post + - dog +permalink: /permalink-conflicts/ +--- + +# Test 2 diff --git a/test/stubs/permalink-conflicts/test3.md b/test/stubs/permalink-conflicts/test3.md new file mode 100644 index 000000000..e110ee526 --- /dev/null +++ b/test/stubs/permalink-conflicts/test3.md @@ -0,0 +1,9 @@ +--- +title: Test Title +tags: + - post + - dog +permalink: permalink-conflicts/ +--- + +# Test 3 diff --git a/test/stubs/permalink-data-layout/test.json b/test/stubs/permalink-data-layout/test.json new file mode 100644 index 000000000..7e9c979ef --- /dev/null +++ b/test/stubs/permalink-data-layout/test.json @@ -0,0 +1,3 @@ +{ + "layout": "permalink-data-layout.njk" +} \ No newline at end of file diff --git a/test/stubs/permalink-data-layout/test.njk b/test/stubs/permalink-data-layout/test.njk new file mode 100644 index 000000000..dcf9bdc56 --- /dev/null +++ b/test/stubs/permalink-data-layout/test.njk @@ -0,0 +1 @@ +Test 1:{{ page.fileSlug }} \ No newline at end of file diff --git a/test/stubs/permalink-false/test.md b/test/stubs/permalink-false/test.md new file mode 100644 index 000000000..a03931c77 --- /dev/null +++ b/test/stubs/permalink-false/test.md @@ -0,0 +1,5 @@ +--- +permalink: false +--- + +This shouldnā€™t write diff --git a/test/stubs/permalink-in-layout-fileslug.ejs b/test/stubs/permalink-in-layout-fileslug.ejs new file mode 100644 index 000000000..f8789ce2c --- /dev/null +++ b/test/stubs/permalink-in-layout-fileslug.ejs @@ -0,0 +1,4 @@ +--- +layout: permalink-in-layout/layout-fileslug.ejs +--- +Current url: <%= permalink %> \ No newline at end of file diff --git a/test/stubs/permalink-in-layout.ejs b/test/stubs/permalink-in-layout.ejs new file mode 100644 index 000000000..6c47953de --- /dev/null +++ b/test/stubs/permalink-in-layout.ejs @@ -0,0 +1,4 @@ +--- +layout: permalink-in-layout/layout.ejs +--- +Current url: <%= permalink %> \ No newline at end of file diff --git a/test/stubs/permalink-markdown-override.md b/test/stubs/permalink-markdown-override.md new file mode 100644 index 000000000..e199a4e8d --- /dev/null +++ b/test/stubs/permalink-markdown-override.md @@ -0,0 +1,7 @@ +--- +title: My Title +permalink: /news/my-test-file/index.html +templateEngineOverride: html,md +--- + +# <%= title %> diff --git a/test/stubs/permalink-markdown-var.md b/test/stubs/permalink-markdown-var.md new file mode 100644 index 000000000..bb58e96d3 --- /dev/null +++ b/test/stubs/permalink-markdown-var.md @@ -0,0 +1,6 @@ +--- +title: My Title +permalink: /news/{{ title | slug }}/index.html +--- + +# <%= title %> diff --git a/test/stubs/permalink-markdown.md b/test/stubs/permalink-markdown.md new file mode 100644 index 000000000..f199f8175 --- /dev/null +++ b/test/stubs/permalink-markdown.md @@ -0,0 +1,6 @@ +--- +title: My Title +permalink: /news/my-test-file/index.html +--- + +# <%= title %> diff --git a/test/stubs/permalinkdate.liquid b/test/stubs/permalinkdate.liquid new file mode 100644 index 000000000..ab30c294b --- /dev/null +++ b/test/stubs/permalinkdate.liquid @@ -0,0 +1,6 @@ +--- +title: Date Permalink +date: "2016-01-01T06:00-06:00" +permalink: "/{{ page.date | date: '%Y/%m/%d' }}/index.html" +--- +Date Permalinks \ No newline at end of file diff --git a/test/stubs/posts/post1.njk b/test/stubs/posts/post1.njk new file mode 100644 index 000000000..faadbf373 --- /dev/null +++ b/test/stubs/posts/post1.njk @@ -0,0 +1 @@ +Post1 diff --git a/test/stubs/posts/posts.json b/test/stubs/posts/posts.json new file mode 100644 index 000000000..1be945ae8 --- /dev/null +++ b/test/stubs/posts/posts.json @@ -0,0 +1,3 @@ +{ + "layout": "mylocallayout.njk" +} diff --git a/test/stubs/posts/posts.njk b/test/stubs/posts/posts.njk new file mode 100644 index 000000000..7e0a7fb78 --- /dev/null +++ b/test/stubs/posts/posts.njk @@ -0,0 +1 @@ +Posts diff --git a/test/stubs/prematureTemplateContent/test.11ty.js b/test/stubs/prematureTemplateContent/test.11ty.js new file mode 100644 index 000000000..cf357146f --- /dev/null +++ b/test/stubs/prematureTemplateContent/test.11ty.js @@ -0,0 +1,3 @@ +module.exports = function(data) { + return data.collections.all[0].templateContent; +}; diff --git a/test/stubs/prematureTemplateContent/test.ejs b/test/stubs/prematureTemplateContent/test.ejs new file mode 100644 index 000000000..e080c8428 --- /dev/null +++ b/test/stubs/prematureTemplateContent/test.ejs @@ -0,0 +1 @@ +<%= collections.all[0].templateContent %> \ No newline at end of file diff --git a/test/stubs/prematureTemplateContent/test.haml b/test/stubs/prematureTemplateContent/test.haml new file mode 100644 index 000000000..aafbbc3c1 --- /dev/null +++ b/test/stubs/prematureTemplateContent/test.haml @@ -0,0 +1 @@ +%p= collections.all[0].templateContent \ No newline at end of file diff --git a/test/stubs/prematureTemplateContent/test.hbs b/test/stubs/prematureTemplateContent/test.hbs new file mode 100644 index 000000000..cedc058a8 --- /dev/null +++ b/test/stubs/prematureTemplateContent/test.hbs @@ -0,0 +1 @@ +{{ sample.templateContent }} \ No newline at end of file diff --git a/test/stubs/prematureTemplateContent/test.liquid b/test/stubs/prematureTemplateContent/test.liquid new file mode 100644 index 000000000..ff264d1ed --- /dev/null +++ b/test/stubs/prematureTemplateContent/test.liquid @@ -0,0 +1 @@ +{{ collections.all[0].templateContent }} \ No newline at end of file diff --git a/test/stubs/prematureTemplateContent/test.md b/test/stubs/prematureTemplateContent/test.md new file mode 100644 index 000000000..862fcd4b1 --- /dev/null +++ b/test/stubs/prematureTemplateContent/test.md @@ -0,0 +1 @@ +{{ sample.templateContent }} diff --git a/test/stubs/prematureTemplateContent/test.mustache b/test/stubs/prematureTemplateContent/test.mustache new file mode 100644 index 000000000..ff264d1ed --- /dev/null +++ b/test/stubs/prematureTemplateContent/test.mustache @@ -0,0 +1 @@ +{{ collections.all[0].templateContent }} \ No newline at end of file diff --git a/test/stubs/prematureTemplateContent/test.njk b/test/stubs/prematureTemplateContent/test.njk new file mode 100644 index 000000000..cedc058a8 --- /dev/null +++ b/test/stubs/prematureTemplateContent/test.njk @@ -0,0 +1 @@ +{{ sample.templateContent }} \ No newline at end of file diff --git a/test/stubs/prematureTemplateContent/test.pug b/test/stubs/prematureTemplateContent/test.pug new file mode 100644 index 000000000..d11072702 --- /dev/null +++ b/test/stubs/prematureTemplateContent/test.pug @@ -0,0 +1 @@ +p= sample.templateContent \ No newline at end of file diff --git a/test/stubs/promise.11ty.js b/test/stubs/promise.11ty.js new file mode 100644 index 000000000..0127ab4ee --- /dev/null +++ b/test/stubs/promise.11ty.js @@ -0,0 +1,5 @@ +module.exports = new Promise((resolve, reject) => { + setTimeout(function() { + resolve("

Zach

"); + }, 100); +}); diff --git a/test/stubs/relative-ejs/dir/included.ejs b/test/stubs/relative-ejs/dir/included.ejs new file mode 100644 index 000000000..6b622104c --- /dev/null +++ b/test/stubs/relative-ejs/dir/included.ejs @@ -0,0 +1 @@ +This is an include. \ No newline at end of file diff --git a/test/stubs/relative-liquid/dir/included.liquid b/test/stubs/relative-liquid/dir/included.liquid new file mode 100644 index 000000000..4c2bb7ad1 --- /dev/null +++ b/test/stubs/relative-liquid/dir/included.liquid @@ -0,0 +1 @@ +TIME IS RELATIVE. \ No newline at end of file diff --git a/test/stubs/renderData/renderData.md b/test/stubs/renderData/renderData.md new file mode 100644 index 000000000..19324c993 --- /dev/null +++ b/test/stubs/renderData/renderData.md @@ -0,0 +1,7 @@ +--- +key1: value1 +renderData: + key2: value2-{{ key1 }}.css +--- + +{{ renderData.key2 }} diff --git a/test/stubs/renderData/renderData.njk b/test/stubs/renderData/renderData.njk new file mode 100644 index 000000000..cf9c7b574 --- /dev/null +++ b/test/stubs/renderData/renderData.njk @@ -0,0 +1,7 @@ +--- +key1: value1 +renderData: + key2: + - value2-{{ key1 }}.css +--- +hi:{{ renderData.key2 }} diff --git a/test/stubs/reuse-permalink/reuse-permalink.json b/test/stubs/reuse-permalink/reuse-permalink.json new file mode 100644 index 000000000..016c180f5 --- /dev/null +++ b/test/stubs/reuse-permalink/reuse-permalink.json @@ -0,0 +1,3 @@ +{ + "permalink": "/{{ page.date | date: '%Y/%m/%d' }}/index.html" +} diff --git a/test/stubs/reuse-permalink/test1.liquid b/test/stubs/reuse-permalink/test1.liquid new file mode 100644 index 000000000..335e82ba7 --- /dev/null +++ b/test/stubs/reuse-permalink/test1.liquid @@ -0,0 +1,6 @@ +--- +title: Test Title +date: "2016-01-01T06:00-06:00" +--- + +# Test 1 diff --git a/test/stubs/string.11ty.js b/test/stubs/string.11ty.js new file mode 100644 index 000000000..e9aa24752 --- /dev/null +++ b/test/stubs/string.11ty.js @@ -0,0 +1 @@ +module.exports = "

Zach

"; diff --git a/test/stubs/tagged-pagination-multiples-layout/test.njk b/test/stubs/tagged-pagination-multiples-layout/test.njk new file mode 100644 index 000000000..e1fc00bfa --- /dev/null +++ b/test/stubs/tagged-pagination-multiples-layout/test.njk @@ -0,0 +1,14 @@ +--- +title: Paged Test +layout: layouts/div-wrapper-layout.njk +testdata: + - one + - two + - three +pagination: + data: testdata + size: 1 + alias: item + addAllPagesToCollections: true +--- +{{ item }} \ No newline at end of file diff --git a/test/stubs/tagged-pagination-multiples/test.njk b/test/stubs/tagged-pagination-multiples/test.njk new file mode 100644 index 000000000..211164a1e --- /dev/null +++ b/test/stubs/tagged-pagination-multiples/test.njk @@ -0,0 +1,12 @@ +--- +title: Paged Test +tags: + - haha +pagination: + data: collections.userCollection + size: 1 + alias: item + addAllPagesToCollections: true +--- + +{{ title }} \ No newline at end of file diff --git a/test/stubs/template-passthrough/.htaccess b/test/stubs/template-passthrough/.htaccess new file mode 100644 index 000000000..e69de29bb diff --git a/test/stubs/template-passthrough/img.jpg b/test/stubs/template-passthrough/img.jpg new file mode 100755 index 000000000..e69de29bb diff --git a/test/stubs/template-passthrough/src/views/avatar.png b/test/stubs/template-passthrough/src/views/avatar.png new file mode 100644 index 000000000..e69de29bb diff --git a/test/stubs/template-passthrough/static/nested/test-nested.css b/test/stubs/template-passthrough/static/nested/test-nested.css new file mode 100755 index 000000000..e69de29bb diff --git a/test/stubs/template-passthrough/static/test.css b/test/stubs/template-passthrough/static/test.css new file mode 100755 index 000000000..e69de29bb diff --git a/test/stubs/template-passthrough/static/test.js b/test/stubs/template-passthrough/static/test.js new file mode 100755 index 000000000..e69de29bb diff --git a/test/stubs/template-passthrough2/.htaccess b/test/stubs/template-passthrough2/.htaccess new file mode 100644 index 000000000..e69de29bb diff --git a/test/stubs/template-passthrough2/img.jpg b/test/stubs/template-passthrough2/img.jpg new file mode 100755 index 000000000..e69de29bb diff --git a/test/stubs/template-passthrough2/src/views/avatar.png b/test/stubs/template-passthrough2/src/views/avatar.png new file mode 100644 index 000000000..e69de29bb diff --git a/test/stubs/template-passthrough2/static/nested/test-nested.css b/test/stubs/template-passthrough2/static/nested/test-nested.css new file mode 100755 index 000000000..e69de29bb diff --git a/test/stubs/template-passthrough2/static/test.css b/test/stubs/template-passthrough2/static/test.css new file mode 100755 index 000000000..e69de29bb diff --git a/test/stubs/template-passthrough2/static/test.js b/test/stubs/template-passthrough2/static/test.js new file mode 100755 index 000000000..e69de29bb diff --git a/test/stubs/templateFrontMatter.ejs b/test/stubs/templateFrontMatter.ejs index 01aab2f48..93663edc4 100644 --- a/test/stubs/templateFrontMatter.ejs +++ b/test/stubs/templateFrontMatter.ejs @@ -1,6 +1,6 @@ --- key1: value1 +key2: value2 key3: value3 --- - c:<%= key1 %>:<%= key2 %>:<%= key3 %> \ No newline at end of file diff --git a/test/stubs/templateFrontMatterJs.ejs b/test/stubs/templateFrontMatterJs.ejs new file mode 100644 index 000000000..06128620e --- /dev/null +++ b/test/stubs/templateFrontMatterJs.ejs @@ -0,0 +1,10 @@ +---js +{ + key1: "value1", + key2: function(value) { + return value.toUpperCase(); + }, + key3: "value3" +} +--- +c:<%= key1 %>:<%= key2("value2") %>:<%= key3 %> diff --git a/test/stubs/templateFrontMatterJson.ejs b/test/stubs/templateFrontMatterJson.ejs new file mode 100644 index 000000000..a414b471b --- /dev/null +++ b/test/stubs/templateFrontMatterJson.ejs @@ -0,0 +1,8 @@ +---json +{ + "key1": "value1", + "key2": "value2", + "key3": "value3" +} +--- +c:<%= key1 %>:<%= key2 %>:<%= key3 %> \ No newline at end of file diff --git a/test/stubs/templateLayoutCacheDuplicates-b/_includes/layout.njk b/test/stubs/templateLayoutCacheDuplicates-b/_includes/layout.njk new file mode 100644 index 000000000..b33259f56 --- /dev/null +++ b/test/stubs/templateLayoutCacheDuplicates-b/_includes/layout.njk @@ -0,0 +1 @@ +Hello B \ No newline at end of file diff --git a/test/stubs/templateLayoutCacheDuplicates/_includes/layout.njk b/test/stubs/templateLayoutCacheDuplicates/_includes/layout.njk new file mode 100644 index 000000000..2f49662a0 --- /dev/null +++ b/test/stubs/templateLayoutCacheDuplicates/_includes/layout.njk @@ -0,0 +1 @@ +Hello A \ No newline at end of file diff --git a/test/stubs/templateMapCollection/paged-cfg-permalink.md b/test/stubs/templateMapCollection/paged-cfg-permalink.md new file mode 100644 index 000000000..5fbe85c07 --- /dev/null +++ b/test/stubs/templateMapCollection/paged-cfg-permalink.md @@ -0,0 +1,10 @@ +--- +title: Paged Test +pagination: + data: collections.userCollection + size: 1 + alias: item +permalink: /{{ item.data.title | slug}}/hello/ +--- + +# {{ title }} diff --git a/test/stubs/templateMapCollection/paged-cfg-tagged-apply-to-all.md b/test/stubs/templateMapCollection/paged-cfg-tagged-apply-to-all.md new file mode 100644 index 000000000..73ba2c9b4 --- /dev/null +++ b/test/stubs/templateMapCollection/paged-cfg-tagged-apply-to-all.md @@ -0,0 +1,12 @@ +--- +title: Paged Test +tags: + - haha +pagination: + data: collections.userCollection + size: 1 + alias: item + addAllPagesToCollections: true +--- + +# {{ title }} diff --git a/test/stubs/templateMapCollection/paged-cfg-tagged-permalink-apply-to-all.md b/test/stubs/templateMapCollection/paged-cfg-tagged-permalink-apply-to-all.md new file mode 100644 index 000000000..48750e870 --- /dev/null +++ b/test/stubs/templateMapCollection/paged-cfg-tagged-permalink-apply-to-all.md @@ -0,0 +1,13 @@ +--- +title: Paged Test +tags: + - haha +pagination: + data: collections.userCollection + size: 1 + alias: item + addAllPagesToCollections: true +permalink: /{{ item.data.title | slug}}/goodbye/ +--- + +# {{ title }} diff --git a/test/stubs/templateMapCollection/paged-cfg-tagged-permalink.md b/test/stubs/templateMapCollection/paged-cfg-tagged-permalink.md new file mode 100644 index 000000000..5dcd32661 --- /dev/null +++ b/test/stubs/templateMapCollection/paged-cfg-tagged-permalink.md @@ -0,0 +1,12 @@ +--- +title: Paged Test +tags: + - haha +pagination: + data: collections.userCollection + size: 1 + alias: item +permalink: /{{ item.data.title | slug}}/goodbye/ +--- + +# {{ title }} diff --git a/test/stubs/templateMapCollection/paged-cfg-tagged.md b/test/stubs/templateMapCollection/paged-cfg-tagged.md new file mode 100644 index 000000000..4e4345b19 --- /dev/null +++ b/test/stubs/templateMapCollection/paged-cfg-tagged.md @@ -0,0 +1,11 @@ +--- +title: Paged Test +tags: + - haha +pagination: + data: collections.userCollection + size: 1 + alias: item +--- + +# {{ title }} diff --git a/test/stubs/templateMapCollection/paged-cfg.md b/test/stubs/templateMapCollection/paged-cfg.md new file mode 100644 index 000000000..bcff64213 --- /dev/null +++ b/test/stubs/templateMapCollection/paged-cfg.md @@ -0,0 +1,8 @@ +--- +title: Paged Test +pagination: + data: collections.userCollection + size: 1 +--- + +# {{ title }} diff --git a/test/stubs/templateMapCollection/paged-tag-dogs-templateContent-alias.md b/test/stubs/templateMapCollection/paged-tag-dogs-templateContent-alias.md new file mode 100644 index 000000000..155e8b0b8 --- /dev/null +++ b/test/stubs/templateMapCollection/paged-tag-dogs-templateContent-alias.md @@ -0,0 +1,13 @@ +--- +title: Paged Test +pagination: + data: collections.dog + size: 2 + alias: dogs +--- + +Before +{% for dog in dogs %} +{{ dog.templateContent }} +{% endfor %} +After diff --git a/test/stubs/templateMapCollection/paged-tag-dogs-templateContent.md b/test/stubs/templateMapCollection/paged-tag-dogs-templateContent.md new file mode 100644 index 000000000..850d39e32 --- /dev/null +++ b/test/stubs/templateMapCollection/paged-tag-dogs-templateContent.md @@ -0,0 +1,12 @@ +--- +title: Paged Test +pagination: + data: collections.dog + size: 1 +--- + +Before +{% for dog in pagination.items %} +{{ dog.templateContent }} +{% endfor %} +After diff --git a/test/stubs/templateMapCollection/paged-tag.md b/test/stubs/templateMapCollection/paged-tag.md new file mode 100644 index 000000000..250f2529b --- /dev/null +++ b/test/stubs/templateMapCollection/paged-tag.md @@ -0,0 +1,8 @@ +--- +title: Paged Test +pagination: + data: collections.dog + size: 1 +--- + +# {{ title }} diff --git a/test/stubs/templateMapCollection/templateContent.md b/test/stubs/templateMapCollection/templateContent.md new file mode 100644 index 000000000..fd5bd9c1d --- /dev/null +++ b/test/stubs/templateMapCollection/templateContent.md @@ -0,0 +1,7 @@ +--- +tags: circle +--- + +# Test + +{{ collections.circle[0].templateContent }} diff --git a/test/stubs/templateMapCollection/test1.md b/test/stubs/templateMapCollection/test1.md new file mode 100644 index 000000000..f8f5da2c5 --- /dev/null +++ b/test/stubs/templateMapCollection/test1.md @@ -0,0 +1,8 @@ +--- +title: Test Title +tags: + - post + - dog +--- + +# Test 1 diff --git a/test/stubs/templateMapCollection/test2.md b/test/stubs/templateMapCollection/test2.md new file mode 100644 index 000000000..53aed0eda --- /dev/null +++ b/test/stubs/templateMapCollection/test2.md @@ -0,0 +1,5 @@ +--- +tags: cat +--- + +# Test 2 diff --git a/test/stubs/templateMapCollection/test3.md b/test/stubs/templateMapCollection/test3.md new file mode 100644 index 000000000..bb8df06c1 --- /dev/null +++ b/test/stubs/templateMapCollection/test3.md @@ -0,0 +1,5 @@ +--- +tags: circle +--- + +# {{ collections.circle.length }}, {{ collections.circle[0].url }} diff --git a/test/stubs/templateMapCollection/test4.md b/test/stubs/templateMapCollection/test4.md new file mode 100644 index 000000000..6e060216f --- /dev/null +++ b/test/stubs/templateMapCollection/test4.md @@ -0,0 +1,8 @@ +--- +title: Test Title 4 +tags: + - post + - dog +--- + +# Test 4 diff --git a/test/stubs/templateMapCollection/test5.md b/test/stubs/templateMapCollection/test5.md new file mode 100644 index 000000000..870f7d034 --- /dev/null +++ b/test/stubs/templateMapCollection/test5.md @@ -0,0 +1,5 @@ +--- +title: Test Title 5 +--- + +# Test 5 diff --git a/test/stubs/templateMapCollection/testWithLayout.md b/test/stubs/templateMapCollection/testWithLayout.md new file mode 100644 index 000000000..cf450d734 --- /dev/null +++ b/test/stubs/templateMapCollection/testWithLayout.md @@ -0,0 +1,5 @@ +--- +layout: layouts/templateMapCollection.njk +--- + +{{ upstream }} diff --git a/test/stubs/templateWithLayout.liquid b/test/stubs/templateWithLayout.liquid new file mode 100644 index 000000000..7b088a365 --- /dev/null +++ b/test/stubs/templateWithLayout.liquid @@ -0,0 +1,8 @@ +--- +layout: layoutLiquid.liquid +keymain: valuemain +title: 'Font Aliasing, or How to Rename a Font in CSS' +permalink: /rename-font/ +--- + +

Hello.

\ No newline at end of file diff --git a/test/stubs/templateWithLayoutBackCompat.ejs b/test/stubs/templateWithLayoutBackCompat.ejs new file mode 100644 index 000000000..7e7c4d96f --- /dev/null +++ b/test/stubs/templateWithLayoutBackCompat.ejs @@ -0,0 +1,8 @@ +--- +layout: defaultLayout_layoutContent +keymain: valuemain +title: 'Font Aliasing, or How to Rename a Font in CSS' +permalink: /rename-font/ +--- + +

Hello.

\ No newline at end of file diff --git a/test/stubs/templateWithLayoutContent.ejs b/test/stubs/templateWithLayoutContent.ejs new file mode 100644 index 000000000..12ec9d794 --- /dev/null +++ b/test/stubs/templateWithLayoutContent.ejs @@ -0,0 +1,7 @@ +--- +layout: defaultLayoutLayoutContent +keymain: valuemain +title: 'Font Aliasing, or How to Rename a Font in CSS' +permalink: /rename-font/ +--- +

Hello.

diff --git a/test/stubs/templatetest-frontmatter/multiple.njk b/test/stubs/templatetest-frontmatter/multiple.njk new file mode 100644 index 000000000..e6f6707ec --- /dev/null +++ b/test/stubs/templatetest-frontmatter/multiple.njk @@ -0,0 +1,6 @@ +--- +tags: + - multi-tag + - multi-tag-2 +--- +{% if tags.includes("multi-tag-2") %}Has multi-tag-2{% endif %} \ No newline at end of file diff --git a/test/stubs/templatetest-frontmatter/single.njk b/test/stubs/templatetest-frontmatter/single.njk new file mode 100644 index 000000000..5ea719107 --- /dev/null +++ b/test/stubs/templatetest-frontmatter/single.njk @@ -0,0 +1,4 @@ +--- +tags: single-tag +--- +{% if tags.includes("single-tag") %}Has single-tag{% endif %} \ No newline at end of file diff --git a/test/stubs/test-override-js-markdown.11ty.js b/test/stubs/test-override-js-markdown.11ty.js new file mode 100644 index 000000000..7c90cef92 --- /dev/null +++ b/test/stubs/test-override-js-markdown.11ty.js @@ -0,0 +1,14 @@ +class Test { + data() { + return { + name: "markdown", + templateEngineOverride: "11ty.js,md" + }; + } + + render(data) { + return `# This is ${data.name}`; + } +} + +module.exports = Test; diff --git a/test/stubs/transform-pages/template.njk b/test/stubs/transform-pages/template.njk new file mode 100644 index 000000000..7209a4c75 --- /dev/null +++ b/test/stubs/transform-pages/template.njk @@ -0,0 +1,15 @@ +--- +pagination: + data: testdata + size: 4 +testdata: + - item1 + - item2 + - item3 + - item4 + - item5 + - item6 + - item7 + - item8 +--- +
    {% for item in pagination.items %}
  1. {{ item }}
  2. {% endfor %}
diff --git a/test/stubs/use-collection.11ty.js b/test/stubs/use-collection.11ty.js new file mode 100644 index 000000000..98b2adee9 --- /dev/null +++ b/test/stubs/use-collection.11ty.js @@ -0,0 +1,5 @@ +module.exports = function({ collections }) { + return `
    ${collections.post + .map(post => `
  • ${post.data.title}
  • `) + .join("")}
`; +}; diff --git a/test/stubs/viperhtml.11ty.js b/test/stubs/viperhtml.11ty.js new file mode 100644 index 000000000..9b171dcea --- /dev/null +++ b/test/stubs/viperhtml.11ty.js @@ -0,0 +1,9 @@ +const viperHTML = require("viperhtml"); + +// Returns buffer +module.exports = function(data) { + return viperHTML.wire()`
+ This is a viper template, ${data.name} + ${[data.html]} +
`; +}; diff --git a/test/stubs/vue-layout.11ty.js b/test/stubs/vue-layout.11ty.js new file mode 100644 index 000000000..3933a6c8b --- /dev/null +++ b/test/stubs/vue-layout.11ty.js @@ -0,0 +1,20 @@ +const Vue = require("vue"); +const renderer = require("vue-server-renderer").createRenderer({ + template: ` +{{ title }} +` +}); + +module.exports = async function(data) { + var app = new Vue({ + template: "

Hello {{ data.name }}, this is a Vue template.

", + data: function() { + return { data }; + } + // components: { + // 'test': ComponentA + // } + }); + + return renderer.renderToString(app, { title: "Test" }); +}; diff --git a/test/stubs/vue.11ty.js b/test/stubs/vue.11ty.js new file mode 100644 index 000000000..048351292 --- /dev/null +++ b/test/stubs/vue.11ty.js @@ -0,0 +1,16 @@ +const Vue = require("vue"); +const renderer = require("vue-server-renderer").createRenderer(); + +module.exports = async function(templateData) { + var app = new Vue({ + template: "

Hello {{ data.name }}, this is a Vue template.

", + data: { + data: templateData + } + // components: { + // 'test': ComponentA + // } + }); + + return renderer.renderToString(app); +}; diff --git a/test/stubs/writeTestJS/sample.js b/test/stubs/writeTestJS/sample.js new file mode 100644 index 000000000..e9aa24752 --- /dev/null +++ b/test/stubs/writeTestJS/sample.js @@ -0,0 +1 @@ +module.exports = "

Zach

"; diff --git a/test/stubs/writeTestJS/test.11ty.js b/test/stubs/writeTestJS/test.11ty.js new file mode 100644 index 000000000..e9aa24752 --- /dev/null +++ b/test/stubs/writeTestJS/test.11ty.js @@ -0,0 +1 @@ +module.exports = "

Zach

"; diff --git a/test/stubs/writeTestMarkdown/sample.md b/test/stubs/writeTestMarkdown/sample.md new file mode 100644 index 000000000..e9aa24752 --- /dev/null +++ b/test/stubs/writeTestMarkdown/sample.md @@ -0,0 +1 @@ +module.exports = "

Zach

"; diff --git a/test/stubs/writeTestMarkdown/sample2.markdown b/test/stubs/writeTestMarkdown/sample2.markdown new file mode 100644 index 000000000..e9aa24752 --- /dev/null +++ b/test/stubs/writeTestMarkdown/sample2.markdown @@ -0,0 +1 @@ +module.exports = "

Zach

";