@sprucelabs/spruce-cli 24.1.2 → 24.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/build/__tests__/behavioral/tests/migrationToInstance/StaticToInstanceTestFileMigrator.test.d.ts +2 -0
  3. package/build/__tests__/behavioral/tests/migrationToInstance/StaticToInstanceTestFileMigrator.test.js +16 -0
  4. package/build/__tests__/behavioral/tests/migrationToInstance/StaticToInstanceTestFileMigrator.test.js.map +1 -1
  5. package/build/__tests__/behavioral/tests/migrationToInstance/support/AbstractInstanceTest2.txt +24 -0
  6. package/build/__tests__/behavioral/tests/migrationToInstance/support/AbstractStaticTest2.txt +24 -0
  7. package/build/__tests__/behavioral/tests/migrationToInstance/support/InstanceTest.txt +8 -0
  8. package/build/__tests__/behavioral/tests/migrationToInstance/support/InstanceTest3.txt +1 -1
  9. package/build/__tests__/behavioral/tests/migrationToInstance/support/InstanceTest4.txt +531 -0
  10. package/build/__tests__/behavioral/tests/migrationToInstance/support/StaticTest.txt +8 -0
  11. package/build/__tests__/behavioral/tests/migrationToInstance/support/StaticTest3.txt +1 -1
  12. package/build/__tests__/behavioral/tests/migrationToInstance/support/StaticTest4.txt +530 -0
  13. package/build/tests/staticToInstanceMigration/StaticToInstanceTestFileMigrator.js +17 -13
  14. package/build/tests/staticToInstanceMigration/StaticToInstanceTestFileMigrator.js.map +1 -1
  15. package/package.json +3 -3
  16. package/src/__tests__/behavioral/tests/migrationToInstance/StaticToInstanceTestFileMigrator.test.ts +16 -0
  17. package/src/__tests__/behavioral/tests/migrationToInstance/support/AbstractInstanceTest2.txt +24 -0
  18. package/src/__tests__/behavioral/tests/migrationToInstance/support/AbstractStaticTest2.txt +24 -0
  19. package/src/__tests__/behavioral/tests/migrationToInstance/support/InstanceTest.txt +8 -0
  20. package/src/__tests__/behavioral/tests/migrationToInstance/support/InstanceTest3.txt +1 -1
  21. package/src/__tests__/behavioral/tests/migrationToInstance/support/InstanceTest4.txt +531 -0
  22. package/src/__tests__/behavioral/tests/migrationToInstance/support/StaticTest.txt +8 -0
  23. package/src/__tests__/behavioral/tests/migrationToInstance/support/StaticTest3.txt +1 -1
  24. package/src/__tests__/behavioral/tests/migrationToInstance/support/StaticTest4.txt +530 -0
  25. package/src/tests/staticToInstanceMigration/StaticToInstanceTestFileMigrator.ts +23 -16
@@ -0,0 +1,531 @@
1
+ import {
2
+ FormViewController,
3
+ SkillViewControllerId,
4
+ buttonAssert,
5
+ formAssert,
6
+ interactor,
7
+ navigationAssert,
8
+ vcAssert,
9
+ } from '@sprucelabs/heartwood-view-controllers'
10
+ import { selectAssert } from '@sprucelabs/schema'
11
+ import { SelectChoice } from '@sprucelabs/spruce-core-schemas'
12
+ import { FormCardViewController } from '@sprucelabs/spruce-form-utils'
13
+ import { eventFaker, fake, seed } from '@sprucelabs/spruce-test-fixtures'
14
+ import { assert, generateId, test, suite } from '@sprucelabs/test-utils'
15
+ import GenerateSkillViewController, {
16
+ CurrentChallengeSchema,
17
+ GenerateStorySchema,
18
+ } from '../../../generation/Generate.svc'
19
+ import { storyElements } from '../../../generation/storyElements'
20
+ import AbstractEightBitTest from '../../support/AbstractEightBitTest'
21
+ import {
22
+ GenerateStoryTargetAndPayload,
23
+ GetStoryStatusTargetAndPayload,
24
+ } from '../../support/EventFaker'
25
+
26
+ @fake.login()
27
+ @suite()
28
+ export default class GenerateSkillViewTest extends AbstractEightBitTest {
29
+ private vc!: SpyGenerateSkillView
30
+ private checkStatusIntervalCb: undefined | (() => Promise<void>)
31
+ private checkStatusIntervalMs!: number | undefined
32
+ private intervalId!: string
33
+ private passedIntervalIdToClear?: string
34
+
35
+ @seed('familyMembers', 3)
36
+ protected async beforeEach(): Promise<void> {
37
+ await super.beforeEach()
38
+
39
+ this.views.setController(
40
+ 'eightbitstories.generate',
41
+ SpyGenerateSkillView
42
+ )
43
+ this.views.setController('forms.card', SpyFormCard)
44
+
45
+ this.vc = this.Vc()
46
+
47
+ await this.eventFaker.fakeListFamilyMembers(() => this.members.find({}))
48
+ await this.loadVc()
49
+
50
+ delete this.checkStatusIntervalCb
51
+ delete this.checkStatusIntervalMs
52
+ delete this.passedIntervalIdToClear
53
+
54
+ this.intervalId = generateId()
55
+
56
+ //@ts-ignore
57
+ GenerateSkillViewController.setInterval = (
58
+ cb: () => Promise<void>,
59
+ intervalMs: number
60
+ ) => {
61
+ this.checkStatusIntervalMs = intervalMs
62
+ this.checkStatusIntervalCb = cb
63
+ return this.intervalId
64
+ }
65
+
66
+ //@ts-ignore
67
+ GenerateSkillViewController.clearInterval = (id: string) => {
68
+ this.passedIntervalIdToClear = id
69
+ }
70
+
71
+ console.log(this.locationIds)
72
+ console.log(this.organizationIds)
73
+ }
74
+
75
+ private get locationIds() {
76
+ console.log('here')
77
+ }
78
+
79
+ private set organizationIds() {
80
+ console.log('here')
81
+ }
82
+
83
+ @test()
84
+ protected async requiresLogin() {
85
+ await vcAssert.assertLoginIsRequired(this.vc)
86
+ }
87
+
88
+ @test()
89
+ protected async rendersExpectedCards() {
90
+ vcAssert.assertSkillViewRendersCards(this.vc, [
91
+ 'elements',
92
+ 'members',
93
+ 'currentChallenge',
94
+ 'controls',
95
+ ])
96
+ }
97
+
98
+ @test()
99
+ protected async controlsCardRendersExpectedButtons() {
100
+ buttonAssert.cardRendersButtons(this.controlsVc, ['back', 'generate'])
101
+ }
102
+
103
+ @test()
104
+ protected async rendersAlertAndRedirectsIfNoMembers() {
105
+ await this.eventFaker.fakeListFamilyMembers(() => [])
106
+ this.vc = this.Vc()
107
+ await vcAssert.assertRendersAlertThenRedirects({
108
+ vc: this.vc,
109
+ router: this.views.getRouter(),
110
+ destination: {
111
+ id: 'eightbitstories.root',
112
+ },
113
+ action: () => this.loadVc(),
114
+ })
115
+ }
116
+
117
+ @test()
118
+ protected async clickingBackGoesBackToRoot() {
119
+ await vcAssert.assertActionRedirects({
120
+ action: () => interactor.clickButton(this.controlsVc, 'back'),
121
+ destination: {
122
+ id: 'eightbitstories.root',
123
+ },
124
+ router: this.views.getRouter(),
125
+ })
126
+ }
127
+
128
+ @test()
129
+ protected elementsAndMembersCardsRendersForms() {
130
+ formAssert.cardRendersForm(this.elementsVc)
131
+ formAssert.cardRendersForm(this.membersVc)
132
+ formAssert.cardRendersForm(this.currentChallengeVc)
133
+ }
134
+
135
+ @test()
136
+ protected async formCardsDoNotRenderButtons() {
137
+ assert.isFalse(this.elementsFormVc.getShouldRenderSubmitControls())
138
+ assert.isFalse(this.membersFormVc.getShouldRenderSubmitControls())
139
+ assert.isFalse(
140
+ this.currentChallengeFormVc.getShouldRenderSubmitControls()
141
+ )
142
+ }
143
+
144
+ @test()
145
+ protected async elementsFormRendersExpectedFields() {
146
+ formAssert.formRendersFields(this.elementsFormVc, ['elements'])
147
+ }
148
+
149
+ @test()
150
+ protected async elementsFormRendersExpectedChoices() {
151
+ const schema = this.elementsFormVc.getSchema()
152
+ selectAssert.assertSelectChoicesMatch(
153
+ schema.fields.elements.options.choices,
154
+ storyElements.map((element) => element.id)
155
+ )
156
+ }
157
+
158
+ @test()
159
+ protected async rendersElementsAsTags() {
160
+ formAssert.formFieldRendersAs(this.elementsFormVc, 'elements', 'tags')
161
+ }
162
+
163
+ @test()
164
+ protected async membersFormRendersExpectedFields() {
165
+ formAssert.formRendersFields(this.membersFormVc, ['members'])
166
+ }
167
+
168
+ @test()
169
+ protected async membersFormRendersAsTags() {
170
+ formAssert.formFieldRendersAs(this.membersFormVc, 'members', 'tags')
171
+ }
172
+
173
+ @test()
174
+ protected async membersRendersExpectedChoices() {
175
+ const members = await this.getAllMembers()
176
+ const expected = members.map((member) => member.id)
177
+
178
+ const schema = this.membersFormVc.getSchema()
179
+ selectAssert.assertSelectChoicesMatch(
180
+ schema.fields.members.options.choices as SelectChoice[],
181
+ expected
182
+ )
183
+ }
184
+
185
+ @test()
186
+ protected async currentChallengeFormRendersAsExpected() {
187
+ formAssert.formRendersField(
188
+ this.currentChallengeFormVc,
189
+ 'currentChallenge'
190
+ )
191
+ formAssert.formFieldRendersAs(
192
+ this.currentChallengeFormVc,
193
+ 'currentChallenge',
194
+ 'textarea'
195
+ )
196
+ }
197
+
198
+ @test()
199
+ protected async clickingGenerateSetsControlsToBusy() {
200
+ await this.eventFaker.fakeGenerateStory(() => {})
201
+
202
+ await this.selectFirstMember()
203
+ await this.selectFirstElement()
204
+
205
+ const promise = this.clickGenerateAndAssertRedirect()
206
+ this.assertFooterIsBusy()
207
+
208
+ await promise
209
+ }
210
+
211
+ @test()
212
+ protected async rendersAlertIfFailsToGenerateStory() {
213
+ await eventFaker.makeEventThrow(
214
+ 'eightbitstories.generate-story::v2023_09_05'
215
+ )
216
+
217
+ const alertVc = await vcAssert.assertRendersAlert(this.vc, () =>
218
+ this.clickGenerate()
219
+ )
220
+
221
+ this.assertFooterIsBusy()
222
+
223
+ await alertVc.hide()
224
+
225
+ this.assertFooterIsNotBusy()
226
+ }
227
+
228
+ @test('submits selected members and elements 1', [0], [0])
229
+ @test('submits selected members and elements 2', [1], [2])
230
+ @test('submits selected members and elements 3', [0, 1], [2, 3])
231
+ protected async generatePassesSelectedMembersAndElements(
232
+ memberIdxs: number[],
233
+ elementIdxs: number[]
234
+ ) {
235
+ let passedPayload: GenerateStoryTargetAndPayload['payload'] | undefined
236
+
237
+ await this.eventFaker.fakeGenerateStory(({ payload }) => {
238
+ passedPayload = payload
239
+ })
240
+
241
+ const selectedMembers = await this.selectMembers(memberIdxs)
242
+ const selectedElements = await this.selectElements(elementIdxs)
243
+
244
+ const currentChallenge = generateId()
245
+ await this.currentChallengeFormVc.setValue(
246
+ 'currentChallenge',
247
+ currentChallenge
248
+ )
249
+
250
+ await this.clickGenerateAndAssertRedirect()
251
+
252
+ assert.isEqualDeep(passedPayload, {
253
+ familyMembers: selectedMembers,
254
+ storyElements: selectedElements,
255
+ currentChallenge,
256
+ storyHash: this.vc.getHash(),
257
+ })
258
+ }
259
+
260
+ @test()
261
+ protected async generatingStoryRedirectsToStoryWithArgs() {
262
+ await this.eventFaker.fakeGenerateStory()
263
+
264
+ await this.selectFirstElement()
265
+ await this.selectFirstMember()
266
+
267
+ const destination = {
268
+ id: 'eightbitstories.story' as SkillViewControllerId,
269
+ args: {
270
+ story: generateId(),
271
+ },
272
+ }
273
+
274
+ await this.clickGenerateAndAssertRedirect(destination)
275
+ }
276
+
277
+ @test()
278
+ protected async callingDestroyRemovesDidGenerateListener() {
279
+ await this.vc.destroy()
280
+
281
+ await eventFaker.handleReactiveEvent(
282
+ 'eightbitstories.did-generate-story::v2023_09_05'
283
+ )
284
+
285
+ await this.emitDidGenerate()
286
+ }
287
+
288
+ @test()
289
+ protected async rendersNullNavigation() {
290
+ navigationAssert.skillViewDoesNotRenderNavigation(this.vc)
291
+ }
292
+
293
+ @test()
294
+ protected async checksForGeneratedStoryAfterSubmitting() {
295
+ let passedTarget: GetStoryStatusTargetAndPayload['target'] | undefined
296
+ await this.eventFaker.fakeGetStoryGenerationStatus(({ target }) => {
297
+ passedTarget = target
298
+ })
299
+
300
+ await this.fakeGenerateSelectEverythingClickGenerateAndInvokeIntervalCb()
301
+
302
+ assert.isEqualDeep(passedTarget, {
303
+ storyHash: this.vc.getHash(),
304
+ })
305
+ }
306
+
307
+ @test()
308
+ protected async passesExpectedIntervalToChecksAfterSubmit() {
309
+ await this.eventFaker.fakeGenerateStory()
310
+ await this.selectElementFamilyMemberAndClickGenerate()
311
+ assert.isEqual(this.checkStatusIntervalMs, 1000 * 10)
312
+ }
313
+
314
+ @test()
315
+ protected async doesNotSetIntervalIfGenerateThrows() {
316
+ await eventFaker.makeEventThrow(
317
+ 'eightbitstories.generate-story::v2023_09_05'
318
+ )
319
+
320
+ await vcAssert.assertRendersAlert(this.vc, () =>
321
+ this.selectElementFamilyMemberAndClickGenerate()
322
+ )
323
+ assert.isUndefined(
324
+ this.checkStatusIntervalCb,
325
+ 'should not have been set'
326
+ )
327
+ }
328
+
329
+ @test()
330
+ protected async redirectsIfResponseIsStoryGenerated() {
331
+ const storyId = generateId()
332
+ await this.eventFaker.fakeGetStoryGenerationStatus(() => {
333
+ return {
334
+ status: 'ready',
335
+ storyId,
336
+ }
337
+ })
338
+
339
+ await this.fakeGenerateSelectEverythingAndClickGenerate()
340
+ await vcAssert.assertActionRedirects({
341
+ action: () => this.checkStatusIntervalCb?.(),
342
+ destination: {
343
+ id: 'eightbitstories.story',
344
+ args: {
345
+ story: storyId,
346
+ },
347
+ },
348
+ router: this.views.getRouter(),
349
+ })
350
+ }
351
+
352
+ @test()
353
+ protected async clearsTimeoutOnBlur() {
354
+ await this.fakeGenerateSelectEverythingAndClickGenerate()
355
+ assert.isFalsy(this.passedIntervalIdToClear)
356
+ await interactor.blur(this.vc)
357
+ assert.isEqual(
358
+ this.passedIntervalIdToClear,
359
+ this.intervalId,
360
+ 'did not pass response to setInterval to clearInterval'
361
+ )
362
+ }
363
+
364
+ private async fakeGenerateSelectEverythingClickGenerateAndInvokeIntervalCb() {
365
+ await this.fakeGenerateSelectEverythingAndClickGenerate()
366
+ await this.checkStatusIntervalCb?.()
367
+ }
368
+
369
+ private async fakeGenerateSelectEverythingAndClickGenerate() {
370
+ await this.eventFaker.fakeGenerateStory()
371
+ await this.selectElementFamilyMemberAndClickGenerate()
372
+ }
373
+
374
+ private async selectElementFamilyMemberAndClickGenerate() {
375
+ await this.selectFirstElement()
376
+ await this.selectFirstMember()
377
+ await this.clickGenerate()
378
+ }
379
+
380
+ private async clickGenerateAndAssertRedirect(destination?: {
381
+ id: SkillViewControllerId
382
+ args: { story: string }
383
+ }) {
384
+ await vcAssert.assertActionRedirects({
385
+ action: async () => {
386
+ await this.clickGenerate()
387
+ await this.emitDidGenerate(destination?.args?.story)
388
+ },
389
+ router: this.views.getRouter(),
390
+ destination,
391
+ })
392
+ }
393
+
394
+ private async emitDidGenerate(storyId?: string) {
395
+ await this.fakedClient.emitAndFlattenResponses(
396
+ 'eightbitstories.did-generate-story::v2023_09_05',
397
+ {
398
+ target: {
399
+ personId: generateId(),
400
+ },
401
+ payload: {
402
+ storyId: storyId ?? generateId(),
403
+ },
404
+ }
405
+ )
406
+ }
407
+
408
+ private async selectFirstElement() {
409
+ const selectedElement = await this.selectElement(0)
410
+ return selectedElement
411
+ }
412
+
413
+ private async selectElement(idx: number) {
414
+ const selectedElements = await this.selectElements([idx])
415
+ return selectedElements[0]
416
+ }
417
+
418
+ private async selectElements(allIdxs: number[]) {
419
+ const selectedElements = allIdxs.map((idx) => storyElements[idx].id)
420
+ await this.elementsFormVc.setValue('elements', selectedElements)
421
+ return selectedElements
422
+ }
423
+
424
+ private async selectFirstMember() {
425
+ const selectedMember = await this.selectMember(0)
426
+ return selectedMember
427
+ }
428
+
429
+ private async selectMember(idx: number) {
430
+ const selectedMembers = await this.selectMembers([idx])
431
+ return selectedMembers[0]
432
+ }
433
+
434
+ private async selectMembers(allIdxs: number[]) {
435
+ const members = await this.getAllMembers()
436
+ const selectedMembers = allIdxs.map((idx) => members[idx].id)
437
+ await this.membersFormVc.setValue('members', selectedMembers as any)
438
+ return selectedMembers
439
+ }
440
+
441
+ private async getAllMembers() {
442
+ return await this.members.find({})
443
+ }
444
+
445
+ private assertFooterIsNotBusy() {
446
+ assert.isFalse(this.getIsFooterBusy())
447
+ }
448
+
449
+ private assertFooterIsBusy() {
450
+ assert.isTrue(this.getIsFooterBusy())
451
+ }
452
+
453
+ private getIsFooterBusy(): boolean | null | undefined {
454
+ return this.controlsVc.getFooter()?.isBusy
455
+ }
456
+
457
+ private async clickGenerate() {
458
+ await interactor.clickButton(this.controlsVc, 'generate')
459
+ }
460
+
461
+ private get membersFormVc() {
462
+ return this.vc.getMembersFormVc()
463
+ }
464
+
465
+ private get elementsFormVc() {
466
+ return this.vc.getElementsFormVc()
467
+ }
468
+
469
+ private async loadVc() {
470
+ await this.views.load(this.vc)
471
+ }
472
+
473
+ private get membersVc() {
474
+ return this.vc.getMembersVc()
475
+ }
476
+
477
+ private get currentChallengeVc() {
478
+ return this.vc.getCurrentChallengeVc()
479
+ }
480
+
481
+ private get currentChallengeFormVc() {
482
+ return this.currentChallengeVc.getFormVc() as FormViewController<CurrentChallengeSchema>
483
+ }
484
+
485
+ private get elementsVc() {
486
+ return this.vc.getElementsVc()
487
+ }
488
+
489
+ private get controlsVc() {
490
+ return this.vc.getControlsCardVc()
491
+ }
492
+
493
+ private Vc(): SpyGenerateSkillView {
494
+ return this.views.Controller(
495
+ 'eightbitstories.generate',
496
+ {}
497
+ ) as SpyGenerateSkillView
498
+ }
499
+ }
500
+
501
+ class SpyGenerateSkillView extends GenerateSkillViewController {
502
+ public getHash() {
503
+ return this.storyHash!
504
+ }
505
+
506
+ public getCurrentChallengeVc() {
507
+ return this.currentChallengeVc
508
+ }
509
+
510
+ public getMembersFormVc() {
511
+ return this.getMembersVc().getFormVc() as FormViewController<GenerateStorySchema>
512
+ }
513
+ public getElementsFormVc() {
514
+ return this.getElementsVc().getFormVc() as FormViewController<GenerateStorySchema>
515
+ }
516
+ public getElementsVc() {
517
+ return this.elementsVc as SpyFormCard
518
+ }
519
+ public getMembersVc() {
520
+ return this.membersVc as SpyFormCard
521
+ }
522
+ public getControlsCardVc() {
523
+ return this.controlsVc
524
+ }
525
+ }
526
+
527
+ class SpyFormCard extends FormCardViewController {
528
+ public getFormVc() {
529
+ return this.formVc
530
+ }
531
+ }
@@ -8,10 +8,13 @@ import AbstractSpruceTest, {
8
8
 
9
9
  @fake.login()
10
10
  export default class StaticTestFinderTest extends AbstractSpruceTest {
11
+ private static responseBody = generateId()
12
+
11
13
  @test()
12
14
  protected static async throwsWithMissing() {
13
15
  const finder = StaticTestFinder.Finder()
14
16
  const err = await assert.doesThrowAsync(() => finder.find())
17
+ console.log(this.responseBody)
15
18
  errorAssert.assertError(err, 'MISSING_PARAMETERS', {
16
19
  parameters: ['lookupDir'],
17
20
  })
@@ -21,6 +24,11 @@ export default class StaticTestFinderTest extends AbstractSpruceTest {
21
24
  protected static async yourNextTest() {
22
25
  assert.isTrue(false)
23
26
  }
27
+
28
+ @test('this has a description')
29
+ protected static async anotherTest() {
30
+ assert.isFalse(true)
31
+ }
24
32
  }
25
33
 
26
34
  class StaticTestFinder {
@@ -20,7 +20,7 @@ export default class RootSkillViewTest extends AbstractGoogleTest {
20
20
  private static fakedIntegrations: PublicHomeIntegration[] = []
21
21
  protected static names: string[] = []
22
22
 
23
- protected static async beforeEach(): Promise<void> {
23
+ protected static async beforeEach() {
24
24
  await super.beforeEach()
25
25
 
26
26
  this.fakedIntegrations = []