mirror of
https://gitlab.com/PronounsPage/PronounsPage.git
synced 2025-08-03 11:07:00 -04:00

- Adds a new test suite with Docker-based smoke tests for all locales. Can be run using the ./smoketest.sh script. - Replaces all calls to Math.random() with a new helper that returns 0.5 in snapshot testing mode, ensuring deterministic snapshots. - Similarly replaces all calls to new Date() and Date.now() with new helpers that return a fixed date in snapshot testing mode. - Replaces checks against NODE_ENV with APP_ENV, to ensure that the bundles can be built with Nuxt for testing without losing code that would otherwise be stripped out by production optimizations. - Adds a database init script that can be used to initialize the database with a single admin user and a long-lived JWT token for use in automation tests. - Adds a JWT decoding/encoding CLI tool for debugging JWTs. Note: Snapshots are not checked in, and must be generated manually. See test/__snapshots__/.gitignore for more information.
496 lines
15 KiB
Bash
Executable File
496 lines
15 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# This script is invoked by smoketest.sh when the diff command is run:
|
|
# ./smoketest.sh diff
|
|
#
|
|
# It provides a simple interactive TUI for reviewing snapshot diffs.
|
|
|
|
# Path to the folder containing this script.
|
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
|
# Get current working directory
|
|
cwd="$(pwd)"
|
|
|
|
# If script_dir is the same as cwd, set script_rel_path to "./$(basename "$0")"
|
|
if [[ "$script_dir" == "$cwd" ]]; then
|
|
main_script_rel_path="./smoketest.sh"
|
|
else
|
|
main_script_rel_path="${script_dir#"$cwd"/}/smoketest.sh"
|
|
fi
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
Usage: ${main_script_rel_path} diff [LOCALE]
|
|
|
|
Interactive TUI for reviewing snapshot diffs. If LOCALE is provided, only
|
|
snapshots for that locale will be shown. Otherwise, all locales will be shown.
|
|
|
|
Options:
|
|
LOCALE Locale to show snapshots for. If not provided, all locales are shown.
|
|
|
|
Examples:
|
|
${main_script_rel_path} diff # Show all snapshots.
|
|
${main_script_rel_path} diff en # Show snapshots for locale 'en'.
|
|
${main_script_rel_path} diff --help # Show this help message.
|
|
EOF
|
|
}
|
|
|
|
# Path to the folder containing this script.
|
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
|
# If any of the arguments are -h or --help, show usage and exit.
|
|
for arg in "$@"; do
|
|
if [[ "$arg" == "-h" || "$arg" == "--help" ]]; then
|
|
usage
|
|
exit 0
|
|
fi
|
|
done
|
|
|
|
# Directory where snapshots are stored.
|
|
SNAPSHOT_DIR="${script_dir}/__snapshots__"
|
|
|
|
# Colors for TUI.
|
|
CLR_RESET="\033[0m"
|
|
CLR_GREEN="\033[1;32m"
|
|
CLR_RED="\033[1;31m"
|
|
CLR_YELLOW="\033[1;33m"
|
|
CLR_CYAN="\033[1;36m"
|
|
CLR_DIM="\033[2m"
|
|
|
|
# Trap ctrl+c so we can do a clean exit from the TUI.
|
|
trap ctrl_c INT
|
|
function ctrl_c() {
|
|
exit 0
|
|
}
|
|
|
|
# Build a list of locales that exist in test/__snapshots__, including any
|
|
# snapshots (passed or failed).
|
|
|
|
declare -A LOCALE_SNAPSHOTS=() # Maps: "locale" -> all snapshot base names.
|
|
declare -A LOCALE_FAILED_COUNT=() # Maps: "locale" -> integer # of .error.html.
|
|
declare -A LOCALE_TOTAL_COUNT=() # Maps: "locale" -> total # of snapshots.
|
|
|
|
# If a locale was passed as an argument, only show snapshots for that locale.
|
|
# Otherwise, show all locales.
|
|
|
|
locale_dirs=()
|
|
for_single_locale=false
|
|
|
|
if [ $# -eq 1 ]; then
|
|
# If a locale was passed, only show snapshots for that locale.
|
|
locale_dirs=( "$SNAPSHOT_DIR/$1" )
|
|
for_single_locale=true
|
|
|
|
# Check if the locale directory exists. If it doesn't, print an error and exit.
|
|
if [ ! -d "${locale_dirs[0]}" ]; then
|
|
echo "No snapshot directory found for locale '$1' in $SNAPSHOT_DIR." >&2
|
|
exit 1
|
|
fi
|
|
else
|
|
# Otherwise, gather a list of possible locale subdirectories. Only consider
|
|
# directories, ignoring anything else.
|
|
locale_dirs=( $(find "$SNAPSHOT_DIR" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | sort) )
|
|
|
|
# If none found, just say so.
|
|
if [ ${#locale_dirs[@]} -eq 0 ]; then
|
|
echo "No snapshot directories found in $SNAPSHOT_DIR." >&2
|
|
exit 0
|
|
fi
|
|
fi
|
|
|
|
for dir in "${locale_dirs[@]}"; do
|
|
loc="$(basename "$dir")"
|
|
|
|
# Gather all *.html inside that directory. Separates expected and actual
|
|
# snapshots, and counts the total number of snapshots.
|
|
html_files=( $(find "$dir" -maxdepth 1 -type f -name '*.html' 2>/dev/null | sort) )
|
|
if [ ${#html_files[@]} -eq 0 ]; then
|
|
LOCALE_SNAPSHOTS["$loc"]=""
|
|
LOCALE_FAILED_COUNT["$loc"]=0
|
|
LOCALE_TOTAL_COUNT["$loc"]=0
|
|
continue
|
|
fi
|
|
|
|
total_count=0
|
|
failed_count=0
|
|
found_bases=()
|
|
failed_idx=0
|
|
|
|
for file in "${html_files[@]}"; do
|
|
base="$(basename "$file")"
|
|
if [[ "$base" == *".error.html" ]]; then
|
|
short="${base%.error.html}"
|
|
failed_count=$((failed_count + 1))
|
|
# Ensure we only add the base once.
|
|
if [[ ! " ${found_bases[*]} " =~ " $short " ]]; then
|
|
# Insert failed snapshots at the beginning.
|
|
found_bases=( "${found_bases[@]:0:$failed_idx}" "$short" "${found_bases[@]:$failed_idx}" )
|
|
failed_idx=$((failed_idx + 1))
|
|
total_count=$((total_count + 1))
|
|
fi
|
|
else
|
|
short="${base%.html}"
|
|
if [[ ! " ${found_bases[*]} " =~ " $short " ]]; then
|
|
found_bases+=( "$short" )
|
|
total_count=$((total_count + 1))
|
|
fi
|
|
fi
|
|
done
|
|
|
|
LOCALE_SNAPSHOTS["$loc"]="${found_bases[*]}"
|
|
LOCALE_FAILED_COUNT["$loc"]=$failed_count
|
|
LOCALE_TOTAL_COUNT["$loc"]=$total_count
|
|
done
|
|
|
|
locales_sorted=( $(printf "%s\n" "${!LOCALE_SNAPSHOTS[@]}" | sort) )
|
|
|
|
# Hide cursor for a less jumpy interface.
|
|
tput civis
|
|
|
|
cleanup() {
|
|
# Re-enable cursor on exit.
|
|
tput cnorm
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
# Reads one keypress (arrow keys, enter, esc, pgup, pgdn, etc.) in raw mode.
|
|
# Returns a code: up/down/pgup/pgdn/enter/esc/backspace/ctrl_c/other
|
|
#
|
|
# Common escape sequences:
|
|
# ↑: ESC [ A
|
|
# ↓: ESC [ B
|
|
# PageUp: ESC [ 5 ~
|
|
# PageDown: ESC [ 6 ~
|
|
read_keypress() {
|
|
IFS= read -rsn1 key 2>/dev/null || true
|
|
|
|
case "$key" in
|
|
$'\x1b') # ESC
|
|
# Try reading up to two (or three) more bytes to detect special keys
|
|
read -rsn2 -t 0.001 rest 2>/dev/null || true
|
|
case "$rest" in
|
|
'[A') echo "up" ;;
|
|
'[B') echo "down" ;;
|
|
'[5')
|
|
# Possibly PageUp if next byte is '~'.
|
|
read -rsn1 -t 0.001 t3 2>/dev/null || true
|
|
if [[ "$t3" == "~" ]]; then
|
|
echo "pgup"
|
|
else
|
|
echo "esc"
|
|
fi
|
|
;;
|
|
'[6')
|
|
# Possibly PageDown if next byte is '~'.
|
|
read -rsn1 -t 0.001 t3 2>/dev/null || true
|
|
if [[ "$t3" == "~" ]]; then
|
|
echo "pgdn"
|
|
else
|
|
echo "esc"
|
|
fi
|
|
;;
|
|
*)
|
|
# If it's not recognized, treat as ESC.
|
|
echo "esc"
|
|
;;
|
|
esac
|
|
;;
|
|
'') # Enter sends empty string.
|
|
echo "enter"
|
|
;;
|
|
$'\x7f') # Backspace.
|
|
echo "backspace"
|
|
;;
|
|
q) # Treat q as backspace.
|
|
echo "backspace"
|
|
;;
|
|
*) # Could be ctrl_c, or random keys.
|
|
# Check for ^C specifically.
|
|
if [[ $key == $'\x03' ]]; then
|
|
echo "ctrl_c"
|
|
else
|
|
echo "other"
|
|
fi
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# print_menu <title> <items[@]> <highlightIndex>
|
|
#
|
|
# Supports paging/scrolling so that long lists don't overflow the screen.
|
|
# PageUp/PageDown will jump the highlight by max_items at a time.
|
|
print_menu() {
|
|
local title="$1"
|
|
shift
|
|
local -a items=("${!1}") # Indirect expansion: if you passed lines[@], this captures its elements
|
|
shift
|
|
local highlightIndex="$1"
|
|
|
|
# How many total items?
|
|
local count="${#items[@]}"
|
|
|
|
# Clear screen.
|
|
clear
|
|
|
|
# Print header.
|
|
echo -e "${CLR_CYAN}${title}${CLR_RESET}"
|
|
echo "Use ↑/↓ or PgUp/PgDn to navigate, Enter to select, ESC/Backspace/q to go back, CTRL+C to quit."
|
|
echo
|
|
|
|
# Get terminal height (number of rows).
|
|
local term_height
|
|
term_height=$(tput lines)
|
|
|
|
# We want to leave some rows for the title/instructions at top, and maybe 1
|
|
# row of margin at the bottom. title+instructions so far = 3 lines, so:
|
|
local menu_start=4
|
|
local max_items=$(( term_height - menu_start - 2 ))
|
|
if (( max_items < 1 )); then
|
|
# In case the terminal is very small.
|
|
max_items=1
|
|
fi
|
|
|
|
# Make sure highlightIndex is in range.
|
|
if (( highlightIndex < 0 )); then
|
|
highlightIndex=0
|
|
elif (( highlightIndex >= count )); then
|
|
highlightIndex=$(( count - 1 ))
|
|
fi
|
|
|
|
# Center the highlight in the menu if possible.
|
|
# First guess: start printing from highlightIndex - half the window.
|
|
local half=$(( max_items / 2 ))
|
|
local start=$(( highlightIndex - half ))
|
|
if (( start < 0 )); then
|
|
start=0
|
|
fi
|
|
|
|
local end=$(( start + max_items - 1 ))
|
|
if (( end >= count )); then
|
|
end=$(( count - 1 ))
|
|
fi
|
|
|
|
# If highlightIndex is below start or above end, adjust so it's visible.
|
|
if (( highlightIndex < start )); then
|
|
start=$highlightIndex
|
|
elif (( highlightIndex > end )); then
|
|
start=$(( highlightIndex - max_items + 1 ))
|
|
fi
|
|
if (( start < 0 )); then
|
|
start=0
|
|
fi
|
|
end=$(( start + max_items - 1 ))
|
|
if (( end >= count )); then
|
|
end=$(( count - 1 ))
|
|
fi
|
|
|
|
# If there are items outside this window, show an indicator.
|
|
if (( start > 0 )); then
|
|
echo -e "${CLR_DIM} ↑ ... more above ...${CLR_RESET}"
|
|
else
|
|
echo
|
|
fi
|
|
|
|
# Print only items[start..end].
|
|
for (( i = start; i <= end; i++ )); do
|
|
local line="${items[$i]}"
|
|
if (( i == highlightIndex )); then
|
|
# Highlighted.
|
|
echo -e " ${CLR_YELLOW}▶ $line${CLR_RESET}"
|
|
else
|
|
# Normal or dim.
|
|
if [[ "$line" == *"[No snapshots]"* ]]; then
|
|
echo -e " ${CLR_DIM}$line${CLR_RESET}"
|
|
else
|
|
echo " $line"
|
|
fi
|
|
fi
|
|
done
|
|
if (( end < count - 1 )); then
|
|
echo -e "${CLR_DIM} ↓ ... more below ...${CLR_RESET}"
|
|
fi
|
|
}
|
|
|
|
# Locale selection menu.
|
|
choose_locale() {
|
|
local idx=0
|
|
while true; do
|
|
# Build an array of menu lines.
|
|
local lines=()
|
|
for loc in "${locales_sorted[@]}"; do
|
|
local total="${LOCALE_TOTAL_COUNT["$loc"]}"
|
|
local fails="${LOCALE_FAILED_COUNT["$loc"]}"
|
|
if [ "$total" -eq 0 ]; then
|
|
lines+=("$loc [No snapshots]")
|
|
else
|
|
if [ "$fails" -gt 0 ]; then
|
|
lines+=("$loc ($fails failed / $total total)")
|
|
else
|
|
lines+=("$loc (all passed / $total total)")
|
|
fi
|
|
fi
|
|
done
|
|
|
|
# Show the menu.
|
|
print_menu "Locales with Snapshots" lines[@] "$idx" >&2
|
|
|
|
# Wait for a key.
|
|
key="$(read_keypress)"
|
|
case "$key" in
|
|
up)
|
|
# Decrement idx carefully.
|
|
if [ "$idx" -le 0 ]; then
|
|
idx=$(( ${#lines[@]} - 1 ))
|
|
else
|
|
idx=$((idx - 1))
|
|
fi
|
|
;;
|
|
down)
|
|
# Increment idx carefully.
|
|
if [ "$idx" -ge $(( ${#lines[@]} - 1 )) ]; then
|
|
idx=0
|
|
else
|
|
idx=$((idx + 1))
|
|
fi
|
|
;;
|
|
pgup)
|
|
# Jump up by one page.
|
|
idx=$(( idx - ( $(tput lines) - 5 ) ))
|
|
if (( idx < 0 )); then
|
|
idx=0
|
|
fi
|
|
;;
|
|
pgdn)
|
|
# Jump down by one page.
|
|
idx=$(( idx + ( $(tput lines) - 5 ) ))
|
|
if (( idx >= ${#lines[@]} )); then
|
|
idx=$(( ${#lines[@]} - 1 ))
|
|
fi
|
|
;;
|
|
enter)
|
|
local line="${lines[$idx]}"
|
|
# If it's "[No snapshots]", ignore selection.
|
|
if [[ "$line" == *"[No snapshots]"* ]]; then
|
|
continue
|
|
fi
|
|
# Otherwise proceed.
|
|
echo "${locales_sorted[$idx]}"
|
|
return 0
|
|
;;
|
|
esc|backspace|ctrl_c)
|
|
# Return empty to signal user wants to quit.
|
|
echo ""
|
|
return 0
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
# Snapshot selection menu.
|
|
choose_snapshot() {
|
|
local loc="$1"
|
|
# Reconstruct the snapshot base names.
|
|
IFS=' ' read -r -a bases <<< "${LOCALE_SNAPSHOTS[$loc]}"
|
|
local idx=0
|
|
|
|
while true; do
|
|
# Build lines[] for display.
|
|
local lines=()
|
|
for base in "${bases[@]}"; do
|
|
local expected="${SNAPSHOT_DIR}/${loc}/${base}.html"
|
|
local actual="${SNAPSHOT_DIR}/${loc}/${base}.error.html"
|
|
|
|
# URL Decode any %2F, %3A, etc. for display.
|
|
base="$(echo -e "${base//%/\\x}")"
|
|
|
|
if [ -f "$actual" ]; then
|
|
lines+=("❌ $base")
|
|
else
|
|
lines+=("✅ $base")
|
|
fi
|
|
done
|
|
|
|
print_menu "Snapshots in locale '$loc'" lines[@] "$idx" >&2
|
|
|
|
key="$(read_keypress)"
|
|
case "$key" in
|
|
up)
|
|
if [ "$idx" -le 0 ]; then
|
|
idx=$(( ${#lines[@]} - 1 ))
|
|
else
|
|
idx=$((idx - 1))
|
|
fi
|
|
;;
|
|
down)
|
|
if [ "$idx" -ge $(( ${#lines[@]} - 1 )) ]; then
|
|
idx=0
|
|
else
|
|
idx=$((idx + 1))
|
|
fi
|
|
;;
|
|
pgup)
|
|
idx=$(( idx - ( $(tput lines) - 5 ) ))
|
|
if (( idx < 0 )); then
|
|
idx=0
|
|
fi
|
|
;;
|
|
pgdn)
|
|
idx=$(( idx + ( $(tput lines) - 5 ) ))
|
|
if (( idx >= ${#lines[@]} )); then
|
|
idx=$(( ${#lines[@]} - 1 ))
|
|
fi
|
|
;;
|
|
enter)
|
|
local base="${bases[$idx]}"
|
|
local expected="${SNAPSHOT_DIR}/${loc}/${base}.html"
|
|
local actual="${SNAPSHOT_DIR}/${loc}/${base}.error.html"
|
|
|
|
# URL Decode any %2F, %3A, etc. for display.
|
|
base="$(echo -e "${base//%/\\x}")"
|
|
|
|
if [ ! -f "$actual" ]; then
|
|
# Passed snapshot.
|
|
clear
|
|
echo -e "${CLR_GREEN}Snapshot '$base' PASSED. No .error.html to compare.${CLR_RESET}" >&2
|
|
echo "Press any key to go back..." >&2
|
|
read -rsn1
|
|
else
|
|
# Compare with git diff.
|
|
clear
|
|
echo "Running diff on '$expected' vs '$actual'..." >&2
|
|
tput cnorm
|
|
git diff --no-index --color=always --color-words "$expected" "$actual" | less -R || true
|
|
# Hide cursor again on return.
|
|
tput civis
|
|
fi
|
|
;;
|
|
esc|backspace|ctrl_c)
|
|
return 0
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
# Main interactive flow
|
|
|
|
# If we're only showing snapshots for a single locale, skip the locale menu.
|
|
if [ "$for_single_locale" = true ]; then
|
|
choose_snapshot "${locale_dirs[0]##*/}"
|
|
# Exit if user pressed ESC/back at the snapshot menu.
|
|
clear
|
|
exit 0
|
|
fi
|
|
|
|
# Otherwise, show the locale menu first.
|
|
while true; do
|
|
chosen_locale="$(choose_locale)"
|
|
if [ -z "$chosen_locale" ]; then
|
|
# Exit if user pressed ESC/back at the locale menu.
|
|
clear
|
|
exit 0
|
|
fi
|
|
|
|
choose_snapshot "$chosen_locale"
|
|
done
|