Get types to work inside of a realm test file locally#4181
Get types to work inside of a realm test file locally#4181tintinthong wants to merge 9 commits intomainfrom
Conversation
9832184 to
c2aff15
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 98321845b0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
packages/host/tests/live-test.html
Outdated
| <script src="/assets/test-support.js"></script> | ||
| <script src="/assets/@cardstack/host.js"></script> |
There was a problem hiding this comment.
Disable QUnit autostart before loading test-support bundle
live-test.html currently loads /assets/test-support.js before /assets/live-test.js, but test-support executes tests/test-helper.js, which unconditionally calls start() and starts QUnit immediately. That makes QUnit.config.autostart = false in live-test.js too late, so the run can start/finish before the realm module is imported (resulting in zero/missing realm tests or mixed host-suite execution). Move the live-test bootstrap (or an early autostart=false script) ahead of test-support.js.
Useful? React with 👍 / 👎.
Preview deployments |
There was a problem hiding this comment.
just as a note, these types can end up diverging from the actual implementations in host. consider using an interface/type that that lives in runtime common or some other exportable package that both these types and the ones in host implement so that we can keep prevent diverging types
There was a problem hiding this comment.
I actually fell back to treating @cardstack/host/test/helpers as paths resolved similar to @cardsatck/boxel-host/commands inside tsconfig
That has simplified my solution and just allowed me to access types locally. In the following PR, I want to implement a setup that doesn't expose the complexity of setting up an integration test to the user. But this PR also covers basics like enabling qunit to be available in typeland for the catalog and experiments realm
We will handle lint of dynamically edited files as a code mode problem in the future
6798b2b to
a834eac
Compare
There was a problem hiding this comment.
Pull request overview
This PR aims to make TypeScript types resolve correctly when writing “realm-local” test code (as shown in sample-command-card.gts), primarily by centralizing needed test deps in the pnpm catalog and updating realm package tsconfigs/deps to reference host test helpers.
Changes:
- Added
@ember/test-helpersto the pnpm catalog and switchedpackages/hostto consume it viacatalog:. - Updated
catalog-realmandexperiments-realmtsconfigs to include**/*.gtsand added a path alias for@cardstack/host/tests/*. - Added a new
sample-command-card.gtsinexperiments-realmthat includes both a card and a QUnit test using host test helpers.
Reviewed changes
Copilot reviewed 7 out of 8 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| pnpm-workspace.yaml | Adds @ember/test-helpers to the centralized pnpm catalog (and normalizes quoting). |
| pnpm-lock.yaml | Lockfile updates reflecting new catalog entry and added realm devDependencies. |
| packages/host/package.json | Switches @ember/test-helpers dependency to catalog: for version centralization. |
| packages/experiments-realm/tsconfig.json | Adds @cardstack/host/tests/* path mapping and includes **/*.gts for typechecking. |
| packages/experiments-realm/sample-command-card.gts | Introduces a sample card plus embedded QUnit test code that imports host test helpers. |
| packages/experiments-realm/package.json | Adds test-related devDependencies needed by the sample test code. |
| packages/catalog-realm/tsconfig.json | Adds @cardstack/host/tests/* path mapping and includes **/*.gts for typechecking. |
| packages/catalog-realm/package.json | Adds test-related devDependencies (presumably for future realm-local test typing). |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| // ── Tests (imports resolved via loader.shimModule in live-test.js) ──────────── | ||
| import { on } from '@ember/modifier'; | ||
| import { service } from '@ember/service'; | ||
| import { click, render } from '@ember/test-helpers'; | ||
| import GlimmerComponent from '@glimmer/component'; | ||
| import { module, test } from 'qunit'; | ||
|
|
||
| import { getService } from '@universal-ember/test-support'; | ||
| import { baseRealm, type Store } from '@cardstack/runtime-common'; | ||
| import { | ||
| setupIntegrationTestRealm, | ||
| setupLocalIndexing, | ||
| setupOnSave, | ||
| setupCardLogs, | ||
| testRealmURL, | ||
| setupRealmCacheTeardown, | ||
| withCachedRealmSetup, | ||
| type TestContextWithSave, | ||
| } from '@cardstack/host/tests/helpers'; | ||
| import { TestRealmAdapter } from '@cardstack/host/tests/helpers/adapter'; | ||
| import { setupMockMatrix } from '@cardstack/host/tests/helpers/mock-matrix'; | ||
| import { setupRenderingTest } from '@cardstack/host/tests/helpers/setup'; | ||
|
|
||
| class CreateCardButton extends GlimmerComponent { | ||
| @service declare store: Store; | ||
|
|
||
| createCard = async () => { | ||
| await this.store.add(new SampleCommandCard({ title: 'Hello from live-test' })); | ||
| }; | ||
|
|
||
| <template> | ||
| <button type='button' {{on 'click' this.createCard}}>Create Card</button> | ||
| </template> | ||
| } | ||
|
|
||
| module('Experiments | SampleCommandCard', function (hooks) { | ||
| setupRenderingTest(hooks); | ||
| setupLocalIndexing(hooks); | ||
| setupOnSave(hooks); | ||
| setupRealmCacheTeardown(hooks); | ||
| setupCardLogs(hooks, async () => | ||
| (getService('loader-service') as any).loader.import(`${baseRealm.url}card-api`), | ||
| ); | ||
|
|
||
| let mockMatrixUtils = setupMockMatrix(hooks, { | ||
| loggedInAs: '@testuser:localhost', | ||
| activeRealms: [testRealmURL], | ||
| autostart: true, | ||
| }); | ||
|
|
||
| let testRealmAdapter: TestRealmAdapter; | ||
|
|
||
| hooks.beforeEach(async function () { | ||
| ({ adapter: testRealmAdapter } = await withCachedRealmSetup(async () => | ||
| setupIntegrationTestRealm({ | ||
| mockMatrixUtils, | ||
| contents: { | ||
| 'sample-command-card.gts': { SampleCommandCard }, | ||
| '.realm.json': '{ "name": "Sample Realm" }', | ||
| }, | ||
| }), | ||
| )); | ||
| }); | ||
|
|
||
| test('clicking Create Card writes a new card to the realm', async function ( | ||
| this: TestContextWithSave, | ||
| assert, | ||
| ) { | ||
| assert.expect(3); | ||
|
|
||
| let savedUrl: URL | undefined; | ||
| this.onSave((url, doc) => { | ||
| savedUrl = url; | ||
| assert.strictEqual( | ||
| (doc as any).data.attributes.title, | ||
| 'Hello from live-test', | ||
| 'saved doc has correct title', | ||
| ); | ||
| }); | ||
|
|
||
| await render(<template><CreateCardButton /></template>); | ||
| await click('button'); | ||
|
|
||
| assert.ok(savedUrl, 'card was saved to realm'); | ||
| let relativePath = `${savedUrl!.href.substring(testRealmURL.length)}.json`; | ||
| let file = await testRealmAdapter.openFile(relativePath); | ||
| assert.ok(file, 'card JSON file exists in the realm adapter'); | ||
| }, | ||
| ); | ||
| }); |
There was a problem hiding this comment.
Im perfroming a full reindex on experiments to be certain this doesn't break the indexer. Which is the key issue
There was a problem hiding this comment.
Seems to have succesfully indexed the file independent of the host
There was a problem hiding this comment.
this is a really important comment: specifically, due to the static nature you have defined the modules tests are run as a result of just loading this card. I don't think we ever want that. that means indexing is actually running tests (other things will end up running tests--like the user just interacting with this card will always end up running tests as a result of using this card). we should never have the module() or test() functions in module scope. A better shape might be:
export function runTests() {
module('my test module', function (hooks) {
test('test 1', function (assert) {});
test('test 2', function (assert) {});
});
}
and then tests are only executed when runTests() is called, not as a side effect of just loading the module.
There was a problem hiding this comment.
i feel so strongly about this, that we should probably include a lint and/or skills for this as well, because users or AI might author cards in this way. this would be a very bad performance hit to our indexing system if this gets into a card.
| assert.expect(3); | ||
|
|
||
| let savedUrl: URL | undefined; | ||
| this.onSave((url, doc) => { | ||
| savedUrl = url; | ||
| assert.strictEqual( | ||
| (doc as any).data.attributes.title, | ||
| 'Hello from live-test', | ||
| 'saved doc has correct title', | ||
| ); | ||
| }); | ||
|
|
||
| await render(<template><CreateCardButton /></template>); | ||
| await click('button'); | ||
|
|
||
| assert.ok(savedUrl, 'card was saved to realm'); | ||
| let relativePath = `${savedUrl!.href.substring(testRealmURL.length)}.json`; | ||
| let file = await testRealmAdapter.openFile(relativePath); | ||
| assert.ok(file, 'card JSON file exists in the realm adapter'); | ||
| }, |
Look at sample-command-card