This commit is contained in:
2026-03-31 16:38:22 -07:00
commit 38940436a7
2112 changed files with 376929 additions and 0 deletions

26
node_modules/@11ty/eleventy-navigation/.eleventy.js generated vendored Normal file
View File

@ -0,0 +1,26 @@
const pkg = require("./package.json");
const EleventyNavigation = require("./eleventy-navigation");
// export the configuration function for plugin
module.exports = function(eleventyConfig) {
try {
eleventyConfig.versionCheck(pkg["11ty"].compatibility);
} catch(e) {
console.log( `WARN: Eleventy Plugin (${pkg.name}) Compatibility: ${e.message}` );
}
eleventyConfig.addFilter("eleventyNavigation", EleventyNavigation.findNavigationEntries);
eleventyConfig.addFilter("eleventyNavigationBreadcrumb", EleventyNavigation.findBreadcrumbEntries);
eleventyConfig.addFilter("eleventyNavigationToHtml", function(pages, options) {
return EleventyNavigation.toHtml.call(eleventyConfig, pages, options);
});
eleventyConfig.addFilter("eleventyNavigationToMarkdown", function(pages, options) {
return EleventyNavigation.toMarkdown.call(eleventyConfig, pages, options);
});
};
module.exports.navigation = {
find: EleventyNavigation.findNavigationEntries,
findBreadcrumbs: EleventyNavigation.findBreadcrumbEntries,
getDependencyGraph: EleventyNavigation.getDependencyGraph,
};

View File

@ -0,0 +1,22 @@
name: Node Unit Tests
on: [push, pull_request]
permissions: read-all
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: ["ubuntu-latest", "macos-latest", "windows-latest"]
node: ["18", "20", "22", "24"]
name: Node.js ${{ matrix.node }} on ${{ matrix.os }}
steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # 4.1.7
- name: Setup node
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # 4.0.3
with:
node-version: ${{ matrix.node }}
# cache: npm
- run: npm install
- run: npm test
env:
YARN_GPG: no

View File

@ -0,0 +1,25 @@
name: Publish Release to npm
on:
release:
types: [published]
permissions: read-all
jobs:
build:
runs-on: ubuntu-latest
environment: GitHub Publish
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # 4.1.7
- uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # 4.0.3
with:
node-version: "22"
registry-url: 'https://registry.npmjs.org'
- run: npm install -g npm@latest
- run: npm ci
- run: npm test
- if: ${{ github.event.release.tag_name != '' && env.NPM_PUBLISH_TAG != '' }}
run: npm publish --provenance --access=public --tag=${{ env.NPM_PUBLISH_TAG }}
env:
NPM_PUBLISH_TAG: ${{ contains(github.event.release.tag_name, '-beta.') && 'beta' || 'latest' }}

21
node_modules/@11ty/eleventy-navigation/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 20192025 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
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
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.

9
node_modules/@11ty/eleventy-navigation/README.md generated vendored Normal file
View File

@ -0,0 +1,9 @@
<p align="center"><img src="https://www.11ty.dev/img/logo-github.svg" width="200" height="200" alt="11ty Logo"></p>
# eleventy-navigation 🕚⚡️🎈🐀
A plugin for creating hierarchical navigation in Eleventy projects. Supports breadcrumbs too!
Used [in production on 11ty.dev](https://www.11ty.dev/docs/)!
## Read the [Full Documentation on 11ty.dev](https://www.11ty.dev/docs/plugins/navigation/)

View File

@ -0,0 +1,277 @@
const DepGraph = require("dependency-graph").DepGraph;
function findNavigationEntries(nodes = [], key = "") {
let keys = key.split(",").filter(k => Boolean(k));
let pages = {};
for(let entry of nodes) {
let data = entry?.data || {};
if(data?.eleventyNavigation) {
let {eleventyNavigation} = data || {};
let pageKey;
if(!key && !eleventyNavigation.parent) { // top level (no parents)
pageKey = "__default";
} else if(keys.includes(eleventyNavigation.parent)) {
pageKey = eleventyNavigation.parent;
}
if(pageKey) {
if(!pages[pageKey]) {
pages[pageKey] = [];
}
let url = eleventyNavigation.url ?? data?.page?.url;
pages[pageKey].push(Object.assign({ data }, eleventyNavigation, {
...(url ? { url } : {}),
pluginType: "eleventy-navigation",
...(keys.length > 0 ? { parentKey: eleventyNavigation.parent } : {}),
}));
}
}
}
return Object.values(pages).flat().sort(function(a, b) {
if(a.pinned && b.pinned) {
return (a.order || 0) - (b.order || 0);
}
let order = [a.order, b.order];
if(a.pinned) {
order[0] = -Infinity;
}
if(b.pinned) {
order[1] = -Infinity;
}
if(order[0] === undefined && order[1] === undefined) {
return 0;
}
if(order[1] === undefined) {
return -1;
}
if(order[0] === undefined) {
return 1;
}
return order[0] - order[1];
}).map(function(entry) {
if(!entry.title) {
entry.title = entry.key;
}
if(entry.key) {
entry.children = findNavigationEntries(nodes, entry.key);
}
return entry;
});
}
function findDependencies(pages, depGraph, parentKey) {
for( let page of pages ) {
depGraph.addNode(page.key, page);
if(parentKey) {
depGraph.addDependency(page.key, parentKey);
}
if(page.children) {
findDependencies(page.children, depGraph, page.key);
}
}
}
function getDependencyGraph(nodes) {
let pages = findNavigationEntries(nodes);
let graph = new DepGraph();
findDependencies(pages, graph);
return graph;
}
function isOptionMatch(options, name) {
// Liquid.js issue #35
if(Array.isArray(options)) {
return options[options.indexOf(name)]
}
return options[name];
}
function findBreadcrumbEntries(nodes, activeKey, options = {}) {
let graph = getDependencyGraph(nodes);
if (isOptionMatch(options, "allowMissing") && !graph.hasNode(activeKey)) {
// Fail gracefully if the key isn't in the graph
return [];
}
let deps = graph.dependenciesOf(activeKey);
if(isOptionMatch(options, "includeSelf")) {
deps.push(activeKey);
}
return activeKey ? deps.map(key => {
let data = Object.assign({}, graph.getNodeData(key));
delete data.children;
data._isBreadcrumb = true;
return data;
}) : [];
}
function getUrlFilter(eleventyConfig) {
// eleventyConfig.pathPrefix was first available in Eleventy 2.0.0-canary.15
// And in Eleventy 2.0.0-canary.15 we recommend the a built-in transform for pathPrefix
if(eleventyConfig.pathPrefix !== undefined) {
return function(url) {
return url;
};
}
if("getFilter" in eleventyConfig) {
// v0.10.0 and above
return eleventyConfig.getFilter("url");
} else if("nunjucksFilters" in eleventyConfig) {
// backwards compat, hardcoded key
return eleventyConfig.nunjucksFilters.url;
} else {
// Theoretically we could just move on here with a `url => url` but then `pathPrefix`
// would not work and it wouldnt be obvious why—so lets fail loudly to avoid that.
throw new Error("Could not find a `url` filter for the eleventy-navigation plugin in eleventyNavigationToHtml filter.");
}
}
function buildHtmlAttr(name, values) {
// values could be array or string
if (!values || !values.length) {
return '';
}
const valueStr = Array.isArray(values) ? values.join(" ") : values;
return ` ${name}="${valueStr}"`;
}
function buildAllHtmlAttrs(attrs) {
return attrs.reduce((acc, { name, values }) => acc + buildHtmlAttr(name, values), '');
}
function navigationToHtml(pages, options = {}) {
options = Object.assign({
listElement: "ul",
listItemElement: "li",
listClass: "",
listItemClass: "",
listItemHasChildrenClass: "",
activeKey: "",
activeListItemClass: "",
anchorClass: "",
activeAnchorClass: "",
useAriaCurrentAttr: false,
showExcerpt: false,
isChildList: false,
useTopLevelDetails: false,
anchorElementWithoutHref: "a", // default, better to use span
}, options);
let isChildList = !!options.isChildList;
options.isChildList = true;
let urlFilter;
if(pages.length && pages[0].pluginType !== "eleventy-navigation") {
throw new Error("Incorrect argument passed to eleventyNavigationToHtml filter. You must call `eleventyNavigation` or `eleventyNavigationBreadcrumb` first, like: `collection.all | eleventyNavigation | eleventyNavigationToHtml | safe`");
}
return pages.length ? `<${options.listElement}${!isChildList && options.listClass ? ` class="${options.listClass}"` : ''}>${pages.map(entry => {
let liClass = [];
let aClass = [];
let aAttrs = [];
if(options.listItemClass) {
liClass.push(options.listItemClass);
}
if(options.anchorClass) {
aClass.push(options.anchorClass);
}
if(entry.url) {
if(!urlFilter) {
// dont get if not used
urlFilter = getUrlFilter(this);
}
aAttrs.push({name: "href", values: urlFilter(entry.url)})
}
if(options.activeKey === entry.key) {
if(options.activeListItemClass) {
liClass.push(options.activeListItemClass);
}
if(options.activeAnchorClass) {
aClass.push(options.activeAnchorClass);
}
if(options.useAriaCurrentAttr) {
aAttrs.push({ name: "aria-current", values: "page" });
}
}
if(options.listItemHasChildrenClass && entry.children && entry.children.length) {
liClass.push(options.listItemHasChildrenClass);
}
if(aClass.length) {
aAttrs.push({ name: "class", values: aClass });
}
let postfix = "";
// Helper to show pin/order in text:
// let hasOrder = entry.order || entry.order === 0;
// if(process.env.ELEVENTY_RUN_MODE === "serve" && (hasOrder || entry.pinned)) {
// postfix = ` (${entry.pinned ? "📌" : ""}${entry.order ?? ""})`;
// }
let aAttrsStr = buildAllHtmlAttrs(aAttrs);
let hasLink = aAttrs.find(entry => entry.name === "href");
let itemTitle = entry.title + postfix;
let titleHtmlStart = `<a${aAttrsStr}>${itemTitle}</a>`;
// purely defensive use of `useTopLevelDetails` here
if(options.anchorElementWithoutHref && !hasLink) {
titleHtmlStart = `<${options.anchorElementWithoutHref}>${itemTitle}</${options.anchorElementWithoutHref}>`;
}
let titleHtmlEnd = "";
if(options.useTopLevelDetails && !isChildList && entry.children) {
if(hasLink) {
// `<a>` must be sibling: no other interactive elements in <summary>
titleHtmlStart = `${titleHtmlStart}<details><summary>${itemTitle}</summary>`;
} else {
titleHtmlStart = `<details><summary>${itemTitle}</summary>`;
}
titleHtmlEnd = "</details>";
}
let childContentStr = entry.children ? navigationToHtml.call(this, entry.children, options) : "";
return `<${options.listItemElement}${buildHtmlAttr("class", liClass)}>${titleHtmlStart}${options.showExcerpt && entry.excerpt ? `: ${entry.excerpt}` : ""}${childContentStr}${titleHtmlEnd}</${options.listItemElement}>`;
}).join("\n")}</${options.listElement}>` : "";
}
function navigationToMarkdown(pages, options = {}) {
options = Object.assign({
showExcerpt: false,
childDepth: 0
}, options);
let childDepth = 1 + options.childDepth;
options.childDepth++;
let urlFilter;
if(pages.length && pages[0].pluginType !== "eleventy-navigation") {
throw new Error("Incorrect argument passed to eleventyNavigationToMarkdown filter. You must call `eleventyNavigation` or `eleventyNavigationBreadcrumb` first, like: `collection.all | eleventyNavigation | eleventyNavigationToMarkdown | safe`");
}
let indent = (new Array(childDepth)).join(" ") || "";
return pages.length ? `${pages.map(entry => {
if(entry.url && !urlFilter) {
// dont get if not used
urlFilter = getUrlFilter(this);
}
return `${indent}* ${entry.url ? `[` : ""}${entry.title}${entry.url ? `](${urlFilter(entry.url)})` : ""}${options.showExcerpt && entry.excerpt ? `: ${entry.excerpt}` : ""}\n${entry.children ? navigationToMarkdown.call(this, entry.children, options) : ""}`;
}).join("")}` : "";
}
module.exports = {
getDependencyGraph,
findNavigationEntries,
findBreadcrumbEntries,
toHtml: navigationToHtml,
toMarkdown: navigationToMarkdown
};

49
node_modules/@11ty/eleventy-navigation/package.json generated vendored Normal file
View File

@ -0,0 +1,49 @@
{
"name": "@11ty/eleventy-navigation",
"version": "1.0.5",
"publishConfig": {
"access": "public"
},
"description": "A plugin for creating hierarchical navigation in Eleventy projects. Supports breadcrumbs too!",
"main": ".eleventy.js",
"scripts": {
"test": "npx ava",
"sample": "npx @11ty/eleventy --input=sample --output=sample/_site --config=sample/.eleventy.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/11ty/eleventy-navigation.git"
},
"bugs": {
"url": "https://github.com/11ty/eleventy-navigation/issues"
},
"homepage": "https://www.11ty.dev/docs/plugins/navigation/",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/11ty"
},
"keywords": [
"eleventy",
"eleventy-plugin"
],
"author": {
"name": "Zach Leatherman",
"email": "zachleatherman@gmail.com",
"url": "https://zachleat.com/"
},
"license": "MIT",
"11ty": {
"compatibility": ">=0.7 || >=1.0.0-canary"
},
"devDependencies": {
"ava": "^6.4.1"
},
"dependencies": {
"dependency-graph": "^1.0.0"
},
"ava": {
"files": [
"./test/*.js"
]
}
}

View File

@ -0,0 +1,9 @@
const EleventyNavigationPlugin = require("../");
module.exports = function(eleventyConfig) {
eleventyConfig.addPlugin(EleventyNavigationPlugin);
return {
pathPrefix: "/sdljfkaldsjlfka/"
}
};

View File

@ -0,0 +1,5 @@
---
eleventyNavigation:
key: Bats
parent: Mammals
---

View File

@ -0,0 +1,5 @@
---
eleventyNavigation:
key: Humans
parent: Mammals
---

View File

@ -0,0 +1,19 @@
<h2>Full List</h2>
{{ collections.all | eleventyNavigation | dump(2) }}
{{ collections.all | eleventyNavigation | eleventyNavigationToHtml }}
<h2>Breadcrumb for Bats</h2>
{{
collections.all
| eleventyNavigationBreadcrumb: "Bats"
| dump(2)
}}
{{
collections.all
| eleventyNavigationBreadcrumb: "Bats"
| eleventyNavigationToHtml
}}

View File

@ -0,0 +1,4 @@
---
eleventyNavigation:
key: Mammals
---

View File

@ -0,0 +1,4 @@
---
eleventyNavigation:
---
This page has no key.

View File

@ -0,0 +1,3 @@
---
---
This page has no navigation.

View File

@ -0,0 +1,11 @@
<h2>Full List</h2>
{{ collections.all | eleventyNavigation | dump(2) | safe }}
{{ collections.all | eleventyNavigation | eleventyNavigationToHtml | safe }}
<h2>Breadcrumb for Bats</h2>
{{ collections.all | eleventyNavigationBreadcrumb("Bats") | dump(2) | safe }}
{{ collections.all | eleventyNavigationBreadcrumb("Bats") | eleventyNavigationToHtml | safe }}

View File

@ -0,0 +1,747 @@
const test = require("ava");
const EleventyNavigation = require("../eleventy-navigation");
test("Empty navigation", t => {
t.deepEqual(EleventyNavigation.findNavigationEntries(), []);
});
test("One root page navigation", t => {
let obj = EleventyNavigation.findNavigationEntries([
{
data: {
eleventyNavigation: {
key: "root1"
},
page: {
url: "root1.html"
}
}
}
]);
t.is(obj[0].key, "root1");
t.is(obj[0].pluginType, "eleventy-navigation");
// Warning, title must be preserved per the public API
t.is(obj[0].title, "root1");
// Warning, url must be preserved per the public API
t.is(obj[0].url, "root1.html");
t.is(obj[0].children.length, 0);
});
test("One root page navigation with separate title", t => {
let obj = EleventyNavigation.findNavigationEntries([
{
data: {
eleventyNavigation: {
key: "root1",
title: "Another title"
},
page: {
url: "root1.html"
}
}
}
]);
t.is(obj[0].key, "root1");
t.is(obj[0].pluginType, "eleventy-navigation");
t.is(obj[0].title, "Another title");
t.is(obj[0].children.length, 0);
});
test("One root, one child page navigation", t => {
let obj = EleventyNavigation.findNavigationEntries([
{
data: {
eleventyNavigation: {
key: "root1"
},
page: {
url: "root1.html"
}
}
},
{
data: {
eleventyNavigation: {
parent: "root1",
key: "child1"
},
page: {
url: "child1.html"
}
}
}
]);
t.is(obj[0].key, "root1");
t.is(obj[0].children.length, 1);
t.is(obj[0].children[0].parent, "root1");
t.is(obj[0].children[0].key, "child1");
});
test("Three layers deep navigation", t => {
let obj = EleventyNavigation.findNavigationEntries([
{
data: {
eleventyNavigation: {
key: "root1"
},
page: {
url: "root1.html"
}
}
},
{
data: {
eleventyNavigation: {
parent: "root1",
key: "child1"
},
page: {
url: "child1.html"
}
}
},
{
data: {
eleventyNavigation: {
parent: "child1",
key: "grandchild1"
},
page: {
url: "grandchild1.html"
}
}
}
]);
t.is(obj[0].key, "root1");
t.is(obj[0].children.length, 1);
t.is(obj[0].children[0].parent, "root1");
t.is(obj[0].children[0].key, "child1");
t.is(obj[0].children[0].children[0].parent, "child1");
t.is(obj[0].children[0].children[0].key, "grandchild1");
});
test("One root, three child navigation (order)", t => {
let obj = EleventyNavigation.findNavigationEntries([
{
data: {
eleventyNavigation: {
key: "root1"
},
page: {
url: "root1.html"
}
}
},
{
data: {
eleventyNavigation: {
parent: "root1",
key: "child1",
order: 3
},
page: {
url: "child1.html"
}
}
},
{
data: {
eleventyNavigation: {
parent: "root1",
key: "child2",
order: 1
},
page: {
url: "child2.html"
}
}
},
{
data: {
eleventyNavigation: {
parent: "root1",
key: "child3",
order: 2
},
page: {
url: "child3.html"
}
}
},
{
data: {
eleventyNavigation: {
parent: "root1",
key: "child4"
},
page: {
url: "child4.html"
}
}
}
]);
t.is(obj[0].key, "root1");
t.is(obj[0].children.length, 4);
t.is(obj[0].children.map(e => e.key).join(","), "child2,child3,child1,child4");
});
test("One root, three child navigation, one with 0 (order)", t => {
let obj = EleventyNavigation.findNavigationEntries([
{
data: {
eleventyNavigation: {
key: "root1"
},
page: {
url: "root1.html"
}
}
},
{
data: {
eleventyNavigation: {
parent: "root1",
key: "child1",
order: 3
},
page: {
url: "child1.html"
}
}
},
{
data: {
eleventyNavigation: {
parent: "root1",
key: "child2",
order: 0
},
page: {
url: "child2.html"
}
}
},
{
data: {
eleventyNavigation: {
parent: "root1",
key: "child3",
order: 2
},
page: {
url: "child3.html"
}
}
},
{
data: {
eleventyNavigation: {
parent: "root1",
key: "child4"
},
page: {
url: "child4.html"
}
}
}
]);
t.is(obj[0].key, "root1");
t.is(obj[0].children.length, 4);
t.is(obj[0].children.map(e => e.key).join(","), "child2,child3,child1,child4");
});
test("One root, three child navigation (implied order)", t => {
let obj = EleventyNavigation.findNavigationEntries([
{
data: {
eleventyNavigation: {
key: "root1"
},
page: {
url: "root1.html"
}
}
},
{
data: {
eleventyNavigation: {
parent: "root1",
key: "child1",
order: 3
},
page: {
url: "child1.html"
}
}
},
{
data: {
eleventyNavigation: {
parent: "root1",
key: "child2"
},
page: {
url: "child2.html"
}
}
},
{
data: {
eleventyNavigation: {
parent: "root1",
key: "child3",
order: -1
},
page: {
url: "child3.html"
}
}
}
]);
t.is(obj[0].key, "root1");
t.is(obj[0].children.length, 3);
t.is(obj[0].children.map(e => e.key).join(","), "child3,child1,child2");
});
test("Show throw an error without a config", t => {
let obj = EleventyNavigation.findNavigationEntries([
{
data: {
eleventyNavigation: {
key: "root1"
},
page: {
url: "root1.html"
}
}
}
]);
t.throws(() => {
EleventyNavigation.toHtml(obj);
});
});
let fakeConfig = {
nunjucksFilters: {
url: url => url
}
};
let fakeNavigationEntries = [
{
data: {
eleventyNavigation: {
key: "root1"
},
page: {
url: "root1.html"
}
}
},
{
data: {
eleventyNavigation: {
parent: "root1",
key: "child1"
},
page: {
url: "child1.html"
}
}
}
];
let fakeNavigationEntriesEmptyUrl = [
{
data: {
eleventyNavigation: {
key: "root1"
},
page: {
url: "root1.html"
}
}
},
{
data: {
eleventyNavigation: {
parent: "root1",
key: "child1"
},
page: {
url: false
}
}
}
];
test("Checking active class on output HTML", t => {
let obj = EleventyNavigation.findNavigationEntries(fakeNavigationEntries);
let html = EleventyNavigation.toHtml.call(fakeConfig, obj);
t.is(html, `<ul><li><a href="root1.html">root1</a><ul><li><a href="child1.html">child1</a></li></ul></li></ul>`);
let activeHtmlItem = EleventyNavigation.toHtml.call(fakeConfig, obj, {
activeKey: "child1",
activeListItemClass: "this-is-the-active-item"
});
t.is(activeHtmlItem, `<ul><li><a href="root1.html">root1</a><ul><li class="this-is-the-active-item"><a href="child1.html">child1</a></li></ul></li></ul>`);
let activeHtmlAnchor = EleventyNavigation.toHtml.call(fakeConfig, obj, {
activeKey: "child1",
activeAnchorClass: "this-is-the-active-anchor"
});
t.is(activeHtmlAnchor, `<ul><li><a href="root1.html">root1</a><ul><li><a href="child1.html" class="this-is-the-active-anchor">child1</a></li></ul></li></ul>`);
let activeHtmlItemAndAnchor = EleventyNavigation.toHtml.call(fakeConfig, obj, {
activeKey: "child1",
activeListItemClass: "this-is-the-active-item",
activeAnchorClass: "this-is-the-active-anchor"
});
t.is(activeHtmlItemAndAnchor, `<ul><li><a href="root1.html">root1</a><ul><li class="this-is-the-active-item"><a href="child1.html" class="this-is-the-active-anchor">child1</a></li></ul></li></ul>`);
});
test("Checking aria-current option on output HTML", t => {
let obj = EleventyNavigation.findNavigationEntries(fakeNavigationEntries);
let html = EleventyNavigation.toHtml.call(fakeConfig, obj);
t.true(html.indexOf(`<li><a href="child1.html">child1</a></li>`) > -1);
let activeHtmlAnchor = EleventyNavigation.toHtml.call(fakeConfig, obj, {
activeKey: "child1",
useAriaCurrentAttr: true
});
t.true(activeHtmlAnchor.indexOf(`<li><a href="child1.html" aria-current="page">child1</a></li>`) > -1);
});
test("Checking has children class on output HTML", t => {
let obj = EleventyNavigation.findNavigationEntries(fakeNavigationEntries);
let activeHtml = EleventyNavigation.toHtml.call(fakeConfig, obj, {
listItemHasChildrenClass: "item-has-children"
});
t.true(activeHtml.indexOf(`<li class="item-has-children"><a href="root1.html">root1</a>`) > -1);
t.true(activeHtml.indexOf(`<li><a href="child1.html">child1</a></li>`) > -1);
});
test("URL override", t => {
let obj = EleventyNavigation.findNavigationEntries([
{
data: {
eleventyNavigation: {
key: "root1",
url: "https://www.zachleat.com/"
},
page: {
url: "root1.html"
}
}
}
]);
t.is(obj[0].url, "https://www.zachleat.com/");
});
test("Breadcrumbs", t => {
let obj = EleventyNavigation.findBreadcrumbEntries([
{
data: {
eleventyNavigation: {
key: "root1"
},
page: {
url: "root1.html"
}
}
},
{
data: {
eleventyNavigation: {
parent: "root1",
key: "child1"
},
page: {
url: "child1.html"
}
}
},
{
data: {
eleventyNavigation: {
parent: "child1",
key: "grandchild1"
},
page: {
url: "grandchild1.html"
}
}
}
], "grandchild1");
t.is(obj.length, 2);
t.is(obj[0].key, "root1");
t.is(obj[1].key, "child1");
});
test("Breadcrumbs (include self)", t => {
let obj = EleventyNavigation.findBreadcrumbEntries([
{
data: {
eleventyNavigation: {
key: "root1"
},
page: {
url: "root1.html"
}
}
},
{
data: {
eleventyNavigation: {
parent: "root1",
key: "child1"
},
page: {
url: "child1.html"
}
}
},
{
data: {
eleventyNavigation: {
parent: "child1",
key: "grandchild1"
},
page: {
url: "grandchild1.html"
}
}
}
], "grandchild1", {
includeSelf: true
});
t.is(obj.length, 3);
t.is(obj[0].key, "root1");
t.is(obj[1].key, "child1");
t.is(obj[2].key, "grandchild1");
});
test("Breadcrumbs (options.allowMissing)", t => {
const entries = EleventyNavigation.findBreadcrumbEntries(
[],
"orphan",
{allowMissing: true}
);
t.is(entries.length, 0);
});
test("Output markdown", t => {
let obj = EleventyNavigation.findNavigationEntries([
{
data: {
eleventyNavigation: {
key: "root1"
},
page: {
url: "root1.html"
}
}
},
{
data: {
eleventyNavigation: {
parent: "root1",
key: "child1"
},
page: {
url: "child1.html"
}
}
},
{
data: {
eleventyNavigation: {
parent: "child1",
key: "grandchild1"
},
page: {
url: "grandchild1.html"
}
}
}
]);
let html = EleventyNavigation.toMarkdown.call(fakeConfig, obj);
t.is(html, `* [root1](root1.html)
* [child1](child1.html)
* [grandchild1](grandchild1.html)
`);
});
test("Navigation entry contains page data", t => {
let obj = EleventyNavigation.findNavigationEntries([
{
data: {
eleventyNavigation: {
key: "root1"
},
page: {
url: "root1.html"
},
tags: [
"robot",
"lesbian"
],
collections: {}
}
}
]);
// Page data like tags should be included in the obj
t.deepEqual(obj[0].data.tags, ["robot", "lesbian"]);
});
test("Missing url", t => {
let obj = EleventyNavigation.findNavigationEntries([
{
data: {
eleventyNavigation: {
key: "root1",
}
}
}
]);
t.is(EleventyNavigation.toHtml.call(fakeConfig, obj), `<ul><li><a>root1</a></li></ul>`);
});
test("Multiple roots", t => {
let obj = EleventyNavigation.findNavigationEntries([
{
data: {
eleventyNavigation: {
key: "root1"
}
}
},
{
data: {
eleventyNavigation: {
parent: "root1",
key: "child1",
}
}
},
{
data: {
eleventyNavigation: {
key: "root2"
},
}
},
{
data: {
eleventyNavigation: {
parent: "root2",
key: "child3",
}
}
}
]);
t.deepEqual(obj.map(e => e.key), ["root1", "root2"]);
t.deepEqual(obj[0].children.map(e => e.key), ["child1"]);
t.deepEqual(obj[1].children.map(e => e.key), ["child3"]);
});
test("Multiple roots, multiple keys", t => {
let obj = EleventyNavigation.findNavigationEntries([
{
data: {
eleventyNavigation: {
key: "root1"
}
}
},
{
data: {
eleventyNavigation: {
parent: "root1",
key: "child1",
}
}
},
{
data: {
eleventyNavigation: {
key: "root2"
},
}
},
{
data: {
eleventyNavigation: {
parent: "root2",
key: "child3",
}
}
}
], "root1,root2");
t.deepEqual(obj.map(e => e.key), ["child1", "child3"]);
t.deepEqual(obj[0].children.map(e => e.key), []);
t.deepEqual(obj[1].children.map(e => e.key), []);
});
test("Breadcrumb include self", t => {
let nodes = [
{
data: {
eleventyNavigation: {
key: "root1",
}
}
}
];
let obj = EleventyNavigation.findBreadcrumbEntries(nodes, "root1", { includeSelf: true });
t.is(EleventyNavigation.toHtml(obj), `<ul><li><a>root1</a></li></ul>`);
});
test("Breadcrumb include self (Liquid.js #35)", t => {
let nodes = [
{
data: {
eleventyNavigation: {
key: "root1",
}
}
}
];
let obj = EleventyNavigation.findBreadcrumbEntries(nodes, "root1", [ "includeSelf", true ]);
t.is(EleventyNavigation.toHtml(obj), `<ul><li><a>root1</a></li></ul>`);
});
test("Use top level details", t => {
let obj = EleventyNavigation.findNavigationEntries(fakeNavigationEntries);
let html = EleventyNavigation.toHtml.call(fakeConfig, obj, {
useTopLevelDetails: true
});
t.is(html, `<ul><li><a href="root1.html">root1</a><details><summary>root1</summary><ul><li><a href="child1.html">child1</a></li></ul></details></li></ul>`);
});
test("Use top level details with empty url", t => {
let obj = EleventyNavigation.findNavigationEntries(fakeNavigationEntriesEmptyUrl);
let html = EleventyNavigation.toHtml.call(fakeConfig, obj, {
useTopLevelDetails: true,
anchorElementWithoutHref: "span",
});
t.is(html, `<ul><li><a href="root1.html">root1</a><details><summary>root1</summary><ul><li><span>child1</span></li></ul></details></li></ul>`);
});