From fec5e230b1f5a7bdb99fd12abbc2c1301d1c3145 Mon Sep 17 00:00:00 2001 From: Edward Faulkner Date: Thu, 16 May 2024 15:40:54 -0400 Subject: [PATCH] removing async computed from tests --- docs/computed-fields.md | 22 - packages/host/app/services/render-service.ts | 1 + packages/host/tests/cards/person.gts | 6 +- .../integration/components/computed-test.gts | 420 ++---------------- 4 files changed, 30 insertions(+), 419 deletions(-) diff --git a/docs/computed-fields.md b/docs/computed-fields.md index bdfe85a007..1febcf0810 100644 --- a/docs/computed-fields.md +++ b/docs/computed-fields.md @@ -37,28 +37,6 @@ When any field consumed by the computed field changes, the value will [rerender] Computed fields are eagerly evaluated, they do not need to be consumed for `computeVia` to run. -## Async computation - -The calculation can be async: - -```typescript -@field slowName = contains(StringCard, { - computeVia: async function () { - await new Promise((resolve) => setTimeout(resolve, 10)); - return this.firstName; - } -}); -``` - -The values of async computed fields are available synchronously in other `computedVia` functions. Since async field values are not guaranteed to be present, you can use card API [`getIfReady`](https://github.com/cardstack/boxel/blob/307d78676ebdb93cee75d61b8812914013a094a7/packages/base/card-api.gts#L2112) avoid errors about the field not being ready: - -```javascript -await getIfReady(this, 'fullName'); -// 'Carl Stack' -await getIfReady(this, 'fullName'); -// {type: 'not-ready', fieldName: 'fullName', instance: …} -``` - ## Computed `linksTo` and `linksToMany` While `computeVia` can currently only be applied to `contains`/`containsMany` fields, there’s a plan to let it work for `linksTo` and `linksToMany` in the future. diff --git a/packages/host/app/services/render-service.ts b/packages/host/app/services/render-service.ts index f0d5076766..97dc08ae31 100644 --- a/packages/host/app/services/render-service.ts +++ b/packages/host/app/services/render-service.ts @@ -123,6 +123,7 @@ export default class RenderService extends Service { return parseCardHtml(html); } + // TODO delete me private async resolveField( params: Omit & { fieldName: string }, ): Promise { diff --git a/packages/host/tests/cards/person.gts b/packages/host/tests/cards/person.gts index 4a9b27d611..d96b557afb 100644 --- a/packages/host/tests/cards/person.gts +++ b/packages/host/tests/cards/person.gts @@ -14,8 +14,7 @@ export class Person extends CardDef { @field email = contains(StringCard); @field posts = contains(NumberCard); @field fullName = contains(StringCard, { - computeVia: async function (this: Person) { - await new Promise((resolve) => setTimeout(resolve, 10)); + computeVia: function (this: Person) { return `${this.firstName ?? ''} ${this.lastName ?? ''}`; }, }); @@ -46,8 +45,7 @@ export class PersonField extends FieldDef { @field email = contains(StringCard); @field posts = contains(NumberCard); @field fullName = contains(StringCard, { - computeVia: async function (this: Person) { - await new Promise((resolve) => setTimeout(resolve, 10)); + computeVia: function (this: Person) { return `${this.firstName ?? ''} ${this.lastName ?? ''}`; }, }); diff --git a/packages/host/tests/integration/components/computed-test.gts b/packages/host/tests/integration/components/computed-test.gts index c62f0212f6..0ec79e74b1 100644 --- a/packages/host/tests/integration/components/computed-test.gts +++ b/packages/host/tests/integration/components/computed-test.gts @@ -154,202 +154,27 @@ module('Integration | computeds', function (hooks) { assert.dom('[data-test="firstName"]').hasText('Mango'); }); - test('can render an asynchronous computed field', async function (assert) { - let { field, contains, CardDef, Component } = cardApi; - let { default: StringField } = string; - class Person extends CardDef { - @field firstName = contains(StringField); - @field slowName = contains(StringField, { - computeVia: 'computeSlowName', - }); - async computeSlowName() { - await new Promise((resolve) => setTimeout(resolve, 10)); - return this.firstName; - } - static isolated = class Isolated extends Component { - - }; - } - - let mango = new Person({ firstName: 'Mango' }); - let root = await renderCard(loader, mango, 'isolated'); - assert.strictEqual(root.textContent!.trim(), 'Mango'); - }); - - test('can render an asynchronous computed field (using an async function in `computeVia`)', async function (assert) { - let { field, contains, CardDef, Component } = cardApi; - let { default: StringField } = string; - class Person extends CardDef { - @field firstName = contains(StringField); - @field slowName = contains(StringField, { - computeVia: async function (this: Person) { - await new Promise((resolve) => setTimeout(resolve, 10)); - return this.firstName; - }, - }); - static isolated = class Isolated extends Component { - - }; - } - - let mango = new Person({ firstName: 'Mango' }); - let root = await renderCard(loader, mango, 'isolated'); - assert.strictEqual(root.textContent!.trim(), 'Mango'); - }); - - test('can indirectly render an asynchronous computed field', async function (assert) { - let { field, contains, CardDef, Component } = cardApi; - let { default: StringField } = string; - class Person extends CardDef { - @field firstName = contains(StringField); - @field slowName = contains(StringField, { - computeVia: 'computeSlowName', - }); - @field slowNameAlias = contains(StringField, { - computeVia: function (this: Person) { - return this.slowName; - }, - }); - async computeSlowName() { - await new Promise((resolve) => setTimeout(resolve, 10)); - return this.firstName; - } - static isolated = class Isolated extends Component { - - }; - } - - let mango = new Person({ firstName: 'Mango' }); - let root = await renderCard(loader, mango, 'isolated'); - assert.strictEqual(root.textContent!.trim(), 'Mango'); - }); - - test('can render a async computed that depends on an async computed: consumer field is first', async function (assert) { - let { field, contains, CardDef, Component } = cardApi; - let { default: StringField } = string; - class Person extends CardDef { - @field firstName = contains(StringField); - @field verySlowName = contains(StringField, { - computeVia: async function (this: Person) { - await new Promise((resolve) => setTimeout(resolve, 100)); - return this.slowName; - }, - }); - @field slowName = contains(StringField, { - computeVia: async function (this: Person) { - await new Promise((resolve) => setTimeout(resolve, 100)); - return this.firstName; - }, - }); - static isolated = class Isolated extends Component { - - }; - } - let mango = new Person({ firstName: 'Mango' }); - let root = await renderCard(loader, mango, 'isolated'); - assert.strictEqual(root.textContent!.trim(), 'Mango'); - }); - - test('can render a nested asynchronous computed field', async function (assert) { - let { field, contains, CardDef, FieldDef, Component } = cardApi; - let { default: StringField } = string; - class Person extends FieldDef { - @field firstName = contains(StringField); - @field slowName = contains(StringField, { - computeVia: 'computeSlowName', - }); - async computeSlowName() { - await new Promise((resolve) => setTimeout(resolve, 10)); - return this.firstName; - } - } - - class Post extends CardDef { - @field title = contains(StringField); - @field author = contains(Person); - static isolated = class Isolated extends Component { - - }; - } - loader.shimModule(`${testRealmURL}test-cards`, { Post, Person }); - - let firstPost = new Post({ - title: 'First Post', - author: new Person({ firstName: 'Mango' }), - }); - let root = await renderCard(loader, firstPost, 'isolated'); - assert.strictEqual( - cleanWhiteSpace(root.textContent!), - 'First Post by Mango', - ); - }); - - test('can render an asynchronous computed composite field', async function (assert) { - let { field, contains, CardDef, FieldDef, Component } = cardApi; - let { default: StringField } = string; - class Person extends FieldDef { - @field firstName = contains(StringField); - static embedded = class Embedded extends Component { - - }; - } - - class Post extends CardDef { - @field title = contains(StringField); - @field author = contains(Person, { computeVia: 'computeSlowAuthor' }); - async computeSlowAuthor() { - await new Promise((resolve) => setTimeout(resolve, 10)); - let person = new Person(); - person.firstName = 'Mango'; - return person; - } - static isolated = class Isolated extends Component { - - }; - } - let firstPost = new Post({ title: 'First Post' }); - await renderCard(loader, firstPost, 'isolated'); - assert.dom('[data-test="title"]').hasText('First Post'); - assert.dom('[data-test="firstName"]').hasText('Mango'); - }); - test('can render a containsMany computed primitive field', async function (assert) { let { field, contains, containsMany, CardDef, Component } = cardApi; let { default: StringField } = string; class Person extends CardDef { @field firstName = contains(StringField); @field languagesSpoken = containsMany(StringField); - @field slowLanguagesSpoken = containsMany(StringField, { - computeVia: 'computeSlowLanguagesSpoken', + @field reverseLanguagesSpoken = containsMany(StringField, { + computeVia: function (this: Person) { + return [...this.languagesSpoken].reverse(); + }, }); - async computeSlowLanguagesSpoken() { - await new Promise((resolve) => setTimeout(resolve, 10)); - return this.languagesSpoken; - } static isolated = class Isolated extends Component { }; } let mango = new Person({ firstName: 'Mango', - languagesSpoken: ['english', 'japanese'], + languagesSpoken: ['japanese', 'english'], }); let root = await renderCard(loader, mango, 'isolated'); @@ -365,16 +190,14 @@ module('Integration | computeds', function (hooks) { class Person extends CardDef { @field firstName = contains(StringField); @field languagesSpoken = containsMany(StringField); - @field slowLanguagesSpoken = containsMany(StringField, { - computeVia: 'computeSlowLanguagesSpoken', + @field reverseLanguagesSpoken = containsMany(StringField, { + computeVia: function (this: Person) { + return [...this.languagesSpoken].reverse(); + }, }); - async computeSlowLanguagesSpoken() { - await new Promise((resolve) => setTimeout(resolve, 10)); - return this.languagesSpoken; - } static isolated = class Isolated extends Component { }; } @@ -382,7 +205,7 @@ module('Integration | computeds', function (hooks) { let mango = new Person({ firstName: 'Mango' }); await renderCard(loader, mango, 'isolated'); // just using to absorb asynchronicity assert.deepEqual( - mango.slowLanguagesSpoken, + mango.reverseLanguagesSpoken, [], 'empty containsMany field is initialized to an empty array', ); @@ -403,16 +226,14 @@ module('Integration | computeds', function (hooks) { class Family extends CardDef { @field people = containsMany(Person); - @field slowPeople = containsMany(Person, { - computeVia: 'computeSlowPeople', + @field reversePeople = containsMany(Person, { + computeVia: function (this: Family) { + return [...this.people].reverse(); + }, }); - async computeSlowPeople() { - await new Promise((resolve) => setTimeout(resolve, 10)); - return this.people; - } static isolated = class Isolated extends Component { }; } @@ -434,7 +255,7 @@ module('Integration | computeds', function (hooks) { [...this.element.querySelectorAll('[data-test-firstName]')].map( (element) => element.textContent?.trim(), ), - ['Mango', 'Van Gogh', 'Hassan', 'Mariko', 'Yume', 'Sakura'], + ['Sakura', 'Yume', 'Mariko', 'Hassan', 'Van Gogh', 'Mango'], ); await renderCard(loader, abdelRahmans, 'edit'); @@ -461,23 +282,21 @@ module('Integration | computeds', function (hooks) { class Family extends CardDef { @field people = containsMany(Person); - @field slowPeople = containsMany(Person, { - computeVia: 'computeSlowPeople', + @field reversePeople = containsMany(Person, { + computeVia: function (this: Family) { + return [...this.people].reverse(); + }, }); - async computeSlowPeople() { - await new Promise((resolve) => setTimeout(resolve, 10)); - return this.people; - } static isolated = class Isolated extends Component { }; } let abdelRahmans = new Family(); await renderCard(loader, abdelRahmans, 'isolated'); // just using to absorb asynchronicity assert.deepEqual( - abdelRahmans.slowPeople, + abdelRahmans.reversePeople, [], 'empty containsMany field is initialized to an empty array', ); @@ -497,15 +316,10 @@ module('Integration | computeds', function (hooks) { class Family extends CardDef { @field people = containsMany(Person); @field totalAge = contains(NumberField, { - computeVia: 'computeTotalAge', + computeVia: function (this: Family) { + return this.people.reduce((sum, person) => (sum += person.age), 0); + }, }); - async computeTotalAge() { - let totalAge = this.people.reduce( - (sum, person) => (sum += person.age), - 0, - ); - return totalAge; - } } loader.shimModule(`${testRealmURL}test-cards`, { Family, Person }); @@ -544,78 +358,6 @@ module('Integration | computeds', function (hooks) { .doesNotExist('input field not rendered for computed'); }); - test('can maintain data consistency for async computed fields', async function (assert) { - let { field, contains, CardDef, FieldDef, Component } = cardApi; - let { default: StringField } = string; - class Location extends FieldDef { - @field city = contains(StringField); - static embedded = class Embedded extends Component { - - }; - } - class Person extends CardDef { - @field firstName = contains(StringField); - @field slowName = contains(StringField, { - computeVia: 'computeSlowName', - }); - @field homeTown = contains(Location); - @field slowHomeTown = contains(Location, { - computeVia: 'computeSlowHomeTown', - }); - async computeSlowName() { - await new Promise((resolve) => setTimeout(resolve, 10)); - return this.firstName; - } - async computeSlowHomeTown() { - await new Promise((resolve) => setTimeout(resolve, 10)); - return this.homeTown; - } - static edit = class Edit extends Component { - - }; - } - loader.shimModule(`${testRealmURL}test-cards`, { Location, Person }); - - let person = new Person({ - firstName: 'Mango', - homeTown: new Location({ city: 'Bronxville' }), - }); - - await renderCard(loader, person, 'edit'); - assert.dom('[data-test-field="slowName"]').containsText('Mango'); - await fillIn('[data-test-field="firstName"] input', 'Van Gogh'); - // We want to ensure data consistency, so that when the template rerenders, - // the template is always showing consistent field values - await waitUntil(() => - document - .querySelector('[data-test-dep-field="firstName"]') - ?.textContent?.includes('Van Gogh'), - ); - assert.dom('[data-test-field="slowName"]').containsText('Van Gogh'); - assert - .dom('[data-test-field="slowHomeTown"] [data-test-location]') - .containsText('Bronxville'); - - await fillIn('[data-test-field="homeTown"] input', 'Scarsdale'); - await waitUntil(() => - document - .querySelector('[data-test-dep-field="homeTown"]') - ?.textContent?.includes('Scarsdale'), - ); - assert - .dom('[data-test-field="slowHomeTown"] [data-test-location]') - .containsText('Scarsdale'); - }); - test('can render a computed linksTo relationship', async function (assert) { let { field, contains, linksTo, CardDef, FieldDef, Component } = cardApi; let { default: StringField } = string; @@ -671,53 +413,6 @@ module('Integration | computeds', function (hooks) { .doesNotExist(); }); - test('can render an asynchronous computed linksTo field', async function (assert) { - let { field, contains, linksTo, CardDef, FieldDef, Component } = cardApi; - let { default: StringField } = string; - class Pet extends CardDef { - @field name = contains(StringField); - static embedded = class Embedded extends Component { - - }; - } - class Person extends FieldDef { - @field firstName = contains(StringField); - @field bestFriend = linksTo(Pet); - static embedded = class Embedded extends Component { - - }; - } - class Post extends CardDef { - @field title = contains(StringField); - @field author = contains(Person); - @field friend = linksTo(Pet, { - computeVia: 'computeFriend', - }); - async computeFriend(this: Post) { - await new Promise((resolve) => setTimeout(resolve, 10)); - return this.author.bestFriend; - } - } - - let friend = new Pet({ name: 'Van Gogh' }); - let author = new Person({ firstName: 'Mango', bestFriend: friend }); - let firstPost = new Post({ title: 'First Post', author }); - - await renderCard(loader, firstPost, 'isolated'); - assert.dom('[data-test-field="title"]').hasText('Title First Post'); - assert - .dom('[data-test-field="author"] [data-test="firstName"]') - .hasText('Mango'); - assert - .dom('[data-test-field="friend"] [data-test="name"]') - .hasText('Van Gogh'); - }); - test('can render a computed linksToMany relationship', async function (this: RenderingTestContext, assert) { let { field, contains, linksTo, linksToMany, CardDef, Component } = cardApi; let { default: StringField } = string; @@ -785,65 +480,4 @@ module('Integration | computeds', function (hooks) { .dom('[data-test-links-to-many="collaborators"] [data-test-remove-card]') .doesNotExist(); }); - - test('can render an asynchronous computed linksToMany field', async function (this: RenderingTestContext, assert) { - let { field, contains, linksTo, linksToMany, CardDef, Component } = cardApi; - let { default: StringField } = string; - class Pet extends CardDef { - @field name = contains(StringField); - static embedded = class Embedded extends Component { - - }; - } - class Person extends CardDef { - @field firstName = contains(StringField); - @field pets = linksToMany(Pet); - static embedded = class Embedded extends Component { - - }; - } - class Post extends CardDef { - @field title = contains(StringField); - @field author = linksTo(Person); - @field factCheckers = linksToMany(Pet); - @field collaborators = linksToMany(Pet, { - computeVia: 'findCollaborators', - }); - async findCollaborators(this: Post) { - await new Promise((resolve) => setTimeout(resolve, 10)); - let mango = this.author.pets.find((p) => p.name === 'Mango'); - return [mango, ...this.factCheckers]; - } - } - - let p1 = new Pet({ id: `${testRealmURL}mango`, name: 'Mango' }); - let p2 = new Pet({ name: 'Tango' }); - let f1 = new Pet({ name: 'A' }); - let f2 = new Pet({ name: 'B' }); - let f3 = new Pet({ name: 'C' }); - let author = new Person({ firstName: 'Van Gogh', pets: [p1, p2] }); - let firstPost = new Post({ - title: 'First Post', - author, - factCheckers: [f1, f2, f3], - }); - - await renderCard(loader, firstPost, 'isolated'); - assert.dom('[data-test-field="title"]').hasText('Title First Post'); - assert - .dom('[data-test-field="author"] [data-test="firstName"]') - .hasText('Van Gogh'); - assert.deepEqual( - [ - ...this.element.querySelectorAll( - '[data-test-field="collaborators"] [data-test="name"]', - ), - ].map((element) => element.textContent?.trim()), - ['Mango', 'A', 'B', 'C'], - ); - }); });