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 {
-
-
- {{#if this.card}}
-
-
-
- {{this.card.id}}
-
-
-
-
- {{else if this.newEntry}}
-
-
-
-
-
- {{else if this.catalogEntry.isLoading}}
-
Loading...
- {{else}}
-
- {{/if}}
-
-
-
-
- @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 {
-
-
-
-
-
- <:trigger>
-
-
- <:content>
- Create a new card
-
-
-
- {{#if (isReady this.openFile.current)}}
-
-
- {{#if this.isRunnable}}
-
- {{else if this.openFileCardJSON}}
- {{#if this.card}}
-
- {{/if}}
- {{else if this.jsonError}}
-
Encountered error parsing JSON
-
{{this.jsonError}}
- {{/if}}
-
-
- {{/if}}
-
-
-
-
-
- @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 {
-
-
- <:ready as |module|>
- {{#each (cardsFromModule module) as |card|}}
-
- {{/each}}
-
- <:error as |error|>
- Encountered {{error.type}} error
- {{error.message}}
-
-
-
-
- @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 {
-
- {{#if this.cardType.type}}
-
-
- {{this.cardType.type.id}}
-
-
- {{this.cardType.type.super.id}}
-
-
- {{this.cardType.type.displayName}}
-
-
-
-
-
-
-
- {{/if}}
-
-
-
- @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 {
- <@fields.firstName/>
- }
- }
- `,
- '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 {
-
- <@fields.name/>
- <@fields.owner/>
- <@fields.birthday/>
-
- }
- }
- `,
- });
- 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 {
- <@fields.name/>
- };
- }
- `,
- );
- // 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 {
-
- <@fields.firstName/> <@fields.lastName/>
- Pet: <@fields.pet/>
-
- };
- }
- `,
- );
- 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 {
- <@fields.company/>
- };
- }
- 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 {
- <@fields.name/> - <@fields.quantity/> @ $<@fields.price/> USD
- };
- }
- 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 {
-
- Invoice
- Vendor: <@fields.vendor/>
- <@fields.lineItems/>
- Balance Due: $<@fields.balanceDue/> USD
-
- };
- }
- `,
- );
-
- 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');