@servicetitan/startup 22.14.0 → 22.16.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.
Files changed (112) hide show
  1. package/dist/cli/__mocks__/create-package.d.ts +3 -0
  2. package/dist/cli/__mocks__/create-package.d.ts.map +1 -0
  3. package/dist/cli/__mocks__/create-package.js +9 -0
  4. package/dist/cli/__mocks__/create-package.js.map +1 -0
  5. package/dist/cli/__mocks__/index.d.ts +2 -0
  6. package/dist/cli/__mocks__/index.d.ts.map +1 -0
  7. package/dist/cli/__mocks__/index.js +18 -0
  8. package/dist/cli/__mocks__/index.js.map +1 -0
  9. package/dist/cli/commands/eslint.d.ts +12 -0
  10. package/dist/cli/commands/eslint.d.ts.map +1 -0
  11. package/dist/cli/commands/eslint.js +47 -0
  12. package/dist/cli/commands/eslint.js.map +1 -0
  13. package/dist/cli/commands/index.d.ts +1 -0
  14. package/dist/cli/commands/index.d.ts.map +1 -1
  15. package/dist/cli/commands/index.js +1 -0
  16. package/dist/cli/commands/index.js.map +1 -1
  17. package/dist/cli/commands/lint.d.ts +5 -0
  18. package/dist/cli/commands/lint.d.ts.map +1 -1
  19. package/dist/cli/commands/lint.js +22 -30
  20. package/dist/cli/commands/lint.js.map +1 -1
  21. package/dist/cli/index.js +20 -6
  22. package/dist/cli/index.js.map +1 -1
  23. package/dist/cli/utils/eslint.d.ts +7 -0
  24. package/dist/cli/utils/eslint.d.ts.map +1 -0
  25. package/dist/cli/utils/eslint.js +57 -0
  26. package/dist/cli/utils/eslint.js.map +1 -0
  27. package/dist/cli/utils/index.d.ts +2 -0
  28. package/dist/cli/utils/index.d.ts.map +1 -1
  29. package/dist/cli/utils/index.js +2 -0
  30. package/dist/cli/utils/index.js.map +1 -1
  31. package/dist/cli/utils/set-node-options.d.ts +7 -0
  32. package/dist/cli/utils/set-node-options.d.ts.map +1 -0
  33. package/dist/cli/utils/set-node-options.js +46 -0
  34. package/dist/cli/utils/set-node-options.js.map +1 -0
  35. package/dist/cli/utils/tcm.d.ts.map +1 -1
  36. package/dist/cli/utils/tcm.js +3 -1
  37. package/dist/cli/utils/tcm.js.map +1 -1
  38. package/dist/utils/get-configuration.d.ts +5 -1
  39. package/dist/utils/get-configuration.d.ts.map +1 -1
  40. package/dist/utils/get-configuration.js +2 -2
  41. package/dist/utils/get-configuration.js.map +1 -1
  42. package/dist/utils/get-jest-config.d.ts +1 -1
  43. package/dist/utils/get-jest-config.d.ts.map +1 -1
  44. package/dist/utils/get-jest-config.js +8 -6
  45. package/dist/utils/get-jest-config.js.map +1 -1
  46. package/dist/utils/get-package-data.js +3 -3
  47. package/dist/utils/get-package-data.js.map +1 -1
  48. package/dist/utils/get-package-name.d.ts.map +1 -1
  49. package/dist/utils/get-package-name.js +2 -2
  50. package/dist/utils/get-package-name.js.map +1 -1
  51. package/dist/utils/get-packages.js +8 -7
  52. package/dist/utils/get-packages.js.map +1 -1
  53. package/dist/webpack/shared.config.d.ts.map +1 -1
  54. package/dist/webpack/shared.config.js +1 -0
  55. package/dist/webpack/shared.config.js.map +1 -1
  56. package/package.json +4 -4
  57. package/src/cli/__mocks__/create-package.ts +12 -0
  58. package/src/cli/__mocks__/index.ts +1 -0
  59. package/src/cli/commands/__tests__/build.test.ts +135 -0
  60. package/src/cli/commands/__tests__/bundle-package.test.ts +72 -0
  61. package/src/cli/commands/__tests__/eslint.test.ts +19 -0
  62. package/src/cli/commands/__tests__/init.test.ts +42 -0
  63. package/src/cli/commands/__tests__/lint.test.ts +114 -0
  64. package/src/cli/commands/__tests__/mfe-package-clean.test.ts +193 -0
  65. package/src/cli/commands/__tests__/mfe-package-publish.test.ts +192 -0
  66. package/src/cli/commands/__tests__/mfe-publish.test.ts +172 -0
  67. package/src/cli/commands/__tests__/prepare-package.test.ts +75 -0
  68. package/src/cli/commands/__tests__/start.test.ts +111 -0
  69. package/src/cli/commands/__tests__/styles-check.test.ts +119 -0
  70. package/src/cli/commands/__tests__/tests.test.ts +43 -0
  71. package/src/cli/commands/eslint.ts +19 -0
  72. package/src/cli/commands/index.ts +1 -0
  73. package/src/cli/commands/lint.ts +29 -42
  74. package/src/cli/index.ts +17 -5
  75. package/src/cli/utils/__tests__/assets-copy.test.ts +48 -52
  76. package/src/cli/utils/__tests__/bundle.test.ts +361 -0
  77. package/src/cli/utils/__tests__/cli-git.test.ts +28 -0
  78. package/src/cli/utils/__tests__/cli-npm.test.ts +166 -0
  79. package/src/cli/utils/__tests__/cli-os.test.ts +103 -0
  80. package/src/cli/utils/__tests__/eslint.test.ts +91 -0
  81. package/src/cli/utils/__tests__/get-module-type.test.ts +56 -0
  82. package/src/cli/utils/__tests__/is-module-installed.test.ts +24 -0
  83. package/src/cli/utils/__tests__/set-node-options.test.ts +131 -0
  84. package/src/cli/utils/__tests__/styles-copy.test.ts +48 -44
  85. package/src/cli/utils/__tests__/tcm.test.ts +101 -192
  86. package/src/cli/utils/__tests__/tsc.test.ts +85 -0
  87. package/src/cli/utils/eslint.ts +50 -0
  88. package/src/cli/utils/index.ts +2 -0
  89. package/src/cli/utils/set-node-options.ts +45 -0
  90. package/src/cli/utils/tcm.ts +3 -1
  91. package/src/utils/__tests__/get-configuration.test.ts +215 -16
  92. package/src/utils/__tests__/get-destination-folders.test.ts +65 -0
  93. package/src/utils/__tests__/get-jest-config.test.ts +134 -0
  94. package/src/utils/__tests__/get-package-data.test.ts +90 -0
  95. package/src/utils/__tests__/get-packages.test.ts +165 -0
  96. package/src/utils/__tests__/get-tsconfig.test.ts +48 -0
  97. package/src/utils/__tests__/log.test.ts +123 -0
  98. package/src/utils/__tests__/to-array.test.ts +19 -0
  99. package/src/utils/get-configuration.ts +8 -3
  100. package/src/utils/get-jest-config.ts +3 -1
  101. package/src/utils/get-package-data.ts +1 -1
  102. package/src/utils/get-package-name.ts +1 -2
  103. package/src/utils/get-packages.ts +2 -2
  104. package/src/webpack/shared.config.ts +1 -0
  105. package/template/package.json +8 -1
  106. package/template/packages/application/package.json +5 -0
  107. package/template/packages/application/src/__tests__/app.test.tsx +33 -0
  108. package/template/packages/application/src/app.tsx +9 -6
  109. package/template/packages/application/src/main-page.tsx +5 -0
  110. package/template/packages/application/src/second-page.tsx +5 -0
  111. package/template/setupTests.ts +27 -0
  112. package/template/tsconfig.test.json +1 -1
@@ -0,0 +1,165 @@
1
+ import execa from 'execa';
2
+ import { isBundle, isLegacy } from '../get-configuration';
3
+
4
+ import {
5
+ Package,
6
+ PackageType,
7
+ getPackages,
8
+ getPackagesGraph,
9
+ splitPackagesByType,
10
+ } from '../get-packages';
11
+
12
+ jest.mock('execa', () => ({ sync: jest.fn() }));
13
+ jest.mock('../get-configuration');
14
+
15
+ describe('[startup] Utils', () => {
16
+ const packages: Pick<Package, 'name' | 'location' | 'type'>[] = [
17
+ { name: 'foo', location: 'packages/foo', type: PackageType.Legacy },
18
+ { name: 'bar', location: 'packages/bar', type: PackageType.TSC },
19
+ { name: 'baz', location: 'packages/baz', type: PackageType.Webpack },
20
+ ];
21
+
22
+ describe(`${getPackages.name}`, () => {
23
+ let options: Parameters<typeof getPackages>[0] | undefined;
24
+ let dependencies: Record<string, string[]> | undefined;
25
+
26
+ function findPackageByLocation(location?: string) {
27
+ return packages.find(p => p.location === location);
28
+ }
29
+
30
+ function packageGraph() {
31
+ return dependencies ?? Object.fromEntries(packages.map(({ name }) => [name, []]));
32
+ }
33
+
34
+ function scopedPackages() {
35
+ let result = packages;
36
+ if (options?.scope) {
37
+ const arrayScope = Array.isArray(options.scope) ? options.scope : [options.scope];
38
+ result = result.filter(({ name }) => arrayScope.includes(name));
39
+ }
40
+ return result;
41
+ }
42
+
43
+ beforeEach(() => {
44
+ options = undefined;
45
+ dependencies = undefined;
46
+
47
+ jest.mocked(isBundle).mockImplementation(
48
+ location => findPackageByLocation(location)?.type === PackageType.Webpack
49
+ );
50
+ jest.mocked(isLegacy).mockImplementation(
51
+ location => findPackageByLocation(location)?.type === PackageType.Legacy
52
+ );
53
+ // @ts-expect-error because implementation doesn't match all exec.sync signatures
54
+ jest.mocked(execa.sync).mockImplementation((_: string, args: string[]): any => {
55
+ return {
56
+ stdout: JSON.stringify(
57
+ args.includes('--graph')
58
+ ? packageGraph()
59
+ : args.includes('--scope')
60
+ ? scopedPackages().map(({ type, ...pkg }) => pkg)
61
+ : packages.map(({ type, ...pkg }) => pkg)
62
+ ),
63
+ };
64
+ });
65
+ });
66
+
67
+ const subject = () => getPackages(options);
68
+
69
+ test('returns lerna packages with type metadata', () => {
70
+ expect(subject()).toEqual(packages);
71
+ expect(execa.sync).toHaveBeenCalledWith('npx', 'lerna la --json'.split(' '));
72
+ });
73
+
74
+ describe.each(['scope', 'ignore'])('with "%s"', option => {
75
+ beforeEach(() => (options = { [option]: 'foo' }));
76
+
77
+ test(`passes "${option}" option to lerna`, () => {
78
+ subject();
79
+
80
+ expect(execa.sync).toHaveBeenCalledWith(
81
+ 'npx',
82
+ `lerna la --${option} foo --json`.split(' ')
83
+ );
84
+ });
85
+ });
86
+
87
+ describe('with "scope"', () => {
88
+ beforeEach(() => (options = { scope: 'foo' }));
89
+
90
+ test('returns filtered packages', () => {
91
+ expect(subject()).toEqual(packages.filter(({ name }) => name === 'foo'));
92
+ });
93
+
94
+ describe('when filtered package has dependencies', () => {
95
+ beforeEach(() => (dependencies = { ...packageGraph(), foo: ['bar', 'external'] }));
96
+
97
+ test('also returns internal dependencies', () => {
98
+ expect(subject()).toEqual(
99
+ packages.filter(({ name }) => ['foo', 'bar'].includes(name))
100
+ );
101
+ });
102
+ });
103
+ });
104
+ });
105
+
106
+ describe(`${getPackagesGraph.name}`, () => {
107
+ const packageGraph = Object.fromEntries(packages.map(({ name }) => [name, []]));
108
+ let options: Parameters<typeof getPackagesGraph>[0] | undefined;
109
+
110
+ beforeEach(() => {
111
+ options = undefined;
112
+ jest.mocked(execa.sync).mockImplementation((): any => ({
113
+ stdout: JSON.stringify(packageGraph),
114
+ }));
115
+ });
116
+
117
+ const subject = () => getPackagesGraph(options);
118
+
119
+ test('returns lerna package graph', () => {
120
+ expect(subject()).toEqual(packageGraph);
121
+ expect(execa.sync).toHaveBeenCalledWith('npx', 'lerna la --graph'.split(' '));
122
+ });
123
+
124
+ describe.each(['scope', 'ignore'])('with "%s"', option => {
125
+ beforeEach(() => (options = { [option]: 'foo' }));
126
+
127
+ test(`passes "${option}" option to lerna`, () => {
128
+ subject();
129
+
130
+ expect(execa.sync).toHaveBeenCalledWith(
131
+ 'npx',
132
+ `lerna la --${option} foo --graph`.split(' ')
133
+ );
134
+ });
135
+ });
136
+ });
137
+
138
+ describe(`${splitPackagesByType.name}`, () => {
139
+ const subject = (packages: Package[]) => splitPackagesByType(packages as Package[]);
140
+
141
+ test('groups packages by type', () => {
142
+ expect(subject(packages as Package[])).toEqual(
143
+ packages.reduce<ReturnType<typeof splitPackagesByType>>(
144
+ (result, pkg) => {
145
+ result[pkg.type].push(pkg as Package);
146
+ return result;
147
+ },
148
+ {
149
+ [PackageType.TSC]: [],
150
+ [PackageType.Webpack]: [],
151
+ [PackageType.Legacy]: [],
152
+ }
153
+ )
154
+ );
155
+ });
156
+
157
+ test('grouping always includes all types', () => {
158
+ expect(subject([])).toEqual({
159
+ [PackageType.TSC]: [],
160
+ [PackageType.Webpack]: [],
161
+ [PackageType.Legacy]: [],
162
+ });
163
+ });
164
+ });
165
+ });
@@ -0,0 +1,48 @@
1
+ import mockFS from 'mock-fs';
2
+ import path from 'path';
3
+
4
+ import { getTsConfig } from '../get-tsconfig';
5
+
6
+ describe(`[startup] Utils`, () => {
7
+ describe(`${getTsConfig.name}`, () => {
8
+ const defaultConfig = 'tsconfig.json';
9
+ const buildConfig = 'tsconfig.build.json';
10
+ let location: string | undefined;
11
+
12
+ const subject = () => getTsConfig(location);
13
+
14
+ beforeEach(() => (location = undefined));
15
+
16
+ afterEach(() => mockFS.restore());
17
+
18
+ test(`returns ${defaultConfig}`, () => {
19
+ expect(subject()).toBe(defaultConfig);
20
+ });
21
+
22
+ describe(`when ${buildConfig} is present`, () => {
23
+ beforeEach(() => mockFS({ [buildConfig]: JSON.stringify({}) }));
24
+
25
+ test(`returns ${buildConfig}`, () => {
26
+ expect(subject()).toBe(buildConfig);
27
+ });
28
+ });
29
+
30
+ describe('when passed a location', () => {
31
+ beforeEach(() => (location = 'packages/foo'));
32
+
33
+ test(`returns {location}/${defaultConfig}`, () => {
34
+ expect(subject()).toBe(path.join(location!, defaultConfig));
35
+ });
36
+
37
+ describe(`when ${buildConfig} is present`, () => {
38
+ beforeEach(() => {
39
+ mockFS({ packages: { foo: { [buildConfig]: JSON.stringify({}) } } });
40
+ });
41
+
42
+ test(`returns {location}/${buildConfig}`, () => {
43
+ expect(subject()).toBe(path.join(location!, buildConfig));
44
+ });
45
+ });
46
+ });
47
+ });
48
+ });
@@ -0,0 +1,123 @@
1
+ import chalk from 'chalk';
2
+ import { log, logErrors } from '../log';
3
+
4
+ describe(`[startup] Utils`, () => {
5
+ describe('log', () => {
6
+ const message = 'foo';
7
+ let stdoutSpy: jest.SpyInstance;
8
+
9
+ beforeEach(() => {
10
+ jest.resetAllMocks();
11
+ stdoutSpy = jest.spyOn(process.stdout, 'write').mockImplementation(jest.fn());
12
+ });
13
+
14
+ test('info() writes cyan text', () => {
15
+ log.info(message);
16
+
17
+ expect(stdoutSpy).toHaveBeenCalledWith(`${chalk.bold.cyan(message)}\n`);
18
+ });
19
+
20
+ test('success() writes green text', () => {
21
+ log.success(message);
22
+
23
+ expect(stdoutSpy).toHaveBeenCalledWith(`${chalk.bold.green(message)}\n`);
24
+ });
25
+
26
+ test('warning() writes orange text', () => {
27
+ log.warning(message);
28
+
29
+ expect(stdoutSpy).toHaveBeenCalledWith(`${chalk.bold.keyword('orange')(message)}\n`);
30
+ });
31
+
32
+ test('log error writes red text', () => {
33
+ log.error(message);
34
+
35
+ expect(stdoutSpy).toHaveBeenCalledWith(`${chalk.bold.red(message)}\n`);
36
+ });
37
+ });
38
+
39
+ describe(`${logErrors.name}`, () => {
40
+ const error = new Error('Oops!');
41
+
42
+ class SubjectClass {
43
+ // eslint-disable-next-line @typescript-eslint/naming-convention
44
+ private _foo: boolean;
45
+
46
+ constructor() {
47
+ this._foo = false;
48
+ }
49
+
50
+ @logErrors
51
+ execute(callback: Function) {
52
+ return callback();
53
+ }
54
+
55
+ @logErrors
56
+ async executeAsync(callback: Function) {
57
+ await callback();
58
+ }
59
+
60
+ @logErrors
61
+ get foo() {
62
+ throw error;
63
+ }
64
+
65
+ set foo(value: boolean) {
66
+ this._foo = value;
67
+ }
68
+ }
69
+
70
+ const instance = new SubjectClass();
71
+
72
+ beforeEach(() => {
73
+ jest.resetAllMocks();
74
+ jest.spyOn(log, 'error').mockImplementation(jest.fn());
75
+ });
76
+
77
+ describe('when decorated function raises no error', () => {
78
+ const subject = () => instance.execute(jest.fn());
79
+
80
+ test('logs nothing', () => {
81
+ subject();
82
+
83
+ expect(log.error).not.toHaveBeenCalled();
84
+ });
85
+ });
86
+
87
+ describe('when decorated function throws error', () => {
88
+ const subject = () =>
89
+ instance.execute(() => {
90
+ throw error;
91
+ });
92
+
93
+ test('logs error', () => {
94
+ expect(subject).toThrowError(error);
95
+
96
+ expect(log.error).toHaveBeenCalledWith(String(error));
97
+ });
98
+ });
99
+
100
+ describe('when decorated asynchronous function throws error', () => {
101
+ const subject = async () =>
102
+ instance.executeAsync(() => {
103
+ throw error;
104
+ });
105
+
106
+ test('logs error', async () => {
107
+ await expect(subject).rejects.toThrowError(error);
108
+
109
+ expect(log.error).toHaveBeenCalledWith(String(error));
110
+ });
111
+ });
112
+
113
+ describe('when decorated accessor throws error', () => {
114
+ const subject = () => instance.foo;
115
+
116
+ test('logs nothing', () => {
117
+ expect(subject).toThrowError(error);
118
+
119
+ expect(log.error).not.toHaveBeenCalled();
120
+ });
121
+ });
122
+ });
123
+ });
@@ -0,0 +1,19 @@
1
+ import { toArray } from '../to-array';
2
+
3
+ describe(`[startup] Utils`, () => {
4
+ describe(`${toArray.name}`, () => {
5
+ const subject = (value: any) => toArray(value);
6
+
7
+ test('when value is an Array, returns the value ', () => {
8
+ [[], [''], [null]].forEach(value => expect(subject(value)).toBe(value));
9
+ });
10
+
11
+ test('when value is not an Array, wraps value within array', () => {
12
+ ['', null, 'foo', {}].forEach(value => expect(subject(value)).toEqual([value]));
13
+ });
14
+
15
+ test('when value us undefined, returns []', () => {
16
+ expect(subject(undefined)).toEqual([]);
17
+ });
18
+ });
19
+ });
@@ -5,8 +5,7 @@ import { Configuration as WebpackDevServerConfiguration } from 'webpack-dev-serv
5
5
  import { ESLint } from 'eslint';
6
6
  import { LinterOptions } from 'stylelint';
7
7
  import { Config } from '@jest/types';
8
-
9
- import { readJson } from '.';
8
+ import { readJson } from './read-json';
10
9
 
11
10
  const allowedWebpackDevServerOptions = ['headers', 'port', 'proxy', 'contentBase'] as const;
12
11
 
@@ -34,7 +33,13 @@ export interface StylelintConfiguration extends Partial<LinterOptions> {
34
33
 
35
34
  export type JestConfiguration = Omit<Config.Argv, '_' | '$0'>;
36
35
 
37
- interface Configuration {
36
+ export interface NodeConfiguration {
37
+ // eslint-disable-next-line @typescript-eslint/naming-convention
38
+ NODE_OPTIONS?: string[];
39
+ }
40
+
41
+ export interface Configuration extends NodeConfiguration {
42
+ [key: string]: any;
38
43
  'webpack'?: false | WebpackConfiguration;
39
44
  'web-component'?: boolean;
40
45
  'legacy'?: boolean;
@@ -1,6 +1,8 @@
1
1
  import path from 'path';
2
2
  import { Config } from '@jest/types';
3
- import { getDestinationFolders, getJestConfiguration, JestConfiguration, toArray } from '.';
3
+ import { JestConfiguration, getJestConfiguration } from './get-configuration';
4
+ import { getDestinationFolders } from './get-destination-folders';
5
+ import { toArray } from './to-array';
4
6
 
5
7
  const getJestConfigBase = (
6
8
  stringify: boolean,
@@ -1,4 +1,4 @@
1
- import { readJson } from '.';
1
+ import { readJson } from './read-json';
2
2
 
3
3
  export function getPackageData(): {
4
4
  dependencies: Record<string, string>;
@@ -1,6 +1,5 @@
1
1
  import path from 'path';
2
-
3
- import { readJson } from '.';
2
+ import { readJson } from './read-json';
4
3
 
5
4
  export function getPackageName(location = './'): string {
6
5
  return readJson(path.join(location, 'package.json')).name;
@@ -1,6 +1,6 @@
1
1
  import execa from 'execa';
2
-
3
- import { isBundle, isLegacy, toArray } from '.';
2
+ import { isBundle, isLegacy } from './get-configuration';
3
+ import { toArray } from './to-array';
4
4
 
5
5
  export enum PackageType {
6
6
  TSC,
@@ -156,6 +156,7 @@ export function createConfig(
156
156
  '**/__tests__/**/*',
157
157
  '**/__mocks__/**/*',
158
158
  '**/*.test.*',
159
+ '**/*.stories.tsx',
159
160
  ],
160
161
  references: [],
161
162
  },
@@ -21,5 +21,12 @@
21
21
  },
22
22
  "workspaces": [
23
23
  "packages/*"
24
- ]
24
+ ],
25
+ "cli": {
26
+ "test": {
27
+ "setupFilesAfterEnv": [
28
+ "./setupTests.ts"
29
+ ]
30
+ }
31
+ }
25
32
  }
@@ -9,6 +9,7 @@
9
9
  "dependencies": {
10
10
  "@servicetitan/design-system": "^12.10.0",
11
11
  "@servicetitan/link-item": "^23.1.0",
12
+ "@servicetitan/react-ioc": "^22.15.0",
12
13
  "feature-a": "^0.0.0",
13
14
  "feature-b": "^0.0.0",
14
15
  "feature-c": "^0.0.0",
@@ -17,6 +18,10 @@
17
18
  "react-router-dom": "^5.3.0"
18
19
  },
19
20
  "devDependencies": {
21
+ "@servicetitan/testing-library": "^0.3.0",
22
+ "@testing-library/jest-dom": "^5.17.0",
23
+ "@testing-library/react": "^12.1.5",
24
+ "@testing-library/react-hooks": "^8.0.1",
20
25
  "@types/react": "^17.0.37",
21
26
  "@types/react-dom": "^17.0.11",
22
27
  "@types/react-router-dom": "^5.3.2"
@@ -0,0 +1,33 @@
1
+ import { mockComponent, mockLocation } from '@servicetitan/testing-library';
2
+ import { render, screen } from '@testing-library/react';
3
+
4
+ import { App } from '../app';
5
+
6
+ jest.mock('../main-page', () => mockComponent('MainPage'));
7
+ jest.mock('../second-page', () => mockComponent('SecondPage'));
8
+
9
+ describe(`${App.name}`, () => {
10
+ const subject = () => render(<App />);
11
+
12
+ test('renders sidebar', () => {
13
+ subject();
14
+
15
+ expect(screen.getByRole('link', { name: 'Main page' })).toBeInTheDocument();
16
+ expect(screen.getByRole('link', { name: 'Second page' })).toBeInTheDocument();
17
+ });
18
+
19
+ const routes = Object.entries({
20
+ MainPage: '/',
21
+ SecondPage: '/second-page',
22
+ }).map(([component, path]) => ({ component, path }));
23
+
24
+ describe.each(routes)('when location is $path', ({ component, path }) => {
25
+ beforeEach(() => mockLocation(path));
26
+
27
+ test(`renders <${component} />`, () => {
28
+ subject();
29
+
30
+ expect(screen).toContainComponent(component);
31
+ });
32
+ });
33
+ });
@@ -1,8 +1,10 @@
1
- import { StrictMode, Fragment, FC } from 'react';
1
+ import { StrictMode, FC } from 'react';
2
2
  import { BrowserRouter, Switch, Route } from 'react-router-dom';
3
3
  import { SideNav, Frame, Page, Sidebar } from '@servicetitan/design-system';
4
4
  import { SideNavLinkItem } from '@servicetitan/link-item';
5
5
 
6
+ import { SecondPage } from './second-page';
7
+ import { MainPage } from './main-page';
6
8
  import './design-system.css';
7
9
  import './app.css';
8
10
 
@@ -28,11 +30,12 @@ export const App: FC = () => (
28
30
  maxWidth="wide"
29
31
  >
30
32
  <Switch>
31
- <Route path="/" exact component={() => <Fragment>Main page</Fragment>} />
32
- <Route
33
- path="/second-page"
34
- component={() => <Fragment>Second page</Fragment>}
35
- />
33
+ <Route path="/" exact>
34
+ <MainPage />
35
+ </Route>
36
+ <Route path="/second-page">
37
+ <SecondPage />
38
+ </Route>
36
39
  </Switch>
37
40
  </Page>
38
41
  </Frame>
@@ -0,0 +1,5 @@
1
+ import { Fragment } from 'react';
2
+
3
+ export function MainPage() {
4
+ return <Fragment>Main page</Fragment>;
5
+ }
@@ -0,0 +1,5 @@
1
+ import { Fragment } from 'react';
2
+
3
+ export function SecondPage() {
4
+ return <Fragment>Second page</Fragment>;
5
+ }
@@ -0,0 +1,27 @@
1
+ import '@servicetitan/testing-library';
2
+ import '@testing-library/jest-dom';
3
+
4
+ Object.defineProperties(window, {
5
+ location: { value: window.location, writable: true, configurable: true },
6
+ matchMedia: {
7
+ value: jest.fn().mockImplementation(query => ({
8
+ matches: false,
9
+ media: query,
10
+ onChange: null,
11
+ addEventListener: jest.fn(),
12
+ removeEventListener: jest.fn(),
13
+ dispatchEvent: jest.fn(),
14
+ })),
15
+ writable: true,
16
+ configurable: true,
17
+ },
18
+ ResizeObserver: {
19
+ value: jest.fn().mockImplementation(() => ({
20
+ disconnect: jest.fn(),
21
+ observe: jest.fn(),
22
+ unobserve: jest.fn(),
23
+ })),
24
+ writable: true,
25
+ configurable: true,
26
+ },
27
+ });
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "extends": "@servicetitan/startup/tsconfig/base",
3
- "include": ["**/__tests__/**/*", "**/*.d.ts"],
3
+ "include": ["**/__tests__/**/*", "**/*.d.ts", "setupTests.ts"],
4
4
  "exclude": ["node_modules"]
5
5
  }