@explorable-viz/fluid 0.12.4 → 0.12.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +6 -2
- package/script/test-website.sh +59 -0
- package/script/util/bundle.sh +3 -0
- package/script/util/clean.sh +5 -0
- package/script/util/compile.sh +13 -0
- package/script/util/install-puppeteer.sh +10 -0
- package/script/util/lisp-case.sh +10 -0
- package/script/util/poll.sh +19 -0
- package/script/util/run-website-tests.sh +15 -0
- package/script/webtest-lib.mjs +101 -0
- package/website/article/package.json +1 -1
- package/website/article/test.mjs +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@explorable-viz/fluid",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.6",
|
|
4
4
|
"description": "A functional programming language which integrates a bidirectional dynamic analysis, connecting outputs to data sources in a fine-grained way. Fluid is implemented in PureScript and runs in the browser.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -20,6 +20,9 @@
|
|
|
20
20
|
"dist/fluid/shared",
|
|
21
21
|
"dist/fluid/fluid/lib",
|
|
22
22
|
"output-es",
|
|
23
|
+
"script/test-website.sh",
|
|
24
|
+
"script/util",
|
|
25
|
+
"script/webtest-lib.mjs",
|
|
23
26
|
"web/lib",
|
|
24
27
|
"website/article"
|
|
25
28
|
],
|
|
@@ -76,6 +79,7 @@
|
|
|
76
79
|
},
|
|
77
80
|
"svelte": "./web/lib/index.js",
|
|
78
81
|
"exports": {
|
|
79
|
-
".": "./web/lib/index.js"
|
|
82
|
+
".": "./web/lib/index.js",
|
|
83
|
+
"./script/*": "./script/*"
|
|
80
84
|
}
|
|
81
85
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Run from a website directory (e.g. website/article/) that has a test.mjs.
|
|
3
|
+
set -e
|
|
4
|
+
|
|
5
|
+
WEBSITE_DIR="$PWD"
|
|
6
|
+
WEBSITE="$(basename "$WEBSITE_DIR")"
|
|
7
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
8
|
+
|
|
9
|
+
echo "Testing website: ${WEBSITE}"
|
|
10
|
+
|
|
11
|
+
"$SCRIPT_DIR/util/install-puppeteer.sh"
|
|
12
|
+
|
|
13
|
+
cd "$WEBSITE_DIR"
|
|
14
|
+
|
|
15
|
+
echo "Building ${WEBSITE}..."
|
|
16
|
+
yarn build
|
|
17
|
+
|
|
18
|
+
# Ensure port is free (previous test run may not have fully released it)
|
|
19
|
+
if command -v lsof > /dev/null 2>&1; then
|
|
20
|
+
lsof -ti:8080 | xargs kill -9 2>/dev/null || true
|
|
21
|
+
elif command -v fuser > /dev/null 2>&1; then
|
|
22
|
+
fuser -k 8080/tcp 2>/dev/null || true
|
|
23
|
+
fi
|
|
24
|
+
sleep 1
|
|
25
|
+
|
|
26
|
+
echo "Starting preview server on port 8080..."
|
|
27
|
+
npx vite preview --port 8080 --host 127.0.0.1 --strictPort &
|
|
28
|
+
SERVER_PID=$!
|
|
29
|
+
|
|
30
|
+
cleanup() {
|
|
31
|
+
echo "Shutting down preview server (PID $SERVER_PID)..."
|
|
32
|
+
kill "$SERVER_PID" 2>/dev/null || true
|
|
33
|
+
wait "$SERVER_PID" 2>/dev/null || true
|
|
34
|
+
# Ensure port is fully released before next test run
|
|
35
|
+
for i in $(seq 1 10); do
|
|
36
|
+
if ! curl -s http://127.0.0.1:8080/ > /dev/null 2>&1; then
|
|
37
|
+
break
|
|
38
|
+
fi
|
|
39
|
+
sleep 0.5
|
|
40
|
+
done
|
|
41
|
+
echo "Everything is cleanly shut down."
|
|
42
|
+
}
|
|
43
|
+
trap cleanup EXIT
|
|
44
|
+
|
|
45
|
+
# Wait for server to be ready
|
|
46
|
+
echo "Waiting for server..."
|
|
47
|
+
for i in $(seq 1 30); do
|
|
48
|
+
if curl -s http://127.0.0.1:8080/ > /dev/null 2>&1; then
|
|
49
|
+
echo "Server is ready."
|
|
50
|
+
break
|
|
51
|
+
fi
|
|
52
|
+
if [ "$i" -eq 30 ]; then
|
|
53
|
+
echo "Error: Server failed to start within 30 seconds." >&2
|
|
54
|
+
exit 1
|
|
55
|
+
fi
|
|
56
|
+
sleep 1
|
|
57
|
+
done
|
|
58
|
+
|
|
59
|
+
"$SCRIPT_DIR/util/run-website-tests.sh"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -xe
|
|
3
|
+
|
|
4
|
+
rm -rf output-es
|
|
5
|
+
yarn tidy
|
|
6
|
+
|
|
7
|
+
if [ "$BUILD_ENV" = "prod" ]; then
|
|
8
|
+
echo "Building for production"
|
|
9
|
+
yarn spago --config spago.prod.dhall build --purs-args '--strict --censor-codes=UserDefinedWarning'
|
|
10
|
+
else
|
|
11
|
+
echo "Building for development"
|
|
12
|
+
yarn spago build --purs-args '--strict --censor-codes=UserDefinedWarning'
|
|
13
|
+
fi
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -e
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
5
|
+
FLUID_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
6
|
+
|
|
7
|
+
echo "Installing Puppeteer browsers..."
|
|
8
|
+
cd "$FLUID_ROOT"
|
|
9
|
+
yarn puppeteer browsers install chrome
|
|
10
|
+
yarn puppeteer browsers install firefox
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Poll until a shell command succeeds.
|
|
3
|
+
# Usage: poll.sh <description> <max_attempts> <shell_command>
|
|
4
|
+
set -e
|
|
5
|
+
|
|
6
|
+
DESCRIPTION="$1"
|
|
7
|
+
MAX_ATTEMPTS="$2"
|
|
8
|
+
COMMAND="$3"
|
|
9
|
+
|
|
10
|
+
echo "$DESCRIPTION"
|
|
11
|
+
for i in $(seq 1 "$MAX_ATTEMPTS"); do
|
|
12
|
+
if eval "$COMMAND"; then
|
|
13
|
+
exit 0
|
|
14
|
+
fi
|
|
15
|
+
echo " Attempt $i..."
|
|
16
|
+
sleep 10
|
|
17
|
+
done
|
|
18
|
+
echo "Error: $DESCRIPTION — timed out." >&2
|
|
19
|
+
exit 1
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Run test.mjs in the current directory against a base URL.
|
|
3
|
+
# Usage: run-website-tests.sh <base-url>
|
|
4
|
+
set -e
|
|
5
|
+
|
|
6
|
+
BASE_URL="${1:-http://127.0.0.1:8080}"
|
|
7
|
+
|
|
8
|
+
if [ ! -f "test.mjs" ]; then
|
|
9
|
+
echo "Error: no test.mjs in $(pwd)" >&2
|
|
10
|
+
exit 1
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
echo "Running tests against $BASE_URL..."
|
|
14
|
+
BASE_URL="$BASE_URL" node -e "import('./test.mjs').then(({ main }) => main()).then(() => process.exit(0)).catch(e => { console.error(e); process.exit(1); })"
|
|
15
|
+
echo "Tests passed."
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import puppeteer from "puppeteer"
|
|
2
|
+
|
|
3
|
+
const TIMEOUT = 60000
|
|
4
|
+
const LOGGING = true
|
|
5
|
+
const HEADLESS = true
|
|
6
|
+
const VIEWPORT = { width: 1200, height: 800, deviceScaleFactor: 1.0 }
|
|
7
|
+
|
|
8
|
+
function log(msg) {
|
|
9
|
+
if (LOGGING) console.log(msg)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function testOutcome(pass, msg) {
|
|
13
|
+
const sym = pass ? "\x1b[32m ✔\x1b[0m" : "\x1b[31m ✖\x1b[0m"
|
|
14
|
+
console.log(`${sym} ${msg}`)
|
|
15
|
+
if (!pass) throw new Error("Test failed")
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function launchBrowser(browserName) {
|
|
19
|
+
return puppeteer.launch({
|
|
20
|
+
browser: browserName,
|
|
21
|
+
headless: HEADLESS,
|
|
22
|
+
defaultViewport: VIEWPORT,
|
|
23
|
+
})
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function waitFor(page, selector) {
|
|
27
|
+
log(`Waiting for ${selector}`)
|
|
28
|
+
try {
|
|
29
|
+
await page.waitForSelector(selector, { timeout: TIMEOUT, visible: true })
|
|
30
|
+
log("-> found")
|
|
31
|
+
testOutcome(true, `${selector}: exists`)
|
|
32
|
+
} catch (e) {
|
|
33
|
+
testOutcome(false, `${selector}: ${e.message}`)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function waitForHidden(page, selector) {
|
|
38
|
+
log(`Waiting for ${selector} (hidden)`)
|
|
39
|
+
await page.waitForSelector(selector, { timeout: TIMEOUT, visible: false })
|
|
40
|
+
log("-> found")
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function click(page, selector) {
|
|
44
|
+
await page.click(selector)
|
|
45
|
+
testOutcome(true, `${selector}: click`)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function checkAttribute(page, selector, attr, expected) {
|
|
49
|
+
const found = await page.$eval(selector, (el, a) => el.getAttribute(a), attr)
|
|
50
|
+
const pass = found === expected
|
|
51
|
+
const errorMsg = pass ? "" : ` (got "${found}")`
|
|
52
|
+
testOutcome(pass, `${selector}: ${attr} == "${expected}"${errorMsg}`)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function checkAttributeContains(page, selector, attr, expected) {
|
|
56
|
+
const found = await page.$eval(selector, (el, a) => el.getAttribute(a), attr)
|
|
57
|
+
const pass = found.includes(expected)
|
|
58
|
+
const errorMsg = pass ? "" : ` (got "${found}")`
|
|
59
|
+
testOutcome(pass, `${selector}: ${attr} contains "${expected}"${errorMsg}`)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export async function checkTextContent(page, selector, expected) {
|
|
63
|
+
await waitFor(page, selector)
|
|
64
|
+
const text = await page.$eval(selector, el => el.textContent)
|
|
65
|
+
const pass = text === expected
|
|
66
|
+
testOutcome(pass, `${selector}: text == "${expected}"${pass ? "" : ` (got "${text}")`}`)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function checkComputedStyle(page, selector, property, expected) {
|
|
70
|
+
await waitFor(page, selector)
|
|
71
|
+
const value = await page.$eval(selector, (el, prop) => getComputedStyle(el)[prop], property)
|
|
72
|
+
const pass = value === expected
|
|
73
|
+
testOutcome(pass, `${selector}: ${property} == "${expected}"${pass ? "" : ` (got "${value}")`}`)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function clickToggle(page) {
|
|
77
|
+
await waitFor(page, "#grid.data-pane-hidden")
|
|
78
|
+
const toggle = "button[title='Show data pane']"
|
|
79
|
+
await waitFor(page, toggle)
|
|
80
|
+
await click(page, toggle)
|
|
81
|
+
await waitFor(page, "#grid:not(.data-pane-hidden)")
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function browserTests(url, browserName, tests) {
|
|
85
|
+
log(`browserTests: ${browserName}`)
|
|
86
|
+
const browser = await launchBrowser(browserName)
|
|
87
|
+
const page = await browser.newPage()
|
|
88
|
+
for (const test of tests) {
|
|
89
|
+
await page.goto(url)
|
|
90
|
+
await test(page)
|
|
91
|
+
}
|
|
92
|
+
await browser.close()
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const BASE_URL = process.env.BASE_URL || "http://127.0.0.1:8080"
|
|
96
|
+
|
|
97
|
+
export async function testURL(suffix, tests) {
|
|
98
|
+
const url = `${BASE_URL}/${suffix}`
|
|
99
|
+
await browserTests(url, "chrome", tests)
|
|
100
|
+
await browserTests(url, "firefox", tests)
|
|
101
|
+
}
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
|
13
13
|
"format": "prettier --write .",
|
|
14
14
|
"lint": "prettier --check . && eslint .",
|
|
15
|
-
"test": "../../script/test-website.sh"
|
|
15
|
+
"test": "../../fluid/script/test-website.sh"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@explorable-viz/fluid": "workspace:*"
|
package/website/article/test.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
checkAttribute, checkAttributeContains, checkComputedStyle, checkTextContent,
|
|
3
3
|
click, clickToggle, testURL, waitFor
|
|
4
|
-
} from "
|
|
4
|
+
} from "@explorable-viz/fluid/script/webtest-lib.mjs"
|
|
5
5
|
|
|
6
6
|
export const main = async () => {
|
|
7
7
|
await testURL("convolution", [
|