@teambit/tester 1.0.106 → 1.0.108

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.
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@teambit/tester",
3
- "version": "1.0.106",
3
+ "version": "1.0.108",
4
4
  "homepage": "https://bit.cloud/teambit/defender/tester",
5
5
  "main": "dist/index.js",
6
6
  "componentId": {
7
7
  "scope": "teambit.defender",
8
8
  "name": "tester",
9
- "version": "1.0.106"
9
+ "version": "1.0.108"
10
10
  },
11
11
  "dependencies": {
12
12
  "chalk": "2.4.2",
@@ -19,43 +19,39 @@
19
19
  "cli-highlight": "2.1.9",
20
20
  "junit-report-builder": "3.0.1",
21
21
  "strip-ansi": "6.0.0",
22
- "core-js": "^3.0.0",
23
- "@babel/runtime": "7.20.0",
24
22
  "@teambit/harmony": "0.4.6",
25
23
  "@teambit/component-id": "1.2.0",
24
+ "@teambit/tests-results": "1.0.4",
26
25
  "@teambit/defender.ui.test-compare-section": "0.0.100",
27
26
  "@teambit/defender.ui.test-compare": "0.0.255",
28
27
  "@teambit/defender.ui.test-page": "0.0.34",
29
28
  "@teambit/bit-error": "0.0.404",
30
- "@teambit/cli": "0.0.839",
31
- "@teambit/logger": "0.0.932",
32
- "@teambit/workspace": "1.0.106",
33
- "@teambit/envs": "1.0.106",
34
- "@teambit/component": "1.0.106",
35
- "@teambit/graphql": "1.0.106",
36
- "@teambit/builder": "1.0.106",
37
- "@teambit/dev-files": "1.0.106",
38
- "@teambit/tests-results": "1.0.4",
39
- "@teambit/ui": "1.0.106",
40
- "@teambit/compiler": "1.0.106",
41
- "@teambit/component-compare": "1.0.106"
29
+ "@teambit/cli": "0.0.840",
30
+ "@teambit/logger": "0.0.933",
31
+ "@teambit/workspace": "1.0.108",
32
+ "@teambit/envs": "1.0.108",
33
+ "@teambit/component": "1.0.108",
34
+ "@teambit/graphql": "1.0.108",
35
+ "@teambit/builder": "1.0.108",
36
+ "@teambit/dev-files": "1.0.108",
37
+ "@teambit/ui": "1.0.108",
38
+ "@teambit/compiler": "1.0.108",
39
+ "@teambit/component-compare": "1.0.108"
42
40
  },
43
41
  "devDependencies": {
44
- "@types/react": "^17.0.8",
45
42
  "@types/fs-extra": "9.0.7",
46
43
  "@types/lodash.compact": "3.0.6",
47
44
  "@types/lodash": "4.14.165",
48
45
  "@types/mocha": "9.1.0",
49
- "@types/node": "12.20.4",
50
- "@types/react-dom": "^17.0.5",
51
- "@types/jest": "^26.0.0",
52
- "@types/testing-library__jest-dom": "5.9.5",
53
- "@teambit/defender.content.tester-overview": "1.95.0"
46
+ "@types/jest": "^29.2.2",
47
+ "@types/testing-library__jest-dom": "^5.9.5",
48
+ "@teambit/defender.content.tester-overview": "1.95.0",
49
+ "@teambit/harmony.envs.core-aspect-env": "0.0.13"
54
50
  },
55
51
  "peerDependencies": {
56
- "@teambit/legacy": "1.0.624",
57
- "react": "^16.8.0 || ^17.0.0",
58
- "react-dom": "^16.8.0 || ^17.0.0"
52
+ "react": "^17.0.0 || ^18.0.0",
53
+ "@types/react": "^18.2.12",
54
+ "@teambit/legacy": "1.0.624"
59
55
  },
60
56
  "license": "Apache-2.0",
61
57
  "optionalDependencies": {},
@@ -69,7 +65,7 @@
69
65
  },
70
66
  "private": false,
71
67
  "engines": {
72
- "node": ">=12.22.0"
68
+ "node": ">=16.0.0"
73
69
  },
74
70
  "repository": {
75
71
  "type": "git",
@@ -78,12 +74,9 @@
78
74
  "keywords": [
79
75
  "bit",
80
76
  "bit-aspect",
77
+ "bit-core-aspect",
81
78
  "components",
82
79
  "collaboration",
83
- "web",
84
- "react",
85
- "react-components",
86
- "angular",
87
- "angular-components"
80
+ "web"
88
81
  ]
89
82
  }
package/tester-env.ts ADDED
@@ -0,0 +1,6 @@
1
+ import { EnvHandler } from '@teambit/envs';
2
+ import { Tester } from './tester';
3
+
4
+ export interface TesterEnv {
5
+ tester(): EnvHandler<Tester>;
6
+ }
@@ -0,0 +1,7 @@
1
+ import { Aspect } from '@teambit/harmony';
2
+
3
+ export const TesterAspect = Aspect.create({
4
+ id: 'teambit.defender/tester',
5
+ });
6
+
7
+ export default TesterAspect;
@@ -0,0 +1,80 @@
1
+ import { GraphqlMain, Schema } from '@teambit/graphql';
2
+ import { ComponentFactory } from '@teambit/component';
3
+ import { withFilter } from 'graphql-subscriptions';
4
+ import { ComponentID } from '@teambit/component-id';
5
+ import gql from 'graphql-tag';
6
+
7
+ import { TesterMain } from './tester.main.runtime';
8
+ import { OnTestsChanged } from './tester.service';
9
+
10
+ export function testerSchema(tester: TesterMain, graphql: GraphqlMain): Schema {
11
+ return {
12
+ typeDefs: gql`
13
+ extend type ComponentHost {
14
+ getTests(id: String!): Tests
15
+ }
16
+
17
+ type Subscription {
18
+ testsChanged(id: String!): Tests
19
+ }
20
+
21
+ type Tests {
22
+ loading: Boolean!
23
+ testsResults: TestsResults
24
+ }
25
+
26
+ type TestsChanged {
27
+ testsResults: TestsResults
28
+ }
29
+
30
+ type TestsResults {
31
+ testFiles: [TestFiles]
32
+ success: Boolean
33
+ start: Int
34
+ }
35
+
36
+ type TestFiles {
37
+ file: String
38
+ tests: [Tests]
39
+ pass: Int
40
+ failed: Int
41
+ pending: Int
42
+ duration: Int
43
+ slow: Boolean
44
+ errorStr: String
45
+ }
46
+
47
+ type Tests {
48
+ ancestor: [String]
49
+ name: String
50
+ duration: String
51
+ status: String
52
+ error: String
53
+ }
54
+ `,
55
+ resolvers: {
56
+ Subscription: {
57
+ testsChanged: {
58
+ subscribe: withFilter(
59
+ () => graphql.pubsub.asyncIterator(OnTestsChanged),
60
+ (payload, variables) => {
61
+ return payload.testsChanged.id === variables.id;
62
+ }
63
+ ),
64
+ },
65
+ },
66
+
67
+ ComponentHost: {
68
+ getTests: async (host: ComponentFactory, { id }: { id: string }) => {
69
+ const componentId = await host.resolveComponentId(id);
70
+ const idHasVersion = ComponentID.fromString(id).hasVersion();
71
+ const component = await host.get(componentId);
72
+ if (!component) return null;
73
+ const testsResults = await tester.getTestsResults(component, idHasVersion);
74
+ if (!testsResults) return null;
75
+ return testsResults;
76
+ },
77
+ },
78
+ },
79
+ };
80
+ }
@@ -0,0 +1,268 @@
1
+ import fs from 'fs-extra';
2
+ import { CLIAspect, CLIMain, MainRuntime } from '@teambit/cli';
3
+ import { Component, IComponent } from '@teambit/component';
4
+ import compact from 'lodash.compact';
5
+ import { EnvsAspect, EnvsExecutionResult, EnvsMain } from '@teambit/envs';
6
+ import { LoggerAspect, LoggerMain } from '@teambit/logger';
7
+ import { Workspace, WorkspaceAspect } from '@teambit/workspace';
8
+ import { GraphqlAspect, GraphqlMain } from '@teambit/graphql';
9
+ import { BuilderAspect, BuilderMain } from '@teambit/builder';
10
+ import { UiMain, UIAspect } from '@teambit/ui';
11
+ import { merge } from 'lodash';
12
+ import DevFilesAspect, { DevFilesMain } from '@teambit/dev-files';
13
+ import { TestsResult } from '@teambit/tests-results';
14
+ import { ComponentsResults, CallbackFn, Tests } from './tester';
15
+ import { TestCmd } from './test.cmd';
16
+ import { TesterAspect } from './tester.aspect';
17
+ import { TesterService } from './tester.service';
18
+ import { TesterTask } from './tester.task';
19
+ import { detectTestFiles } from './utils';
20
+ import { testerSchema } from './tester.graphql';
21
+ import { testsResultsToJUnitFormat } from './utils/junit-generator';
22
+
23
+ export type TesterExtensionConfig = {
24
+ /**
25
+ * regex of the text environment.
26
+ */
27
+ testRegex: string;
28
+
29
+ /**
30
+ * determine whether to watch on start.
31
+ */
32
+ watchOnStart: boolean;
33
+ patterns: string[];
34
+ };
35
+
36
+ export type TesterOptions = {
37
+ /**
38
+ * start the tester in watch mode.
39
+ */
40
+ watch: boolean;
41
+
42
+ /**
43
+ * start the tester in debug mode.
44
+ */
45
+ debug: boolean;
46
+
47
+ /**
48
+ * start the tester in debug mode.
49
+ */
50
+ ui?: boolean;
51
+
52
+ /**
53
+ * initiate the tester on given env.
54
+ */
55
+ env?: string;
56
+
57
+ /**
58
+ * generate JUnit files on the specified dir
59
+ */
60
+ junit?: string;
61
+
62
+ /**
63
+ * show code coverage
64
+ */
65
+ coverage?: boolean;
66
+
67
+ callback?: CallbackFn;
68
+ };
69
+
70
+ export class TesterMain {
71
+ static runtime = MainRuntime;
72
+ static dependencies = [
73
+ CLIAspect,
74
+ EnvsAspect,
75
+ WorkspaceAspect,
76
+ LoggerAspect,
77
+ GraphqlAspect,
78
+ UIAspect,
79
+ DevFilesAspect,
80
+ BuilderAspect,
81
+ ];
82
+
83
+ constructor(
84
+ private patterns: string[],
85
+ /**
86
+ * graphql extension.
87
+ */
88
+ private graphql: GraphqlMain,
89
+
90
+ /**
91
+ * envs extension.
92
+ */
93
+ private envs: EnvsMain,
94
+
95
+ /**
96
+ * workspace extension.
97
+ */
98
+ private workspace: Workspace,
99
+
100
+ /**
101
+ * tester service.
102
+ */
103
+ readonly service: TesterService,
104
+
105
+ /**
106
+ * build task.
107
+ */
108
+ readonly task: TesterTask,
109
+
110
+ private devFiles: DevFilesMain,
111
+
112
+ private builder: BuilderMain
113
+ ) {}
114
+
115
+ _testsResults: { [componentId: string]: ComponentsResults } | undefined[] = [];
116
+
117
+ async test(components: Component[], opts?: TesterOptions): Promise<EnvsExecutionResult<Tests>> {
118
+ const options = this.getOptions(opts);
119
+ const envsRuntime = await this.envs.createEnvironment(components);
120
+ if (opts?.env) {
121
+ return envsRuntime.runEnv(opts.env, this.service, options);
122
+ }
123
+ const results = await envsRuntime.run(this.service, options);
124
+ if (opts?.junit) {
125
+ await this.generateJUnit(opts?.junit, results);
126
+ }
127
+ return results;
128
+ }
129
+
130
+ private async generateJUnit(filePath: string, testsResults: EnvsExecutionResult<Tests>) {
131
+ const components = testsResults.results.map((envResult) => envResult.data?.components).flat();
132
+ const jUnit = testsResultsToJUnitFormat(compact(components));
133
+ await fs.outputFile(filePath, jUnit);
134
+ }
135
+
136
+ /**
137
+ * watch all components for changes and test upon each.
138
+ */
139
+ async watch(components: Component[], opts?: TesterOptions) {
140
+ const options = this.getOptions(opts);
141
+ const envsRuntime = await this.envs.createEnvironment(components);
142
+ if (opts?.env) {
143
+ return envsRuntime.runEnv(opts.env, this.service, options);
144
+ }
145
+
146
+ this.service.onTestRunComplete((results) => {
147
+ results.components.forEach((component) => {
148
+ this._testsResults[component.componentId.toString()] = component;
149
+ });
150
+ });
151
+ return envsRuntime.run(this.service, options);
152
+ }
153
+
154
+ async uiWatch() {
155
+ const components = await this.workspace.list();
156
+ return this.watch(components, { watch: true, debug: false, ui: true });
157
+ }
158
+
159
+ async getTestsResults(
160
+ component: IComponent,
161
+ idHasVersion = true
162
+ ): Promise<{ testsResults?: TestsResult; loading: boolean } | undefined> {
163
+ const entry = component.get(TesterAspect.id);
164
+ const isModified = !idHasVersion && (await component.isModified());
165
+ const data = this.builder.getDataByAspect(component, TesterAspect.id) as { tests: TestsResult };
166
+ if ((entry || data) && !isModified) {
167
+ return { testsResults: data?.tests || entry?.data.tests, loading: false };
168
+ }
169
+ return this.getTestsResultsFromState(component);
170
+ }
171
+
172
+ private getTestsResultsFromState(component: IComponent) {
173
+ const tests = this._testsResults[component.id.toString()];
174
+ return { testsResults: tests?.results, loading: tests?.loading || false };
175
+ }
176
+
177
+ /**
178
+ * Get the tests patterns from the config. (used as default patterns in case the env does not provide them via getTestsDevPatterns)
179
+ * @returns
180
+ */
181
+ getPatterns() {
182
+ return this.patterns;
183
+ }
184
+
185
+ getComponentDevPatterns(component: Component) {
186
+ const env = this.envs.calculateEnv(component, { skipWarnings: !!this.workspace?.inInstallContext }).env;
187
+ const componentPatterns: string[] = env.getTestsDevPatterns
188
+ ? env.getTestsDevPatterns(component)
189
+ : this.getPatterns();
190
+ return { name: 'tests', pattern: componentPatterns };
191
+ }
192
+
193
+ getDevPatternToRegister() {
194
+ return this.getComponentDevPatterns.bind(this);
195
+ }
196
+
197
+ /**
198
+ * get all test files of a component.
199
+ */
200
+ getTestFiles(component: Component) {
201
+ return detectTestFiles(component, this.devFiles);
202
+ }
203
+
204
+ private getOptions(options?: TesterOptions): TesterOptions {
205
+ const defaults = {
206
+ watch: false,
207
+ debug: false,
208
+ };
209
+
210
+ return merge(defaults, options);
211
+ }
212
+
213
+ static defaultConfig = {
214
+ /**
215
+ * default test regex for which files tester to apply on.
216
+ */
217
+ patterns: ['**/*.spec.+(js|ts|jsx|tsx)', '**/*.test.+(js|ts|jsx|tsx)'],
218
+
219
+ /**
220
+ * determine whether to watch on start.
221
+ */
222
+ watchOnStart: false,
223
+ };
224
+
225
+ static async provider(
226
+ [cli, envs, workspace, loggerAspect, graphql, ui, devFiles, builder]: [
227
+ CLIMain,
228
+ EnvsMain,
229
+ Workspace,
230
+ LoggerMain,
231
+ GraphqlMain,
232
+ UiMain,
233
+ DevFilesMain,
234
+ BuilderMain
235
+ ],
236
+ config: TesterExtensionConfig
237
+ ) {
238
+ const logger = loggerAspect.createLogger(TesterAspect.id);
239
+ const testerService = new TesterService(workspace, logger, graphql.pubsub, devFiles);
240
+ envs.registerService(testerService);
241
+ const tester = new TesterMain(
242
+ config.patterns,
243
+ graphql,
244
+ envs,
245
+ workspace,
246
+ testerService,
247
+ new TesterTask(TesterAspect.id, devFiles),
248
+ devFiles,
249
+ builder
250
+ );
251
+ devFiles.registerDevPattern(tester.getDevPatternToRegister());
252
+
253
+ if (workspace) {
254
+ ui.registerOnStart(async () => {
255
+ if (!config.watchOnStart) return undefined;
256
+ await tester.uiWatch();
257
+ return undefined;
258
+ });
259
+ }
260
+ cli.register(new TestCmd(tester, workspace, logger));
261
+
262
+ graphql.register(testerSchema(tester, graphql));
263
+
264
+ return tester;
265
+ }
266
+ }
267
+
268
+ TesterAspect.addRuntime(TesterMain);
package/tester.task.ts ADDED
@@ -0,0 +1,116 @@
1
+ import { BuildContext, BuiltTaskResult, BuildTask, CAPSULE_ARTIFACTS_DIR } from '@teambit/builder';
2
+ import fs from 'fs-extra';
3
+ import { join } from 'path';
4
+ import { Compiler, CompilerAspect } from '@teambit/compiler';
5
+ import { DevFilesMain } from '@teambit/dev-files';
6
+ import { ComponentMap } from '@teambit/component';
7
+ import { Tester } from './tester';
8
+ import { detectTestFiles } from './utils';
9
+ import { testsResultsToJUnitFormat } from './utils/junit-generator';
10
+
11
+ /**
12
+ * tester build task. Allows to test components during component build.
13
+ */
14
+ export class TesterTask implements BuildTask {
15
+ readonly name = 'TestComponents';
16
+ readonly dependencies = [CompilerAspect.id];
17
+ constructor(readonly aspectId: string, private devFiles: DevFilesMain) {}
18
+
19
+ async execute(context: BuildContext): Promise<BuiltTaskResult> {
20
+ const components = context.capsuleNetwork.originalSeedersCapsules.getAllComponents();
21
+ const tester: Tester = context.env.getTester();
22
+ const componentsSpecFiles = ComponentMap.as(components, (component) => {
23
+ return detectTestFiles(component, this.devFiles);
24
+ });
25
+
26
+ const testCount = componentsSpecFiles.toArray().reduce((acc, [, specs]) => acc + specs.length, 0);
27
+ if (testCount === 0)
28
+ return {
29
+ artifacts: [],
30
+ componentsResults: [],
31
+ };
32
+
33
+ const patternsWithCapsule = ComponentMap.as(components, (component) => {
34
+ const componentSpecFiles = componentsSpecFiles.get(component);
35
+ if (!componentSpecFiles) throw new Error('capsule not found');
36
+ const [, specs] = componentSpecFiles;
37
+ const capsule = context.capsuleNetwork.graphCapsules.getCapsule(component.id);
38
+ if (!capsule) throw new Error('capsule not found');
39
+ const compiler: Compiler = context.env.getCompiler();
40
+ if (!compiler) {
41
+ throw new Error(`compiler not found for ${component.id.toString()}`);
42
+ }
43
+ // @ts-ignore. not sure why ts complain that compiler might be undefined, when we check it above.
44
+ const distFolder = compiler.getDistDir() || compiler.distDir;
45
+ return {
46
+ componentDir: join(capsule.path, distFolder),
47
+ paths: specs.map((specFile) => {
48
+ const distPath = compiler.getDistPathBySrcPath(specFile.relative);
49
+ // TODO: fix spec type file need to capsule will return files with type AbstractVinyl
50
+ return { path: join(capsule.path, distPath), relative: distPath };
51
+ }),
52
+ };
53
+ });
54
+
55
+ const specFilesWithCapsule = ComponentMap.as(components, (component) => {
56
+ const patternEntry = patternsWithCapsule.get(component);
57
+ // @ts-ignore
58
+ const [, val] = patternEntry;
59
+ return val.paths;
60
+ });
61
+
62
+ const testerContext = Object.assign(context, {
63
+ release: true,
64
+ specFiles: specFilesWithCapsule,
65
+ rootPath: context.capsuleNetwork.capsulesRootDir,
66
+ patterns: patternsWithCapsule,
67
+ });
68
+
69
+ // TODO: remove after fix AbstractVinyl on capsule
70
+ // @ts-ignore
71
+ const testsResults = await tester.test(testerContext);
72
+
73
+ // write junit files
74
+ await Promise.all(
75
+ testsResults.components.map(async (compResult) => {
76
+ const junit = testsResultsToJUnitFormat([compResult]);
77
+ const capsule = context.capsuleNetwork.graphCapsules.getCapsule(compResult.componentId);
78
+ if (!capsule) {
79
+ throw new Error(`unable to find ${compResult.componentId.toString()} in capsules`);
80
+ }
81
+ await fs.outputFile(join(capsule.path, getJUnitArtifactPath()), junit);
82
+ })
83
+ );
84
+
85
+ return {
86
+ artifacts: getArtifactDef(), // @ts-ignore
87
+ componentsResults: testsResults.components.map((componentTests) => {
88
+ const componentErrors = componentTests.errors;
89
+ const component = context.capsuleNetwork.graphCapsules.getCapsule(componentTests.componentId)?.component;
90
+ if (!component) {
91
+ throw new Error(`unable to find ${componentTests.componentId.toString()} in capsules`);
92
+ }
93
+
94
+ return {
95
+ component,
96
+ metadata: { tests: componentTests.results },
97
+ errors: componentErrors,
98
+ };
99
+ }),
100
+ };
101
+ }
102
+ }
103
+
104
+ export function getJUnitArtifactPath() {
105
+ return join(CAPSULE_ARTIFACTS_DIR, '__bit_junit.xml');
106
+ }
107
+
108
+ export function getArtifactDef() {
109
+ return [
110
+ {
111
+ name: 'junit',
112
+ globPatterns: [getJUnitArtifactPath()],
113
+ rootDir: CAPSULE_ARTIFACTS_DIR,
114
+ },
115
+ ];
116
+ }