From 5d193773d5069f60cc48b0bb501baef2f29a9c49 Mon Sep 17 00:00:00 2001 From: tintinthong Date: Tue, 7 Apr 2026 17:31:58 +0800 Subject: [PATCH 1/3] fix tests --- packages/host/app/lib/externals.ts | 70 +++++-------------- packages/host/docs/live-tests.md | 26 +++++++ .../scripts/live-test-wait-for-servers.sh | 13 +++- packages/host/testem-live.js | 11 ++- packages/host/tests/live-test.js | 11 +-- 5 files changed, 70 insertions(+), 61 deletions(-) create mode 100644 packages/host/docs/live-tests.md diff --git a/packages/host/app/lib/externals.ts b/packages/host/app/lib/externals.ts index ffd74cf6ddc..c9e13737ead 100644 --- a/packages/host/app/lib/externals.ts +++ b/packages/host/app/lib/externals.ts @@ -188,60 +188,26 @@ export function shimExternals(virtualNetwork: VirtualNetwork) { shimHostCommands(virtualNetwork); } -// Shims test-only module IDs into the virtual network as throwing stubs so -// realm cards that colocate test imports can load in any environment without -// executing test code. live-test.js overrides these at the loader level with -// the real implementations when a test runner is present. +// Shims test-only module IDs into the virtual network as empty fallbacks so +// realm cards that co-locate test imports (e.g. *.test.gts) can load in any +// environment without crashing. These are never actually called in production +// — they just prevent import resolution errors. +// +// In live-test runs, live-test.js overrides these at the *realm loader* level +// (via loader.shimModule) with the real implementations before importing test +// modules. The loader-level shim takes precedence over this network-level +// fallback, so the real helpers are used during test execution. export function shimModulesForLiveTests(virtualNetwork: VirtualNetwork) { - const windowQUnit = (globalThis as any).QUnit; - - const testOnlyStub = (moduleId: string) => - new Proxy( - {}, - { - get: () => { - throw new Error( - `${moduleId} is only available in a test environment.`, - ); - }, - }, - ); - - // Use real @ember/test-helpers only when QUnit is running; stub otherwise. - virtualNetwork.shimModule( - '@ember/test-helpers', - windowQUnit ? emberTestHelpers : testOnlyStub('@ember/test-helpers'), - ); - - // Always stub host test helpers here — live-test.js shimModule() on the - // realm loader overrides these with real implementations at test time. - virtualNetwork.shimModule( - '@cardstack/host/tests/helpers', - testOnlyStub('@cardstack/host/tests/helpers'), - ); - virtualNetwork.shimModule( - '@cardstack/host/tests/helpers/mock-matrix', - testOnlyStub('@cardstack/host/tests/helpers/mock-matrix'), - ); - virtualNetwork.shimModule( - '@cardstack/host/tests/helpers/setup', - testOnlyStub('@cardstack/host/tests/helpers/setup'), - ); - virtualNetwork.shimModule( - '@cardstack/host/tests/helpers/adapter', - testOnlyStub('@cardstack/host/tests/helpers/adapter'), - ); + virtualNetwork.shimModule('@ember/test-helpers', emberTestHelpers); + virtualNetwork.shimModule('@cardstack/host/tests/helpers', {}); + virtualNetwork.shimModule('@cardstack/host/tests/helpers/mock-matrix', {}); + virtualNetwork.shimModule('@cardstack/host/tests/helpers/setup', {}); + virtualNetwork.shimModule('@cardstack/host/tests/helpers/adapter', {}); virtualNetwork.shimModule( '@cardstack/host/tests/helpers/render-component', - testOnlyStub('@cardstack/host/tests/helpers/render-component'), - ); - virtualNetwork.shimModule( - '@cardstack/host/tests/helpers/base-realm', - testOnlyStub('@cardstack/host/tests/helpers/base-realm'), - ); - virtualNetwork.shimModule( - '@universal-ember/test-support', - testOnlyStub('@universal-ember/test-support'), + {}, ); - virtualNetwork.shimModule('@ember/owner', testOnlyStub('@ember/owner')); + virtualNetwork.shimModule('@cardstack/host/tests/helpers/base-realm', {}); + virtualNetwork.shimModule('@universal-ember/test-support', {}); + virtualNetwork.shimModule('@ember/owner', {}); } diff --git a/packages/host/docs/live-tests.md b/packages/host/docs/live-tests.md new file mode 100644 index 00000000000..9f4f6ceeb30 --- /dev/null +++ b/packages/host/docs/live-tests.md @@ -0,0 +1,26 @@ +# Live Tests (Card-Based) + +Live tests run directly against a realm server. Test modules are `.gts` files inside a realm that export a `runTests()` function — they are auto-discovered via the realm's `_mtimes` endpoint. + +## Run in Browser + +Requires servers to already be running. + +- Experiments realm: `http://localhost:4200/tests/index.html?liveTest=true&realmURL=http://localhost:4201/experiments/&hidepassed` +- Catalog realm: `http://localhost:4200/tests/index.html?liveTest=true&realmURL=http://localhost:4201/catalog/&hidepassed` + +## Run as a Script + +Requires realm servers to be running (experiments + catalog). If you already have `pnpm start:all` running, that is sufficient. + +```sh +# Terminal 1 — start realm servers if not already running +mise run test-services:host + +# Terminal 2 — run all realm test suites (catalog + experiments) +cd packages/host +pnpm test:live + +# Or target a single realm +REALM_URL=http://localhost:4201/experiments/ pnpm test:live +``` diff --git a/packages/host/scripts/live-test-wait-for-servers.sh b/packages/host/scripts/live-test-wait-for-servers.sh index fc4587ce41c..d129b955f80 100755 --- a/packages/host/scripts/live-test-wait-for-servers.sh +++ b/packages/host/scripts/live-test-wait-for-servers.sh @@ -2,13 +2,20 @@ READY_PATH="_readiness-check?acceptHeader=application%2Fvnd.api%2Bjson" BASE_REALM_READY="http-get://localhost:4201/base/${READY_PATH}" -CATALOG_REALM_READY="http-get://localhost:4201/catalog/${READY_PATH}" SYNAPSE_URL="http://localhost:8008" SMTP_4_DEV_URL="http://localhost:5001" -READY_URLS="$BASE_REALM_READY|$CATALOG_REALM_READY|$SYNAPSE_URL|$SMTP_4_DEV_URL" +if [ -n "$1" ]; then + REALM_URL="$1" + REALM_HOST=$(echo "$REALM_URL" | sed 's|http://||') + REALM_READY="http-get://${REALM_HOST}${READY_PATH}" + READY_URLS="$BASE_REALM_READY|$REALM_READY|$SYNAPSE_URL|$SMTP_4_DEV_URL" +else + CATALOG_REALM_READY="http-get://localhost:4201/catalog/${READY_PATH}" + READY_URLS="$BASE_REALM_READY|$CATALOG_REALM_READY|$SYNAPSE_URL|$SMTP_4_DEV_URL" +fi -WAIT_ON_TIMEOUT=600000 NODE_NO_WARNINGS=1 start-server-and-test \ +WAIT_ON_TIMEOUT=600000 NODE_NO_WARNINGS=1 REALM_URL="${REALM_URL:-}" start-server-and-test \ 'pnpm run wait' \ "$READY_URLS" \ 'ember test --config-file testem-live.js --path ./dist' diff --git a/packages/host/testem-live.js b/packages/host/testem-live.js index 696942ad6e3..54e385fea36 100644 --- a/packages/host/testem-live.js +++ b/packages/host/testem-live.js @@ -6,9 +6,16 @@ const XunitReporter = require('testem/lib/reporters/xunit_reporter'); const fs = require('fs'); const path = require('path'); +const DEFAULT_REALM_URLS = ['http://localhost:4201/catalog/']; + +const realmURLs = process.env.REALM_URL + ? [process.env.REALM_URL] + : DEFAULT_REALM_URLS; + const config = { - test_page: - 'tests/index.html?liveTest=true&realmURL=http://localhost:4201/catalog/&hidepassed', + test_page: realmURLs.map( + (url) => `tests/index.html?liveTest=true&realmURL=${url}&hidepassed`, + ), disable_watching: true, browser_timeout: 120, browser_no_activity_timeout: 120, diff --git a/packages/host/tests/live-test.js b/packages/host/tests/live-test.js index 5e13a1f8b52..474ce03e56d 100644 --- a/packages/host/tests/live-test.js +++ b/packages/host/tests/live-test.js @@ -1,7 +1,7 @@ import * as QUnit from 'qunit'; /** - * Discovers all .gts/.ts module URLs in a realm using the _mtimes endpoint, + * Discovers all *.test.gts module URLs in a realm using the _mtimes endpoint, * which returns a flat map of every file URL in the realm in one request. * Only modules that export a `runTests` function will actually register tests. * @@ -12,15 +12,18 @@ async function discoverTestModules(realmURL) { const resp = await fetch(`${realmURL}_mtimes`, { headers: { Accept: 'application/vnd.api+json' }, }); + if (!resp.ok) { + throw new Error( + `Cannot access realm ${realmURL} (HTTP ${resp.status}). Check that the realm is publicly readable.`, + ); + } const { data: { attributes: { mtimes }, }, } = await resp.json(); - return Object.keys(mtimes) - .filter((url) => url.endsWith('.gts') || url.endsWith('.ts')) - .map((url) => url.replace(/\.(gts|ts)$/, '')); + return Object.keys(mtimes).filter((url) => url.endsWith('.test.gts')); } // eslint-disable-next-line ember/no-test-import-export From 57fc0dc9a1a470d044fe16972b2c703906e765b8 Mon Sep 17 00:00:00 2001 From: tintinthong Date: Wed, 8 Apr 2026 16:23:05 +0800 Subject: [PATCH 2/3] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- packages/host/docs/live-tests.md | 6 +++--- packages/host/testem-live.js | 3 ++- packages/host/tests/live-test.js | 4 +++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/host/docs/live-tests.md b/packages/host/docs/live-tests.md index 9f4f6ceeb30..8899a70b41e 100644 --- a/packages/host/docs/live-tests.md +++ b/packages/host/docs/live-tests.md @@ -1,6 +1,6 @@ # Live Tests (Card-Based) -Live tests run directly against a realm server. Test modules are `.gts` files inside a realm that export a `runTests()` function — they are auto-discovered via the realm's `_mtimes` endpoint. +Live tests run directly against a realm server. Test modules are `*.test.gts` files inside a realm that export a `runTests()` function — files must follow this naming pattern to be auto-discovered via the realm's `_mtimes` endpoint. ## Run in Browser @@ -17,10 +17,10 @@ Requires realm servers to be running (experiments + catalog). If you already hav # Terminal 1 — start realm servers if not already running mise run test-services:host -# Terminal 2 — run all realm test suites (catalog + experiments) +# Terminal 2 — run the default live test suite (catalog realm) cd packages/host pnpm test:live -# Or target a single realm +# Or target a different single realm, such as experiments REALM_URL=http://localhost:4201/experiments/ pnpm test:live ``` diff --git a/packages/host/testem-live.js b/packages/host/testem-live.js index 54e385fea36..4306fbd48f0 100644 --- a/packages/host/testem-live.js +++ b/packages/host/testem-live.js @@ -14,7 +14,8 @@ const realmURLs = process.env.REALM_URL const config = { test_page: realmURLs.map( - (url) => `tests/index.html?liveTest=true&realmURL=${url}&hidepassed`, + (url) => + `tests/index.html?liveTest=true&realmURL=${encodeURIComponent(url)}&hidepassed`, ), disable_watching: true, browser_timeout: 120, diff --git a/packages/host/tests/live-test.js b/packages/host/tests/live-test.js index 474ce03e56d..8cc0a9112e0 100644 --- a/packages/host/tests/live-test.js +++ b/packages/host/tests/live-test.js @@ -23,7 +23,9 @@ async function discoverTestModules(realmURL) { }, } = await resp.json(); - return Object.keys(mtimes).filter((url) => url.endsWith('.test.gts')); + return Object.keys(mtimes) + .filter((url) => url.endsWith('.test.gts')) + .map((url) => url.slice(0, -'.test.gts'.length)); } // eslint-disable-next-line ember/no-test-import-export From a71063b9262e77db8700df74d40acfecdb7060b2 Mon Sep 17 00:00:00 2001 From: tintinthong Date: Wed, 8 Apr 2026 19:45:00 +0800 Subject: [PATCH 3/3] apply copilot fixes --- packages/host/docs/live-tests.md | 2 +- packages/host/scripts/live-test-wait-for-servers.sh | 13 ++++++++++--- packages/host/tests/live-test.js | 2 +- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/host/docs/live-tests.md b/packages/host/docs/live-tests.md index 8899a70b41e..6fe8e796fc2 100644 --- a/packages/host/docs/live-tests.md +++ b/packages/host/docs/live-tests.md @@ -21,6 +21,6 @@ mise run test-services:host cd packages/host pnpm test:live -# Or target a different single realm, such as experiments +# Or target a specific realm via the REALM_URL env var (trailing slash optional) REALM_URL=http://localhost:4201/experiments/ pnpm test:live ``` diff --git a/packages/host/scripts/live-test-wait-for-servers.sh b/packages/host/scripts/live-test-wait-for-servers.sh index d129b955f80..f2b5bf5664c 100755 --- a/packages/host/scripts/live-test-wait-for-servers.sh +++ b/packages/host/scripts/live-test-wait-for-servers.sh @@ -5,9 +5,16 @@ BASE_REALM_READY="http-get://localhost:4201/base/${READY_PATH}" SYNAPSE_URL="http://localhost:8008" SMTP_4_DEV_URL="http://localhost:5001" -if [ -n "$1" ]; then - REALM_URL="$1" - REALM_HOST=$(echo "$REALM_URL" | sed 's|http://||') +if [ -n "$REALM_URL" ]; then + REALM_HOST="$REALM_URL" + case "$REALM_HOST" in + http://*) REALM_HOST="${REALM_HOST#http://}" ;; + https://*) REALM_HOST="${REALM_HOST#https://}" ;; + esac + case "$REALM_HOST" in + */) ;; + *) REALM_HOST="${REALM_HOST}/" ;; + esac REALM_READY="http-get://${REALM_HOST}${READY_PATH}" READY_URLS="$BASE_REALM_READY|$REALM_READY|$SYNAPSE_URL|$SMTP_4_DEV_URL" else diff --git a/packages/host/tests/live-test.js b/packages/host/tests/live-test.js index 8cc0a9112e0..fcd54bc16a2 100644 --- a/packages/host/tests/live-test.js +++ b/packages/host/tests/live-test.js @@ -25,7 +25,7 @@ async function discoverTestModules(realmURL) { return Object.keys(mtimes) .filter((url) => url.endsWith('.test.gts')) - .map((url) => url.slice(0, -'.test.gts'.length)); + .map((url) => url.slice(0, -'.gts'.length)); } // eslint-disable-next-line ember/no-test-import-export