@sprucelabs/spruce-cli 15.1.8 → 15.2.0

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.
@@ -1,7 +1,9 @@
1
+ import { diskUtil } from '@sprucelabs/spruce-skill-utils'
1
2
  import { test, assert } from '@sprucelabs/test'
2
3
  import LintService from '../../../services/LintService'
3
4
  import AbstractTestTest from '../../../tests/AbstractTestTest'
4
5
  import testUtil from '../../../tests/utilities/test.utility'
6
+ import uiAssert from '../../../tests/utilities/uiAssert.utility'
5
7
 
6
8
  export default class CreatingBehavioralTestsTest extends AbstractTestTest {
7
9
  @test()
@@ -10,7 +12,7 @@ export default class CreatingBehavioralTestsTest extends AbstractTestTest {
10
12
  }
11
13
 
12
14
  @test()
13
- protected static async requiresInstallIfSkilLNotInstalled() {
15
+ protected static async requiresInstallIfFeatureNotInstalled() {
14
16
  await this.installTests('testsInNodeModule')
15
17
 
16
18
  const testFeature = this.getFeatureInstaller().getFeature('test')
@@ -32,15 +34,7 @@ export default class CreatingBehavioralTestsTest extends AbstractTestTest {
32
34
  )
33
35
  protected static async canCreateBehavioralTest(testName: string) {
34
36
  LintService.enableLinting()
35
- await this.installTests()
36
- const promise = this.Action('test', 'create').execute({
37
- type: 'behavioral',
38
- nameReadable: 'Can book appointment',
39
- nameCamel: 'canBookAppointment',
40
- namePascal: 'CanBookAppointment',
41
- })
42
-
43
- await this.waitForInput()
37
+ const { promise } = await this.installAndStartTestActionAndWaitForInput()
44
38
 
45
39
  this.selectOptionBasedOnLabel(testName)
46
40
 
@@ -60,4 +54,129 @@ export default class CreatingBehavioralTestsTest extends AbstractTestTest {
60
54
  /false.*?does not equal.*?true/gis
61
55
  )
62
56
  }
57
+
58
+ @test('finds folders inside behavioral', 'behavioral')
59
+ @test('finds folders inside implementation', 'implementation')
60
+ protected static async promptsToSelectFolderIfInsideTestDir(
61
+ testType: string
62
+ ) {
63
+ await this.installTests()
64
+
65
+ this.createTestSubDir(testType, 'dummy1')
66
+
67
+ const { promise } = await this.installStartTestSelectSubclassWaitForInput(
68
+ testType
69
+ )
70
+
71
+ uiAssert.assertSelectRenderChoice(this.ui, '.', testType)
72
+ uiAssert.assertSelectRenderChoice(this.ui, `dummy1`, `${testType}/dummy1`)
73
+
74
+ await this.ui.sendInput('.')
75
+
76
+ await promise
77
+ }
78
+
79
+ @test()
80
+ protected static async listsManyDirsIfExistInsideTestDir() {
81
+ await this.installTests()
82
+
83
+ const dirs = ['dir1', 'dir2', 'dir3']
84
+
85
+ for (const dir of dirs) {
86
+ this.createTestSubDir('behavioral', dir)
87
+ }
88
+
89
+ const { promise } = await this.installStartTestSelectSubclassWaitForInput()
90
+
91
+ for (const dir of dirs) {
92
+ uiAssert.assertSelectRenderChoice(this.ui, `${dir}`, `behavioral/${dir}`)
93
+ }
94
+
95
+ await this.ui.sendInput('.')
96
+
97
+ await promise
98
+ }
99
+
100
+ @test('can select subdir 1', 'test')
101
+ @test('can select subdir 2', 'test-2')
102
+ protected static async selectingAnOptionRendersToSubDir(dirName: string) {
103
+ await this.installTests()
104
+ this.createTestSubDir('behavioral', dirName)
105
+
106
+ const { promise } = await this.installStartTestSelectSubclassWaitForInput(
107
+ 'behavioral'
108
+ )
109
+
110
+ await this.ui.sendInput(`${dirName}`)
111
+
112
+ const results = await promise
113
+
114
+ const expectedPath = this.resolvePath(
115
+ 'src',
116
+ '__tests__',
117
+ 'behavioral',
118
+ dirName,
119
+ 'CanBookAppointment.test.ts'
120
+ )
121
+
122
+ assert.isEqual(expectedPath, results.files?.[0]?.path)
123
+ }
124
+
125
+ @test()
126
+ protected static async doesNotListFiles() {
127
+ await this.installTests()
128
+ this.createTestSubDir('behavioral', 'subdir')
129
+
130
+ const file = this.resolveTestDir('behavioral', 'test.ts')
131
+ diskUtil.writeFile(file, 'what the!?')
132
+
133
+ const { promise } = await this.installStartTestSelectSubclassWaitForInput()
134
+
135
+ uiAssert.assertSelectDidNotRenderChoice(
136
+ this.ui,
137
+ 'test',
138
+ `behavioral/test/test.ts`
139
+ )
140
+
141
+ await promise
142
+ }
143
+
144
+ private static async installStartTestSelectSubclassWaitForInput(
145
+ testType?: string,
146
+ selectedSubClass?: string
147
+ ) {
148
+ const { promise } = await this.installAndStartTestActionAndWaitForInput(
149
+ testType
150
+ )
151
+
152
+ await this.ui.sendInput(selectedSubClass ?? '')
153
+
154
+ await uiAssert.assertRendersSelect(this.ui)
155
+
156
+ return { promise }
157
+ }
158
+
159
+ private static createTestSubDir(...testDirs: string[]) {
160
+ const newDir = this.resolveTestDir(...testDirs)
161
+ diskUtil.createDir(newDir)
162
+ }
163
+
164
+ private static resolveTestDir(...testDirs: string[]) {
165
+ return this.resolvePath('src', '__tests__', ...testDirs)
166
+ }
167
+
168
+ private static async installAndStartTestActionAndWaitForInput(
169
+ testType = 'behavioral'
170
+ ) {
171
+ await this.installTests()
172
+ const promise = this.Action('test', 'create').execute({
173
+ type: testType,
174
+ nameReadable: 'Can book appointment',
175
+ nameCamel: 'canBookAppointment',
176
+ namePascal: 'CanBookAppointment',
177
+ })
178
+
179
+ await this.waitForInput()
180
+ return { promise }
181
+ }
63
182
  }
@@ -19,7 +19,7 @@ export default class CreateAction extends AbstractAction<OptionsSchema> {
19
19
  const { testDestinationDir, namePascal, nameCamel, type } =
20
20
  normalizedOptions
21
21
 
22
- const resolvedDestination = diskUtil.resolvePath(
22
+ let resolvedDestination = diskUtil.resolvePath(
23
23
  this.cwd,
24
24
  testDestinationDir,
25
25
  type
@@ -28,7 +28,6 @@ export default class CreateAction extends AbstractAction<OptionsSchema> {
28
28
  this.ui.startLoading('Checking potential parent test classes')
29
29
 
30
30
  const testFeature = this.parent as TestFeature
31
- const candidates = await testFeature.buildParentClassCandidates()
32
31
 
33
32
  this.ui.stopLoading()
34
33
 
@@ -36,34 +35,22 @@ export default class CreateAction extends AbstractAction<OptionsSchema> {
36
35
  | undefined
37
36
  | { name: string; importPath: string; isDefaultExport: boolean }
38
37
 
39
- if (candidates.length > 0) {
40
- const idx = await this.ui.prompt({
41
- type: 'select',
42
- isRequired: true,
43
- label: 'Which abstract test class do you want to extend?',
44
- options: {
45
- choices: [
46
- { value: '', label: 'AbstractSpruceTest (default)' },
47
- ...candidates.map((candidate, idx) => ({
48
- value: `${idx}`,
49
- label: candidate.label,
50
- })),
51
- ],
52
- },
53
- })
54
-
55
- if (idx !== '' && candidates[+idx]) {
56
- const match = candidates[+idx]
38
+ const candidates = await testFeature.buildParentClassCandidates()
57
39
 
58
- if (match) {
59
- await this.optionallyInstallFeatureBasedOnSelection(match)
40
+ if (candidates.length > 0) {
41
+ parentTestClass =
42
+ await this.promptForParentTestClassAndOptionallyInstallDependencies(
43
+ candidates,
44
+ parentTestClass,
45
+ resolvedDestination
46
+ )
47
+ }
60
48
 
61
- parentTestClass = this.buildParentClassFromCandidate(
62
- match,
63
- resolvedDestination
64
- )
65
- }
66
- }
49
+ if (diskUtil.doesDirExist(resolvedDestination)) {
50
+ resolvedDestination = await this.promptForSubDir(
51
+ resolvedDestination,
52
+ type
53
+ )
67
54
  }
68
55
 
69
56
  this.ui.startLoading('Generating test file...')
@@ -83,6 +70,74 @@ export default class CreateAction extends AbstractAction<OptionsSchema> {
83
70
  hints: ["run `spruce test` in your skill when you're ready!"],
84
71
  }
85
72
  }
73
+ private async promptForSubDir(resolvedDestination: string, type: string) {
74
+ const subdirs = diskUtil
75
+ .readDir(resolvedDestination)
76
+ .filter((d) =>
77
+ diskUtil.isDir(diskUtil.resolvePath(resolvedDestination, d))
78
+ )
79
+
80
+ if (subdirs.length > 0) {
81
+ const match = await this.ui.prompt({
82
+ type: 'select',
83
+ label: 'Where should I write this test?',
84
+ isRequired: true,
85
+ options: {
86
+ choices: [
87
+ {
88
+ value: '.',
89
+ label: `${type}`,
90
+ },
91
+ ...subdirs.map((dir) => ({
92
+ value: `${dir}`,
93
+ label: `${type}/${dir}`,
94
+ })),
95
+ ],
96
+ },
97
+ })
98
+
99
+ resolvedDestination = diskUtil.resolvePath(resolvedDestination, match)
100
+ }
101
+ return resolvedDestination
102
+ }
103
+
104
+ private async promptForParentTestClassAndOptionallyInstallDependencies(
105
+ candidates: ParentClassCandidate[],
106
+ parentTestClass:
107
+ | { name: string; importPath: string; isDefaultExport: boolean }
108
+ | undefined,
109
+ resolvedDestination: string
110
+ ) {
111
+ const idx = await this.ui.prompt({
112
+ type: 'select',
113
+ isRequired: true,
114
+ label: 'Which abstract test class do you want to extend?',
115
+ options: {
116
+ choices: [
117
+ { value: '', label: 'AbstractSpruceTest (default)' },
118
+ ...candidates.map((candidate, idx) => ({
119
+ value: `${idx}`,
120
+ label: candidate.label,
121
+ })),
122
+ ],
123
+ },
124
+ })
125
+
126
+ if (idx !== '' && candidates[+idx]) {
127
+ const match = candidates[+idx]
128
+
129
+ if (match) {
130
+ await this.optionallyInstallFeatureBasedOnSelection(match)
131
+
132
+ parentTestClass = this.buildParentClassFromCandidate(
133
+ match,
134
+ resolvedDestination
135
+ )
136
+ }
137
+ }
138
+ return parentTestClass
139
+ }
140
+
86
141
  private async optionallyInstallFeatureBasedOnSelection(
87
142
  match: ParentClassCandidate
88
143
  ) {
@@ -1,7 +1,7 @@
1
1
  import { assert } from '@sprucelabs/test'
2
2
  import SpyInterface from '../../interfaces/SpyInterface'
3
3
 
4
- const uiAssertUtil = {
4
+ const uiAssert = {
5
5
  async assertRendersSelect(ui: SpyInterface) {
6
6
  await ui.waitForInput()
7
7
 
@@ -10,6 +10,8 @@ const uiAssertUtil = {
10
10
  last.options.options.choices,
11
11
  `I expected a select, I did not find one!`
12
12
  )
13
+
14
+ return last.options
13
15
  },
14
16
 
15
17
  assertSelectDidNotRenderChoice(
@@ -25,6 +27,15 @@ const uiAssertUtil = {
25
27
  })
26
28
  },
27
29
 
30
+ assertSelectRenderChoice(ui: SpyInterface, value: string, label: string) {
31
+ const last = ui.getLastInvocation()
32
+
33
+ assert.doesInclude(last.options.options.choices, {
34
+ value,
35
+ label,
36
+ })
37
+ },
38
+
28
39
  async assertRendersConfirmWriteFile(ui: SpyInterface) {
29
40
  await ui.waitForInput()
30
41
 
@@ -42,4 +53,4 @@ const uiAssertUtil = {
42
53
  },
43
54
  }
44
55
 
45
- export default uiAssertUtil
56
+ export default uiAssert