@featurevisor/core 1.35.3 → 2.0.1

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 (252) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +0 -6
  3. package/coverage/clover.xml +321 -237
  4. package/coverage/coverage-final.json +8 -8
  5. package/coverage/lcov-report/index.html +77 -47
  6. package/coverage/lcov-report/lib/builder/allocator.js.html +14 -14
  7. package/coverage/lcov-report/lib/builder/index.html +16 -16
  8. package/coverage/lcov-report/lib/builder/revision.js.html +3 -3
  9. package/coverage/lcov-report/lib/builder/traffic.js.html +88 -64
  10. package/coverage/lcov-report/lib/list/index.html +116 -0
  11. package/coverage/lcov-report/lib/{tester → list}/matrix.js.html +90 -66
  12. package/coverage/lcov-report/lib/tester/helpers.js.html +295 -0
  13. package/coverage/lcov-report/lib/tester/index.html +20 -35
  14. package/coverage/lcov-report/src/builder/allocator.ts.html +2 -2
  15. package/coverage/lcov-report/src/builder/index.html +15 -15
  16. package/coverage/lcov-report/src/builder/revision.ts.html +1 -1
  17. package/coverage/lcov-report/src/builder/traffic.ts.html +96 -24
  18. package/coverage/lcov-report/src/list/index.html +116 -0
  19. package/coverage/lcov-report/src/{tester → list}/matrix.ts.html +87 -21
  20. package/coverage/lcov-report/src/tester/helpers.ts.html +313 -0
  21. package/coverage/lcov-report/src/tester/index.html +20 -35
  22. package/coverage/lcov.info +592 -436
  23. package/lib/assess-distribution/index.d.ts +1 -1
  24. package/lib/assess-distribution/index.js +102 -162
  25. package/lib/assess-distribution/index.js.map +1 -1
  26. package/lib/benchmark/index.js +87 -143
  27. package/lib/benchmark/index.js.map +1 -1
  28. package/lib/builder/allocator.d.ts +1 -1
  29. package/lib/builder/allocator.js +12 -12
  30. package/lib/builder/allocator.js.map +1 -1
  31. package/lib/builder/allocator.spec.js +22 -22
  32. package/lib/builder/allocator.spec.js.map +1 -1
  33. package/lib/builder/buildDatafile.d.ts +4 -3
  34. package/lib/builder/buildDatafile.js +311 -388
  35. package/lib/builder/buildDatafile.js.map +1 -1
  36. package/lib/builder/buildProject.d.ts +2 -1
  37. package/lib/builder/buildProject.js +96 -183
  38. package/lib/builder/buildProject.js.map +1 -1
  39. package/lib/builder/convertToV1.d.ts +10 -0
  40. package/lib/builder/convertToV1.js +119 -0
  41. package/lib/builder/convertToV1.js.map +1 -0
  42. package/lib/builder/getFeatureRanges.d.ts +1 -1
  43. package/lib/builder/getFeatureRanges.js +32 -105
  44. package/lib/builder/getFeatureRanges.js.map +1 -1
  45. package/lib/builder/hashes.d.ts +4 -0
  46. package/lib/builder/hashes.js +70 -0
  47. package/lib/builder/hashes.js.map +1 -0
  48. package/lib/builder/revision.js +2 -2
  49. package/lib/builder/revision.js.map +1 -1
  50. package/lib/builder/revision.spec.js +1 -1
  51. package/lib/builder/revision.spec.js.map +1 -1
  52. package/lib/builder/traffic.d.ts +1 -1
  53. package/lib/builder/traffic.js +57 -49
  54. package/lib/builder/traffic.js.map +1 -1
  55. package/lib/builder/traffic.spec.js +14 -14
  56. package/lib/builder/traffic.spec.js.map +1 -1
  57. package/lib/cli/cli.js +60 -129
  58. package/lib/cli/cli.js.map +1 -1
  59. package/lib/cli/plugins.js +14 -16
  60. package/lib/cli/plugins.js.map +1 -1
  61. package/lib/config/parsers.js +1 -1
  62. package/lib/config/parsers.js.map +1 -1
  63. package/lib/config/projectConfig.d.ts +8 -6
  64. package/lib/config/projectConfig.js +31 -72
  65. package/lib/config/projectConfig.js.map +1 -1
  66. package/lib/datasource/adapter.d.ts +1 -1
  67. package/lib/datasource/adapter.js +2 -5
  68. package/lib/datasource/adapter.js.map +1 -1
  69. package/lib/datasource/datasource.d.ts +2 -1
  70. package/lib/datasource/datasource.js +107 -148
  71. package/lib/datasource/datasource.js.map +1 -1
  72. package/lib/datasource/filesystemAdapter.d.ts +1 -1
  73. package/lib/datasource/filesystemAdapter.js +224 -360
  74. package/lib/datasource/filesystemAdapter.js.map +1 -1
  75. package/lib/evaluate/index.d.ts +1 -1
  76. package/lib/evaluate/index.js +120 -188
  77. package/lib/evaluate/index.js.map +1 -1
  78. package/lib/find-duplicate-segments/findDuplicateSegments.d.ts +1 -1
  79. package/lib/find-duplicate-segments/findDuplicateSegments.js +40 -128
  80. package/lib/find-duplicate-segments/findDuplicateSegments.js.map +1 -1
  81. package/lib/find-duplicate-segments/index.js +27 -82
  82. package/lib/find-duplicate-segments/index.js.map +1 -1
  83. package/lib/find-usage/index.d.ts +7 -5
  84. package/lib/find-usage/index.js +333 -507
  85. package/lib/find-usage/index.js.map +1 -1
  86. package/lib/generate-code/index.js +36 -91
  87. package/lib/generate-code/index.js.map +1 -1
  88. package/lib/generate-code/typescript.js +117 -157
  89. package/lib/generate-code/typescript.js.map +1 -1
  90. package/lib/index.d.ts +0 -1
  91. package/lib/index.js +0 -1
  92. package/lib/index.js.map +1 -1
  93. package/lib/info/index.js +45 -133
  94. package/lib/info/index.js.map +1 -1
  95. package/lib/init/index.d.ts +1 -1
  96. package/lib/init/index.js +16 -64
  97. package/lib/init/index.js.map +1 -1
  98. package/lib/linter/attributeSchema.d.ts +21 -6
  99. package/lib/linter/attributeSchema.js +18 -4
  100. package/lib/linter/attributeSchema.js.map +1 -1
  101. package/lib/linter/checkCircularDependency.d.ts +1 -1
  102. package/lib/linter/checkCircularDependency.js +22 -80
  103. package/lib/linter/checkCircularDependency.js.map +1 -1
  104. package/lib/linter/checkPercentageExceedingSlot.d.ts +1 -1
  105. package/lib/linter/checkPercentageExceedingSlot.js +36 -76
  106. package/lib/linter/checkPercentageExceedingSlot.js.map +1 -1
  107. package/lib/linter/conditionSchema.d.ts +1 -1
  108. package/lib/linter/conditionSchema.js +89 -41
  109. package/lib/linter/conditionSchema.js.map +1 -1
  110. package/lib/linter/featureSchema.d.ts +345 -197
  111. package/lib/linter/featureSchema.js +313 -172
  112. package/lib/linter/featureSchema.js.map +1 -1
  113. package/lib/linter/groupSchema.js +6 -6
  114. package/lib/linter/groupSchema.js.map +1 -1
  115. package/lib/linter/lintProject.js +306 -480
  116. package/lib/linter/lintProject.js.map +1 -1
  117. package/lib/linter/printError.js +7 -7
  118. package/lib/linter/printError.js.map +1 -1
  119. package/lib/linter/segmentSchema.js +2 -2
  120. package/lib/linter/segmentSchema.js.map +1 -1
  121. package/lib/linter/testSchema.d.ts +155 -3
  122. package/lib/linter/testSchema.js +47 -17
  123. package/lib/linter/testSchema.js.map +1 -1
  124. package/lib/list/index.d.ts +1 -0
  125. package/lib/list/index.js +349 -517
  126. package/lib/list/index.js.map +1 -1
  127. package/lib/{tester → list}/matrix.d.ts +1 -1
  128. package/lib/{tester → list}/matrix.js +50 -42
  129. package/lib/list/matrix.js.map +1 -0
  130. package/lib/{tester → list}/matrix.spec.js +7 -7
  131. package/lib/list/matrix.spec.js.map +1 -0
  132. package/lib/site/exportSite.js +25 -71
  133. package/lib/site/exportSite.js.map +1 -1
  134. package/lib/site/generateHistory.d.ts +1 -1
  135. package/lib/site/generateHistory.js +26 -82
  136. package/lib/site/generateHistory.js.map +1 -1
  137. package/lib/site/generateSiteSearchIndex.d.ts +1 -1
  138. package/lib/site/generateSiteSearchIndex.js +182 -259
  139. package/lib/site/generateSiteSearchIndex.js.map +1 -1
  140. package/lib/site/getLastModifiedFromHistory.d.ts +1 -1
  141. package/lib/site/getLastModifiedFromHistory.js +2 -2
  142. package/lib/site/getLastModifiedFromHistory.js.map +1 -1
  143. package/lib/site/getOwnerAndRepoFromUrl.js +6 -6
  144. package/lib/site/getOwnerAndRepoFromUrl.js.map +1 -1
  145. package/lib/site/getRelativePaths.js +7 -7
  146. package/lib/site/getRelativePaths.js.map +1 -1
  147. package/lib/site/getRepoDetails.js +20 -20
  148. package/lib/site/getRepoDetails.js.map +1 -1
  149. package/lib/site/index.js +25 -73
  150. package/lib/site/index.js.map +1 -1
  151. package/lib/site/serveSite.js +10 -10
  152. package/lib/site/serveSite.js.map +1 -1
  153. package/lib/tester/helpers.d.ts +2 -0
  154. package/lib/tester/helpers.js +71 -0
  155. package/lib/tester/helpers.js.map +1 -0
  156. package/lib/tester/helpers.spec.js +115 -0
  157. package/lib/tester/helpers.spec.js.map +1 -0
  158. package/lib/tester/index.d.ts +0 -1
  159. package/lib/tester/index.js +0 -1
  160. package/lib/tester/index.js.map +1 -1
  161. package/lib/tester/prettyDuration.js +11 -11
  162. package/lib/tester/prettyDuration.js.map +1 -1
  163. package/lib/tester/printTestResult.d.ts +1 -1
  164. package/lib/tester/printTestResult.js +35 -15
  165. package/lib/tester/printTestResult.js.map +1 -1
  166. package/lib/tester/testFeature.d.ts +4 -2
  167. package/lib/tester/testFeature.js +264 -226
  168. package/lib/tester/testFeature.js.map +1 -1
  169. package/lib/tester/testProject.d.ts +3 -7
  170. package/lib/tester/testProject.js +145 -246
  171. package/lib/tester/testProject.js.map +1 -1
  172. package/lib/tester/testSegment.d.ts +5 -2
  173. package/lib/tester/testSegment.js +65 -102
  174. package/lib/tester/testSegment.js.map +1 -1
  175. package/lib/utils/extractKeys.d.ts +2 -1
  176. package/lib/utils/extractKeys.js +57 -12
  177. package/lib/utils/extractKeys.js.map +1 -1
  178. package/lib/utils/git.d.ts +1 -1
  179. package/lib/utils/git.js +23 -23
  180. package/lib/utils/git.js.map +1 -1
  181. package/lib/utils/pretty.js +2 -4
  182. package/lib/utils/pretty.js.map +1 -1
  183. package/package.json +5 -6
  184. package/src/assess-distribution/index.ts +3 -2
  185. package/src/benchmark/index.ts +3 -3
  186. package/src/builder/allocator.spec.ts +1 -1
  187. package/src/builder/allocator.ts +1 -1
  188. package/src/builder/buildDatafile.ts +161 -124
  189. package/src/builder/buildProject.ts +6 -3
  190. package/src/builder/convertToV1.ts +166 -0
  191. package/src/builder/getFeatureRanges.ts +1 -1
  192. package/src/builder/hashes.ts +109 -0
  193. package/src/builder/traffic.ts +40 -16
  194. package/src/cli/cli.ts +1 -1
  195. package/src/cli/plugins.ts +0 -2
  196. package/src/config/projectConfig.ts +13 -10
  197. package/src/datasource/adapter.ts +1 -1
  198. package/src/datasource/datasource.ts +23 -2
  199. package/src/datasource/filesystemAdapter.ts +11 -12
  200. package/src/evaluate/index.ts +7 -6
  201. package/src/find-duplicate-segments/findDuplicateSegments.ts +1 -1
  202. package/src/find-usage/index.ts +111 -44
  203. package/src/generate-code/index.ts +1 -3
  204. package/src/generate-code/typescript.ts +7 -29
  205. package/src/index.ts +0 -1
  206. package/src/info/index.ts +2 -2
  207. package/src/init/index.ts +2 -2
  208. package/src/linter/attributeSchema.ts +18 -2
  209. package/src/linter/checkCircularDependency.ts +1 -1
  210. package/src/linter/checkPercentageExceedingSlot.ts +28 -8
  211. package/src/linter/conditionSchema.ts +66 -10
  212. package/src/linter/featureSchema.ts +312 -116
  213. package/src/linter/lintProject.ts +9 -4
  214. package/src/linter/testSchema.ts +42 -3
  215. package/src/list/index.ts +18 -30
  216. package/src/{tester → list}/matrix.ts +33 -11
  217. package/src/site/exportSite.ts +2 -4
  218. package/src/site/generateHistory.ts +1 -1
  219. package/src/site/generateSiteSearchIndex.ts +58 -50
  220. package/src/site/getLastModifiedFromHistory.ts +1 -1
  221. package/src/tester/helpers.spec.ts +149 -0
  222. package/src/tester/helpers.ts +76 -0
  223. package/src/tester/index.ts +0 -1
  224. package/src/tester/printTestResult.ts +25 -3
  225. package/src/tester/testFeature.ts +270 -124
  226. package/src/tester/testProject.ts +28 -49
  227. package/src/tester/testSegment.ts +48 -40
  228. package/src/utils/extractKeys.ts +58 -1
  229. package/src/utils/git.ts +1 -1
  230. package/tsconfig.cjs.json +1 -0
  231. package/coverage/lcov-report/lib/tester/checkIfObjectsAreEqual.js.html +0 -151
  232. package/coverage/lcov-report/src/tester/checkIfObjectsAreEqual.ts.html +0 -157
  233. package/lib/restore/index.d.ts +0 -4
  234. package/lib/restore/index.js +0 -91
  235. package/lib/restore/index.js.map +0 -1
  236. package/lib/tester/checkIfArraysAreEqual.d.ts +0 -1
  237. package/lib/tester/checkIfArraysAreEqual.js +0 -18
  238. package/lib/tester/checkIfArraysAreEqual.js.map +0 -1
  239. package/lib/tester/checkIfObjectsAreEqual.d.ts +0 -1
  240. package/lib/tester/checkIfObjectsAreEqual.js +0 -23
  241. package/lib/tester/checkIfObjectsAreEqual.js.map +0 -1
  242. package/lib/tester/checkIfObjectsAreEqual.spec.js +0 -26
  243. package/lib/tester/checkIfObjectsAreEqual.spec.js.map +0 -1
  244. package/lib/tester/matrix.js.map +0 -1
  245. package/lib/tester/matrix.spec.js.map +0 -1
  246. package/src/restore/index.ts +0 -42
  247. package/src/tester/checkIfArraysAreEqual.ts +0 -16
  248. package/src/tester/checkIfObjectsAreEqual.spec.ts +0 -31
  249. package/src/tester/checkIfObjectsAreEqual.ts +0 -24
  250. /package/lib/{tester → list}/matrix.spec.d.ts +0 -0
  251. /package/lib/tester/{checkIfObjectsAreEqual.spec.d.ts → helpers.spec.d.ts} +0 -0
  252. /package/src/{tester → list}/matrix.spec.ts +0 -0
@@ -1,26 +1,29 @@
1
- import {
1
+ import type {
2
2
  DatafileContent,
3
3
  TestFeature,
4
4
  TestResult,
5
5
  TestResultAssertion,
6
6
  TestResultAssertionError,
7
7
  } from "@featurevisor/types";
8
- import { createInstance, MAX_BUCKETED_NUMBER } from "@featurevisor/sdk";
8
+ import {
9
+ createInstance,
10
+ FeaturevisorInstance,
11
+ LogLevel,
12
+ MAX_BUCKETED_NUMBER,
13
+ OverrideOptions,
14
+ } from "@featurevisor/sdk";
9
15
 
10
16
  import { Datasource } from "../datasource";
11
17
  import { ProjectConfig } from "../config";
12
18
 
13
- import { checkIfArraysAreEqual } from "./checkIfArraysAreEqual";
14
- import { checkIfObjectsAreEqual } from "./checkIfObjectsAreEqual";
15
- import { getFeatureAssertionsFromMatrix } from "./matrix";
19
+ import { checkIfArraysAreEqual, checkIfObjectsAreEqual } from "./helpers";
16
20
  import type { DatafileContentByEnvironment } from "./testProject";
17
21
 
18
22
  export async function testFeature(
19
23
  datasource: Datasource,
20
24
  projectConfig: ProjectConfig,
21
25
  test: TestFeature,
22
- options: { verbose?: boolean; showDatafile?: boolean } = {},
23
- patterns,
26
+ options: { verbose?: boolean; quiet?: boolean; showDatafile?: boolean; [key: string]: any } = {},
24
27
  datafileContentByEnvironment: DatafileContentByEnvironment,
25
28
  ): Promise<TestResult> {
26
29
  const testStartTime = Date.now();
@@ -38,166 +41,309 @@ export async function testFeature(
38
41
  };
39
42
 
40
43
  for (let aIndex = 0; aIndex < test.assertions.length; aIndex++) {
41
- const assertions = getFeatureAssertionsFromMatrix(aIndex, test.assertions[aIndex]);
44
+ const assertionStartTime = Date.now();
45
+ const assertion = test.assertions[aIndex];
46
+
47
+ const testResultAssertion: TestResultAssertion = {
48
+ description: assertion.description as string,
49
+ duration: 0,
50
+ passed: true,
51
+ errors: [],
52
+ };
53
+
54
+ const datafileContent = datafileContentByEnvironment.get(assertion.environment || false);
55
+
56
+ if (options.showDatafile) {
57
+ console.log("");
58
+ console.log(JSON.stringify(datafileContent, null, 2));
59
+ console.log("");
60
+ }
42
61
 
43
- for (let bIndex = 0; bIndex < assertions.length; bIndex++) {
44
- const assertionStartTime = Date.now();
45
- const assertion = assertions[bIndex];
62
+ let logLevel: LogLevel = "warn";
63
+ if (options.verbose) {
64
+ logLevel = "debug";
65
+ } else if (options.quiet) {
66
+ logLevel = "fatal";
67
+ }
46
68
 
47
- const testResultAssertion: TestResultAssertion = {
48
- description: assertion.description as string,
49
- duration: 0,
50
- passed: true,
51
- errors: [],
52
- };
69
+ const sdk: FeaturevisorInstance = createInstance({
70
+ datafile: datafileContent as DatafileContent,
71
+ sticky: assertion.sticky ? assertion.sticky : {},
72
+ hooks: [
73
+ {
74
+ name: "tester",
75
+ bucketValue: ({ bucketValue }) => {
76
+ if (typeof assertion.at !== "undefined") {
77
+ return assertion.at * (MAX_BUCKETED_NUMBER / 100);
78
+ }
53
79
 
54
- if (patterns.assertionPattern && !patterns.assertionPattern.test(assertion.description)) {
55
- continue;
56
- }
80
+ return bucketValue;
81
+ },
82
+ },
83
+ ],
84
+ logLevel,
85
+ });
86
+
87
+ const feature = await datasource.readFeature(featureKey);
88
+ if (!feature) {
89
+ testResult.notFound = true;
90
+ testResult.passed = false;
91
+
92
+ return testResult;
93
+ }
94
+
95
+ if (assertion.context) {
96
+ sdk.setContext(assertion.context);
97
+ }
98
+
99
+ /**
100
+ * expectedToBeEnabled
101
+ */
102
+ function testExpectedToBeEnabled(sdk, assertion, details = {}) {
103
+ const isEnabled = sdk.isEnabled(featureKey, assertion.context || {});
57
104
 
58
- const datafileContent = datafileContentByEnvironment.get(assertion.environment || false);
105
+ if (isEnabled !== assertion.expectedToBeEnabled) {
106
+ testResult.passed = false;
107
+ testResultAssertion.passed = false;
59
108
 
60
- if (options.showDatafile) {
61
- console.log("");
62
- console.log(JSON.stringify(datafileContent, null, 2));
63
- console.log("");
109
+ (testResultAssertion.errors as TestResultAssertionError[]).push({
110
+ type: "flag",
111
+ expected: assertion.expectedToBeEnabled,
112
+ actual: isEnabled,
113
+ details,
114
+ });
64
115
  }
116
+ }
65
117
 
66
- const sdk = createInstance({
67
- datafile: datafileContent,
68
- configureBucketValue: () => {
69
- return assertion.at * (MAX_BUCKETED_NUMBER / 100);
70
- },
71
- });
118
+ if ("expectedToBeEnabled" in assertion) {
119
+ testExpectedToBeEnabled(sdk, assertion);
120
+ }
72
121
 
73
- if (options.verbose) {
74
- sdk.setLogLevels(["debug", "info", "warn", "error"]);
122
+ /**
123
+ * expectedVariation
124
+ */
125
+ function testExpectedVariation(sdk, assertion, details = {}) {
126
+ const overrideOptions: OverrideOptions = {};
127
+ if (assertion.defaultVariationValue) {
128
+ overrideOptions.defaultVariationValue = assertion.defaultVariationValue;
75
129
  }
76
130
 
77
- const feature = await datasource.readFeature(featureKey);
78
- if (!feature) {
79
- testResult.notFound = true;
131
+ const variation = sdk.getVariation(featureKey, assertion.context || {}, overrideOptions);
132
+
133
+ if (variation !== assertion.expectedVariation) {
80
134
  testResult.passed = false;
135
+ testResultAssertion.passed = false;
81
136
 
82
- return testResult;
137
+ (testResultAssertion.errors as TestResultAssertionError[]).push({
138
+ type: "variation",
139
+ expected: assertion.expectedVariation,
140
+ actual: variation,
141
+ details,
142
+ });
83
143
  }
144
+ }
84
145
 
85
- // isEnabled
86
- if ("expectedToBeEnabled" in assertion) {
87
- const isEnabled = sdk.isEnabled(featureKey, assertion.context);
88
-
89
- if (isEnabled !== assertion.expectedToBeEnabled) {
90
- testResult.passed = false;
91
- testResultAssertion.passed = false;
146
+ if ("expectedVariation" in assertion) {
147
+ testExpectedVariation(sdk, assertion);
148
+ }
92
149
 
93
- (testResultAssertion.errors as TestResultAssertionError[]).push({
94
- type: "flag",
95
- expected: assertion.expectedToBeEnabled,
96
- actual: isEnabled,
97
- });
150
+ /**
151
+ * expectedVariables
152
+ */
153
+ function testExpectedVariables(sdk, assertion, details = {}) {
154
+ Object.keys(assertion.expectedVariables).forEach(function (variableKey) {
155
+ const expectedValue =
156
+ assertion.expectedVariables && assertion.expectedVariables[variableKey];
157
+
158
+ const overrideOptions: OverrideOptions = {};
159
+ if (assertion.defaultVariableValues && assertion.defaultVariableValues[variableKey]) {
160
+ overrideOptions.defaultVariableValue = assertion.defaultVariableValues[variableKey];
98
161
  }
99
- }
100
162
 
101
- // variation
102
- if ("expectedVariation" in assertion) {
103
- const variation = sdk.getVariation(featureKey, assertion.context);
163
+ const actualValue = sdk.getVariable(
164
+ featureKey,
165
+ variableKey,
166
+ assertion.context || {},
167
+ overrideOptions,
168
+ );
169
+
170
+ let passed;
171
+
172
+ const variableSchema = feature.variablesSchema?.[variableKey];
104
173
 
105
- if (variation !== assertion.expectedVariation) {
174
+ if (!variableSchema) {
106
175
  testResult.passed = false;
107
176
  testResultAssertion.passed = false;
108
177
 
109
178
  (testResultAssertion.errors as TestResultAssertionError[]).push({
110
- type: "variation",
179
+ type: "variable",
111
180
  expected: assertion.expectedVariation,
112
- actual: variation,
181
+ actual: undefined,
182
+ message: `schema for variable "${variableKey}" not found in feature`,
113
183
  });
114
- }
115
- }
116
184
 
117
- // variables
118
- if (typeof assertion.expectedVariables === "object") {
119
- Object.keys(assertion.expectedVariables).forEach(function (variableKey) {
120
- const expectedValue =
121
- assertion.expectedVariables && assertion.expectedVariables[variableKey];
122
- const actualValue = sdk.getVariable(featureKey, variableKey, assertion.context);
185
+ return;
186
+ }
123
187
 
124
- let passed;
188
+ if (variableSchema.type === "json") {
189
+ // JSON type
190
+ const parsedExpectedValue =
191
+ typeof expectedValue === "string" ? JSON.parse(expectedValue as string) : expectedValue;
125
192
 
126
- const variableSchema = feature.variablesSchema?.find((v) => v.key === variableKey);
193
+ if (Array.isArray(actualValue)) {
194
+ passed = checkIfArraysAreEqual(parsedExpectedValue, actualValue);
195
+ } else if (typeof actualValue === "object") {
196
+ passed = checkIfObjectsAreEqual(parsedExpectedValue, actualValue);
197
+ } else {
198
+ passed = JSON.stringify(parsedExpectedValue) === JSON.stringify(actualValue);
199
+ }
127
200
 
128
- if (!variableSchema) {
201
+ if (!passed) {
129
202
  testResult.passed = false;
130
203
  testResultAssertion.passed = false;
131
204
 
132
205
  (testResultAssertion.errors as TestResultAssertionError[]).push({
133
206
  type: "variable",
134
- expected: assertion.expectedVariation,
135
- actual: undefined,
136
- message: `schema for variable "${variableKey}" not found in feature`,
207
+ expected:
208
+ typeof expectedValue !== "string" ? JSON.stringify(expectedValue) : expectedValue,
209
+ actual: typeof actualValue !== "string" ? JSON.stringify(actualValue) : actualValue,
210
+ details: {
211
+ ...details,
212
+ variableKey,
213
+ },
137
214
  });
215
+ }
216
+ } else {
217
+ // other types
218
+ if (typeof expectedValue === "object") {
219
+ passed = checkIfObjectsAreEqual(expectedValue, actualValue);
220
+ } else if (Array.isArray(expectedValue)) {
221
+ passed = checkIfArraysAreEqual(expectedValue, actualValue);
222
+ } else {
223
+ passed = expectedValue === actualValue;
224
+ }
138
225
 
139
- return;
226
+ if (!passed) {
227
+ testResult.passed = false;
228
+ testResultAssertion.passed = false;
229
+
230
+ (testResultAssertion.errors as TestResultAssertionError[]).push({
231
+ type: "variable",
232
+ expected: expectedValue as string,
233
+ actual: actualValue as string,
234
+ details: {
235
+ ...details,
236
+ variableKey,
237
+ },
238
+ });
140
239
  }
240
+ }
241
+ });
242
+ }
141
243
 
142
- if (variableSchema.type === "json") {
143
- // JSON type
144
- const parsedExpectedValue =
145
- typeof expectedValue === "string"
146
- ? JSON.parse(expectedValue as string)
147
- : expectedValue;
148
-
149
- if (Array.isArray(actualValue)) {
150
- passed = checkIfArraysAreEqual(parsedExpectedValue, actualValue);
151
- } else if (typeof actualValue === "object") {
152
- passed = checkIfObjectsAreEqual(parsedExpectedValue, actualValue);
153
- } else {
154
- passed = JSON.stringify(parsedExpectedValue) === JSON.stringify(actualValue);
155
- }
244
+ if (typeof assertion.expectedVariables === "object") {
245
+ testExpectedVariables(sdk, assertion);
246
+ }
156
247
 
157
- if (!passed) {
158
- testResult.passed = false;
159
- testResultAssertion.passed = false;
160
-
161
- (testResultAssertion.errors as TestResultAssertionError[]).push({
162
- type: "variable",
163
- expected:
164
- typeof expectedValue !== "string" ? JSON.stringify(expectedValue) : expectedValue,
165
- actual: typeof actualValue !== "string" ? JSON.stringify(actualValue) : actualValue,
166
- details: {
167
- variableKey,
168
- },
169
- });
170
- }
171
- } else {
172
- // other types
173
- if (typeof expectedValue === "object") {
174
- passed = checkIfObjectsAreEqual(expectedValue, actualValue);
175
- } else if (Array.isArray(expectedValue)) {
176
- passed = checkIfArraysAreEqual(expectedValue, actualValue);
177
- } else {
178
- passed = expectedValue === actualValue;
179
- }
248
+ /**
249
+ * expectedEvaluations
250
+ */
251
+ function testExpectedEvaluations(sdk, assertion, rootDetails = {}) {
252
+ function testEvaluation(type, evaluation, expected, details = {}) {
253
+ for (const [key, value] of Object.entries(expected)) {
254
+ if (evaluation[key] !== value) {
255
+ testResult.passed = false;
256
+ testResultAssertion.passed = false;
180
257
 
181
- if (!passed) {
182
- testResult.passed = false;
183
- testResultAssertion.passed = false;
184
-
185
- (testResultAssertion.errors as TestResultAssertionError[]).push({
186
- type: "variable",
187
- expected: expectedValue as string,
188
- actual: actualValue as string,
189
- details: {
190
- variableKey,
191
- },
192
- });
193
- }
258
+ (testResultAssertion.errors as TestResultAssertionError[]).push({
259
+ type: "evaluation",
260
+ expected: value as string | number | boolean | null | undefined,
261
+ actual: evaluation[key],
262
+ details: {
263
+ ...rootDetails,
264
+ ...details,
265
+ evaluationType: type,
266
+ evaluationKey: key,
267
+ },
268
+ });
194
269
  }
195
- });
270
+ }
196
271
  }
197
272
 
198
- testResultAssertion.duration = Date.now() - assertionStartTime;
199
- testResult.assertions.push(testResultAssertion);
273
+ if (assertion.expectedEvaluations.flag) {
274
+ const evaluation = sdk.evaluateFlag(featureKey, assertion.context || {});
275
+ testEvaluation("flag", evaluation, assertion.expectedEvaluations.flag);
276
+ }
277
+
278
+ if (assertion.expectedEvaluations.variation) {
279
+ const evaluation = sdk.evaluateVariation(featureKey, assertion.context || {});
280
+ testEvaluation("variation", evaluation, assertion.expectedEvaluations.variation);
281
+ }
282
+
283
+ if (assertion.expectedEvaluations.variables) {
284
+ const variableKeys = Object.keys(assertion.expectedEvaluations.variables);
285
+
286
+ for (const variableKey of variableKeys) {
287
+ const evaluation = sdk.evaluateVariable(featureKey, variableKey, assertion.context || {});
288
+ testEvaluation(
289
+ "variable",
290
+ evaluation,
291
+ assertion.expectedEvaluations.variables[variableKey],
292
+ { variableKey },
293
+ );
294
+ }
295
+ }
200
296
  }
297
+
298
+ if (assertion.expectedEvaluations) {
299
+ testExpectedEvaluations(sdk, assertion);
300
+ }
301
+
302
+ /**
303
+ * children
304
+ */
305
+ if (Array.isArray(assertion.children)) {
306
+ let childIndex = 0;
307
+
308
+ for (const child of assertion.children) {
309
+ const childSdk = sdk.spawn(child.context || {}, {
310
+ sticky: assertion.sticky || {},
311
+ });
312
+
313
+ // expectedToBeEnabled
314
+ if (typeof child.expectedToBeEnabled !== "undefined") {
315
+ testExpectedToBeEnabled(childSdk, child, {
316
+ childIndex,
317
+ });
318
+ }
319
+
320
+ // expectedVariation
321
+ if (typeof child.expectedVariation !== "undefined") {
322
+ testExpectedVariation(childSdk, child, {
323
+ childIndex,
324
+ });
325
+ }
326
+
327
+ // expectedVariables
328
+ if (typeof child.expectedVariables === "object") {
329
+ testExpectedVariables(childSdk, child, {
330
+ childIndex,
331
+ });
332
+ }
333
+
334
+ // expectedEvaluations
335
+ if (typeof child.expectedEvaluations === "object") {
336
+ testExpectedEvaluations(childSdk, child, {
337
+ childIndex,
338
+ });
339
+ }
340
+
341
+ childIndex++;
342
+ }
343
+ }
344
+
345
+ testResultAssertion.duration = Date.now() - assertionStartTime;
346
+ testResult.assertions.push(testResultAssertion);
201
347
  }
202
348
 
203
349
  testResult.duration = Date.now() - testStartTime;
@@ -1,7 +1,6 @@
1
1
  import * as fs from "fs";
2
- import * as path from "path";
3
2
 
4
- import { TestSegment, TestFeature, DatafileContent } from "@featurevisor/types";
3
+ import type { TestSegment, TestFeature, Test, DatafileContent } from "@featurevisor/types";
5
4
 
6
5
  import { testSegment } from "./testSegment";
7
6
  import { testFeature } from "./testFeature";
@@ -13,6 +12,7 @@ import { printTestResult } from "./printTestResult";
13
12
  import { buildDatafile } from "../builder";
14
13
  import { SCHEMA_VERSION } from "../config";
15
14
  import { Plugin } from "../cli";
15
+ import { listEntities } from "../list";
16
16
 
17
17
  export interface TestProjectOptions {
18
18
  keyPattern?: string;
@@ -24,11 +24,6 @@ export interface TestProjectOptions {
24
24
  inflate?: number;
25
25
  }
26
26
 
27
- export interface TestPatterns {
28
- keyPattern?: RegExp;
29
- assertionPattern?: RegExp;
30
- }
31
-
32
27
  export interface ExecutionResult {
33
28
  passed: boolean;
34
29
  assertionsCount: {
@@ -40,22 +35,18 @@ export interface ExecutionResult {
40
35
  export type DatafileContentByEnvironment = Map<string | false, DatafileContent>;
41
36
 
42
37
  export async function executeTest(
43
- testFile: string,
38
+ test: Test,
44
39
  deps: Dependencies,
45
40
  options: TestProjectOptions,
46
- patterns: TestPatterns,
47
41
  datafileContentByEnvironment: DatafileContentByEnvironment,
48
42
  ): Promise<ExecutionResult | undefined> {
49
43
  const { datasource, projectConfig, rootDirectoryPath } = deps;
50
44
 
51
- const relativeTestFilePath = path
52
- .join(projectConfig.testsDirectoryPath, datasource.getTestSpecName(testFile))
53
- .replace(rootDirectoryPath + path.sep, "");
54
-
55
- const t = await datasource.readTest(testFile);
45
+ const extension = datasource.getExtension();
46
+ const relativeTestFilePath = test.key + (extension ? `.${extension}` : "");
56
47
 
57
- const tAsSegment = t as TestSegment;
58
- const tAsFeature = t as TestFeature;
48
+ const tAsSegment = test as TestSegment;
49
+ const tAsFeature = test as TestFeature;
59
50
  const key = tAsSegment.segment || tAsFeature.feature;
60
51
  const type = tAsSegment.segment ? "segment" : "feature";
61
52
 
@@ -68,26 +59,21 @@ export async function executeTest(
68
59
  };
69
60
 
70
61
  if (!key) {
71
- console.error(` => Invalid test: ${JSON.stringify(t)}`);
62
+ console.error(` => Invalid test: ${JSON.stringify(test)}`);
72
63
  executionResult.passed = false;
73
64
 
74
65
  return executionResult;
75
66
  }
76
67
 
77
- if (patterns.keyPattern && !patterns.keyPattern.test(key)) {
78
- return;
79
- }
80
-
81
68
  let testResult;
82
69
  if (type === "segment") {
83
- testResult = await testSegment(datasource, tAsSegment, patterns);
70
+ testResult = await testSegment(datasource, tAsSegment, options);
84
71
  } else {
85
72
  testResult = await testFeature(
86
73
  datasource,
87
74
  projectConfig,
88
75
  tAsFeature,
89
76
  options,
90
- patterns,
91
77
  datafileContentByEnvironment,
92
78
  );
93
79
  }
@@ -117,9 +103,9 @@ export async function executeTest(
117
103
 
118
104
  export async function testProject(
119
105
  deps: Dependencies,
120
- options: TestProjectOptions = {},
106
+ testOptions: TestProjectOptions = {},
121
107
  ): Promise<boolean> {
122
- const { projectConfig, datasource } = deps;
108
+ const { rootDirectoryPath, projectConfig, datasource, options } = deps;
123
109
 
124
110
  let hasError = false;
125
111
 
@@ -130,22 +116,8 @@ export async function testProject(
130
116
  return hasError;
131
117
  }
132
118
 
133
- const testFiles = await datasource.listTests();
134
-
135
- if (testFiles.length === 0) {
136
- console.error(`No tests found in: ${projectConfig.testsDirectoryPath}`);
137
- hasError = true;
138
-
139
- return hasError;
140
- }
141
-
142
119
  const startTime = Date.now();
143
120
 
144
- const patterns: TestPatterns = {
145
- keyPattern: options.keyPattern ? new RegExp(options.keyPattern) : undefined,
146
- assertionPattern: options.assertionPattern ? new RegExp(options.assertionPattern) : undefined,
147
- };
148
-
149
121
  let passedTestsCount = 0;
150
122
  let failedTestsCount = 0;
151
123
 
@@ -170,7 +142,7 @@ export async function testProject(
170
142
  existingState,
171
143
  );
172
144
 
173
- datafileContentByEnvironment.set(environment, datafileContent);
145
+ datafileContentByEnvironment.set(environment, datafileContent as DatafileContent);
174
146
  }
175
147
  }
176
148
 
@@ -189,17 +161,24 @@ export async function testProject(
189
161
  existingState,
190
162
  );
191
163
 
192
- datafileContentByEnvironment.set(false, datafileContent);
164
+ datafileContentByEnvironment.set(false, datafileContent as DatafileContent);
193
165
  }
194
166
 
195
- for (const testFile of testFiles) {
196
- const executionResult = await executeTest(
197
- testFile,
198
- deps,
199
- options,
200
- patterns,
201
- datafileContentByEnvironment,
202
- );
167
+ const tests = await listEntities<Test>(
168
+ {
169
+ rootDirectoryPath,
170
+ projectConfig,
171
+ datasource,
172
+ options: {
173
+ ...testOptions,
174
+ applyMatrix: true,
175
+ },
176
+ },
177
+ "test",
178
+ );
179
+
180
+ for (const test of tests) {
181
+ const executionResult = await executeTest(test, deps, options, datafileContentByEnvironment);
203
182
 
204
183
  if (!executionResult) {
205
184
  continue;