Update snapshot tests to only save snapshot for one render strategy
This commit is contained in:
parent
8bb3ff5266
commit
e930333fc2
|
@ -1,256 +0,0 @@
|
|||
h5 {
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
}
|
||||
.footer {
|
||||
margin: 16px;
|
||||
}
|
||||
.footer p {
|
||||
text-align: center;
|
||||
}
|
||||
summary {
|
||||
display: list-item;
|
||||
}
|
||||
.details-collapsible {
|
||||
display: inline-block;
|
||||
border: 1px solid #aaa;
|
||||
border-radius: 4px;
|
||||
padding: 0.5em 0.5em 0;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
.collapse-details {
|
||||
font-weight: bold;
|
||||
margin: -0.5em -0.5em 0;
|
||||
padding: 0.5em;
|
||||
}
|
||||
.details-collapsible[open] {
|
||||
padding: 0.5em;
|
||||
}
|
||||
.details-collapsible[open] .collapse-details {
|
||||
border-bottom: 1px solid #aaa;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
.details-collapsible .collapse-details.collapse-spell::before {
|
||||
content: "Click to show spell";
|
||||
}
|
||||
.details-collapsible[open] .collapse-details.collapse-spell::before {
|
||||
content: "Click to hide spell";
|
||||
}
|
||||
/* if the collapsible is closed, hide the "hide recipe" message */
|
||||
.details-collapsible:not([open]) > .collapse-details > .collapse-recipe-hide {
|
||||
display: none;
|
||||
visibility: hidden;
|
||||
}
|
||||
/* if the collapsible is open, hide the "show recipe" message */
|
||||
.details-collapsible[open] > .collapse-details > .collapse-recipe-show {
|
||||
display: none;
|
||||
visibility: hidden;
|
||||
}
|
||||
blockquote.crafting-info {
|
||||
font-size: inherit;
|
||||
}
|
||||
/* we do a bit of crafting (mostly stolen from https://computercraft.info/wiki) */
|
||||
.crafting-tables {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
max-width: 256px;
|
||||
}
|
||||
@media (min-width: 992px) {
|
||||
.crafting-tables {
|
||||
max-width: calc(2*256px + 1*4px);
|
||||
}
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
.crafting-tables {
|
||||
max-width: calc(3*256px + 2*4px);
|
||||
}
|
||||
}
|
||||
.details-collapsible .crafting-tables {
|
||||
margin-top: -5px;
|
||||
}
|
||||
.crafting-tables h5 {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.crafting-table {
|
||||
position: relative;
|
||||
width: 256px;
|
||||
height: 132px;
|
||||
background-color: #c6c6c6;
|
||||
border-radius: 16px;
|
||||
}
|
||||
.crafting-table > img { /* background */
|
||||
image-rendering: pixelated;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.crafting-table-grid {
|
||||
position: absolute;
|
||||
top: 14px;
|
||||
left: 14px;
|
||||
display: grid;
|
||||
grid: repeat(3, 32px) / repeat(3, 32px);
|
||||
gap: 4px;
|
||||
}
|
||||
.crafting-table-result {
|
||||
position: absolute;
|
||||
top: 50px;
|
||||
left: 202px;
|
||||
}
|
||||
.crafting-table-result .badge {
|
||||
position: absolute;
|
||||
background-color: #292929;
|
||||
bottom: -4px;
|
||||
right: -4px;
|
||||
}
|
||||
.item-texture {
|
||||
image-rendering: pixelated;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
.multi-textures .texture {
|
||||
position: absolute;
|
||||
}
|
||||
.multi-textures .texture:not(.multi-texture-active) {
|
||||
z-index: -1;
|
||||
}
|
||||
.spotlight {
|
||||
position: relative;
|
||||
width: min-content;
|
||||
margin: -6px 0 8px 0;
|
||||
}
|
||||
.spotlight .texture {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
}
|
||||
a.permalink {
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
a.permalink:hover {
|
||||
color: lightgray;
|
||||
}
|
||||
p {
|
||||
margin: 0.5ex 0;
|
||||
}
|
||||
p.fake-li {
|
||||
margin: 0;
|
||||
}
|
||||
p.fake-li::before {
|
||||
content: "\2022";
|
||||
margin: 1ex;
|
||||
}
|
||||
.linkout::before {
|
||||
content: "Link: ";
|
||||
}
|
||||
p.todo-note {
|
||||
font-style: italic;
|
||||
color: lightgray;
|
||||
}
|
||||
.obfuscated {
|
||||
filter: blur(1em);
|
||||
user-select: none;
|
||||
}
|
||||
.spoilered {
|
||||
filter: blur(1ex);
|
||||
transition: filter 0.04s linear;
|
||||
-moz-transition: filter 0.04s linear;
|
||||
}
|
||||
.spoilered:hover {
|
||||
filter: blur(0.5ex);
|
||||
}
|
||||
.spoilered.unspoilered {
|
||||
filter: blur(0);
|
||||
}
|
||||
canvas.spell-viz {
|
||||
--dot-color: #777f;
|
||||
--dot-color: #777f;
|
||||
--start-dot-color: #f009;
|
||||
--moving-dot-color: #0fa9;
|
||||
--path-color: darkgray;
|
||||
--visited-path-color: #0c8;
|
||||
--dot-scale: 0.0625;
|
||||
--moving-dot-scale: 0.125;
|
||||
--line-scale: 0.08333;
|
||||
--pausetext-scale: 0.5;
|
||||
--dark-mode: 0;
|
||||
}
|
||||
.dropdown-menu .divider {
|
||||
margin: 4px 0;
|
||||
}
|
||||
.nobr {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.hidden {
|
||||
display: none;
|
||||
visibility: hidden;
|
||||
}
|
||||
@media (min-width: 40rem) {
|
||||
.book-body {
|
||||
width: 70%;
|
||||
left: 5%;
|
||||
position: relative;
|
||||
float: left;
|
||||
}
|
||||
.toc-container {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
width: 25%;
|
||||
float: left;
|
||||
max-height: 80vh;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
.toc-permalink {
|
||||
display: none;
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background-color: #201a20;
|
||||
color: #ddd;
|
||||
}
|
||||
.jumbotron {
|
||||
background-color: #323;
|
||||
}
|
||||
.navbar-default {
|
||||
background-color: #2b1f2b;
|
||||
border-color: #080808;
|
||||
}
|
||||
/* please help, i'm not a frontend dev, i'm shocked this works at all */
|
||||
.navbar-default .navbar-brand,
|
||||
.navbar-default .navbar-nav > li > a,
|
||||
.navbar-default .navbar-nav > li > a:focus {
|
||||
color: #aaa;
|
||||
}
|
||||
.navbar-default .navbar-brand:focus,
|
||||
.navbar-default .navbar-brand:hover,
|
||||
.navbar-default .navbar-nav > li > a:hover {
|
||||
color: #ccc;
|
||||
}
|
||||
.navbar-default .navbar-nav > .open > a,
|
||||
.navbar-default .navbar-nav > .open > a:focus,
|
||||
.navbar-default .navbar-nav > .open > a:hover {
|
||||
color: #bbb;
|
||||
background-color: #1e131e;
|
||||
}
|
||||
.dropdown-menu {
|
||||
background-color: #402a40;
|
||||
min-width: 120px;
|
||||
}
|
||||
.dropdown-menu .divider {
|
||||
background-color: #080808;
|
||||
}
|
||||
.dropdown-menu > li > a {
|
||||
color: #bbb;
|
||||
}
|
||||
.dropdown-menu > li > a:focus,
|
||||
.dropdown-menu > li > a:hover {
|
||||
color: #ccc;
|
||||
background-color: #553455;
|
||||
}
|
||||
canvas.spell-viz {
|
||||
/* hack */
|
||||
--dark-mode: 1;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,476 +0,0 @@
|
|||
"use strict";
|
||||
import semver from 'https://cdn.jsdelivr.net/npm/semver@7.5.4/+esm';
|
||||
const speeds = [0, 0.25, 0.5, 1, 2, 4];
|
||||
const scrollThreshold = 100;
|
||||
const rfaQueue = [];
|
||||
const colorCache = new Map();
|
||||
function getColorRGB(ctx, str) {
|
||||
if (!colorCache.has(str)) {
|
||||
ctx.fillStyle = str;
|
||||
ctx.clearRect(0, 0, 1, 1);
|
||||
ctx.fillRect(0, 0, 1, 1);
|
||||
const imgData = ctx.getImageData(0, 0, 1, 1);
|
||||
colorCache.set(str, imgData.data);
|
||||
}
|
||||
return colorCache.get(str);
|
||||
}
|
||||
function startAngle(str) {
|
||||
switch (str) {
|
||||
case "east":
|
||||
return 0;
|
||||
case "north_east":
|
||||
return 1;
|
||||
case "north_west":
|
||||
return 2;
|
||||
case "west":
|
||||
return 3;
|
||||
case "south_west":
|
||||
return 4;
|
||||
case "south_east":
|
||||
return 5;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
function offsetAngle(str) {
|
||||
switch (str) {
|
||||
case "w":
|
||||
return 0;
|
||||
case "q":
|
||||
return 1;
|
||||
case "a":
|
||||
return 2;
|
||||
case "s":
|
||||
return 3;
|
||||
case "d":
|
||||
return 4;
|
||||
case "e":
|
||||
return 5;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
function initializeElem(canvas) {
|
||||
const str = canvas.dataset.string;
|
||||
let angle = startAngle(canvas.dataset.start);
|
||||
const perWorld = canvas.dataset.perWorld === "True";
|
||||
// build geometry
|
||||
const points = [[0, 0]];
|
||||
let lastPoint = points[0];
|
||||
let minPoint = lastPoint,
|
||||
maxPoint = lastPoint;
|
||||
for (const ch of "w" + str) {
|
||||
const addAngle = offsetAngle(ch);
|
||||
if (addAngle < 0) continue;
|
||||
angle = (angle + addAngle) % 6;
|
||||
const trueAngle = (Math.PI / 3) * angle;
|
||||
const [lx, ly] = lastPoint;
|
||||
const newPoint = [lx + Math.cos(trueAngle), ly - Math.sin(trueAngle)];
|
||||
points.push(newPoint);
|
||||
lastPoint = newPoint;
|
||||
const [mix, miy] = minPoint;
|
||||
minPoint = [Math.min(mix, newPoint[0]), Math.min(miy, newPoint[1])];
|
||||
const [max, may] = maxPoint;
|
||||
maxPoint = [Math.max(max, newPoint[0]), Math.max(may, newPoint[1])];
|
||||
}
|
||||
const size = Math.min(canvas.width, canvas.height) * 0.8;
|
||||
const scale =
|
||||
size /
|
||||
Math.max(3, Math.max(maxPoint[1] - minPoint[1], maxPoint[0] - minPoint[0]));
|
||||
const center = [
|
||||
(minPoint[0] + maxPoint[0]) * 0.5,
|
||||
(minPoint[1] + maxPoint[1]) * 0.5,
|
||||
];
|
||||
const truePoints = points.map((p) => [
|
||||
canvas.width * 0.5 + scale * (p[0] - center[0]),
|
||||
canvas.height * 0.5 + scale * (p[1] - center[1]),
|
||||
]);
|
||||
let uniqPoints = [];
|
||||
l1: for (const point of truePoints) {
|
||||
for (const pt of uniqPoints) {
|
||||
if (
|
||||
Math.abs(point[0] - pt[0]) < 0.00001 &&
|
||||
Math.abs(point[1] - pt[1]) < 0.00001
|
||||
) {
|
||||
continue l1;
|
||||
}
|
||||
}
|
||||
uniqPoints.push(point);
|
||||
}
|
||||
// rendering code
|
||||
const speed = 0.0025;
|
||||
const context = canvas.getContext("2d");
|
||||
const negaProgress = -3;
|
||||
let progress = 0;
|
||||
let scrollTimeout = 1e309;
|
||||
let speedLevel = 3;
|
||||
let speedIncrement = 0;
|
||||
function speedScale() {
|
||||
return speeds[speedLevel];
|
||||
}
|
||||
const style = getComputedStyle(canvas);
|
||||
const getProp = (n) => style.getPropertyValue(n);
|
||||
const tick = (dt) => {
|
||||
scrollTimeout += dt;
|
||||
if (canvas.offsetParent === null) return;
|
||||
const strokeStyle = getProp("--path-color");
|
||||
const strokeVisitedStyle = getProp("--visited-path-color");
|
||||
const startDotStyle = getProp("--start-dot-color");
|
||||
const dotStyle = getProp("--dot-color");
|
||||
const movDotStyle = getProp("--moving-dot-color");
|
||||
const strokeWidth = scale * +getProp("--line-scale");
|
||||
const dotRadius = scale * +getProp("--dot-scale");
|
||||
const movDotRadius = scale * +getProp("--moving-dot-scale");
|
||||
const pauseScale = scale * +getProp("--pausetext-scale");
|
||||
const bodyBg = scale * +getProp("--pausetext-scale");
|
||||
const darkMode = +getProp("--dark-mode");
|
||||
const bgColors = getColorRGB(
|
||||
context,
|
||||
getComputedStyle(document.body).backgroundColor
|
||||
);
|
||||
if (!perWorld) {
|
||||
progress +=
|
||||
speed * dt * (progress > 0 ? speedScale() : Math.sqrt(speedScale()));
|
||||
}
|
||||
if (progress >= truePoints.length - 1) {
|
||||
progress = negaProgress;
|
||||
}
|
||||
let ix = Math.floor(progress),
|
||||
frac = progress - ix,
|
||||
core = null,
|
||||
fadeColor = 0;
|
||||
if (ix < 0) {
|
||||
const rawFade = (2 * progress) / negaProgress - 1;
|
||||
fadeColor = 1 - Math.abs(rawFade);
|
||||
context.strokeStyle = rawFade > 0 ? strokeVisitedStyle : strokeStyle;
|
||||
ix = rawFade > 0 ? truePoints.length - 2 : 0;
|
||||
frac = +(rawFade > 0);
|
||||
} else {
|
||||
context.strokeStyle = strokeVisitedStyle;
|
||||
}
|
||||
const [lx, ly] = truePoints[ix];
|
||||
const [rx, ry] = truePoints[ix + 1];
|
||||
core = [lx + (rx - lx) * frac, ly + (ry - ly) * frac];
|
||||
context.clearRect(0, 0, canvas.width, canvas.height);
|
||||
context.beginPath();
|
||||
context.lineWidth = strokeWidth;
|
||||
context.moveTo(truePoints[0][0], truePoints[0][1]);
|
||||
for (let i = 1; i < ix + 1; i++) {
|
||||
context.lineTo(truePoints[i][0], truePoints[i][1]);
|
||||
}
|
||||
context.lineTo(core[0], core[1]);
|
||||
context.stroke();
|
||||
context.beginPath();
|
||||
context.strokeStyle = strokeStyle;
|
||||
context.moveTo(core[0], core[1]);
|
||||
for (let i = ix + 1; i < truePoints.length; i++) {
|
||||
context.lineTo(truePoints[i][0], truePoints[i][1]);
|
||||
}
|
||||
context.stroke();
|
||||
for (let i = 0; i < uniqPoints.length; i++) {
|
||||
context.beginPath();
|
||||
context.fillStyle = i == 0 && !perWorld ? startDotStyle : dotStyle;
|
||||
const radius = i == 0 && !perWorld ? movDotRadius : dotRadius;
|
||||
context.arc(uniqPoints[i][0], uniqPoints[i][1], radius, 0, 2 * Math.PI);
|
||||
context.fill();
|
||||
}
|
||||
if (!perWorld) {
|
||||
context.beginPath();
|
||||
context.fillStyle = movDotStyle;
|
||||
context.arc(core[0], core[1], movDotRadius, 0, 2 * Math.PI);
|
||||
context.fill();
|
||||
}
|
||||
if (fadeColor) {
|
||||
context.fillStyle = `rgba(${bgColors[0]}, ${bgColors[1]}, ${bgColors[2]}, ${fadeColor})`;
|
||||
context.fillRect(0, 0, canvas.width, canvas.height);
|
||||
}
|
||||
if (scrollTimeout <= 2000) {
|
||||
context.fillStyle = `rgba(200, 200, 200, ${
|
||||
(2000 - scrollTimeout) / 1000
|
||||
})`;
|
||||
context.font = `${pauseScale}px sans-serif`;
|
||||
context.fillText(
|
||||
// these variables are filled by Jinja
|
||||
// slightly scuffed, but it works for now
|
||||
speedScale() ? `${speedScale()}x` : "Paused",
|
||||
0.2 * scale,
|
||||
canvas.height - 0.2 * scale
|
||||
);
|
||||
}
|
||||
};
|
||||
rfaQueue.push(tick);
|
||||
// scrolling input
|
||||
if (!perWorld) {
|
||||
canvas.addEventListener("wheel", (ev) => {
|
||||
speedIncrement += ev.deltaY;
|
||||
const oldSpeedLevel = speedLevel;
|
||||
if (speedIncrement >= scrollThreshold) {
|
||||
speedLevel--;
|
||||
} else if (speedIncrement <= -scrollThreshold) {
|
||||
speedLevel++;
|
||||
}
|
||||
if (oldSpeedLevel != speedLevel) {
|
||||
speedIncrement = 0;
|
||||
speedLevel = Math.max(0, Math.min(speeds.length - 1, speedLevel));
|
||||
scrollTimeout = 0;
|
||||
}
|
||||
ev.preventDefault();
|
||||
});
|
||||
}
|
||||
}
|
||||
let cycleNodes = [];
|
||||
function hookLoad(elem) {
|
||||
let init = false;
|
||||
const canvases = elem.querySelectorAll("canvas");
|
||||
elem.addEventListener("toggle", () => {
|
||||
if (!init) {
|
||||
canvases.forEach(initializeElem);
|
||||
init = true;
|
||||
}
|
||||
cycleNodes = document.querySelectorAll(".details-collapsible[open] .cycle-textures");
|
||||
if (elem.hasAttribute("open")) {
|
||||
for (const child of cycleNodes) {
|
||||
setEnabledMultiTexture(child, cycleIndex);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
function hookToggle(elem) {
|
||||
const details = Array.from(
|
||||
document.querySelectorAll("details." + elem.dataset.target)
|
||||
);
|
||||
elem.addEventListener("click", () => {
|
||||
if (details.some((x) => x.open)) {
|
||||
details.forEach((x) => (x.open = false));
|
||||
} else {
|
||||
details.forEach((x) => (x.open = true));
|
||||
}
|
||||
});
|
||||
}
|
||||
const params = new URLSearchParams(document.location.search);
|
||||
function hookSpoiler(elem) {
|
||||
if (params.get("nospoiler") !== null) {
|
||||
elem.classList.add("unspoilered");
|
||||
} else {
|
||||
const thunk = (ev) => {
|
||||
if (!elem.classList.contains("unspoilered")) {
|
||||
ev.preventDefault();
|
||||
ev.stopImmediatePropagation();
|
||||
elem.classList.add("unspoilered");
|
||||
}
|
||||
elem.removeEventListener("click", thunk);
|
||||
};
|
||||
elem.addEventListener("click", thunk);
|
||||
if (elem instanceof HTMLAnchorElement) {
|
||||
const href = elem.getAttribute("href");
|
||||
if (href.startsWith("#")) {
|
||||
elem.addEventListener("click", () =>
|
||||
document
|
||||
.getElementById(href.substring(1))
|
||||
.querySelector(".spoilered")
|
||||
.classList.add("unspoilered")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let startTime = null;
|
||||
function hookSyncAnimations(elem) {
|
||||
elem.addEventListener("animationstart", (e) => {
|
||||
for (const anim of e.target.getAnimations()) {
|
||||
if (startTime == null) {
|
||||
startTime = anim.startTime;
|
||||
} else {
|
||||
anim.startTime = startTime;
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
let currentGaslight = 0;
|
||||
let intersectingGaslights = 0;
|
||||
let lastLookTimeMs = 0;
|
||||
function startGaslighting(timeMs) {
|
||||
let newGaslight = Math.round(20 * (timeMs - lastLookTimeMs) / 1000);
|
||||
if (newGaslight >= 40) {
|
||||
currentGaslight = newGaslight - 40;
|
||||
}
|
||||
for (const elem of gaslightNodes) {
|
||||
setEnabledMultiTexture(elem, currentGaslight);
|
||||
}
|
||||
}
|
||||
function stopGaslighting(timeMs) {
|
||||
lastLookTimeMs = timeMs;
|
||||
for (const elem of gaslightNodes) {
|
||||
setEnabledMultiTexture(elem, null);
|
||||
}
|
||||
}
|
||||
let isFirstIntersectionEvent = true;
|
||||
function hookIntersectionObserver(entries) {
|
||||
const wasLooking = intersectingGaslights > 0;
|
||||
let earliestStartMs = Number.MAX_VALUE;
|
||||
let latestStopMs = 0;
|
||||
for (const entry of entries) {
|
||||
if (entry.isIntersecting) {
|
||||
intersectingGaslights++;
|
||||
earliestStartMs = Math.min(earliestStartMs, entry.time);
|
||||
} else {
|
||||
if (!isFirstIntersectionEvent) intersectingGaslights--;
|
||||
latestStopMs = Math.max(latestStopMs, entry.time);
|
||||
}
|
||||
}
|
||||
isFirstIntersectionEvent = false;
|
||||
intersectingGaslights = Math.max(intersectingGaslights, 0);
|
||||
const isLooking = intersectingGaslights > 0;
|
||||
if (!wasLooking && isLooking) {
|
||||
startGaslighting(earliestStartMs);
|
||||
} else if (wasLooking && !isLooking) {
|
||||
stopGaslighting(latestStopMs);
|
||||
}
|
||||
}
|
||||
function hookVisibilityChange() {
|
||||
const time = performance.now();
|
||||
if (document.visibilityState === "visible") {
|
||||
startGaslighting(time);
|
||||
} else {
|
||||
stopGaslighting(time);
|
||||
}
|
||||
}
|
||||
let gaslightNodes;
|
||||
let cycleIndex = 0;
|
||||
let cycleTimeoutID;
|
||||
function setEnabledMultiTexture(elem, index) {
|
||||
Array.from(elem.children).forEach((child, i) => {
|
||||
if (index !== null && i === (index % elem.children.length)) {
|
||||
child.classList.add("multi-texture-active");
|
||||
} else {
|
||||
child.classList.remove("multi-texture-active");
|
||||
}
|
||||
});
|
||||
}
|
||||
function doCycleTexturesForever() {
|
||||
cycleIndex += 1;
|
||||
for (const elem of cycleNodes) {
|
||||
setEnabledMultiTexture(elem, cycleIndex);
|
||||
}
|
||||
cycleTimeoutID = setTimeout(doCycleTexturesForever, 2000);
|
||||
}
|
||||
// these are filled by Jinja
|
||||
const BOOK_URL = "https://object-object.github.io/HexMod";
|
||||
const VERSION = "latest";
|
||||
const LANG = "en_us";
|
||||
// Creates an element in the form `<li><a href=${href}>${text}</a></li>`
|
||||
function dropdownItem(text, href) {
|
||||
let a = document.createElement("a");
|
||||
a.href = href;
|
||||
a.textContent = text;
|
||||
let li = document.createElement("li");
|
||||
li.appendChild(a);
|
||||
return li;
|
||||
}
|
||||
function versionDropdownItem(sitemap, version) {
|
||||
const {defaultPath, langPaths} = sitemap[version];
|
||||
// link to the current language if available, else link to the default language
|
||||
let path;
|
||||
if (langPaths.hasOwnProperty(LANG)) {
|
||||
path = langPaths[LANG];
|
||||
} else {
|
||||
path = defaultPath;
|
||||
}
|
||||
return dropdownItem(version, BOOK_URL + path);
|
||||
}
|
||||
function versionDropdownItems(sitemap, versions) {
|
||||
return versions.map((version) => (
|
||||
versionDropdownItem(sitemap, version)
|
||||
));
|
||||
}
|
||||
function dropdownSeparator() {
|
||||
let li = document.createElement("li");
|
||||
li.className = "divider";
|
||||
li.setAttribute("role", "separator");
|
||||
return li;
|
||||
}
|
||||
// Like array.filter(predicate), but also returns the items which didn't match the filter.
|
||||
function partition(array, predicate) {
|
||||
let matched = [];
|
||||
let unmatched = [];
|
||||
array.forEach((value, index) => {
|
||||
if (predicate(value, index, array)) {
|
||||
matched.push(value);
|
||||
} else {
|
||||
unmatched.push(value);
|
||||
}
|
||||
});
|
||||
return [matched, unmatched];
|
||||
}
|
||||
function sortSitemapVersions(sitemap) {
|
||||
let [versions, branches] = partition(Object.keys(sitemap), (v) => semver.valid(v) != null);
|
||||
// branches ascending, versions descending
|
||||
// eg. ["dev", "main"], ["0.10.0", "0.9.0"]
|
||||
branches.sort();
|
||||
versions.sort(semver.rcompare);
|
||||
return [branches, versions];
|
||||
}
|
||||
// Fills the version dropdown menus and the "old version" message.
|
||||
function addDropdowns(sitemap) {
|
||||
let [branches, versions] = sortSitemapVersions(sitemap);
|
||||
// reveal the "old version" message if this page is a version number, but not the latest one
|
||||
// this isn't a dropdown, but it's here since we have the data anyway
|
||||
if (versions.slice(1).includes(VERSION)) {
|
||||
document.getElementById("old-version-notice").classList.remove("hidden")
|
||||
}
|
||||
// versions
|
||||
document.getElementById("version-dropdown").append(
|
||||
...versionDropdownItems(sitemap, branches),
|
||||
dropdownSeparator(),
|
||||
...versionDropdownItems(sitemap, versions),
|
||||
);
|
||||
// languages for the current version
|
||||
const langPaths = sitemap[VERSION].langPaths;
|
||||
const langs = Object.keys(langPaths).sort();
|
||||
document.getElementById("lang-dropdown").append(
|
||||
...langs.map((lang) => dropdownItem(lang, BOOK_URL + langPaths[lang])),
|
||||
);
|
||||
// return sitemap for chaining, i guess
|
||||
return sitemap
|
||||
}
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
// fetch the sitemap from the root and use it to generate the navbar
|
||||
fetch(`${BOOK_URL}/meta/sitemap.json`)
|
||||
.then(r => r.json())
|
||||
.then(addDropdowns)
|
||||
.catch(e => console.error(e))
|
||||
document.querySelectorAll(".details-collapsible").forEach(hookLoad);
|
||||
document.querySelectorAll("a.toggle-link").forEach(hookToggle);
|
||||
document.querySelectorAll(".spoilered").forEach(hookSpoiler);
|
||||
document.querySelectorAll(".animated-sync").forEach(hookSyncAnimations);
|
||||
doCycleTexturesForever();
|
||||
function tick(prevTime, time) {
|
||||
const dt = time - prevTime;
|
||||
for (const q of rfaQueue) {
|
||||
q(dt);
|
||||
}
|
||||
requestAnimationFrame((t) => tick(time, t));
|
||||
}
|
||||
requestAnimationFrame((t) => tick(t, t));
|
||||
$(function () {
|
||||
$('[data-toggle="tooltip"]').tooltip()
|
||||
});
|
||||
$(".cycle-textures > .texture")
|
||||
.on("mouseenter", () => { // start hover
|
||||
if (cycleTimeoutID != null) {
|
||||
clearTimeout(cycleTimeoutID);
|
||||
}
|
||||
})
|
||||
.on("mouseleave", () => { // stop hover
|
||||
cycleTimeoutID = setTimeout(doCycleTexturesForever, 1000);
|
||||
});
|
||||
gaslightNodes = document.querySelectorAll(".gaslight-textures");
|
||||
for (const elem of gaslightNodes) {
|
||||
setEnabledMultiTexture(elem, 0);
|
||||
}
|
||||
const observer = new IntersectionObserver(hookIntersectionObserver, {
|
||||
rootMargin: "32px 32px 32px 32px",
|
||||
});
|
||||
gaslightNodes.forEach((elem) => observer.observe(elem));
|
||||
document.addEventListener("visibilitychange", hookVisibilityChange);
|
||||
});
|
File diff suppressed because it is too large
Load diff
|
@ -4,7 +4,7 @@ import subprocess
|
|||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from pytest import FixtureRequest, TempPathFactory
|
||||
from pytest import TempPathFactory
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from hexdoc.cli.main import render
|
||||
|
@ -19,42 +19,44 @@ RENDERED_FILENAMES = [
|
|||
]
|
||||
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
def output_dir(request: FixtureRequest, tmp_path_factory: TempPathFactory) -> Path:
|
||||
if isinstance(request.cls, type):
|
||||
basename = request.cls.__name__
|
||||
else:
|
||||
basename = "tmp"
|
||||
return tmp_path_factory.mktemp(basename)
|
||||
@pytest.fixture(scope="session")
|
||||
def app_output_dir(tmp_path_factory: TempPathFactory) -> Path:
|
||||
return tmp_path_factory.mktemp("app", numbered=False)
|
||||
|
||||
|
||||
@pytest.fixture(scope="class", params=RENDERED_FILENAMES)
|
||||
def rendered_file(request: FixtureRequest, output_dir: Path) -> Path:
|
||||
return output_dir / request.param
|
||||
@pytest.fixture(scope="session")
|
||||
def subprocess_output_dir(tmp_path_factory: TempPathFactory) -> Path:
|
||||
return tmp_path_factory.mktemp("subprocess", numbered=False)
|
||||
|
||||
|
||||
class TestApp:
|
||||
def test_render(self, output_dir: Path):
|
||||
render(
|
||||
props_file=PROPS_FILE,
|
||||
output_dir=output_dir,
|
||||
lang="en_us",
|
||||
)
|
||||
|
||||
def test_file(self, rendered_file: Path, path_snapshot: SnapshotAssertion):
|
||||
assert rendered_file == path_snapshot
|
||||
def test_render_app(app_output_dir: Path):
|
||||
render(
|
||||
props_file=PROPS_FILE,
|
||||
output_dir=app_output_dir,
|
||||
lang="en_us",
|
||||
)
|
||||
|
||||
|
||||
class TestSubprocess:
|
||||
def test_render(self, output_dir: Path):
|
||||
cmd = [
|
||||
"hexdoc",
|
||||
"render",
|
||||
PROPS_FILE.as_posix(),
|
||||
output_dir.as_posix(),
|
||||
"--lang=en_us",
|
||||
]
|
||||
subprocess.run(cmd)
|
||||
def test_render_subprocess(subprocess_output_dir: Path):
|
||||
cmd = [
|
||||
"hexdoc",
|
||||
"render",
|
||||
PROPS_FILE.as_posix(),
|
||||
subprocess_output_dir.as_posix(),
|
||||
"--lang=en_us",
|
||||
]
|
||||
subprocess.run(cmd)
|
||||
|
||||
def test_file(self, rendered_file: Path, path_snapshot: SnapshotAssertion):
|
||||
assert rendered_file == path_snapshot
|
||||
|
||||
@pytest.mark.parametrize("filename", RENDERED_FILENAMES)
|
||||
def test_files(
|
||||
filename: str,
|
||||
app_output_dir: Path,
|
||||
subprocess_output_dir: Path,
|
||||
path_snapshot: SnapshotAssertion,
|
||||
):
|
||||
app_file = app_output_dir / filename
|
||||
subprocess_file = subprocess_output_dir / filename
|
||||
|
||||
assert app_file.read_bytes() == subprocess_file.read_bytes()
|
||||
assert app_file == path_snapshot
|
||||
|
|
Loading…
Reference in a new issue