Update snapshot tests to only save snapshot for one render strategy

This commit is contained in:
object-Object 2023-10-25 00:25:51 -04:00
parent 8bb3ff5266
commit e930333fc2
9 changed files with 35 additions and 15411 deletions

View file

@ -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;
}
}

View file

@ -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);
});

View file

@ -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