From e9325f46564d24e00cbd0ae9383441450bc5345e Mon Sep 17 00:00:00 2001
From: Taylor Romero <me@taylorrome.ro>
Date: Mon, 29 Jul 2024 15:26:56 -0600
Subject: [PATCH] minor: active record card

---
 src/pages/concepts/views.md | 314 +++++++++++++++++++++++++++++++++++-
 1 file changed, 309 insertions(+), 5 deletions(-)

diff --git a/src/pages/concepts/views.md b/src/pages/concepts/views.md
index 0f57d9d8..bf56be75 100644
--- a/src/pages/concepts/views.md
+++ b/src/pages/concepts/views.md
@@ -1572,7 +1572,9 @@ export default class WhosOnWifiCardTest extends AbstractSpruceFixtureTest {
 
         await this.eventFaker.fakeListFamilyMembers(({ target }) => {
             passedTarget = target
-            return []
+            return {
+                people: []
+            }
         })
 
         await this.vc.load(organizationId)
@@ -1591,13 +1593,13 @@ import { SpruceSchemas, Person } from '@sprucelabs/spruce-core-schemas'
 
 export class EventFaker {
     public async fakeListFamilyMembers(
-        cb?: (targetAndPayload: ListConnectPeopleTargetAndPayload) => void | Person[]
+        cb?: (targetAndPayload: ListConnectPeopleTargetAndPayload) => void | ListConnectedPeopleResponse
     ) {
         await eventFaker.on(
             'eightbitstories.list-familyMembers::v2024_07_22',
             (targetAndPayload) => {
-                return {
-                    people: cb?.(targetAndPayload) ?? [],
+                return cb?.(targetAndPayload) ?? {
+                    people: [],
                 }
             }
         )
@@ -1605,7 +1607,309 @@ export class EventFaker {
 }
 
 export type ListFamilyMembersTargetAndPayload =
-    SpruceSchemas.Eightbitstories.v2024_07_22.ListFamilyMembers
+    SpruceSchemas.Eightbitstories.v2024_07_22.ListFamilyMembersEmitTargetAndPayload
+export type ListConnectedPeopleResponse =
+    SpruceSchemas.Eightbitstories.v2024_07_22.ListFamilyMembersResponsePayload
+
+```
+</details>
+
+<details>
+<summary><strong>Test 6a</strong>: Assert the expected rows are rendered</summary>
+
+```ts
+import { vcAssert } from '@sprucelabs/heartwood-view-controllers'
+import { AbstractFixtureTest } from '@sprucelabs/spruce-test-fixtures'
+import { assert, generateId, test } from '@sprucelabs/test-utils'
+import MyCardViewController from '../../../viewControllers/MyCard.vc'
+import { Person } from '@sprucelabs/spruce-core-schemas'
+import EventFaker, { ListFamilyMembersTargetAndPayload } from '../../support/EventFaker'
+
+export default class WhosOnWifiCardTest extends AbstractSpruceFixtureTest {
+    private static vc: MyCardViewController
+    private static eventFaker: EventFaker
+
+    protected static async beforeEach() {
+        await super.beforeEach()
+        this.eventFaker = new EventFaker()
+        this.vc = this.views.Controller('eightbitstories.my-card', {})
+    }
+
+    @test()
+    protected static async rendersAsInstanceOfActiveRecordCard() {
+        vcAssert.assertIsActiveRecordCard(this.vc)
+    }
+
+    @test()
+    protected static async emitsListConnectedPeopleOnLoad() {
+        const organizationId = generateId()
+
+        let passedTarget:
+            | ListFamilyMembersTargetAndPayload['target']
+            | undefined
+
+        await this.eventFaker.fakeListFamilyMembers(({ target }) => {
+            passedTarget = target
+            return {
+                people: []
+            }
+        })
+
+        await this.vc.load(organizationId)
+
+        assert.isEqualDeep(passedTarget, { organizationId })
+    }
+
+    @test()
+    protected static async rendersRowForResults() {
+        const organizationId = generateId()
+
+        const person: Person = {
+            id: generateId(),
+            casualName: generateId(),
+            networkInterface: 'eth0',
+        }
+
+        await this.eventFaker.fakeListConnectedPeople(() => [person])
+
+        await this.vc.load(organizationId)
+
+        listAssert.listRendersRow(this.vc.getListVc(), person.id)
+    }
+}
+
+```
+
+> **Note**: You should get an error that `getListVc()` doesn't exist. To fix this, we'll need a `Spy` test double to expose the `ActiveRecordCard`'s `getListVc()` method.
+
+</details>
+
+<details>
+<summary><strong>Test 6b</strong>: Create the Test Double</summary>
+
+Here we're going to create the `SpyMyCard` test double, override the controller using `this.views.setController()`, and typecast the controller to `SpyMyCard` to expose the `getListVc()` method.
+
+```ts
+import { vcAssert } from '@sprucelabs/heartwood-view-controllers'
+import { AbstractFixtureTest } from '@sprucelabs/spruce-test-fixtures'
+import { assert, generateId, test } from '@sprucelabs/test-utils'
+import MyCardViewController from '../../../viewControllers/MyCard.vc'
+import { Person } from '@sprucelabs/spruce-core-schemas'
+import EventFaker, { ListFamilyMembersTargetAndPayload } from '../../support/EventFaker'
+
+export default class WhosOnWifiCardTest extends AbstractSpruceFixtureTest {
+    private static vc: SpyMyCard
+    private static eventFaker: EventFaker
+
+    protected static async beforeEach() {
+        await super.beforeEach()
+        this.eventFaker = new EventFaker()
+
+        this.views.setController('eightbitstories.my-card', SpyMyCard)
+        this.vc = this.views.Controller('eightbitstories.my-card', {}) as SpyMyCard
+    }
+
+    @test()
+    protected static async rendersAsInstanceOfActiveRecordCard() {
+        vcAssert.assertIsActiveRecordCard(this.vc)
+    }
+
+    @test()
+    protected static async emitsListConnectedPeopleOnLoad() {
+        const organizationId = generateId()
+
+        let passedTarget:
+            | ListFamilyMembersTargetAndPayload['target']
+            | undefined
+
+        await this.eventFaker.fakeListFamilyMembers(({ target }) => {
+            passedTarget = target
+            return {
+                people: []
+            }
+        })
+
+        await this.vc.load(organizationId)
+
+        assert.isEqualDeep(passedTarget, { organizationId })
+    }
+
+    @test()
+    protected static async rendersRowForResults() {
+        const organizationId = generateId()
+
+        const person: Person = {
+            id: generateId(),
+            casualName: generateId(),
+            networkInterface: 'eth0',
+        }
+
+        await this.eventFaker.fakeListConnectedPeople(() => [person])
+
+        await this.vc.load(organizationId)
+
+        listAssert.listRendersRow(this.vc.getListVc(), person.id)
+    }
+}
+
+class SpyMyCard extends MyCardViewController {
+    public getListVc() {
+        return this.activeRecordCardVc.getListVc()
+    }
+}
+
+```
+
+> **Note**: Even though the test will pass, you'll get a type error because `activeRecordCard` is private in `MyCardViewController`. We'll address that in the production code while we make the test pass.
+
+</details>
+
+<details>
+<summary><strong>Production 6</strong>: Render the row as expected</summary>
+
+We are doing 2 things here:
+
+1. Setting `activeRecordCardVc` to `protected` so that `SpyMyCard` can access it.
+2. Updating the `rowTransformer` to use the family member's id for the row id and the family member's name for the row cell.
+
+```ts
+import {
+    AbstractViewController,
+    ViewControllerOptions,
+    Card,
+    buildActiveRecordCard,
+    ActiveRecordCardViewController,
+} from '@sprucelabs/heartwood-view-controllers'
+
+export default class MyCardViewController extends AbstractViewController<Card> {
+    public static id = 'my-card'
+    protected activeRecordCardVc: ActiveRecordCardViewController
+
+    public constructor(options: ViewControllerOptions) {
+        super(options)
+        this.activeRecordCardVc = this.ActiveCardVc()
+    }
+
+    private ActiveCardVc() {
+        return this.Controller(
+            'active-record-card',
+            buildActiveRecordCard({
+                id: 'my-cards-id',
+                header: {
+                    title: "Family Members",
+                },
+                eventName: 'eightbitstories.list-family-members::v2024_07_22',
+                responseKey: 'familyMembers',
+                rowTransformer: (familyMember) => ({
+                    id: familyMember.id,
+                    cells: [{
+                        text: {
+                            content: familyMember.name
+                        }
+                    }],
+                }),
+            })
+        )
+    }
+
+    public async load(organizationId: string) {
+        this.activeRecordCardVc.setTarget({ organizationId })
+        await this.activeRecordCardVc.load()
+    }
+
+    public render() {
+        return this.activeRecordCardVc.render()
+    }
+}
+
+```
+
+</details>
+
+<details>
+<summary><strong>Test 7</strong>: Dry the test</summary>
+
+There is quite a bit happening here:
+
+1. Moved a lot of things to the `beforeEach()` to simplify the tests
+    1. The `organizationId`
+    2. The `familyMembers` return from the event
+    3. The `lastListFamilyMembersTarget` from the event
+2. Created a `load()` method to pass the `organizationId` to `load()` for us
+
+
+```ts
+import { vcAssert } from '@sprucelabs/heartwood-view-controllers'
+import { AbstractFixtureTest } from '@sprucelabs/spruce-test-fixtures'
+import { assert, generateId, test } from '@sprucelabs/test-utils'
+import MyCardViewController from '../../../viewControllers/MyCard.vc'
+import { Person } from '@sprucelabs/spruce-core-schemas'
+import EventFaker, { ListFamilyMembersTargetAndPayload } from '../../support/EventFaker'
+
+export default class WhosOnWifiCardTest extends AbstractSpruceFixtureTest {
+    private static vc: SpyMyCard
+    private static eventFaker: EventFaker
+    private static organizationId: string
+    private static lastListFamilyMembersTarget:
+        | ListFamilyMembersTargetAndPayload['target']
+        | undefined
+
+    private static familyMembers: Person[] = []
+
+    protected static async beforeEach() {
+        await super.beforeEach()
+        
+        this.eventFaker = new EventFaker()
+        this.organizationId = generateId()
+        this.familyMembers = []
+
+        this.views.setController('eightbitstories.my-card', SpyMyCard)
+        this.vc = this.views.Controller('eightbitstories.my-card', {}) as SpyMyCard
+
+        delete this.lastListFamilyMembersTarget
+        
+        await this.eventFaker.fakeListFamilyMembers(({ target }) => {
+            this.lastListFamilyMembersTarget = target
+            return {
+                people: this.familyMembers
+            }
+        })
+    }
+
+    @test()
+    protected static async rendersAsInstanceOfActiveRecordCard() {
+        vcAssert.assertIsActiveRecordCard(this.vc)
+    }
+
+    @test()
+    protected static async emitsListConnectedPeopleOnLoad() {
+        await this.load()
+        assert.isEqualDeep(this.lastListFamilyMembersTarget, { organizationId: this.organizationId })
+    }
+
+    @test()
+    protected static async rendersRowForResults() {
+        this.familyMembers.push({
+            id: generateId(),
+            casualName: generateId(),
+            networkInterface: 'eth0',
+        })
+
+        await this.load()
+
+        listAssert.listRendersRow(this.vc.getListVc(), this.familyMembers[0].id)
+    }
+
+    protected static async load() {
+        await this.vc.load(this.organizationId)
+    }
+}
+
+class SpyMyCard extends MyCardViewController {
+    public getListVc() {
+        return this.activeRecordCardVc.getListVc()
+    }
+}
 
 ```