@teambit/tester 0.0.555 → 0.0.556

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": "0.0.555",
3
+ "version": "0.0.556",
4
4
  "homepage": "https://bit.dev/teambit/defender/tester",
5
5
  "main": "dist/index.js",
6
6
  "componentId": {
7
7
  "scope": "teambit.defender",
8
8
  "name": "tester",
9
- "version": "0.0.555"
9
+ "version": "0.0.556"
10
10
  },
11
11
  "dependencies": {
12
12
  "chalk": "2.4.2",
@@ -24,24 +24,24 @@
24
24
  "@babel/runtime": "7.12.18",
25
25
  "core-js": "^3.0.0",
26
26
  "@teambit/documenter.ui.heading": "4.1.1",
27
- "@teambit/cli": "0.0.386",
28
- "@teambit/logger": "0.0.471",
29
- "@teambit/workspace": "0.0.555",
30
- "@teambit/component": "0.0.555",
31
- "@teambit/graphql": "0.0.555",
32
- "@teambit/builder": "0.0.555",
33
- "@teambit/dev-files": "0.0.555",
34
- "@teambit/envs": "0.0.555",
35
- "@teambit/tests-results": "0.0.72",
36
- "@teambit/ui": "0.0.555",
37
- "@teambit/compiler": "0.0.555",
38
- "@teambit/bit-error": "0.0.372",
39
- "@teambit/defender.ui.test-loader": "0.0.465",
40
- "@teambit/defender.ui.test-table": "0.0.468",
27
+ "@teambit/cli": "0.0.387",
28
+ "@teambit/logger": "0.0.472",
29
+ "@teambit/workspace": "0.0.556",
30
+ "@teambit/component": "0.0.556",
31
+ "@teambit/graphql": "0.0.556",
32
+ "@teambit/builder": "0.0.556",
33
+ "@teambit/dev-files": "0.0.556",
34
+ "@teambit/envs": "0.0.556",
35
+ "@teambit/tests-results": "0.0.73",
36
+ "@teambit/ui": "0.0.556",
37
+ "@teambit/compiler": "0.0.556",
38
+ "@teambit/bit-error": "0.0.373",
39
+ "@teambit/defender.ui.test-loader": "0.0.466",
40
+ "@teambit/defender.ui.test-table": "0.0.469",
41
41
  "@teambit/design.ui.alert-card": "0.0.13",
42
42
  "@teambit/design.ui.empty-box": "0.0.352",
43
43
  "@teambit/design.ui.separator": "0.0.351",
44
- "@teambit/mdx.ui.mdx-layout": "0.0.467"
44
+ "@teambit/mdx.ui.mdx-layout": "0.0.468"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@types/react": "^17.0.8",
@@ -57,7 +57,7 @@
57
57
  },
58
58
  "peerDependencies": {
59
59
  "@apollo/client": "^3.0.0",
60
- "@teambit/legacy": "1.0.172",
60
+ "@teambit/legacy": "1.0.173",
61
61
  "react-dom": "^16.8.0 || ^17.0.0",
62
62
  "react": "^16.8.0 || ^17.0.0"
63
63
  },
@@ -85,12 +85,18 @@
85
85
  "react": "-"
86
86
  },
87
87
  "peerDependencies": {
88
- "@teambit/legacy": "1.0.172",
88
+ "@teambit/legacy": "1.0.173",
89
89
  "react-dom": "^16.8.0 || ^17.0.0",
90
90
  "react": "^16.8.0 || ^17.0.0"
91
91
  }
92
92
  }
93
93
  },
94
+ "files": [
95
+ "dist",
96
+ "!dist/tsconfig.tsbuildinfo",
97
+ "README.md",
98
+ "README.mdx"
99
+ ],
94
100
  "private": false,
95
101
  "engines": {
96
102
  "node": ">=12.22.0"
@@ -1,2 +0,0 @@
1
- export { NoTestFilesFound } from './no-test-files-found';
2
- export { NoMatchingComponents } from './no-matching-components';
@@ -1,5 +0,0 @@
1
- export class NoMatchingComponents extends Error {
2
- constructor(pattern: string) {
3
- super(`could not find components matching to pattern: ${pattern}`);
4
- }
5
- }
@@ -1,7 +0,0 @@
1
- import { BitError } from '@teambit/bit-error';
2
-
3
- export class NoTestFilesFound extends BitError {
4
- constructor(testRegex: string) {
5
- super(`no test files for regex: '${testRegex}' found for any of the components in the workspace.`);
6
- }
7
- }
package/index.ts DELETED
@@ -1,8 +0,0 @@
1
- import { TesterAspect } from './tester.aspect';
2
-
3
- export type { Tester, Tests, TesterContext, CallbackFn, SpecFiles, ComponentPatternsMap } from './tester';
4
- export type { TesterMain } from './tester.main.runtime';
5
- export type { TesterUI } from './tester.ui.runtime';
6
-
7
- export { TesterAspect };
8
- export default TesterAspect;
package/test.cmd.tsx DELETED
@@ -1,93 +0,0 @@
1
- import { Command, CommandOptions } from '@teambit/cli';
2
- import chalk from 'chalk';
3
- import { Logger } from '@teambit/logger';
4
- import { Workspace } from '@teambit/workspace';
5
- import { ConsumerNotFound } from '@teambit/legacy/dist/consumer/exceptions';
6
- import { Timer } from '@teambit/legacy/dist/toolbox/timer';
7
- import { Box, Text } from 'ink';
8
- import React from 'react';
9
- import { NoMatchingComponents } from './exceptions';
10
-
11
- import type { TesterMain } from './tester.main.runtime';
12
-
13
- type TestFlags = {
14
- watch: boolean;
15
- debug: boolean;
16
- env?: string;
17
- scope?: string;
18
- junit?: string;
19
- coverage?: boolean;
20
- };
21
-
22
- export class TestCmd implements Command {
23
- name = 'test [pattern]';
24
- description = 'test set of components in your workspace';
25
- alias = 'at';
26
- group = 'development';
27
- shortDescription = '';
28
- options = [
29
- ['w', 'watch', 'start the tester in watch mode.'],
30
- ['d', 'debug', 'start the tester in debug mode.'],
31
- ['', 'junit <filepath>', 'write tests results as JUnit XML format into the specified file path'],
32
- ['', 'coverage', 'show code coverage data'],
33
- ['e', 'env <id>', 'test only the given env'],
34
- ['s', 'scope <scope>', 'name of the scope to test'],
35
- // TODO: we need to reduce this redundant casting every time.
36
- ] as CommandOptions;
37
-
38
- constructor(private tester: TesterMain, private workspace: Workspace, private logger: Logger) {}
39
-
40
- async render(
41
- [userPattern]: [string],
42
- { watch = false, debug = false, env, scope, junit, coverage = false }: TestFlags
43
- ) {
44
- this.logger.off();
45
- const timer = Timer.create();
46
- const scopeName = typeof scope === 'string' ? scope : undefined;
47
- timer.start();
48
- if (!this.workspace) throw new ConsumerNotFound();
49
- const pattern = userPattern && userPattern.toString();
50
- const components =
51
- pattern || scopeName ? await this.workspace.byPattern(pattern || '*', scopeName) : await this.workspace.list();
52
-
53
- if (!components.length) throw new NoMatchingComponents(pattern);
54
-
55
- this.logger.console(
56
- `testing total of ${components.length} components in workspace '${chalk.cyan(this.workspace.name)}'`
57
- );
58
-
59
- let code = 0;
60
- if (watch && !debug) {
61
- await this.tester.watch(components, {
62
- watch,
63
- debug,
64
- env,
65
- coverage,
66
- });
67
- } else {
68
- const tests = await this.tester.test(components, {
69
- watch,
70
- debug,
71
- env,
72
- junit,
73
- coverage,
74
- });
75
- tests?.results?.forEach((test) => (test.data?.errors?.length ? (code = 1) : null));
76
- }
77
- const { seconds } = timer.stop();
78
-
79
- if (watch) return <Box></Box>;
80
- return {
81
- code,
82
- data: (
83
- <Box>
84
- <Text>tested </Text>
85
- <Text color="cyan">{components.length} </Text>
86
- <Text>components in </Text>
87
- <Text color="cyan">{seconds} </Text>
88
- <Text>seconds.</Text>
89
- </Box>
90
- ),
91
- };
92
- }
93
- }
package/tester.aspect.ts DELETED
@@ -1,7 +0,0 @@
1
- import { Aspect } from '@teambit/harmony';
2
-
3
- export const TesterAspect = Aspect.create({
4
- id: 'teambit.defender/tester',
5
- });
6
-
7
- export default TesterAspect;
package/tester.graphql.ts DELETED
@@ -1,83 +0,0 @@
1
- import { GraphqlMain, Schema } from '@teambit/graphql';
2
- import { ComponentFactory } from '@teambit/component';
3
- import { withFilter } from 'graphql-subscriptions';
4
- import gql from 'graphql-tag';
5
-
6
- import { TesterMain } from './tester.main.runtime';
7
- import { OnTestsChanged } from './tester.service';
8
-
9
- export function testerSchema(tester: TesterMain, graphql: GraphqlMain): Schema {
10
- return {
11
- typeDefs: gql`
12
- extend type ComponentHost {
13
- getTests(id: String!): Tests
14
- }
15
-
16
- type Subscription {
17
- testsChanged(id: String!): Tests
18
- }
19
-
20
- type Tests {
21
- loading: Boolean!
22
- testsResults: TestsResults
23
- }
24
-
25
- type TestsChanged {
26
- testsResults: TestsResults
27
- }
28
-
29
- type TestsResults {
30
- testFiles: [TestFiles]
31
- success: Boolean
32
- start: Int
33
- }
34
-
35
- type TestFiles {
36
- file: String
37
- tests: [Tests]
38
- pass: Int
39
- failed: Int
40
- pending: Int
41
- duration: Int
42
- slow: Boolean
43
- error: TestError
44
- }
45
-
46
- type TestError {
47
- failureMessage: String
48
- error: String
49
- }
50
-
51
- type Tests {
52
- ancestor: [String]
53
- name: String
54
- duration: String
55
- status: String
56
- error: String
57
- }
58
- `,
59
- resolvers: {
60
- Subscription: {
61
- testsChanged: {
62
- subscribe: withFilter(
63
- () => graphql.pubsub.asyncIterator(OnTestsChanged),
64
- (payload, variables) => {
65
- return payload.testsChanged.id === variables.id;
66
- }
67
- ),
68
- },
69
- },
70
-
71
- ComponentHost: {
72
- getTests: async (host: ComponentFactory, { id }: { id: string }) => {
73
- const componentId = await host.resolveComponentId(id);
74
- const component = await host.get(componentId);
75
- if (!component) return null;
76
- const testsResults = await tester.getTestsResults(component);
77
- if (!testsResults) return null;
78
- return testsResults;
79
- },
80
- },
81
- },
82
- };
83
- }
@@ -1,245 +0,0 @@
1
- import fs from 'fs-extra';
2
- import { CLIAspect, CLIMain, MainRuntime } from '@teambit/cli';
3
- import { Component } 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
- /**
85
- * graphql extension.
86
- */
87
- private graphql: GraphqlMain,
88
-
89
- /**
90
- * envs extension.
91
- */
92
- private envs: EnvsMain,
93
-
94
- /**
95
- * workspace extension.
96
- */
97
- private workspace: Workspace,
98
-
99
- /**
100
- * tester service.
101
- */
102
- readonly service: TesterService,
103
-
104
- /**
105
- * build task.
106
- */
107
- readonly task: TesterTask,
108
-
109
- private devFiles: DevFilesMain,
110
-
111
- private builder: BuilderMain
112
- ) {}
113
-
114
- _testsResults: { [componentId: string]: ComponentsResults } | undefined[] = [];
115
-
116
- async test(components: Component[], opts?: TesterOptions): Promise<EnvsExecutionResult<Tests>> {
117
- const options = this.getOptions(opts);
118
- const envsRuntime = await this.envs.createEnvironment(components);
119
- if (opts?.env) {
120
- return envsRuntime.runEnv(opts.env, this.service, options);
121
- }
122
- const results = await envsRuntime.run(this.service, options);
123
- if (opts?.junit) {
124
- await this.generateJUnit(opts?.junit, results);
125
- }
126
- return results;
127
- }
128
-
129
- private async generateJUnit(filePath: string, testsResults: EnvsExecutionResult<Tests>) {
130
- const components = testsResults.results.map((envResult) => envResult.data?.components).flat();
131
- const jUnit = testsResultsToJUnitFormat(compact(components));
132
- await fs.outputFile(filePath, jUnit);
133
- }
134
-
135
- /**
136
- * watch all components for changes and test upon each.
137
- */
138
- async watch(components: Component[], opts?: TesterOptions) {
139
- const options = this.getOptions(opts);
140
- const envsRuntime = await this.envs.createEnvironment(components);
141
- if (opts?.env) {
142
- return envsRuntime.runEnv(opts.env, this.service, options);
143
- }
144
-
145
- this.service.onTestRunComplete((results) => {
146
- results.components.forEach((component) => {
147
- this._testsResults[component.componentId.toString()] = component;
148
- });
149
- });
150
- return envsRuntime.run(this.service, options);
151
- }
152
-
153
- async uiWatch() {
154
- const components = await this.workspace.list();
155
- return this.watch(components, { watch: true, debug: false, ui: true });
156
- }
157
-
158
- async getTestsResults(component: Component): Promise<{ testsResults?: TestsResult; loading: boolean } | undefined> {
159
- const entry = component.state.aspects.get(TesterAspect.id);
160
- const componentStatus = await this.workspace?.getComponentStatus(component);
161
- const data = this.builder.getDataByAspect(component, TesterAspect.id) as { tests: TestsResult };
162
- if ((entry || data) && !componentStatus?.modifyInfo?.hasModifiedFiles) {
163
- return { testsResults: data?.tests || entry?.data.tests, loading: false };
164
- }
165
- return this.getTestsResultsFromState(component);
166
- }
167
-
168
- private getTestsResultsFromState(component: Component) {
169
- const tests = this._testsResults[component.id.toString()];
170
- return { testsResults: tests?.results, loading: tests?.loading || false };
171
- }
172
-
173
- /**
174
- * get all test files of a component.
175
- */
176
- getTestFiles(component: Component) {
177
- return detectTestFiles(component, this.devFiles);
178
- }
179
-
180
- private getOptions(options?: TesterOptions): TesterOptions {
181
- const defaults = {
182
- watch: false,
183
- debug: false,
184
- };
185
-
186
- return merge(defaults, options);
187
- }
188
-
189
- static defaultConfig = {
190
- /**
191
- * default test regex for which files tester to apply on.
192
- */
193
- patterns: ['**/*.spec.+(js|ts|jsx|tsx)', '**/*.test.+(js|ts|jsx|tsx)'],
194
-
195
- /**
196
- * determine whether to watch on start.
197
- */
198
- watchOnStart: true,
199
- };
200
-
201
- static async provider(
202
- [cli, envs, workspace, loggerAspect, graphql, ui, devFiles, builder]: [
203
- CLIMain,
204
- EnvsMain,
205
- Workspace,
206
- LoggerMain,
207
- GraphqlMain,
208
- UiMain,
209
- DevFilesMain,
210
- BuilderMain
211
- ],
212
- config: TesterExtensionConfig
213
- ) {
214
- const logger = loggerAspect.createLogger(TesterAspect.id);
215
- const testerService = new TesterService(workspace, config.patterns, logger, graphql.pubsub, devFiles);
216
- envs.registerService(testerService);
217
- devFiles.registerDevPattern(config.patterns);
218
- const tester = new TesterMain(
219
- graphql,
220
- envs,
221
- workspace,
222
- testerService,
223
- new TesterTask(TesterAspect.id, devFiles),
224
- devFiles,
225
- builder
226
- );
227
-
228
- if (workspace && !workspace.consumer.isLegacy) {
229
- cli.unregister('test');
230
- ui.registerOnStart(async () => {
231
- if (!config.watchOnStart) return undefined;
232
- await tester.uiWatch();
233
- return undefined;
234
- });
235
-
236
- cli.register(new TestCmd(tester, workspace, logger));
237
- }
238
-
239
- graphql.register(testerSchema(tester, graphql));
240
-
241
- return tester;
242
- }
243
- }
244
-
245
- TesterAspect.addRuntime(TesterMain);
@@ -1,161 +0,0 @@
1
- import { Logger } from '@teambit/logger';
2
- import { resolve } from 'path';
3
- import React from 'react';
4
- import { Text, Newline } from 'ink';
5
- import { EnvService, ExecutionContext, EnvDefinition } from '@teambit/envs';
6
- import { ComponentMap } from '@teambit/component';
7
- import { Workspace } from '@teambit/workspace';
8
- import highlight from 'cli-highlight';
9
- import { PubSub } from 'graphql-subscriptions';
10
- import { DevFilesMain } from '@teambit/dev-files';
11
- import { Tester, Tests, CallbackFn } from './tester';
12
- import { TesterAspect } from './tester.aspect';
13
- import { TesterOptions } from './tester.main.runtime';
14
- import { detectTestFiles } from './utils';
15
- import { NoTestFilesFound } from './exceptions';
16
-
17
- const chalk = require('chalk');
18
-
19
- export const OnTestsChanged = 'OnTestsChanged';
20
-
21
- export type TesterDescriptor = {
22
- /**
23
- * id of the tester (e.g. jest/mocha)
24
- */
25
- id: string;
26
-
27
- /**
28
- * display name of the tester (e.g. Jest / Mocha)
29
- */
30
- displayName: string;
31
-
32
- /**
33
- * icon of the configured tester.
34
- */
35
- icon: string;
36
-
37
- /**
38
- * string containing the config for display.
39
- */
40
- config: string;
41
-
42
- version?: string;
43
- };
44
-
45
- export class TesterService implements EnvService<Tests, TesterDescriptor> {
46
- name = 'tester';
47
-
48
- constructor(
49
- readonly workspace: Workspace,
50
- /**
51
- * regex used to identify which files to test.
52
- */
53
- readonly patterns: string[],
54
-
55
- private logger: Logger,
56
-
57
- private pubsub: PubSub,
58
-
59
- private devFiles: DevFilesMain
60
- ) {}
61
-
62
- _callback: CallbackFn | undefined;
63
-
64
- render(env: EnvDefinition) {
65
- const descriptor = this.getDescriptor(env);
66
- return (
67
- <Text key={descriptor?.id}>
68
- <Text color="cyan">configured tester: </Text>
69
- <Text>
70
- {descriptor?.id} ({descriptor?.displayName} @ {descriptor?.version})
71
- </Text>
72
- <Newline />
73
- <Text underline color="cyan">
74
- tester config:
75
- </Text>
76
- <Newline />
77
- <Text>
78
- {/* refactor a separate component which highlights for cli */}
79
- {highlight(descriptor?.config || '', { language: 'javascript', ignoreIllegals: true })}
80
- </Text>
81
- <Newline />
82
- </Text>
83
- );
84
- }
85
-
86
- getDescriptor(environment: EnvDefinition) {
87
- if (!environment.env.getTester) return undefined;
88
- const tester: Tester = environment.env.getTester();
89
-
90
- return {
91
- id: tester.id || '',
92
- displayName: tester.displayName || '',
93
- icon: tester.icon || '',
94
- config: tester.displayConfig ? tester.displayConfig() : '',
95
- version: tester.version ? tester.version() : '?',
96
- };
97
- }
98
-
99
- onTestRunComplete(callback: CallbackFn) {
100
- this._callback = callback;
101
- }
102
-
103
- async run(context: ExecutionContext, options: TesterOptions): Promise<Tests> {
104
- const tester: Tester = context.env.getTester();
105
- const specFiles = ComponentMap.as(context.components, (component) => {
106
- return detectTestFiles(component, this.devFiles);
107
- });
108
- const testCount = specFiles.toArray().reduce((acc, [, specs]) => acc + specs.length, 0);
109
-
110
- const componentWithTests = specFiles.toArray().reduce((acc: number, [, specs]) => {
111
- if (specs.length > 0) acc += 1;
112
- return acc;
113
- }, 0);
114
-
115
- if (testCount === 0 && !options.ui) throw new NoTestFilesFound(this.patterns.join(','));
116
-
117
- if (!options.ui)
118
- this.logger.console(`testing ${componentWithTests} components with environment ${chalk.cyan(context.id)}\n`);
119
-
120
- const patterns = ComponentMap.as(context.components, (component) => {
121
- const dir = this.workspace.componentDir(component.id);
122
- const componentPatterns = this.devFiles.getDevPatterns(component, TesterAspect.id);
123
- return componentPatterns.map((pattern: string) => ({ path: resolve(dir, pattern), relative: pattern }));
124
- });
125
-
126
- const testerContext = Object.assign(context, {
127
- release: false,
128
- specFiles,
129
- patterns,
130
- rootPath: this.workspace.path,
131
- workspace: this.workspace,
132
- debug: options.debug,
133
- watch: options.watch,
134
- ui: options.ui,
135
- coverage: options.coverage,
136
- });
137
-
138
- if (options.watch && options.ui && tester.watch) {
139
- if (tester.onTestRunComplete) {
140
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
141
- tester.onTestRunComplete((results) => {
142
- if (this._callback) this._callback(results);
143
- results.components.forEach((component) => {
144
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
145
- this.pubsub.publish(OnTestsChanged, {
146
- testsChanged: {
147
- id: component.componentId.toString(),
148
- testsResults: component.results,
149
- loading: component.loading,
150
- },
151
- });
152
- });
153
- });
154
- }
155
-
156
- return tester.watch(testerContext);
157
- }
158
-
159
- return tester.test(testerContext);
160
- }
161
- }
package/tester.task.ts DELETED
@@ -1,111 +0,0 @@
1
- import { BuildContext, BuiltTaskResult, BuildTask } 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
- const JUNIT_PATH = '__bit_junit.xml';
12
-
13
- /**
14
- * tester build task. Allows to test components during component build.
15
- */
16
- export class TesterTask implements BuildTask {
17
- readonly name = 'TestComponents';
18
- readonly dependencies = [CompilerAspect.id];
19
- constructor(readonly aspectId: string, private devFiles: DevFilesMain) {}
20
-
21
- async execute(context: BuildContext): Promise<BuiltTaskResult> {
22
- const tester: Tester = context.env.getTester();
23
- const componentsSpecFiles = ComponentMap.as(context.components, (component) => {
24
- return detectTestFiles(component, this.devFiles);
25
- });
26
-
27
- const testCount = componentsSpecFiles.toArray().reduce((acc, [, specs]) => acc + specs.length, 0);
28
- if (testCount === 0)
29
- return {
30
- artifacts: [],
31
- componentsResults: [],
32
- };
33
-
34
- const specFilesWithCapsule = ComponentMap.as(context.components, (component) => {
35
- const componentSpecFiles = componentsSpecFiles.get(component);
36
- if (!componentSpecFiles) throw new Error('capsule not found');
37
- const [, specs] = componentSpecFiles;
38
- return specs.map((specFile) => {
39
- const capsule = context.capsuleNetwork.graphCapsules.getCapsule(component.id);
40
- if (!capsule) throw new Error('capsule not found');
41
- const compiler: Compiler = context.env.getCompiler();
42
- const distPath = compiler.getDistPathBySrcPath(specFile.relative);
43
-
44
- // TODO: fix spec type file need to capsule will return files with type AbstractVinyl
45
- return { path: join(capsule.path, distPath), relative: distPath };
46
- });
47
- });
48
-
49
- const testerContext = Object.assign(context, {
50
- release: true,
51
- specFiles: specFilesWithCapsule,
52
- rootPath: context.capsuleNetwork.capsulesRootDir,
53
- patterns: specFilesWithCapsule,
54
- });
55
-
56
- // TODO: remove after fix AbstractVinyl on capsule
57
- // @ts-ignore
58
- const testsResults = await tester.test(testerContext);
59
-
60
- // write junit files
61
- await Promise.all(
62
- testsResults.components.map(async (compResult) => {
63
- const junit = testsResultsToJUnitFormat([compResult]);
64
- const capsule = context.capsuleNetwork.graphCapsules.getCapsule(compResult.componentId);
65
- if (!capsule) {
66
- throw new Error(`unable to find ${compResult.componentId.toString()} in capsules`);
67
- }
68
- await fs.writeFile(join(capsule.path, JUNIT_PATH), junit);
69
- await this.writeNpmIgnore(capsule.path);
70
- })
71
- );
72
-
73
- return {
74
- artifacts: [
75
- {
76
- name: 'junit',
77
- globPatterns: [JUNIT_PATH],
78
- },
79
- ], // @ts-ignore
80
- componentsResults: testsResults.components.map((componentTests) => {
81
- const componentErrors = componentTests.results?.testFiles.reduce((errors: string[], file) => {
82
- if (file?.error?.failureMessage) {
83
- errors.push(file.error.failureMessage);
84
- }
85
- file.tests.forEach((test) => {
86
- if (test.error) errors.push(test.error);
87
- if (test.failure) errors.push(test.failure);
88
- });
89
-
90
- return errors;
91
- }, []);
92
- const component = context.capsuleNetwork.graphCapsules.getCapsule(componentTests.componentId)?.component;
93
- if (!component) {
94
- throw new Error(`unable to find ${componentTests.componentId.toString()} in capsules`);
95
- }
96
- return {
97
- component,
98
- metadata: { tests: componentTests.results },
99
- errors: componentErrors,
100
- };
101
- }),
102
- };
103
- }
104
-
105
- private async writeNpmIgnore(dir: string) {
106
- const NPM_IGNORE_FILE = '.npmignore';
107
- const npmIgnorePath = join(dir, NPM_IGNORE_FILE);
108
- const npmIgnoreEntriesStr = `\n${JUNIT_PATH}\n`;
109
- await fs.appendFile(npmIgnorePath, npmIgnoreEntriesStr);
110
- }
111
- }
package/tester.ts DELETED
@@ -1,126 +0,0 @@
1
- import { Component, ComponentID, ComponentMap } from '@teambit/component';
2
- import { ExecutionContext } from '@teambit/envs';
3
- import { AbstractVinyl } from '@teambit/legacy/dist/consumer/component/sources';
4
- import { TestsResult } from '@teambit/tests-results';
5
-
6
- export type Tests = {
7
- components: ComponentsResults[];
8
- errors?: Error[];
9
- };
10
-
11
- export type ComponentsResults = {
12
- componentId: ComponentID;
13
- results?: TestsResult;
14
- loading?: boolean;
15
- };
16
-
17
- export type SpecFiles = ComponentMap<AbstractVinyl[]>;
18
- export type ComponentPatternsMap = ComponentMap<{ path: string; relative: string }[]>;
19
-
20
- export interface TesterContext extends ExecutionContext {
21
- /**
22
- * whether the tester run for release (during bit build/tag) or not (during bit test command).
23
- */
24
- release: boolean;
25
-
26
- /**
27
- * list of components to test.
28
- */
29
- components: Component[];
30
-
31
- /**
32
- * component workspace.
33
- */
34
- // workspace: Workspace;
35
-
36
- /**
37
- * defines whether tester is expected to run in quiet mode.
38
- */
39
- quiet?: boolean;
40
-
41
- /**
42
- * list of spec files to test.
43
- */
44
- specFiles: SpecFiles;
45
-
46
- /**
47
- * rootPath of the component workspace or the capsule root dir (during build).
48
- */
49
- rootPath: string;
50
-
51
- /**
52
- * determines whether tester is expected to run in debug mode.
53
- */
54
- debug?: boolean;
55
-
56
- /**
57
- * is start from ui
58
- */
59
- ui?: boolean;
60
-
61
- /**
62
- * determines whether to start the tester in watch mode.
63
- */
64
- watch?: boolean;
65
-
66
- /**
67
- * whether the tester should show code coverage
68
- */
69
- coverage?: boolean;
70
-
71
- /**
72
- * array of patterns to test.
73
- */
74
- patterns: ComponentPatternsMap;
75
- }
76
-
77
- /**
78
- * tester interface allows extensions to implement a component tester into bit.
79
- */
80
- export interface Tester {
81
- /**
82
- * display name of the tester.
83
- */
84
- displayName?: string;
85
-
86
- /**
87
- * icon of the tester.
88
- */
89
- icon?: string;
90
-
91
- /**
92
- * serialized config of the tester.
93
- */
94
- displayConfig?(): string;
95
-
96
- /**
97
- * path to the config in the filesystem.
98
- */
99
- configPath?: string;
100
-
101
- /**
102
- * id of the tester.
103
- */
104
- id: string;
105
-
106
- /**
107
- * on test run complete. (applies only during watch)
108
- * @param callback
109
- */
110
- onTestRunComplete?(callback: CallbackFn): Promise<void>;
111
-
112
- /**
113
- * execute tests on all components in the given execution context.
114
- */
115
- test(context: TesterContext): Promise<Tests>;
116
-
117
- /**
118
- * watch tests on all components
119
- */
120
- watch?(context: TesterContext): Promise<Tests>;
121
- /**
122
- * return the tester version.
123
- */
124
- version(): string;
125
- }
126
- export type CallbackFn = (testSuite: Tests) => void;
@@ -1,42 +0,0 @@
1
- import { ComponentType } from 'react';
2
- import { UIRuntime } from '@teambit/ui';
3
- import { SlotRegistry, Slot } from '@teambit/harmony';
4
- import { ComponentAspect, ComponentUI } from '@teambit/component';
5
- import { TestsSection } from './tests.section';
6
- import { TesterAspect } from './tester.aspect';
7
-
8
- export type EmptyStateSlot = SlotRegistry<ComponentType>;
9
- export class TesterUI {
10
- static dependencies = [ComponentAspect];
11
-
12
- static runtime = UIRuntime;
13
-
14
- stageKey?: string;
15
-
16
- constructor(private component: ComponentUI, private emptyStateSlot: EmptyStateSlot) {}
17
-
18
- /**
19
- * register a new tester empty state. this allows to register a different empty state from each environment for example.
20
- */
21
- registerEmptyState(emptyStateComponent: ComponentType) {
22
- this.emptyStateSlot.register(emptyStateComponent);
23
- return this;
24
- }
25
-
26
- static slots = [Slot.withType<ComponentType>()];
27
-
28
- static async provider([component]: [ComponentUI], config, [emptyStateSlot]: [EmptyStateSlot]) {
29
- const testerUi = new TesterUI(component, emptyStateSlot);
30
-
31
- const section = new TestsSection(emptyStateSlot);
32
-
33
- component.registerRoute(section.route);
34
- component.registerNavigation(section.navigationLink, section.order);
35
-
36
- return testerUi;
37
- }
38
- }
39
-
40
- export default TesterUI;
41
-
42
- TesterAspect.addRuntime(TesterUI);
package/tests.section.tsx DELETED
@@ -1,18 +0,0 @@
1
- import React from 'react';
2
- import { Section } from '@teambit/component';
3
- import { TestsPage } from './ui/tests-page';
4
- import { EmptyStateSlot } from './tester.ui.runtime';
5
-
6
- export class TestsSection implements Section {
7
- constructor(private emptyStateSlot: EmptyStateSlot) {}
8
-
9
- route = {
10
- path: '~tests',
11
- children: <TestsPage emptyState={this.emptyStateSlot} />,
12
- };
13
- navigationLink = {
14
- href: '~tests',
15
- children: 'Tests',
16
- };
17
- order = 40;
18
- }
package/tsconfig.json DELETED
@@ -1,34 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "lib": [
4
- "es2019",
5
- "DOM",
6
- "ES6",
7
- "DOM.Iterable",
8
- "ScriptHost"
9
- ],
10
- "target": "es2015",
11
- "module": "commonjs",
12
- "jsx": "react",
13
- "declaration": true,
14
- "sourceMap": true,
15
- "skipLibCheck": true,
16
- "moduleResolution": "node",
17
- "esModuleInterop": true,
18
- "outDir": "dist",
19
- "composite": true,
20
- "emitDeclarationOnly": true,
21
- "experimentalDecorators": true,
22
- "emitDecoratorMetadata": true,
23
- "allowSyntheticDefaultImports": true,
24
- "strictPropertyInitialization": false,
25
- "strict": true,
26
- "noImplicitAny": false,
27
- "rootDir": ".",
28
- "preserveConstEnums": true,
29
- "resolveJsonModule": true
30
- },
31
- "exclude": [
32
- "dist"
33
- ]
34
- }
package/types/asset.d.ts DELETED
@@ -1,29 +0,0 @@
1
- declare module '*.png' {
2
- const value: any;
3
- export = value;
4
- }
5
- declare module '*.svg' {
6
- import type { FunctionComponent, SVGProps } from 'react';
7
-
8
- export const ReactComponent: FunctionComponent<SVGProps<SVGSVGElement> & { title?: string }>;
9
- const src: string;
10
- export default src;
11
- }
12
-
13
- // @TODO Gilad
14
- declare module '*.jpg' {
15
- const value: any;
16
- export = value;
17
- }
18
- declare module '*.jpeg' {
19
- const value: any;
20
- export = value;
21
- }
22
- declare module '*.gif' {
23
- const value: any;
24
- export = value;
25
- }
26
- declare module '*.bmp' {
27
- const value: any;
28
- export = value;
29
- }
package/types/style.d.ts DELETED
@@ -1,42 +0,0 @@
1
- declare module '*.module.css' {
2
- const classes: { readonly [key: string]: string };
3
- export default classes;
4
- }
5
- declare module '*.module.scss' {
6
- const classes: { readonly [key: string]: string };
7
- export default classes;
8
- }
9
- declare module '*.module.sass' {
10
- const classes: { readonly [key: string]: string };
11
- export default classes;
12
- }
13
-
14
- declare module '*.module.less' {
15
- const classes: { readonly [key: string]: string };
16
- export default classes;
17
- }
18
-
19
- declare module '*.less' {
20
- const classes: { readonly [key: string]: string };
21
- export default classes;
22
- }
23
-
24
- declare module '*.css' {
25
- const classes: { readonly [key: string]: string };
26
- export default classes;
27
- }
28
-
29
- declare module '*.sass' {
30
- const classes: { readonly [key: string]: string };
31
- export default classes;
32
- }
33
-
34
- declare module '*.scss' {
35
- const classes: { readonly [key: string]: string };
36
- export default classes;
37
- }
38
-
39
- declare module '*.mdx' {
40
- const component: any;
41
- export default component;
42
- }
@@ -1,20 +0,0 @@
1
- .testsPage {
2
- padding: 50px 40px 100px 40px;
3
- width: 100%;
4
- overflow-y: auto;
5
- max-width: 1440px;
6
- margin: 0 auto;
7
- box-sizing: border-box;
8
-
9
- .title {
10
- margin-bottom: 24px;
11
- font-size: var(--bit-h-xs, 26px);
12
- }
13
- .separator {
14
- margin-bottom: 41px;
15
- }
16
- }
17
-
18
- .testBlock {
19
- margin-bottom: 50px;
20
- }
package/ui/tests-page.tsx DELETED
@@ -1,134 +0,0 @@
1
- import { useQuery, useSubscription, gql } from '@apollo/client';
2
- import { ComponentContext } from '@teambit/component';
3
- import { H1 } from '@teambit/documenter.ui.heading';
4
- import { Separator } from '@teambit/design.ui.separator';
5
- import { EmptyBox } from '@teambit/design.ui.empty-box';
6
- import { MDXLayout } from '@teambit/mdx.ui.mdx-layout';
7
- import { AlertCard } from '@teambit/design.ui.alert-card';
8
- import { TestLoader } from '@teambit/defender.ui.test-loader';
9
- import classNames from 'classnames';
10
- import React, { HTMLAttributes, useContext } from 'react';
11
- import { TestTable } from '@teambit/defender.ui.test-table';
12
- import { EmptyStateSlot } from '../tester.ui.runtime';
13
- import styles from './tests-page.module.scss';
14
-
15
- const TESTS_SUBSCRIPTION_CHANGED = gql`
16
- subscription OnTestsChanged($id: String!) {
17
- testsChanged(id: $id) {
18
- testsResults {
19
- testFiles {
20
- file
21
- duration
22
- pass
23
- failed
24
- pending
25
- error {
26
- failureMessage
27
- }
28
- tests {
29
- ancestor
30
- duration
31
- status
32
- name
33
- error
34
- }
35
- }
36
- }
37
- }
38
- }
39
- `;
40
-
41
- const GET_COMPONENT = gql`
42
- query($id: String!) {
43
- getHost {
44
- id # for GQL caching
45
- getTests(id: $id) {
46
- loading
47
- testsResults {
48
- testFiles {
49
- file
50
- duration
51
- pass
52
- failed
53
- pending
54
- error {
55
- failureMessage
56
- }
57
- tests {
58
- ancestor
59
- duration
60
- status
61
- name
62
- error
63
- }
64
- }
65
- }
66
- }
67
- }
68
- }
69
- `;
70
-
71
- type TestsPageProps = {
72
- emptyState: EmptyStateSlot;
73
- } & HTMLAttributes<HTMLDivElement>;
74
-
75
- export function TestsPage({ className, emptyState }: TestsPageProps) {
76
- const component = useContext(ComponentContext);
77
- const onTestsChanged = useSubscription(TESTS_SUBSCRIPTION_CHANGED, { variables: { id: component.id.toString() } });
78
- const { data } = useQuery(GET_COMPONENT, {
79
- variables: { id: component.id._legacy.name },
80
- });
81
-
82
- const testData = onTestsChanged.data?.testsChanged || data?.getHost?.getTests;
83
- const testResults = testData?.testsResults?.testFiles;
84
-
85
- // TODO: change loading EmptyBox
86
- if (testData?.loading) return <TestLoader />;
87
-
88
- const env = component.environment?.id;
89
- const EmptyStateTemplate = emptyState.get(env || '');
90
-
91
- if (
92
- (testResults === null || testData?.testsResults === null) &&
93
- component.host === 'teambit.workspace/workspace' &&
94
- EmptyStateTemplate
95
- ) {
96
- return (
97
- <div className={classNames(styles.testsPage, className)}>
98
- <div>
99
- <H1 className={styles.title}>Tests</H1>
100
- <Separator isPresentational className={styles.separator} />
101
- <AlertCard
102
- level="info"
103
- title="There are no
104
- tests for this Component. Learn how to add tests:"
105
- >
106
- <MDXLayout>
107
- <EmptyStateTemplate />
108
- </MDXLayout>
109
- </AlertCard>
110
- </div>
111
- </div>
112
- );
113
- }
114
-
115
- if (testResults === null || testData?.testsResults === null) {
116
- return (
117
- <EmptyBox
118
- title="This component doesn’t have any tests."
119
- linkText="Learn how to add tests to your components"
120
- link="https://harmony-docs.bit.dev/testing/overview/"
121
- />
122
- );
123
- }
124
-
125
- return (
126
- <div className={classNames(styles.testsPage, className)}>
127
- <div>
128
- <H1 className={styles.title}>Tests</H1>
129
- <Separator isPresentational className={styles.separator} />
130
- <TestTable testResults={testResults} className={styles.testBlock} />
131
- </div>
132
- </div>
133
- );
134
- }
@@ -1,12 +0,0 @@
1
- import { Component } from '@teambit/component';
2
- import { DevFilesMain } from '@teambit/dev-files';
3
- import { TesterAspect } from '../tester.aspect';
4
-
5
- /**
6
- * detect test files in components
7
- */
8
- export function detectTestFiles(component: Component, devFiles: DevFilesMain) {
9
- const files = devFiles.getDevFiles(component);
10
- const testFiles = files.get(TesterAspect.id);
11
- return component.state.filesystem.files.filter((file) => testFiles.includes(file.relative));
12
- }
package/utils/index.ts DELETED
@@ -1 +0,0 @@
1
- export { detectTestFiles } from './detect-spec-files';
@@ -1,35 +0,0 @@
1
- import junitReportBuilder from 'junit-report-builder';
2
- import stripAnsi from 'strip-ansi';
3
- import { ComponentsResults } from '../tester';
4
-
5
- export function testsResultsToJUnitFormat(components: ComponentsResults[]): string {
6
- const builder = junitReportBuilder.newBuilder();
7
- components.forEach((compResult) => {
8
- const suite = builder.testSuite().name(compResult.componentId.toString());
9
- if (compResult.results?.start) {
10
- suite.timestamp(new Date(compResult.results?.start).toISOString());
11
- }
12
- compResult.results?.testFiles.forEach((testFile) => {
13
- if (testFile.error?.error) {
14
- const testCase = suite.testCase().className(testFile.file).name(testFile.file);
15
- testCase.error(stripAnsi(testFile.error.error as string));
16
- }
17
- testFile.tests.forEach((test) => {
18
- const testCase = suite.testCase().className(testFile.file).name(test.name);
19
- if (test.error) {
20
- testCase.error(stripAnsi(test.error));
21
- }
22
- if (test.failure) {
23
- testCase.failure(stripAnsi(test.failure));
24
- }
25
- if (test.status === 'skipped' || test.status === 'pending') {
26
- testCase.skipped();
27
- }
28
- if (test.duration) {
29
- testCase.time(test.duration / 1000);
30
- }
31
- });
32
- });
33
- });
34
- return builder.build();
35
- }