Skip to content

Commit c2aff15

Browse files
committed
get types to work
1 parent 521ee77 commit c2aff15

12 files changed

Lines changed: 17142 additions & 9155 deletions

File tree

packages/catalog-realm/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@
88
"@cardstack/boxel-ui": "workspace:*",
99
"@cardstack/local-types": "workspace:*",
1010
"@cardstack/runtime-common": "workspace:*",
11+
"@ember/test-helpers": "catalog:",
1112
"@types/lodash": "catalog:",
13+
"@types/qunit": "catalog:",
14+
"@universal-ember/test-support": "catalog:",
1215
"@types/uuid": "catalog:",
1316
"chess.js": "catalog:",
1417
"concurrently": "catalog:",

packages/catalog-realm/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
},
2828
"types": ["@cardstack/local-types"]
2929
},
30+
"include": ["**/*.ts", "**/*.gts", "../local-types/test-support.d.ts"],
3031
"glint": {
3132
"environment": ["ember-loose", "ember-template-imports"]
3233
}

packages/experiments-realm/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
"@cardstack/boxel-ui": "workspace:*",
99
"@cardstack/local-types": "workspace:*",
1010
"@cardstack/runtime-common": "workspace:*",
11+
"@ember/test-helpers": "catalog:",
12+
"@types/qunit": "catalog:",
13+
"@universal-ember/test-support": "catalog:",
1114
"@cardstack/view-transitions": "catalog:",
1215
"@types/lodash": "catalog:",
1316
"ember-animated": "catalog:",

packages/experiments-realm/sample-command-card.gts

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,95 @@ export class SampleCommandCard extends CardDef {
1212
</template>
1313
};
1414
}
15+
16+
// ── Tests (imports resolved via loader.shimModule in live-test.js) ────────────
17+
import { on } from '@ember/modifier';
18+
import { service } from '@ember/service';
19+
import { click, render } from '@ember/test-helpers';
20+
import GlimmerComponent from '@glimmer/component';
21+
import { module, test } from 'qunit';
22+
23+
import { getService } from '@universal-ember/test-support';
24+
import { baseRealm } from '@cardstack/runtime-common';
25+
import type StoreService from '@cardstack/host/services/store';
26+
import {
27+
setupIntegrationTestRealm,
28+
setupLocalIndexing,
29+
setupOnSave,
30+
setupCardLogs,
31+
testRealmURL,
32+
setupRealmCacheTeardown,
33+
withCachedRealmSetup,
34+
type TestContextWithSave,
35+
} from '@cardstack/host/tests/helpers';
36+
import { setupMockMatrix } from '@cardstack/host/tests/helpers/mock-matrix';
37+
import { setupRenderingTest } from '@cardstack/host/tests/helpers/setup';
38+
import type { TestRealmAdapter } from '@cardstack/host/tests/helpers/adapter';
39+
40+
class CreateCardButton extends GlimmerComponent {
41+
@service declare store: StoreService;
42+
43+
createCard = async () => {
44+
await this.store.add(new SampleCommandCard({ title: 'Hello from live-test' }));
45+
};
46+
47+
<template>
48+
<button type='button' {{on 'click' this.createCard}}>Create Card</button>
49+
</template>
50+
}
51+
52+
module('Experiments | SampleCommandCard', function (hooks) {
53+
setupRenderingTest(hooks);
54+
setupLocalIndexing(hooks);
55+
setupOnSave(hooks);
56+
setupRealmCacheTeardown(hooks);
57+
setupCardLogs(hooks, async () =>
58+
(getService('loader-service') as any).loader.import(`${baseRealm.url}card-api`),
59+
);
60+
61+
let mockMatrixUtils = setupMockMatrix(hooks, {
62+
loggedInAs: '@testuser:localhost',
63+
activeRealms: [testRealmURL],
64+
autostart: true,
65+
});
66+
67+
let testRealmAdapter: TestRealmAdapter;
68+
69+
hooks.beforeEach(async function () {
70+
({ adapter: testRealmAdapter } = await withCachedRealmSetup(async () =>
71+
setupIntegrationTestRealm({
72+
mockMatrixUtils,
73+
contents: {
74+
'sample-command-card.gts': { SampleCommandCard },
75+
'.realm.json': '{ "name": "Sample Realm" }',
76+
},
77+
}),
78+
));
79+
});
80+
81+
test('clicking Create Card writes a new card to the realm', async function (
82+
this: TestContextWithSave,
83+
assert,
84+
) {
85+
assert.expect(3);
86+
87+
let savedUrl: URL | undefined;
88+
this.onSave((url, doc) => {
89+
savedUrl = url;
90+
assert.strictEqual(
91+
(doc as any).data.attributes.title,
92+
'Hello from live-test',
93+
'saved doc has correct title',
94+
);
95+
});
96+
97+
await render(<template><CreateCardButton /></template>);
98+
await click('button');
99+
100+
assert.ok(savedUrl, 'card was saved to realm');
101+
let relativePath = `${savedUrl!.href.substring(testRealmURL.length)}.json`;
102+
let file = await testRealmAdapter.openFile(relativePath);
103+
assert.ok(file, 'card JSON file exists in the realm adapter');
104+
},
105+
);
106+
});

packages/experiments-realm/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
},
2929
"types": ["@cardstack/local-types"]
3030
},
31+
"include": ["**/*.ts", "**/*.gts", "../local-types/test-support.d.ts"],
3132
"glint": {
3233
"environment": ["ember-loose", "ember-template-imports"]
3334
}

packages/host/ember-cli-build.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
'use strict';
22

3+
const path = require('path');
34
const EmberApp = require('ember-cli/lib/broccoli/ember-app');
45
const { compatBuild } = require('@embroider/compat');
56
const { Webpack } = require('@embroider/webpack');
@@ -47,6 +48,11 @@ module.exports = function (defaults) {
4748
packagerOptions: {
4849
...{
4950
webpackConfig: {
51+
entry: {
52+
// Separate bundle for the live realm-test runner (live-test.html).
53+
// Key must match the <script src> path so embroider rewrites the HTML correctly.
54+
'assets/live-test.js': path.resolve(__dirname, 'tests/live-test.js'),
55+
},
5056
devtool: 'source-map',
5157
module: {
5258
rules: [

packages/host/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
"@cardstack/view-transitions": "catalog:",
4949
"@ember/optional-features": "^2.0.0",
5050
"@ember/string": "^3.1.1",
51-
"@ember/test-helpers": "^3.3.1",
51+
"@ember/test-helpers": "catalog:",
5252
"@ember/test-waiters": "catalog:",
5353
"@embroider/compat": "^3.5.5",
5454
"@embroider/core": "^3.4.15",

packages/host/tests/live-test.html

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<!doctype html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8" />
5+
<title>Realm Test Runner</title>
6+
7+
<!--
8+
Bootstrap: copy the environment config meta tag from the already-processed
9+
tests/index.html before any scripts run. This is needed because:
10+
- {{content-for "head"}} is only substituted for tests/index.html and
11+
index.html (ember-cli classicEntrypoints), not for this file.
12+
- The config/environment.js module reads that meta tag at runtime.
13+
- Without it, Application.create(config.APP) throws.
14+
Using synchronous XHR so the meta tag is in the DOM before vendor/host JS run.
15+
-->
16+
<script>
17+
(function () {
18+
var xhr = new XMLHttpRequest();
19+
xhr.open('GET', '/tests/index.html', false /* sync */);
20+
xhr.send(null);
21+
if (xhr.status === 200) {
22+
var tmp = document.createElement('html');
23+
tmp.innerHTML = xhr.responseText;
24+
var meta = tmp.querySelector('meta[name$="/config/environment"]');
25+
if (meta) {
26+
document.head.insertAdjacentHTML('beforeend', meta.outerHTML);
27+
}
28+
}
29+
window.__LIVE_TEST__ = true;
30+
})();
31+
</script>
32+
33+
<link rel="stylesheet" href="/assets/vendor.css" />
34+
<link rel="stylesheet" href="/assets/@cardstack/host.css" />
35+
<link rel="stylesheet" href="/assets/test-support.css" />
36+
</head>
37+
<body>
38+
<div id="qunit"></div>
39+
<div id="qunit-fixture">
40+
<div id="ember-testing-container">
41+
<div id="ember-testing"></div>
42+
</div>
43+
</div>
44+
45+
<script type="module">
46+
import * as ContentTag from '/assets/content-tag/standalone.js';
47+
globalThis.ContentTagGlobal = ContentTag;
48+
</script>
49+
<script src="/assets/vendor.js"></script>
50+
<script src="/assets/test-support.js"></script>
51+
<script src="/assets/@cardstack/host.js"></script>
52+
<script src="/assets/live-test.js"></script>
53+
</body>
54+
</html>

packages/host/tests/live-test.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import Application from '@cardstack/host/app';
2+
import config from '@cardstack/host/config/environment';
3+
import * as QUnit from 'qunit';
4+
import { setApplication } from '@ember/test-helpers';
5+
import { setup } from 'qunit-dom';
6+
import { getService } from '@universal-ember/test-support';
7+
import { useTestWaiters } from '@cardstack/runtime-common';
8+
import * as TestWaiters from '@ember/test-waiters';
9+
10+
// Guard: embroider includes all tests/** files in the normal test bundle.
11+
// When loaded there, window.__LIVE_TEST__ is absent — bail out immediately.
12+
if (window.__LIVE_TEST__) {
13+
setApplication(Application.create(config.APP));
14+
setup(QUnit.assert);
15+
QUnit.dump.maxDepth = 20;
16+
useTestWaiters(TestWaiters);
17+
18+
// Must be false before realm modules are loaded — QUnit must not fire before
19+
// loader.import() has had a chance to register all QUnit.module()/test() calls.
20+
QUnit.config.autostart = false;
21+
22+
loadRealmTests().catch(console.error);
23+
}
24+
25+
async function loadRealmTests() {
26+
const helpers = await import('@cardstack/host/tests/helpers');
27+
const mockMatrix = await import('@cardstack/host/tests/helpers/mock-matrix');
28+
const testSetup = await import('@cardstack/host/tests/helpers/setup');
29+
const helperAdapter = await import('@cardstack/host/tests/helpers/adapter');
30+
31+
let loader = getService('loader-service').loader;
32+
33+
// Bridge the webpack-bundled test helpers into the Loader so realm-loaded
34+
// .gts files can import them by their module name at runtime.
35+
loader.shimModule('qunit', QUnit);
36+
loader.shimModule('@cardstack/host/tests/helpers', helpers);
37+
loader.shimModule('@cardstack/host/tests/helpers/mock-matrix', mockMatrix);
38+
loader.shimModule('@cardstack/host/tests/helpers/setup', testSetup);
39+
loader.shimModule('@cardstack/host/tests/helpers/adapter', helperAdapter);
40+
41+
// Read realm URL from query string:
42+
// /tests/live-test.html?realmURL=http://localhost:4201/experiments/
43+
const params = new URLSearchParams(window.location.search);
44+
const realmURL =
45+
params.get('realmURL') ?? 'http://localhost:4201/experiments/';
46+
const testModule = params.get('module') ?? 'sample-command-card';
47+
48+
// loader.import fires QUnit.module()/test() as side effects
49+
await loader.import(`${realmURL}${testModule}`);
50+
51+
QUnit.start();
52+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Opt-in type declarations for colocated realm test files.
2+
// Add this path to your realm package's tsconfig "include" array:
3+
// "../local-types/test-support.d.ts"
4+
//
5+
// Also add to your realm package's devDependencies:
6+
// "@ember/test-helpers": "catalog:"
7+
// "@types/qunit": "catalog:"
8+
// "@universal-ember/test-support": "catalog:"
9+
//
10+
// The declarations below cover @cardstack/host internal modules which have no
11+
// separate npm package. All other test imports resolve via node_modules.
12+
13+
// ── @cardstack/host services ─────────────────────────────────────────────────
14+
declare module '@cardstack/host/services/store' {
15+
export default class StoreService {
16+
add(instance: unknown): Promise<unknown>;
17+
peek(id: string): unknown;
18+
}
19+
}
20+
21+
// ── @cardstack/host test helpers ─────────────────────────────────────────────
22+
declare module '@cardstack/host/tests/helpers/adapter' {
23+
export interface TestRealmAdapter {
24+
openFile(path: string): Promise<{ content: string | Uint8Array } | undefined>;
25+
}
26+
}
27+
28+
declare module '@cardstack/host/tests/helpers' {
29+
import type { TestRealmAdapter } from '@cardstack/host/tests/helpers/adapter';
30+
31+
export interface SetupRealmResult {
32+
adapter: TestRealmAdapter;
33+
}
34+
35+
export function setupIntegrationTestRealm(opts: {
36+
mockMatrixUtils: unknown;
37+
contents: Record<string, unknown>;
38+
}): Promise<SetupRealmResult>;
39+
40+
export function setupLocalIndexing(hooks: unknown): void;
41+
export function setupOnSave(hooks: unknown): void;
42+
export function setupCardLogs(hooks: unknown, fn: () => Promise<unknown>): void;
43+
export function setupRealmCacheTeardown(hooks: unknown): void;
44+
export function withCachedRealmSetup<T>(fn: () => Promise<T>): Promise<T>;
45+
46+
export const testRealmURL: string;
47+
48+
export interface TestContextWithSave {
49+
onSave(cb: (url: URL, doc: unknown) => void): void;
50+
}
51+
}
52+
53+
declare module '@cardstack/host/tests/helpers/mock-matrix' {
54+
export function setupMockMatrix(
55+
hooks: unknown,
56+
opts: {
57+
loggedInAs: string;
58+
activeRealms: string[];
59+
autostart: boolean;
60+
},
61+
): unknown;
62+
}
63+
64+
declare module '@cardstack/host/tests/helpers/setup' {
65+
export function setupRenderingTest(hooks: unknown): void;
66+
}

0 commit comments

Comments
 (0)