Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 18 additions & 52 deletions packages/host/app/lib/externals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', {});
}
26 changes: 26 additions & 0 deletions packages/host/docs/live-tests.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Live Tests (Card-Based)

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

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 the default live test suite (catalog realm)
cd packages/host
pnpm test:live

# Or target a specific realm via the REALM_URL env var (trailing slash optional)
REALM_URL=http://localhost:4201/experiments/ pnpm test:live
```
20 changes: 17 additions & 3 deletions packages/host/scripts/live-test-wait-for-servers.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,27 @@

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 "$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
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'
12 changes: 10 additions & 2 deletions packages/host/testem-live.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,17 @@ 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=${encodeURIComponent(url)}&hidepassed`,
),
disable_watching: true,
browser_timeout: 120,
browser_no_activity_timeout: 120,
Expand Down
11 changes: 8 additions & 3 deletions packages/host/tests/live-test.js
Original file line number Diff line number Diff line change
@@ -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.
*
Expand All @@ -12,15 +12,20 @@ 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)$/, ''));
.filter((url) => url.endsWith('.test.gts'))
.map((url) => url.slice(0, -'.gts'.length));
}

// eslint-disable-next-line ember/no-test-import-export
Expand Down
Loading