From c890b67c75ec088de991b602b4553c5fab28b26a Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Wed, 1 Nov 2023 11:16:26 -0400 Subject: [PATCH] host: Remove /code (#779) --- .../editor/catalog-entry-editor.gts | 155 --- .../host/app/components/editor/code-link.gts | 27 - packages/host/app/components/editor/go.gts | 441 --------- .../host/app/components/editor/module.gts | 53 - .../host/app/components/editor/schema.gts | 383 -------- packages/host/app/router.ts | 3 - packages/host/app/templates/card-error.hbs | 2 - packages/host/app/templates/card.hbs | 2 - packages/host/app/templates/code.hbs | 10 - packages/host/tests/acceptance/basic-test.ts | 269 +----- .../components/catalog-entry-editor-test.gts | 913 ------------------ .../realm-server/dom-tests/realm-dom-test.js | 2 +- 12 files changed, 13 insertions(+), 2247 deletions(-) delete mode 100644 packages/host/app/components/editor/catalog-entry-editor.gts delete mode 100644 packages/host/app/components/editor/code-link.gts delete mode 100644 packages/host/app/components/editor/go.gts delete mode 100644 packages/host/app/components/editor/module.gts delete mode 100644 packages/host/app/components/editor/schema.gts delete mode 100644 packages/host/app/templates/code.hbs delete mode 100644 packages/host/tests/integration/components/catalog-entry-editor-test.gts diff --git a/packages/host/app/components/editor/catalog-entry-editor.gts b/packages/host/app/components/editor/catalog-entry-editor.gts deleted file mode 100644 index 4252c55965..0000000000 --- a/packages/host/app/components/editor/catalog-entry-editor.gts +++ /dev/null @@ -1,155 +0,0 @@ -import { hash } from '@ember/helper'; -import { on } from '@ember/modifier'; -import { action } from '@ember/object'; -import { LinkTo } from '@ember/routing'; -import { service } from '@ember/service'; -import Component from '@glimmer/component'; - -import { tracked } from '@glimmer/tracking'; - -import { CardContainer } from '@cardstack/boxel-ui/components'; -import { Button } from '@cardstack/boxel-ui/components'; - -import { - catalogEntryRef, - type CodeRef, - humanReadable, - SupportedMimeType, -} from '@cardstack/runtime-common'; -//@ts-ignore glint does not think this is consumed-but it is consumed in the template - -import CardEditor from '@cardstack/host/components/card-editor'; -import { getSearchResults } from '@cardstack/host/resources/search'; -import type CardService from '@cardstack/host/services/card-service'; -import type LoaderService from '@cardstack/host/services/loader-service'; - -import { CardDef } from 'https://cardstack.com/base/card-api'; -import { type CatalogEntry } from 'https://cardstack.com/base/catalog-entry'; - -interface Signature { - Args: { - ref: CodeRef; - }; -} - -export default class CatalogEntryEditor extends Component { - - - @service declare cardService: CardService; - @service declare loaderService: LoaderService; - catalogEntryRef = catalogEntryRef; - catalogEntry = getSearchResults(this, () => ({ - filter: { - on: this.catalogEntryRef, - eq: { ref: this.args.ref }, - }, - })); - @tracked entry: CatalogEntry | undefined; - @tracked newEntry: CatalogEntry | undefined; - - get card() { - return this.entry ?? this.catalogEntry.instances[0]; - } - - @action - async createEntry(): Promise { - let loader = this.loaderService.loader; - let realmInfoResponse = await loader.fetch( - `${this.cardService.defaultURL}_info`, - { headers: { Accept: SupportedMimeType.RealmInfo } }, - ); - - let resource = { - attributes: { - title: humanReadable(this.args.ref), - description: `Catalog entry for ${humanReadable(this.args.ref)}`, - ref: this.args.ref, - demo: undefined, - }, - meta: { - adoptsFrom: this.catalogEntryRef, - realmInfo: (await realmInfoResponse.json())?.data?.attributes, - realmURL: this.cardService.defaultURL.href, - fields: { - demo: { - adoptsFrom: this.args.ref, - }, - }, - }, - }; - this.newEntry = (await this.cardService.createFromSerialized( - resource, - { data: resource }, - this.cardService.defaultURL, - )) as CatalogEntry; - } - - @action - onCancel() { - this.newEntry = undefined; - } - - @action - onSave(card: CardDef) { - this.entry = card as CatalogEntry; - } -} - -function ensureJsonExtension(url: string) { - if (!url.endsWith('.json')) { - return `${url}.json`; - } - return url; -} diff --git a/packages/host/app/components/editor/code-link.gts b/packages/host/app/components/editor/code-link.gts deleted file mode 100644 index 9193227d19..0000000000 --- a/packages/host/app/components/editor/code-link.gts +++ /dev/null @@ -1,27 +0,0 @@ -import { LinkTo } from '@ember/routing'; -import Component from '@glimmer/component'; - -interface Signature { - Args: {}; -} - -export default class CodeLink extends Component { - -} - -declare module '@glint/environment-ember-loose/registry' { - export default interface Registry { - 'Editor::CodeLink': typeof CodeLink; - } -} diff --git a/packages/host/app/components/editor/go.gts b/packages/host/app/components/editor/go.gts deleted file mode 100644 index 104bbc0c79..0000000000 --- a/packages/host/app/components/editor/go.gts +++ /dev/null @@ -1,441 +0,0 @@ -import { registerDestructor } from '@ember/destroyable'; -import { on } from '@ember/modifier'; -import { action } from '@ember/object'; -import type Owner from '@ember/owner'; -import { service } from '@ember/service'; -import { buildWaiter } from '@ember/test-waiters'; -import { isTesting } from '@embroider/macros'; -import Component from '@glimmer/component'; - -//@ts-ignore cached not available yet in definitely typed -import { cached, tracked } from '@glimmer/tracking'; - -import { restartableTask, timeout } from 'ember-concurrency'; - -import momentFrom from 'ember-moment/helpers/moment-from'; - -import { AddButton, Tooltip } from '@cardstack/boxel-ui/components'; - -import { - SupportedMimeType, - SingleCardDocument, - isCardDocument, - isSingleCardDocument, - logger, - chooseCard, - catalogEntryRef, - createNewCard, -} from '@cardstack/runtime-common'; -import { RealmPaths } from '@cardstack/runtime-common/paths'; - -import CardEditor from '@cardstack/host/components/card-editor'; - -import ENV from '@cardstack/host/config/environment'; -import monacoModifier from '@cardstack/host/modifiers/monaco'; -import { file, FileResource, isReady } from '@cardstack/host/resources/file'; -import { maybe } from '@cardstack/host/resources/maybe'; -import type CardService from '@cardstack/host/services/card-service'; -import type LoaderService from '@cardstack/host/services/loader-service'; -import type MessageService from '@cardstack/host/services/message-service'; -import type { - MonacoSDK, - IStandaloneCodeEditor, -} from '@cardstack/host/services/monaco-service'; -import MonacoService from '@cardstack/host/services/monaco-service'; - -import type { CardDef } from 'https://cardstack.com/base/card-api'; - -import { CatalogEntry } from 'https://cardstack.com/base/catalog-entry'; - -import FileTree from './file-tree'; -import Module from './module'; - -import type OperatorModeStateService from '../../services/operator-mode-state-service'; - -const { ownRealmURL: ownRealmURLString } = ENV; -const ownRealmURL = new URL(ownRealmURLString); -const log = logger('component:go'); -const waiter = buildWaiter('code-route:load-card-waiter'); - -interface Signature { - Args: { - monaco: MonacoSDK; - onEditorSetup?(editor: IStandaloneCodeEditor): void; - }; -} - -export default class Go extends Component { - - - @service declare loaderService: LoaderService; - @service declare cardService: CardService; - @service declare messageService: MessageService; - @service declare operatorModeStateService: OperatorModeStateService; - @service declare monacoService: MonacoService; - @tracked jsonError: string | undefined; - @tracked card: CardDef | undefined; - // note this is only subscribed to events from our own realm - private subscription: { url: string; unsubscribe: () => void } | undefined; - - constructor(owner: Owner, args: Signature['Args']) { - super(owner, args); - let url = `${this.cardService.defaultURL}_message`; - this.subscription = { - url, - unsubscribe: this.messageService.subscribe( - url, - ({ type, data: dataStr }) => { - if (type !== 'index') { - return; - } - let data = JSON.parse(dataStr); - if (!this.card || data.type !== 'incremental') { - return; - } - let invalidations = data.invalidations as string[]; - if (invalidations.includes(this.card.id)) { - this.loadCard.perform(new URL(this.card.id)); - } - }, - ), - }; - registerDestructor(this, () => { - this.subscription?.unsubscribe(); - }); - } - - @action - contentChanged(content: string) { - this.contentChangedTask.perform(content); - } - - contentChangedTask = restartableTask(async (content: string) => { - await timeout(500); - if ( - this.openFile.current?.state !== 'ready' || - content === this.openFile.current?.content - ) { - return; - } - - let isJSON = this.openFile.current.name.endsWith('.json'); - let json = isJSON && this.safeJSONParse(content); - - // Here lies the difference in how json files and other source code files - // are treated during editing in the code editor - if (json && isSingleCardDocument(json)) { - // writes json instance but doesn't update state of the file resource - // relies on message service subscription to update state - await this.saveFileSerializedCard(json); - return; - } else { - //writes source code and updates the state of the file resource - await this.writeSourceCodeToFile(this.openFile.current, content); - } - }); - - safeJSONParse(content: string) { - try { - return JSON.parse(content); - } catch (err) { - log.warn(`content ${content} is not valid JSON, skipping write`); - return; - } - } - - get language(): string | undefined { - if (this.operatorModeStateService.state.codePath) { - const editorLanguages = this.args.monaco.languages.getLanguages(); - let extension = - '.' + - this.operatorModeStateService.state.codePath.pathname.split('.').pop(); - let language = editorLanguages.find((lang) => - lang.extensions?.find((ext) => ext === extension), - ); - return language?.id ?? 'plaintext'; - } - return undefined; - } - - openFile = maybe(this, (context) => { - const relativePath = - this.operatorModeStateService.state.codePath?.toString(); - if (relativePath) { - return file(context, () => ({ - url: new RealmPaths(this.cardService.defaultURL).fileURL(relativePath) - .href, - onStateChange: (state) => { - if (state === 'not-found') { - this.operatorModeStateService.state.codePath = null; - } - }, - })); - } else { - return undefined; - } - }); - - writeSourceCodeToFile(file: FileResource, content: string) { - if (file.state !== 'ready') - throw new Error('File is not ready to be written to'); - - return file.write(content); - } - - async saveFileSerializedCard(json: SingleCardDocument) { - let realmPath = new RealmPaths(this.cardService.defaultURL); - let openPath = this.operatorModeStateService.state.codePath; - if (!openPath) { - return; - } - - let url = realmPath.fileURL(openPath.toString()!.replace(/\.json$/, '')); - if (this.openFile.current?.state !== 'ready') { - throw new Error(`Cannot save, ${url} is not open`); - } - let realmURL = this.openFile.current.realmURL; - if (!realmURL) { - throw new Error(`Cannot determine realm for ${url}`); - } - - let doc = this.monacoService.reverseFileSerialization( - json, - url.href, - realmURL, - ); - let card: CardDef | undefined; - try { - card = await this.cardService.createFromSerialized(doc.data, doc, url); - } catch (e) { - console.error( - 'JSON is not a valid card--TODO this should be an error message in the code editor', - ); - return; - } - - try { - await this.cardService.saveModel(this, card); - await this.loadCard.perform(url); - } catch (e) { - console.error('Failed to save single card document', e); - } - } - - @cached - get openFileCardJSON() { - this.jsonError = undefined; - if ( - this.openFile.current?.state === 'ready' && - this.openFile.current.name.endsWith('.json') - ) { - let maybeCard: any; - try { - maybeCard = JSON.parse(this.openFile.current.content); - } catch (err: any) { - this.jsonError = err.message; - return undefined; - } - if (isCardDocument(maybeCard)) { - let url = this.openFile.current.url.replace(/\.json$/, ''); - if (!url) { - return undefined; - } - this.loadCard.perform(new URL(url)); - return maybeCard; - } - } - return undefined; - } - - private loadCard = restartableTask(async (url: URL) => { - await this.withTestWaiters(async () => { - this.card = await this.cardService.loadModel(this, url); - }); - }); - - private async withTestWaiters(cb: () => Promise) { - let token = waiter.beginAsync(); - try { - let result = await cb(); - // only do this in test env--this makes sure that we also wait for any - // interior card instance async as part of our ember-test-waiters - if (isTesting()) { - await this.cardService.cardsSettled(); - } - return result; - } finally { - waiter.endAsync(token); - } - } - @action - onSave(card: CardDef) { - this.card = card; - } - - get path() { - return this.operatorModeStateService.state.codePath ?? '/'; - } - - get isRunnable(): boolean { - let filename = this.path.toString(); - return ['.gjs', '.js', '.gts', '.ts'].some((extension) => - filename.endsWith(extension), - ); - } - - @action - removeFile() { - if (!this.openFile.current || !('url' in this.openFile.current)) { - return; - } - this.remove.perform(this.openFile.current.url); - } - - private remove = restartableTask(async (url: string) => { - let headersAccept = this.openFileCardJSON - ? SupportedMimeType.CardJson - : SupportedMimeType.CardSource; - url = this.openFileCardJSON ? url.replace(/\.json$/, '') : url; - let response = await this.loaderService.loader.fetch(url, { - method: 'DELETE', - headers: { Accept: headersAccept }, - }); - if (!response.ok) { - throw new Error( - `could not delete file, status: ${response.status} - ${ - response.statusText - }. ${await response.text()}`, - ); - } - }); - - @action - async createNew() { - this.createNewCard.perform(); - } - - private createNewCard = restartableTask(async () => { - let card = await chooseCard({ - filter: { - on: catalogEntryRef, - eq: { isField: false }, - }, - }); - if (!card) { - return; - } - let newCard = await createNewCard(card.ref, new URL(card.id)); - if (!newCard) { - throw new Error( - `bug: could not create new card from catalog entry ${JSON.stringify( - catalogEntryRef, - )}`, - ); - } - let path = `${newCard.id.slice(ownRealmURLString.length)}.json`; - this.operatorModeStateService.state.codePath = new URL(path); - }); -} - -declare module '@glint/environment-ember-loose/registry' { - export default interface Registry { - 'Editor::Go': typeof Go; - } -} diff --git a/packages/host/app/components/editor/module.gts b/packages/host/app/components/editor/module.gts deleted file mode 100644 index dd11159eee..0000000000 --- a/packages/host/app/components/editor/module.gts +++ /dev/null @@ -1,53 +0,0 @@ -import Component from '@glimmer/component'; - -//@ts-ignore cached not available yet in definitely typed -import { cached } from '@glimmer/tracking'; - -import { ModuleSyntax } from '@cardstack/runtime-common/module-syntax'; - -import type { Ready } from '@cardstack/host/resources/file'; - -import type { BaseDef } from 'https://cardstack.com/base/card-api'; - -import ImportModule from './import-module'; -import Schema from './schema'; - -interface Signature { - Args: { - file: Ready; - }; -} - -export default class Module extends Component { - - - @cached - get moduleSyntax() { - return new ModuleSyntax(this.args.file.content); - } -} - -function cardsFromModule( - module: Record, - _never?: never, // glint insists that w/o this last param that there are actually no params -): (typeof BaseDef)[] { - return Object.values(module).filter( - (maybeCard) => typeof maybeCard === 'function' && 'baseDef' in maybeCard, - ); -} diff --git a/packages/host/app/components/editor/schema.gts b/packages/host/app/components/editor/schema.gts deleted file mode 100644 index ea81ae57bc..0000000000 --- a/packages/host/app/components/editor/schema.gts +++ /dev/null @@ -1,383 +0,0 @@ -import { fn } from '@ember/helper'; - -//@ts-ignore glint does not think this is consumed-but it is consumed in the template -import { hash } from '@ember/helper'; -import { on } from '@ember/modifier'; -import { action } from '@ember/object'; -import { service } from '@ember/service'; -import Component from '@glimmer/component'; -//@ts-ignore cached not available yet in definitely typed -import { cached, tracked } from '@glimmer/tracking'; - -import { restartableTask } from 'ember-concurrency'; -import Modifier from 'ember-modifier'; - -import { - Button, - BoxelInput, - CardContainer, - FieldContainer, - Label, -} from '@cardstack/boxel-ui/components'; - -import { - chooseCard, - catalogEntryRef, - identifyCard, - internalKeyFor, - moduleFrom, -} from '@cardstack/runtime-common'; -import { isCodeRef, type CodeRef } from '@cardstack/runtime-common/code-ref'; -import type { ModuleSyntax } from '@cardstack/runtime-common/module-syntax'; -import { RealmPaths } from '@cardstack/runtime-common/paths'; -import type { Filter } from '@cardstack/runtime-common/query'; - -import { eq } from '@cardstack/boxel-ui/helpers'; - -import { getCardType, type Type } from '@cardstack/host/resources/card-type'; -import type { Ready } from '@cardstack/host/resources/file'; -import type CardService from '@cardstack/host/services/card-service'; - -import type LoaderService from '@cardstack/host/services/loader-service'; - -import type { BaseDef, FieldType } from 'https://cardstack.com/base/card-api'; -import type { CatalogEntry } from 'https://cardstack.com/base/catalog-entry'; - -import CatalogEntryEditor from './catalog-entry-editor'; - -interface Signature { - Args: { - card: typeof BaseDef; - file: Ready; - moduleSyntax: ModuleSyntax; - }; -} - -export default class Schema extends Component { - - - @service declare loaderService: LoaderService; - @service declare cardService: CardService; - @tracked newFieldName: string | undefined; - @tracked newFieldType: FieldType = 'contains'; - - @cached - get ref() { - let ref = identifyCard(this.args.card); - if (!ref) { - throw new Error(`bug: unable to identify card ${this.args.card.name}`); - } - return ref as { module: string; name: string }; - } - - @cached - get realmPath() { - return new RealmPaths( - this.loaderService.loader.reverseResolution( - this.cardService.defaultURL.href, - ), - ); - } - - @cached - get cardType() { - return getCardType(this, () => this.args.card); - } - - get isNewFieldDisabled() { - return Boolean(this.errorMsg) || !this.newFieldName; - } - - @cached - get errorMsg(): string | undefined { - if (!this.newFieldName) { - return; - } - if ( - this.cardType.type?.fields.find( - (field) => field.name === this.newFieldName, - ) - ) { - return `The field name "${this.newFieldName}" already exists, please choose a different name.`; - } - return; - } - - @action - isOwnField(fieldName: string): boolean { - return Object.keys( - Object.getOwnPropertyDescriptors(this.args.card.prototype), - ).includes(fieldName); - } - @action - isThisCard(card: Type | CodeRef): boolean { - return ( - internalKeyFor(this.ref, undefined) === - (isCodeRef(card) ? internalKeyFor(card, undefined) : card.id) - ); - } - - @action - inRealm(url: string): boolean { - return this.realmPath.inRealm(new URL(url)); - } - - @action - modulePath(url: string): string { - return this.realmPath.local(new URL(url)); - } - - @action - moduleSchemaURL(url: string): string { - return `${this.loaderService.loader.resolve(url)}?schema`; - } - - @action - addField() { - this.makeField.perform(); - } - - @action - deleteField(fieldName: string) { - this.args.moduleSyntax.removeField( - { type: 'exportedName', name: this.ref.name }, - fieldName, - ); - this.write.perform(this.args.moduleSyntax.code()); - } - - @action - setNewFieldName(value: string) { - this.newFieldName = value; - } - - @action - setNewFieldType(fieldType: FieldType) { - this.newFieldType = fieldType; - } - - private makeField = restartableTask(async () => { - let filter: Filter = - this.newFieldType === 'linksTo' || this.newFieldType === 'linksToMany' - ? { - on: catalogEntryRef, - eq: { isField: false }, - } - : { - on: catalogEntryRef, - not: { - eq: { ref: this.ref }, - }, - }; - let fieldEntry: CatalogEntry | undefined = await chooseCard({ filter }); - if (!fieldEntry) { - return; - } - - if (!this.newFieldName) { - throw new Error('bug: new field name is not specified'); - } - this.args.moduleSyntax.addField( - { type: 'exportedName', name: this.ref.name }, - this.newFieldName, - fieldEntry.ref, - this.newFieldType, - undefined, - undefined, - undefined, - ); - await this.write.perform(this.args.moduleSyntax.code()); - }); - - private write = restartableTask(async (src: string) => { - if (this.args.file.state !== 'ready') { - throw new Error(`the file ${this.args.file.url} is not open`); - } - // note that this write will cause the component to rerender, so - // any code after this write will not be executed since the component will - // get torn down before subsequent code can execute - this.args.file.write(src, true); - }); -} - -function cardId(card: Type | CodeRef): string { - if (isCodeRef(card)) { - return internalKeyFor(card, undefined); - } else { - return card.id; - } -} - -function cardModule(card: Type | CodeRef): string { - if (isCodeRef(card)) { - return moduleFrom(card); - } else { - return card.module; - } -} - -interface RadioInitializerSignature { - element: HTMLInputElement; - Args: { - Positional: [model: boolean, inputType: boolean]; - }; -} - -class RadioInitializer extends Modifier { - modify( - element: HTMLInputElement, - [model, inputType]: RadioInitializerSignature['Args']['Positional'], - ) { - element.checked = model === inputType; - } -} diff --git a/packages/host/app/router.ts b/packages/host/app/router.ts index 1d76f5f9a0..94c27db13b 100644 --- a/packages/host/app/router.ts +++ b/packages/host/app/router.ts @@ -24,8 +24,5 @@ Router.map(function () { if (!path || hostsOwnAssets) { this.route('index-card', { path: '/' }); - this.route('code'); - } else { - this.route('code', { path: `${path}/code` }); } }); diff --git a/packages/host/app/templates/card-error.hbs b/packages/host/app/templates/card-error.hbs index eecc8d3413..155e10f571 100644 --- a/packages/host/app/templates/card-error.hbs +++ b/packages/host/app/templates/card-error.hbs @@ -4,5 +4,3 @@ @operatorModeState={{this.model.operatorModeState}} /> - - \ No newline at end of file diff --git a/packages/host/app/templates/card.hbs b/packages/host/app/templates/card.hbs index 036d0a4fdd..42ddf9f88d 100644 --- a/packages/host/app/templates/card.hbs +++ b/packages/host/app/templates/card.hbs @@ -6,8 +6,6 @@ {{/if}} - - {{on-key 'Ctrl+.' this.toggleOperatorMode}} {{! Ctrl+. doesn't work in ubuntu }} {{on-key 'Ctrl+,' this.toggleOperatorMode}} diff --git a/packages/host/app/templates/code.hbs b/packages/host/app/templates/code.hbs deleted file mode 100644 index e3523dd6e4..0000000000 --- a/packages/host/app/templates/code.hbs +++ /dev/null @@ -1,10 +0,0 @@ -{{#unless this.model.isFastBoot}} - {{page-title 'Boxel'}} - - - - - - - {{outlet}} -{{/unless}} \ No newline at end of file diff --git a/packages/host/tests/acceptance/basic-test.ts b/packages/host/tests/acceptance/basic-test.ts index 50f9411f86..fd69de9dff 100644 --- a/packages/host/tests/acceptance/basic-test.ts +++ b/packages/host/tests/acceptance/basic-test.ts @@ -1,23 +1,12 @@ -import { - find, - visit, - currentURL, - click, - waitFor, - fillIn, - waitUntil, -} from '@ember/test-helpers'; +import { find, visit, currentURL } from '@ember/test-helpers'; -import percySnapshot from '@percy/ember'; import { setupApplicationTest } from 'ember-qunit'; import window from 'ember-window-mock'; import { setupWindowMock } from 'ember-window-mock/test-support'; -import { module, skip, test } from 'qunit'; +import { module, test } from 'qunit'; import { baseRealm } from '@cardstack/runtime-common'; -import { type LooseSingleCardDocument } from '@cardstack/runtime-common'; - import { Realm } from '@cardstack/runtime-common/realm'; import type LoaderService from '@cardstack/host/services/loader-service'; @@ -27,11 +16,7 @@ import { TestRealmAdapter, setupLocalIndexing, setupServerSentEvents, - testRealmURL, - getMonacoContent, sourceFetchReturnUrlHandle, - waitForSyntaxHighlighting, - type TestContextWithSSE, } from '../helpers'; const indexCardSource = ` @@ -161,266 +146,36 @@ module('Acceptance | basic tests', function (hooks) { await realm.ready; }); - test('visiting / (there is no realm here)', async function (assert) { - await visit('/'); - - assert.strictEqual(currentURL(), '/'); - assert - .dom('[data-test-moved]') - .containsText('The card code editor has moved to /code'); - await click('[data-test-code-link]'); - assert.strictEqual(currentURL(), '/code'); - }); - test('visiting realm root', async function (assert) { await visit('/test/'); assert.strictEqual(currentURL(), '/test/'); assert.dom('[data-test-index-card]').containsText('Hello, world'); - assert - .dom('[data-test-moved]') - .containsText('The card code editor has moved to /code'); - await click('[data-test-code-link]'); - assert.strictEqual(currentURL(), '/code'); - }); - - test('Can expand/collapse directories file tree', async function (assert) { - await visit('/code'); - await waitFor('[data-test-file]'); - assert - .dom('[data-test-directory="Person/"]') - .exists('Person/ directory entry is rendered'); - assert - .dom('[data-test-file="person.gts"]') - .exists('person.gts file entry is rendered'); - await click('[data-test-directory="Person/"]'); - await waitFor('[data-test-file="Person/1.json"]'); - assert - .dom('[data-test-file="Person/1.json"]') - .exists('Person/1.json file entry is rendered'); - await click('[data-test-directory="Person/"]'); - assert - .dom('[data-test-file="Person/1.json"]') - .doesNotExist('Person/1.json file entry is not rendered'); - }); - - skip('Can view a card instance', async function (assert) { - await visit('/code'); - await waitFor('[data-test-file]'); - await click('[data-test-directory="Person/"]'); - await waitFor('[data-test-file="Person/1.json"]'); - - await click('[data-test-file="Person/1.json"]'); - - assert.strictEqual( - currentURL(), - '/code?openDirs=Person%2F&openFile=Person%2F1.json', - ); - assert - .dom('[data-test-file="Person/1.json"]') - .exists('Person/1.json file entry is rendered'); - assert.dom('[data-test-person]').containsText('First name: Hassan'); - assert.dom('[data-test-person]').containsText('Last name: Abdel-Rahman'); - assert.dom('[data-test-person]').containsText('Title: Hassan Abdel-Rahman'); - assert.deepEqual(JSON.parse(getMonacoContent()), { - data: { - type: 'card', - attributes: { - firstName: 'Hassan', - lastName: 'Abdel-Rahman', - }, - meta: { - adoptsFrom: { - module: `../person`, - name: 'Person', - }, - }, - }, - }); - - assert.dom('[data-test-person]').hasStyle( - { - color: 'rgb(0, 128, 0)', - }, - 'expected scoped CSS to apply to card instance', - ); - - await waitForSyntaxHighlighting('"Person"', 'rgb(4, 81, 165)'); - await percySnapshot(assert); - }); - - test('Card instance live updates when index changes', async function (assert) { - let expectedEvents = [ - { - type: 'index', - data: { - type: 'incremental', - invalidations: [`${testRealmURL}Person/1`], - }, - }, - ]; - - await visit('/code'); - await waitFor('[data-test-file]'); - await click('[data-test-directory="Person/"]'); - await waitFor('[data-test-file="Person/1.json"]'); - await click('[data-test-file="Person/1.json"]'); - - await this.expectEvents( - assert, - realm, - adapter, - expectedEvents, - async () => { - await realm.write( - 'Person/1.json', - JSON.stringify({ - data: { - type: 'card', - attributes: { - firstName: 'HassanXXX', - }, - meta: { - adoptsFrom: { - module: '../person', - name: 'Person', - }, - }, - }, - } as LooseSingleCardDocument), - ); - }, - ); - await waitUntil( - () => - document - .querySelector('[data-test-person]')! - .textContent?.includes('HassanXXX'), - ); - assert.dom('[data-test-person]').containsText('First name: HassanXXX'); - }); - - skip('Can view a card schema', async function (assert) { - await visit('/code'); - await waitFor('[data-test-file]'); - await click('[data-test-file="person.gts"]'); - await waitFor('[data-test-card-id]'); - - assert.strictEqual(currentURL(), '/code?openFile=person.gts'); - assert - .dom('[data-test-card-id]') - .containsText(`${testRealmURL}person/Person`); - assert - .dom('[data-test-adopts-from]') - .containsText(`${baseRealm.url}card-api/Card`); - assert.dom('[data-test-field="firstName"]').exists(); - assert.dom('[data-test-field="lastName"]').exists(); - assert.strictEqual( - getMonacoContent(), - personCardSource, - 'the monaco content is correct', - ); - - // Syntax highlighting is breadth-first, this is the latest and deepest token - await waitForSyntaxHighlighting("''", 'rgb(163, 21, 21)'); - await waitFor('[data-test-boxel-card-container] [data-test-description]'); - - await percySnapshot(assert); }); test('glimmer-scoped-css smoke test', async function (assert) { - await visit('/code'); + await visit('/'); - const buttonElement = find('[data-test-create-new-card-button]'); + const cardContainerElement = find('[data-test-boxel-card-container]'); - assert.ok(buttonElement); + assert.ok(cardContainerElement); - if (!buttonElement) { - throw new Error('[data-test-create-new-card-button] element not found'); + if (!cardContainerElement) { + throw new Error('[data-test-boxel-card-container] element not found'); } - const buttonElementScopedCssAttribute = Array.from(buttonElement.attributes) + const buttonElementScopedCssAttribute = Array.from( + cardContainerElement.attributes, + ) .map((attribute) => attribute.localName) .find((attributeName) => attributeName.startsWith('data-scopedcss')); if (!buttonElementScopedCssAttribute) { throw new Error( - 'Scoped CSS attribute not found on [data-test-create-new-card-button]', + 'Scoped CSS attribute not found on [data-test-boxel-card-container]', ); } - assert.dom('[data-test-create-new-card-button] + style').doesNotExist(); - }); - - skip('can create a new card', async function (assert) { - await visit('/code'); - await click('[data-test-create-new-card-button]'); - assert - .dom('[data-test-card-catalog-modal] [data-test-boxel-header-title]') - .containsText('Choose a CatalogEntry card'); - await waitFor('[data-test-card-catalog-modal] [data-test-realm-name]'); - - await click(`[data-test-select="${testRealmURL}person-entry"]`); - await click('[data-test-card-catalog-go-button]'); - await waitFor(`[data-test-create-new-card="Person"]`); - await waitFor(`[data-test-field="firstName"] input`); - - await fillIn('[data-test-field="firstName"] input', 'Mango'); - await fillIn('[data-test-field="lastName"] input', 'Abdel-Rahman'); - await fillIn('[data-test-field="description"] input', 'Person'); - await fillIn('[data-test-field="thumbnailURL"] input', './mango.png'); - await click('[data-test-save-card]'); - await waitUntil(() => currentURL() === '/code?openFile=Person%2F2.json'); - - await click('[data-test-directory="Person/"]'); - await waitFor('[data-test-file="Person/2.json"]'); - assert - .dom('[data-test-file="Person/2.json"]') - .exists('Person/2.json file entry is rendered'); - assert.dom('[data-test-person]').containsText('First name: Mango'); - assert.dom('[data-test-person]').containsText('Last name: Abdel-Rahman'); - assert.dom('[data-test-person]').containsText('Title: Mango Abdel-Rahman'); - assert.deepEqual(JSON.parse(getMonacoContent()), { - data: { - type: 'card', - attributes: { - firstName: 'Mango', - lastName: 'Abdel-Rahman', - description: 'Person', - thumbnailURL: './mango.png', - }, - meta: { - adoptsFrom: { - module: `../person`, - name: 'Person', - }, - }, - }, - }); - let fileRef = await adapter.openFile('Person/2.json'); - if (!fileRef) { - throw new Error('file not found'); - } - assert.deepEqual( - JSON.parse(fileRef.content as string), - { - data: { - type: 'card', - attributes: { - firstName: 'Mango', - lastName: 'Abdel-Rahman', - description: 'Person', - thumbnailURL: './mango.png', - }, - meta: { - adoptsFrom: { - module: `../person`, - name: 'Person', - }, - }, - }, - }, - 'file contents are correct', - ); + assert.dom('[data-test-boxel-card-container] + style').doesNotExist(); }); }); diff --git a/packages/host/tests/integration/components/catalog-entry-editor-test.gts b/packages/host/tests/integration/components/catalog-entry-editor-test.gts deleted file mode 100644 index 487925dbd5..0000000000 --- a/packages/host/tests/integration/components/catalog-entry-editor-test.gts +++ /dev/null @@ -1,913 +0,0 @@ -import { waitUntil, waitFor, fillIn, click } from '@ember/test-helpers'; -import GlimmerComponent from '@glimmer/component'; - -import { setupRenderingTest } from 'ember-qunit'; -import { module, test } from 'qunit'; - -import { baseRealm, CodeRef } from '@cardstack/runtime-common'; -import { Loader } from '@cardstack/runtime-common/loader'; -import { Realm } from '@cardstack/runtime-common/realm'; - -import CardCatalogModal from '@cardstack/host/components/card-catalog/modal'; -import CardPrerender from '@cardstack/host/components/card-prerender'; -import CreateCardModal from '@cardstack/host/components/create-card-modal'; -import CatalogEntryEditor from '@cardstack/host/components/editor/catalog-entry-editor'; - -import type LoaderService from '@cardstack/host/services/loader-service'; - -import { - TestRealm, - TestRealmAdapter, - testRealmURL, - setupLocalIndexing, -} from '../../helpers'; -import { renderComponent } from '../../helpers/render-component'; - -let loader: Loader; - -module('Integration | catalog-entry-editor', function (hooks) { - let adapter: TestRealmAdapter; - let realm: Realm; - setupRenderingTest(hooks); - setupLocalIndexing(hooks); - - hooks.beforeEach(async function () { - loader = (this.owner.lookup('service:loader-service') as LoaderService) - .loader; - - adapter = new TestRealmAdapter({ - 'person.gts': ` - import { contains, field, Component, FieldDef } from "https://cardstack.com/base/card-api"; - import StringCard from "https://cardstack.com/base/string"; - export class Person extends FieldDef { - @field firstName = contains(StringCard); - @field title = contains(StringCard, { - computeVia: function (this: Person) { - return this.firstName; - }, - }); - @field description = contains(StringCard, { computeVia: () => 'Person' }); - @field thumbnailURL = contains(StringCard, { computeVia: () => './person.svg' }); - static embedded = class Embedded extends Component { - - } - } - `, - 'pet.gts': ` - import { contains, field, Component, CardDef } from "https://cardstack.com/base/card-api"; - import StringCard from "https://cardstack.com/base/string"; - import BooleanCard from "https://cardstack.com/base/boolean"; - import DateCard from "https://cardstack.com/base/date"; - import { Person } from "./person"; - export class Pet extends CardDef { - @field name = contains(StringCard); - @field lovesWalks = contains(BooleanCard); - @field birthday = contains(DateCard); - @field owner = contains(Person); - @field title = contains(StringCard, { - computeVia: function (this: Pet) { - return this.name; - }, - }); - static embedded = class Embedded extends Component { - - } - } - `, - }); - realm = await TestRealm.createWithAdapter(adapter, loader, this.owner); - await realm.ready; - }); - - test('can publish new catalog entry', async function (assert) { - const args: CodeRef = { module: `${testRealmURL}pet`, name: 'Pet' }; - await renderComponent( - class TestDriver extends GlimmerComponent { - - }, - ); - - await waitFor('button[data-test-catalog-entry-publish]', { timeout: 5000 }); - await click('[data-test-catalog-entry-publish]'); - // for some reason this takes long enough in CI that it seems - // to trigger a timeout error using the default timeout - await waitFor('[data-test-ref]', { timeout: 5000 }); - await waitFor('[data-test-field="realmName"]', { timeout: 5000 }); - - assert - .dom('[data-test-catalog-entry-editor]') - .exists('catalog entry editor exists'); - assert - .dom('[data-test-field="title"] input') - .hasValue( - `Pet from ${testRealmURL}pet`, - 'title input field value is correct', - ); - assert - .dom('[data-test-field="description"] input') - .hasValue( - `Catalog entry for Pet from ${testRealmURL}pet`, - 'description input field value is correct', - ); - assert - .dom('[data-test-ref]') - .containsText(`Module: ${testRealmURL}pet Name: Pet`); - assert - .dom('[data-test-field="realmName"]') - .containsText(`Unnamed Workspace`); - assert - .dom('[data-test-field="demo"] [data-test-field="name"] input') - .hasValue('', 'demo card name input field is correct'); - assert - .dom( - '[data-test-field="demo"] [data-test-field="lovesWalks"] label:nth-of-type(2) input', - ) - .isChecked('demo card lovesWalks input field is correct'); - - await fillIn('[data-test-field="title"] input', 'Pet test'); - await fillIn('[data-test-field="description"] input', 'Test description'); - await fillIn( - '[data-test-field="demo"] [data-test-field="description"] input', - 'Beagle', - ); - await fillIn( - '[data-test-field="demo"] [data-test-field="thumbnailURL"] input', - './jackie.png', - ); - await fillIn('[data-test-field="name"] input', 'Jackie'); - await click('[data-test-field="lovesWalks"] label:nth-of-type(1) input'); - await fillIn('[data-test-field="firstName"] input', 'BN'); - await click('button[data-test-save-card]'); - - await waitUntil(() => !document.querySelector('[data-test-saving]')); - - let entry = await realm.searchIndex.card( - new URL(`${testRealmURL}CatalogEntry/1`), - ); - assert.ok(entry, 'the new catalog entry was created'); - - let fileRef = await adapter.openFile('CatalogEntry/1.json'); - if (!fileRef) { - throw new Error('file not found'); - } - assert.deepEqual( - JSON.parse(fileRef.content as string), - { - data: { - type: 'card', - attributes: { - title: 'Pet test', - description: 'Test description', - ref: { - module: `${testRealmURL}pet`, - name: 'Pet', - }, - demo: { - name: 'Jackie', - lovesWalks: true, - birthday: null, - owner: { - firstName: 'BN', - }, - description: 'Beagle', - thumbnailURL: './jackie.png', - }, - }, - meta: { - adoptsFrom: { - module: 'https://cardstack.com/base/catalog-entry', - name: 'CatalogEntry', - }, - fields: { - demo: { - adoptsFrom: { - module: `${testRealmURL}pet`, - name: 'Pet', - }, - }, - }, - }, - }, - }, - 'file contents are correct', - ); - }); - - test('can edit existing catalog entry', async function (assert) { - await realm.write( - 'pet-catalog-entry.json', - JSON.stringify({ - data: { - type: 'card', - attributes: { - title: 'Pet', - description: 'Catalog entry', - ref: { - module: `${testRealmURL}pet`, - name: 'Pet', - }, - demo: { - name: 'Jackie', - lovesWalks: true, - birthday: null, - owner: { - firstName: 'BN', - }, - }, - }, - meta: { - adoptsFrom: { - module: `${baseRealm.url}catalog-entry`, - name: 'CatalogEntry', - }, - fields: { - demo: { - adoptsFrom: { - module: `${testRealmURL}pet`, - name: 'Pet', - }, - }, - }, - }, - }, - }), - ); - - const args: CodeRef = { module: `${testRealmURL}pet`, name: 'Pet' }; - await renderComponent( - class TestDriver extends GlimmerComponent { - - }, - ); - - await waitFor('[data-test-format-button="edit"]'); - await click('[data-test-format-button="edit"]'); - - assert - .dom('[data-test-catalog-entry-id]') - .hasText(`${testRealmURL}pet-catalog-entry`); - assert - .dom('[data-test-field="title"] input') - .hasValue('Pet', 'title input field value is correct'); - assert - .dom('[data-test-field="description"] input') - .hasValue('Catalog entry', 'description input field value is correct'); - assert - .dom('[data-test-ref]') - .containsText(`Module: ${testRealmURL}pet Name: Pet`); - assert - .dom('[data-test-field="realmName"]') - .containsText(`Unnamed Workspace`); - assert - .dom('[data-test-field="demo"] [data-test-field="name"] input') - .hasValue('Jackie', 'demo card name input field is correct'); - assert - .dom('[data-test-field="lovesWalks"] label:nth-of-type(1) input') - .isChecked('title input field value is correct'); - assert - .dom('[data-test-field="owner"] [data-test-field="firstName"] input') - .hasValue( - 'BN', - 'demo card owner first name input field value is correct', - ); - - await fillIn('[data-test-field="title"] input', 'test title'); - await fillIn('[data-test-field="description"] input', 'test description'); - await fillIn('[data-test-field="name"] input', 'Jackie Wackie'); - await fillIn('[data-test-field="firstName"] input', 'EA'); - - await click('button[data-test-save-card]'); - await waitUntil(() => !document.querySelector('[data-test-saving]')); - - assert.dom('[data-test-title]').hasText('test title'); - assert.dom('[data-test-description]').hasText('test description'); - assert.dom('[data-test-realm-name]').hasText('in Unnamed Workspace'); - assert - .dom('[data-test-demo] [data-test-pet-name]') - .hasText('Jackie Wackie'); - assert.dom('[data-test-demo] [data-test-pet-owner]').exists(); - assert.dom('[data-test-demo] [data-test-pet-owner]').hasText('EA'); - - let maybeError = await realm.searchIndex.card( - new URL(`${testRealmURL}pet-catalog-entry`), - ); - if (maybeError?.type === 'error') { - throw new Error( - `unexpected error when getting card from index: ${maybeError.error.detail}`, - ); - } - let { doc } = maybeError!; - assert.strictEqual( - doc?.data.attributes?.title, - 'test title', - 'catalog entry title was updated', - ); - assert.strictEqual( - doc?.data.attributes?.description, - 'test description', - 'catalog entry description was updated', - ); - assert.strictEqual( - doc?.data.attributes?.demo?.name, - 'Jackie Wackie', - 'demo name field was updated', - ); - assert.strictEqual( - doc?.data.attributes?.demo?.owner?.firstName, - 'EA', - 'demo owner firstName field was updated', - ); - }); - - test('can edit existing catalog entry that uses relative references', async function (assert) { - await realm.write( - 'dir/pet-catalog-entry.json', - JSON.stringify({ - data: { - type: 'card', - attributes: { - title: 'Pet', - description: 'Catalog entry', - ref: { - module: `../pet`, - name: 'Pet', - }, - demo: { - name: 'Jackie', - lovesWalks: true, - birthday: null, - owner: { - firstName: 'BN', - }, - }, - }, - meta: { - adoptsFrom: { - module: `${baseRealm.url}catalog-entry`, - name: 'CatalogEntry', - }, - fields: { - demo: { - adoptsFrom: { - module: `../pet`, - name: 'Pet', - }, - }, - }, - }, - }, - }), - ); - - const args: CodeRef = { module: `${testRealmURL}pet`, name: 'Pet' }; - await renderComponent( - class TestDriver extends GlimmerComponent { - - }, - ); - - await waitFor('[data-test-format-button="edit"]'); - await click('[data-test-format-button="edit"]'); - - assert - .dom('[data-test-catalog-entry-id]') - .hasText(`${testRealmURL}dir/pet-catalog-entry`); - assert - .dom('[data-test-field="title"] input') - .hasValue('Pet', 'title input field value is correct'); - assert - .dom('[data-test-field="description"] input') - .hasValue('Catalog entry', 'description input field value is correct'); - assert.dom('[data-test-ref]').containsText(`Module: ../pet Name: Pet`); - assert - .dom('[data-test-field="demo"] [data-test-field="name"] input') - .hasValue('Jackie', 'demo card name input field is correct'); - assert - .dom('[data-test-field="lovesWalks"] label:nth-of-type(1) input') - .isChecked('title input field value is correct'); - assert - .dom('[data-test-field="owner"] [data-test-field="firstName"] input') - .hasValue( - 'BN', - 'demo card owner first name input field value is correct', - ); - - await fillIn('[data-test-field="title"] input', 'test title'); - await fillIn('[data-test-field="description"] input', 'test description'); - await fillIn('[data-test-field="name"] input', 'Jackie Wackie'); - await fillIn('[data-test-field="firstName"] input', 'EA'); - - await click('button[data-test-save-card]'); - await waitUntil(() => !document.querySelector('[data-test-saving]')); - - assert.dom('[data-test-title]').hasText('test title'); - assert.dom('[data-test-description]').hasText('test description'); - assert - .dom('[data-test-demo] [data-test-pet-name]') - .hasText('Jackie Wackie'); - assert.dom('[data-test-demo] [data-test-pet-owner]').exists(); - assert.dom('[data-test-demo] [data-test-pet-owner]').hasText('EA'); - - let maybeError = await realm.searchIndex.card( - new URL(`${testRealmURL}dir/pet-catalog-entry`), - ); - if (maybeError?.type === 'error') { - throw new Error( - `unexpected error when getting card from index: ${maybeError.error.detail}`, - ); - } - let { doc } = maybeError!; - assert.strictEqual( - doc?.data.attributes?.title, - 'test title', - 'catalog entry title was updated', - ); - assert.strictEqual( - doc?.data.attributes?.description, - 'test description', - 'catalog entry description was updated', - ); - assert.strictEqual( - doc?.data.attributes?.demo?.name, - 'Jackie Wackie', - 'demo name field was updated', - ); - assert.strictEqual( - doc?.data.attributes?.demo?.owner?.firstName, - 'EA', - 'demo owner firstName field was updated', - ); - }); - - test('can create new card with missing composite field value', async function (assert) { - const args: CodeRef = { module: `${testRealmURL}pet`, name: 'Pet' }; - await renderComponent( - class TestDriver extends GlimmerComponent { - - }, - ); - - await waitFor('button[data-test-catalog-entry-publish]'); - await click('[data-test-catalog-entry-publish]'); - await waitFor('[data-test-ref]'); - - await fillIn('[data-test-field="name"] input', 'Jackie'); - await click('button[data-test-save-card]'); - await waitUntil(() => !document.querySelector('[data-test-saving]')); - - let entry = await realm.searchIndex.card( - new URL(`${testRealmURL}CatalogEntry/1`), - ); - assert.ok(entry, 'catalog entry was created'); - - await renderComponent( - class TestDriver extends GlimmerComponent { - - }, - ); - - await waitFor('[data-test-format-button="edit"]'); - await click('[data-test-format-button="edit"]'); - assert.dom('[data-test-field="firstName"] input').exists(); - - let fileRef = await adapter.openFile('CatalogEntry/1.json'); - if (!fileRef) { - throw new Error('file not found'); - } - assert.deepEqual( - JSON.parse(fileRef.content as string), - { - data: { - type: 'card', - attributes: { - title: `Pet from ${testRealmURL}pet`, - description: `Catalog entry for Pet from ${testRealmURL}pet`, - ref: { - module: `${testRealmURL}pet`, - name: 'Pet', - }, - demo: { - name: 'Jackie', - lovesWalks: false, - birthday: null, - owner: { - firstName: null, - }, - description: null, - thumbnailURL: null, - }, - }, - meta: { - adoptsFrom: { - module: 'https://cardstack.com/base/catalog-entry', - name: 'CatalogEntry', - }, - fields: { - demo: { - adoptsFrom: { - module: `${testRealmURL}pet`, - name: 'Pet', - }, - }, - }, - }, - }, - }, - 'file contents are correct', - ); - }); - - test('can create new catalog entry with all demo card field values missing', async function (assert) { - const args: CodeRef = { module: `${testRealmURL}person`, name: 'Person' }; - await renderComponent( - class TestDriver extends GlimmerComponent { - - }, - ); - - await waitFor('button[data-test-catalog-entry-publish]'); - await click('[data-test-catalog-entry-publish]'); - await waitFor('[data-test-ref]'); - - await click('button[data-test-save-card]'); - await waitUntil(() => !document.querySelector('[data-test-saving]')); - - await renderComponent( - class TestDriver extends GlimmerComponent { - - }, - ); - - await waitFor('[data-test-format-button="edit"]'); - await click('[data-test-format-button="edit"]'); - assert.dom('[data-test-field="firstName"] input').exists(); - - let entry = await realm.searchIndex.card( - new URL(`${testRealmURL}CatalogEntry/1`), - ); - assert.ok(entry, 'catalog entry was created'); - - let fileRef = await adapter.openFile('CatalogEntry/1.json'); - if (!fileRef) { - throw new Error('file not found'); - } - assert.deepEqual( - JSON.parse(fileRef.content as string), - { - data: { - type: 'card', - attributes: { - demo: { - firstName: null, - }, - title: `Person from ${testRealmURL}person`, - description: `Catalog entry for Person from ${testRealmURL}person`, - ref: { - module: `${testRealmURL}person`, - name: 'Person', - }, - }, - meta: { - adoptsFrom: { - module: 'https://cardstack.com/base/catalog-entry', - name: 'CatalogEntry', - }, - fields: { - demo: { - adoptsFrom: { - module: `${testRealmURL}person`, - name: 'Person', - }, - }, - }, - }, - }, - }, - 'file contents are correct', - ); - }); - - test('it can render catalog entry for card with linksTo field', async function (assert) { - await realm.write( - 'pet.gts', - ` - import { contains, field, CardDef, Component } from "https://cardstack.com/base/card-api"; - import StringCard from "https://cardstack.com/base/string"; - export class Pet extends CardDef { - @field name = contains(StringCard); - static embedded = class Embedded extends Component { - - }; - } - `, - ); - // note that person.gts already exists in beforeEach, so using a different module so we don't collide - await realm.write( - 'nice-person.gts', - ` - import { contains, field, linksTo, CardDef, Component } from "https://cardstack.com/base/card-api"; - import StringCard from "https://cardstack.com/base/string"; - import { Pet } from "./pet"; - export class NicePerson extends CardDef { - @field firstName = contains(StringCard); - @field lastName = contains(StringCard); - @field pet = linksTo(Pet); - static embedded = class Embedded extends Component { - - }; - } - `, - ); - await realm.write( - 'jackie-pet.json', - JSON.stringify({ - data: { - type: 'card', - attributes: { - name: 'Jackie', - }, - meta: { - adoptsFrom: { - module: `${testRealmURL}pet`, - name: 'Pet', - }, - }, - }, - }), - ); - await realm.write( - 'person-entry.json', - JSON.stringify({ - data: { - type: 'card', - attributes: { - title: 'Person', - description: 'Catalog entry', - ref: { - module: `${testRealmURL}nice-person`, - name: 'NicePerson', - }, - demo: { - firstName: 'Burcu', - lastName: 'Noyan', - }, - }, - relationships: { - 'demo.pet': { - links: { - self: `${testRealmURL}jackie-pet`, - }, - }, - }, - meta: { - fields: { - demo: { - adoptsFrom: { - module: `${testRealmURL}nice-person`, - name: 'NicePerson', - }, - }, - }, - adoptsFrom: { - module: `${baseRealm.url}catalog-entry`, - name: 'CatalogEntry', - }, - }, - }, - }), - ); - - const args: CodeRef = { - module: `${testRealmURL}nice-person`, - name: 'NicePerson', - }; - await renderComponent( - class TestDriver extends GlimmerComponent { - - }, - ); - - await waitFor('[data-test-ref]'); - assert - .dom(`[data-test-ref]`) - .hasText(`Module: ${testRealmURL}nice-person Name: NicePerson`); - - await waitFor('[data-test-person-name]'); - assert.dom('[data-test-person-name]').hasText('Burcu Noyan'); - - await waitFor('[data-test-pet-name]'); - assert.dom('[data-test-pet-name]').exists(); - assert.dom('[data-test-pet-name]').hasText('Jackie'); - }); - - test('can use card classes defined on the same module as fields', async function (assert) { - await realm.write( - 'invoice.gts', - ` - import { contains, containsMany, field, linksTo, CardDef, FieldDef, Component } from "https://cardstack.com/base/card-api"; - import NumberCard from "https://cardstack.com/base/number"; - import StringCard from "https://cardstack.com/base/string"; - class Vendor extends CardDef { - @field company = contains(StringCard); - @field title = contains(StringCard, { - computeVia: function (this: Vendor) { - return this.company; - }, - }); - @field description = contains(StringCard, { computeVia: () => 'Vendor' }); - @field thumbnailURL = contains(StringCard, { computeVia: () => null }); - static embedded = class Embedded extends Component { - - }; - } - class Item extends FieldDef { - @field name = contains(StringCard); - @field price = contains(NumberCard); - @field title = contains(StringCard, { - computeVia: function (this: Item) { - return this.name + ' ' + this.price; - }, - }); - @field description = contains(StringCard, { computeVia: () => null }); - @field thumbnailURL = contains(StringCard, { computeVia: () => null }); - } - class LineItem extends Item { - @field quantity = contains(NumberCard); - static embedded = class Embedded extends Component { - - }; - } - export class Invoice extends CardDef { - @field vendor = linksTo(Vendor); - @field lineItems = containsMany(LineItem); - @field balanceDue = contains(NumberCard, { computeVia: function(this: Invoice) { - return this.lineItems.length === 0 ? 0 : this.lineItems.map(i => i.price * i.quantity).reduce((a, b) => (a + b)); - }}); - @field title = contains(StringCard, { - computeVia: function (this: Invoice) { - return this.vendor ? 'Invoice from ' + this.vendor.title : 'Invoice' - }, - }); - @field description = contains(StringCard, { computeVia: () => 'Invoice' }); - @field thumbnailURL = contains(StringCard, { computeVia: () => null }); - static embedded = class Embedded extends Component { - - }; - } - `, - ); - - const args: CodeRef = { module: `${testRealmURL}invoice`, name: 'Invoice' }; - await renderComponent( - class TestDriver extends GlimmerComponent { - - }, - ); - - await waitFor('button[data-test-catalog-entry-publish]'); - await click('[data-test-catalog-entry-publish]'); - await waitFor('[data-test-ref]'); - - await click('[data-test-field="lineItems"] [data-test-add-new]'); - await fillIn('[data-test-field="name"] input', 'Keyboard'); - await fillIn('[data-test-field="quantity"] input', '2'); - await fillIn('[data-test-field="price"] input', '150'); - - await click('[data-test-field="vendor"] [data-test-add-new]'); - await waitFor('[data-test-card-catalog-modal]'); - await waitFor('[data-test-card-catalog-create-new-button]'); - - await click('[data-test-card-catalog-create-new-button]'); - await waitFor('[data-test-create-new-card="Vendor"]'); - await fillIn('[data-test-field="company"] input', 'Big Tech'); - - await click('[data-test-create-new-card="Vendor"] [data-test-save-card]'); - await waitUntil(() => !document.querySelector('[data-test-saving]')); - - await click('button[data-test-save-card]'); - await waitUntil(() => !document.querySelector('[data-test-saving]')); - - assert.dom('[data-test-company]').hasText('Big Tech'); - assert - .dom('[data-test-line-item="Keyboard"]') - .hasText('Keyboard - 2 @ $ 150 USD'); - assert.dom('[data-test-balance-due]').hasText('300'); - - let entry = await realm.searchIndex.card( - new URL(`${testRealmURL}CatalogEntry/1`), - ); - assert.ok(entry, 'the new catalog entry was created'); - - let fileRef = await adapter.openFile('CatalogEntry/1.json'); - if (!fileRef) { - throw new Error('file not found'); - } - assert.deepEqual( - JSON.parse(fileRef.content as string), - { - data: { - type: 'card', - attributes: { - title: `Invoice from ${testRealmURL}invoice`, - description: `Catalog entry for Invoice from ${testRealmURL}invoice`, - ref: { - module: `${testRealmURL}invoice`, - name: 'Invoice', - }, - demo: { - lineItems: [ - { - name: 'Keyboard', - quantity: 2, - price: 150, - }, - ], - }, - }, - meta: { - adoptsFrom: { - module: `${baseRealm.url}catalog-entry`, - name: 'CatalogEntry', - }, - fields: { - demo: { - adoptsFrom: { - module: `${testRealmURL}invoice`, - name: 'Invoice', - }, - }, - }, - }, - relationships: { - 'demo.vendor': { - links: { - self: `${testRealmURL}cards/1`, - }, - }, - }, - }, - }, - 'file contents are correct', - ); - - let vendorfileRef = await realm.searchIndex.card( - new URL(`${testRealmURL}cards/1`), - ); - if (!vendorfileRef || !('doc' in vendorfileRef)) { - throw new Error('file not found'); - } - assert.deepEqual( - vendorfileRef?.doc.data.meta.adoptsFrom, - { - type: 'fieldOf', - field: 'vendor', - card: { - module: `${testRealmURL}invoice`, - name: 'Invoice', - }, - }, - 'newly created vendor file has correct meta.adoptsFrom', - ); - }); -}); diff --git a/packages/realm-server/dom-tests/realm-dom-test.js b/packages/realm-server/dom-tests/realm-dom-test.js index cd3c2a2304..c869c86121 100644 --- a/packages/realm-server/dom-tests/realm-dom-test.js +++ b/packages/realm-server/dom-tests/realm-dom-test.js @@ -80,7 +80,7 @@ QUnit.module( hooks.afterEach(resetTestContainer); test('renders app', async function (assert) { - await boot(testRealmURL, 'a'); + await boot(testRealmURL, 'p'); assert.strictEqual(testDocument().location.href, `${testRealmURL}/`); let p = querySelector('p'); assert.ok(p, '

element exists');