@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@explorable-viz/fluid",
3
- "version": "0.12.4",
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,3 @@
1
+ #!/usr/bin/env bash
2
+ set -xe
3
+ yarn purs-backend-es bundle-app --no-build --main $2 --to dist/$1/fluid.js ${@:3} > /dev/null
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env bash
2
+ set -xe
3
+
4
+ rm -rf dist/$1
5
+ mkdir -p dist/$1
@@ -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,10 @@
1
+ #!/usr/bin/env bash
2
+ set -e
3
+
4
+ toLispCase() {
5
+ INPUT="$1"
6
+ RESULT=$(echo "$INPUT" | sed -E 's/([a-z0-9])([A-Z])/\1-\2/g' | tr '[:upper:]' '[:lower:]')
7
+ echo "$RESULT"
8
+ }
9
+
10
+ toLispCase "$1"
@@ -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:*"
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  checkAttribute, checkAttributeContains, checkComputedStyle, checkTextContent,
3
3
  click, clickToggle, testURL, waitFor
4
- } from "../../script/webtest-lib.mjs"
4
+ } from "@explorable-viz/fluid/script/webtest-lib.mjs"
5
5
 
6
6
  export const main = async () => {
7
7
  await testURL("convolution", [