@elliemae/ds-monorepo-devops 3.60.0-next.5 → 3.60.0-next.50

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 (33) hide show
  1. package/bin/cli.mjs +18 -1
  2. package/bin/execGitReverseMerge/index.mjs +3 -3
  3. package/bin/execSyncNxTags/index.mjs +2 -2
  4. package/bin/execTest/index.mjs +148 -0
  5. package/bin/execute-commands-map.mjs +5 -0
  6. package/bin/forcePnpmCatalogVersions/index.mjs +2 -2
  7. package/bin/utils/CLI_COLORS.mjs +53 -0
  8. package/bin/utils/index.mjs +6 -0
  9. package/configs/jest/jest.config.mjs +4 -7
  10. package/configs/jest/noConsoleMode.mjs +1 -1
  11. package/configs/jest/testing/.swcrc +23 -0
  12. package/configs/jest/testing/jest-extended-dom.cjs +27 -0
  13. package/configs/jest/testing/jest.config.base.mjs +70 -0
  14. package/configs/jest/testing/jest.node.config.cjs +9 -0
  15. package/configs/jest/testing/mocks/axios.js +15 -0
  16. package/configs/jest/testing/mocks/cssModule.js +1 -0
  17. package/configs/jest/testing/mocks/frame.js +2 -0
  18. package/configs/jest/testing/mocks/html.js +1 -0
  19. package/configs/jest/testing/mocks/iframe.js +2 -0
  20. package/configs/jest/testing/mocks/image.js +1 -0
  21. package/configs/jest/testing/mocks/pui-app-loader.js +1 -0
  22. package/configs/jest/testing/mocks/pui-user-monitoring.js +3 -0
  23. package/configs/jest/testing/mocks/retry-axios.js +3 -0
  24. package/configs/jest/testing/mocks/svg.js +2 -0
  25. package/configs/jest/testing/mocks/webpack-hmr.js +1 -0
  26. package/configs/jest/testing/resolver.cjs +43 -0
  27. package/configs/jest/testing/setup-react-env.js +77 -0
  28. package/configs/jest/testing/setup-tests.js +64 -0
  29. package/configs/jest/testing/swcrc.config.cjs +11 -0
  30. package/configs/jest/testing/utils.cjs +30 -0
  31. package/configs/playwright/playwright-component-testing-config.mjs +2 -2
  32. package/package.json +47 -10
  33. package/configs/jest/virtualListFix.mjs +0 -21
package/bin/cli.mjs CHANGED
@@ -3,13 +3,30 @@ import { checkCommandOrInquire, executeCommandsMap } from './execute-commands-ma
3
3
 
4
4
  function parseArgumentsIntoOptions(rawArgs) {
5
5
  const args = arg(
6
- {},
6
+ {
7
+ '--passWithNoTests': Boolean,
8
+ '--silent': Boolean,
9
+ // the CI pipeline goes "runInBand"
10
+ // but it was never implemented and it doesn't work so we allow and ignore the flag
11
+ '--runInBand': Boolean, // ignored
12
+ '--coverage': String,
13
+ '--maxWorkers': String,
14
+ '--filterFnFilePath': String,
15
+ '--testPathPatterns': String,
16
+ },
7
17
  {
8
18
  argv: rawArgs.slice(2),
9
19
  },
10
20
  );
11
21
  return {
12
22
  command: args._[0],
23
+ runInBand: args['--runInBand'], // CLI flag, not available as a question and ignored.
24
+ passWithNoTests: args['--passWithNoTests'], // CLI flag, not available as a question.
25
+ silent: args['--silent'], // CLI flag, not available as a question.
26
+ coverage: args['--coverage'], // CLI flag, not available as a question.
27
+ maxWorkers: args['--maxWorkers'], // CLI flag, not available as a question.
28
+ filterFnFilePath: args['--filterFnFilePath'], // CLI flag, not available as a question.
29
+ testPathPatterns: args['--testPathPatterns'], // CLI flag, not available as a question.
13
30
  };
14
31
  }
15
32
 
@@ -1,11 +1,10 @@
1
1
  /* eslint-disable max-lines */
2
2
  /* eslint-disable max-len */
3
3
  /* eslint-disable complexity, no-console, max-statements */
4
- import defGlob from 'glob';
4
+ import { glob } from 'glob';
5
5
  import inquirer from 'inquirer';
6
6
  import { exec } from 'node:child_process';
7
7
  import fs from 'node:fs';
8
- const { glob } = defGlob;
9
8
 
10
9
  const execSyntaxSugar = async ({ command, dryRun = false, silentFail = false }) => {
11
10
  const noLFNorCRLFCmd = command.replace(/\r?\n|\r/g, '');
@@ -24,7 +23,7 @@ const execSyntaxSugar = async ({ command, dryRun = false, silentFail = false })
24
23
  });
25
24
  };
26
25
 
27
- export async function promptForCommandOptions() {
26
+ export async function promptForCommandOptions(options) {
28
27
  const preAnswears = await inquirer.prompt([
29
28
  {
30
29
  type: 'list',
@@ -72,6 +71,7 @@ export async function promptForCommandOptions() {
72
71
  const answers = await inquirer.prompt(questions);
73
72
 
74
73
  return {
74
+ ...options,
75
75
  ...answers,
76
76
  };
77
77
  }
@@ -1,9 +1,8 @@
1
1
  /* eslint-disable complexity, no-console, max-statements */
2
2
  import inquirer from 'inquirer';
3
- import defGlob from 'glob';
3
+ import { glob } from 'glob';
4
4
  import path from 'node:path';
5
5
  import fs from 'node:fs';
6
- const { glob } = defGlob;
7
6
 
8
7
  export async function promptForCommandOptions(options) {
9
8
  const questions = [];
@@ -19,6 +18,7 @@ export async function promptForCommandOptions(options) {
19
18
  const answers = await inquirer.prompt(questions);
20
19
 
21
20
  return {
21
+ ...options,
22
22
  ...answers,
23
23
  };
24
24
  }
@@ -0,0 +1,148 @@
1
+ /* eslint-disable max-len */
2
+ /* eslint-disable complexity, no-console, max-statements */
3
+ import inquirer from 'inquirer';
4
+ import { exec, getCIEnv, logError, logSuccess } from '../utils/index.mjs';
5
+
6
+ /**
7
+ * @typedef {object} commandOptions
8
+ * @property {boolean} [commandOptions.passWithNoTests=true] - Indicates whether to pass the test command even if no tests are found.
9
+ * @property {boolean} [commandOptions.silent=false] - Indicates whether to run the test command in silent mode.
10
+ * @property {string} [commandOptions.coverage='false'] - Indicates whether to collect coverage information.
11
+ * @property {string} [commandOptions.maxWorkers='50%'] - Maximum number of workers the test command can use.
12
+ * @property {string} [commandOptions.filterFnFilePath=''] - Path to a module exporting a filtering function.
13
+ * @property {string} [commandOptions.testPathPatterns=''] - A regexp pattern string that is matched against all tests paths before executing the test.
14
+ */
15
+
16
+ // we don't want this command to prompt for question, it is meant to be used in CI/CD or automated scripts
17
+ // only flags provided via command line arguments will be used.
18
+ export async function promptForCommandOptions(options) {
19
+ const questions = [];
20
+ // // flag bypasses this question
21
+ // if (!options.passWithNoTests) {
22
+ // questions.push({
23
+ // type: 'confirm',
24
+ // name: 'passWithNoTests',
25
+ // message: 'pass the test command even if no tests are found?',
26
+ // default: true,
27
+ // });
28
+ // }
29
+ // // --silent flag bypasses this question
30
+ // if (!options.silent) {
31
+ // questions.push({
32
+ // type: 'confirm',
33
+ // name: 'silent',
34
+ // message: 'run the test command in silent mode (no output unless error)?',
35
+ // default: false,
36
+ // });
37
+ // }
38
+ // // --coverage flag bypasses this question
39
+ // if (!options.coverage) {
40
+ // questions.push({
41
+ // type: 'list',
42
+ // name: 'coverage',
43
+ // message: 'collect coverage information?',
44
+ // choices: ['true', 'false', 'CI'],
45
+ // default: 'false',
46
+ // });
47
+ // }
48
+ // // --maxWorkers flag bypasses this question
49
+ // if (!options.maxWorkers) {
50
+ // questions.push({
51
+ // type: 'input',
52
+ // name: 'maxWorkers',
53
+ // message: 'maximum number of workers the test command can use (number or percentage)?',
54
+ // default: '50%',
55
+ // });
56
+ // }
57
+ // // --filterFnFilePath flag bypasses this question
58
+ // if (!options.filterFnFilePath && !options.fffp) {
59
+ // questions.push({
60
+ // type: 'input',
61
+ // name: 'filterFnFilePath',
62
+ // message: 'Path to a module exporting a filtering function',
63
+ // default: '',
64
+ // });
65
+ // }
66
+ // // --testPathPatterns flag bypasses this question
67
+ // if (!options.testPathPatterns && !options.tpp) {
68
+ // questions.push({
69
+ // type: 'input',
70
+ // name: 'testPathPatterns',
71
+ // message: 'A regexp pattern string that is matched against all tests paths before executing the test',
72
+ // default: '',
73
+ // });
74
+ // }
75
+ const answers = await inquirer.prompt(questions);
76
+ return {
77
+ ...options,
78
+ ...answers,
79
+ };
80
+ }
81
+
82
+ /**
83
+ * Takes in command line arguments and returns a string of jest flags to be appended when invoking jest.
84
+ *
85
+ * @param {commandOptions} commandOptions - The command options.
86
+ * @returns {string}
87
+ */
88
+ const getJestFlags = (commandOptions) => {
89
+ // with this we ensure the white spaces are correct without having to worry about them
90
+ const flagsArray = [commandOptions.maxWorkers ? `--maxWorkers=${commandOptions.maxWorkers}` : ''];
91
+ const isCi = getCIEnv();
92
+
93
+ // ******* Flags based on arguments *******
94
+ // coverage may be "CI" | "false" | "true"
95
+ switch (
96
+ `${commandOptions.coverage}` // the string cast is just to be sure commandOptions.coverage is a string no matter what
97
+ ) {
98
+ case 'CI':
99
+ if (isCi) {
100
+ flagsArray.push('--coverage');
101
+ }
102
+ break;
103
+ case 'false':
104
+ break;
105
+ case 'true':
106
+ default:
107
+ flagsArray.push('--coverage');
108
+ break;
109
+ }
110
+
111
+ // flags that depends on if the commandOptions was provided by the invoking command
112
+ if (commandOptions.passWithNoTests !== false && commandOptions.passWithNoTests !== 'false')
113
+ flagsArray.push('--passWithNoTests');
114
+ if (commandOptions.silent) flagsArray.push('--silent');
115
+ if (commandOptions.filterFnFilePath) flagsArray.push(`--filter=${commandOptions.filterFnFilePath}`);
116
+ if (commandOptions.testPathPatterns) flagsArray.push(`--testPathPatterns=${commandOptions.testPathPatterns}`);
117
+
118
+ // ******* automatic flags based on opinionated choices *******
119
+ if (isCi) flagsArray.push('--ci --no-colors');
120
+
121
+ return flagsArray.join(' ');
122
+ };
123
+
124
+ /**
125
+ * A wrapper of jest command execution with options provided via command line or prompted.
126
+ *
127
+ * Adapted from pui-cli but simplified and modified to fit ds-monorepo-devops usage within dimsum monorepo, not inheriting pui-cli tech debts and unused features.
128
+ * @param {commandOptions} commandOptions - The command options.
129
+ * @returns {Promise<void>}
130
+ */
131
+ const runTests = async (commandOptions) => {
132
+ const jestFlags = getJestFlags(commandOptions);
133
+ try {
134
+ if (getCIEnv()) {
135
+ await exec('rimraf ./reports');
136
+ }
137
+ await exec(`cross-env NODE_ENV=test jest ${jestFlags}`);
138
+ logSuccess('Unit test execution completed');
139
+ } catch (err) {
140
+ logError('Unit test execution failed', err);
141
+ process.exit(-1);
142
+ }
143
+ };
144
+
145
+ export const execTest = async (options) => {
146
+ const commandOptions = await promptForCommandOptions(options);
147
+ await runTests(commandOptions);
148
+ };
@@ -2,11 +2,13 @@ import inquirer from 'inquirer';
2
2
  import { execSyncNxTags } from './execSyncNxTags/index.mjs';
3
3
  import { execGitReverseMerge } from './execGitReverseMerge/index.mjs';
4
4
  import { execForcePnpmCatalogVersions } from './forcePnpmCatalogVersions/index.mjs';
5
+ import { execTest } from './execTest/index.mjs';
5
6
 
6
7
  const COMMANDS = {
7
8
  SYNC_NX_TAGS: 'sync-nx-tags',
8
9
  REVERSE_MERGE_BRANCH: 'reverse-merge-branch',
9
10
  FORCE_PNPM_CATALOG_VERSIONS: 'force-pnpm-catalog-versions',
11
+ TEST: 'test',
10
12
  EXIT: 'exit',
11
13
  };
12
14
  export const checkCommandOrInquire = async (options) => {
@@ -37,6 +39,9 @@ export async function executeCommandsMap(args, options) {
37
39
  case COMMANDS.FORCE_PNPM_CATALOG_VERSIONS:
38
40
  execForcePnpmCatalogVersions(options);
39
41
  break;
42
+ case COMMANDS.TEST:
43
+ execTest(options);
44
+ break;
40
45
  default:
41
46
  break;
42
47
  }
@@ -1,9 +1,8 @@
1
1
  /* eslint-disable complexity, no-console, max-statements */
2
2
  import inquirer from 'inquirer';
3
- import defGlob from 'glob';
3
+ import { glob } from 'glob';
4
4
  import path from 'node:path';
5
5
  import fs from 'node:fs';
6
- const { glob } = defGlob;
7
6
 
8
7
  export async function promptForCommandOptions(options) {
9
8
  const questions = [];
@@ -19,6 +18,7 @@ export async function promptForCommandOptions(options) {
19
18
  const answers = await inquirer.prompt(questions);
20
19
 
21
20
  return {
21
+ ...options,
22
22
  ...answers,
23
23
  };
24
24
  }
@@ -0,0 +1,53 @@
1
+ /* eslint-disable no-console */
2
+ // https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
3
+ export const CLI_COLORS = {
4
+ reset: '\x1b[0m',
5
+ bold: '\x1b[1m',
6
+ dim: '\x1b[2m',
7
+ underline: '\x1b[4m',
8
+ strikethrough: '\x1b[9m',
9
+ black: '\x1b[30m',
10
+ red: '\x1b[31m',
11
+ green: '\x1b[32m',
12
+ yellow: '\x1b[33m',
13
+ blue: '\x1b[34m',
14
+ magenta: '\x1b[35m',
15
+ cyan: '\x1b[36m',
16
+ white: '\x1b[37m',
17
+ gray: '\x1b[90m',
18
+ brightRed: '\x1b[91m',
19
+ brightGreen: '\x1b[92m',
20
+ brightYellow: '\x1b[93m',
21
+ brightBlue: '\x1b[94m',
22
+ brightMagenta: '\x1b[95m',
23
+ brightCyan: '\x1b[96m',
24
+ brightWhite: '\x1b[97m',
25
+ bgBlack: '\x1b[40m',
26
+ bgRed: '\x1b[41m',
27
+ bgGreen: '\x1b[42m',
28
+ bgYellow: '\x1b[43m',
29
+ bgBlue: '\x1b[44m',
30
+ bgMagenta: '\x1b[45m',
31
+ bgCyan: '\x1b[46m',
32
+ bgWhite: '\x1b[47m',
33
+ bgGray: '\x1b[100m',
34
+ bgBrightRed: '\x1b[101m',
35
+ bgBrightGreen: '\x1b[102m',
36
+ bgBrightYellow: '\x1b[103m',
37
+ bgBrightBlue: '\x1b[104m',
38
+ bgBrightMagenta: '\x1b[105m',
39
+ bgBrightCyan: '\x1b[106m',
40
+ bgBrightWhite: '\x1b[107m',
41
+ };
42
+
43
+ export const logRed = (...args) => {
44
+ console.log(CLI_COLORS.red, ...args, CLI_COLORS.reset);
45
+ };
46
+ export const logError = logRed;
47
+ export const logYellow = (...args) => {
48
+ console.log(CLI_COLORS.yellow, ...args, CLI_COLORS.reset);
49
+ };
50
+ export const logGreen = (...args) => {
51
+ console.log(CLI_COLORS.green, ...args, CLI_COLORS.reset);
52
+ };
53
+ export const logSuccess = logGreen;
@@ -0,0 +1,6 @@
1
+ import { execaCommand } from 'execa';
2
+
3
+ export { logError, logGreen, logRed, logSuccess, logYellow } from './CLI_COLORS.mjs';
4
+
5
+ export const exec = async (command, options = { stdio: 'inherit' }) => execaCommand(command, options);
6
+ export const getCIEnv = () => process.env.CI === 'true';
@@ -1,7 +1,7 @@
1
1
  /* eslint-disable max-len */
2
- import { jestConfig } from '@elliemae/pui-cli';
3
2
  import path from 'node:path';
4
3
  import { fileURLToPath } from 'url';
4
+ import { jestConfig } from './testing/jest.config.base.mjs';
5
5
 
6
6
  const __filename = fileURLToPath(import.meta.url); // get the resolved path to the file
7
7
  const __dirname = path.dirname(__filename); // get the name of the directory
@@ -15,7 +15,7 @@ const getFileFromCurrentFolder = (fileName) => path.normalize(path.resolve(__dir
15
15
  * @returns {string[]} An array of setup files for Jest.
16
16
  */
17
17
  const getSetupFilesAfterEnvBasedOnFlags = ({ silenceConsole = false }) => {
18
- const setupFilesAfterEnv = [getFileFromCurrentFolder('virtualListFix.mjs')];
18
+ const setupFilesAfterEnv = [];
19
19
 
20
20
  if (silenceConsole) {
21
21
  setupFilesAfterEnv.push(getFileFromCurrentFolder('noConsoleMode.mjs'));
@@ -66,12 +66,9 @@ export const config = ({ relativePathAfterTestsFolder = undefined, firstLevelSpr
66
66
  ? `.*/tests/${relativePathAfterTestsFolder}$`
67
67
  : 'tests/.*\\.test\\.[jt]sx?$',
68
68
  testTimeout: 120000,
69
- setupFilesAfterEnv: [...jestConfig.setupFilesAfterEnv, ...getSetupFilesAfterEnvBasedOnFlags({ silenceConsole })],
69
+ setupFilesAfterEnv: [...getSetupFilesAfterEnvBasedOnFlags({ silenceConsole }), ...jestConfig.setupFilesAfterEnv],
70
70
  ...firstLevelSpread,
71
- transformIgnorePatterns: [
72
- ...jestConfig.transformIgnorePatterns,
73
- // '/node_modules/(?!(\@elliemae\/pui-cli|lodash-es|react-select|react-dates|d3|internmap|delaunator|robust-predicates))/',
74
- ],
71
+ transformIgnorePatterns: [...jestConfig.transformIgnorePatterns],
75
72
  };
76
73
  };
77
74
  export default config;
@@ -14,4 +14,4 @@ jest.mock('@testing-library/react', () => {
14
14
  : global.console.jestLog?.(prettyDOM(el, maxLength, options));
15
15
  },
16
16
  };
17
- });
17
+ });
@@ -0,0 +1,23 @@
1
+ {
2
+ "jsc": {
3
+ "parser": {
4
+ "syntax": "typescript",
5
+ "jsx": true,
6
+ "tsx": true,
7
+ "decorators": true,
8
+ "dynamicImport": true
9
+ },
10
+ "target": "es2022",
11
+ "transform": {
12
+ "react": {
13
+ "runtime": "automatic",
14
+ "importSource": "react",
15
+ "development":true,
16
+ "refresh": false
17
+ }
18
+ }
19
+ },
20
+ "module":{
21
+ "type":"commonjs"
22
+ }
23
+ }
@@ -0,0 +1,27 @@
1
+ const { setImmediate } = require('node:timers');
2
+ const JSDOMEnvironment = require('jest-fixed-jsdom');
3
+
4
+ class JSDOMEnvironmentExtended extends JSDOMEnvironment {
5
+ constructor(...args) {
6
+ super(...args);
7
+ const polyfillRAF = setImmediate;
8
+ if (!globalThis.requestAnimationFrame)
9
+ Object.defineProperties(globalThis, {
10
+ requestAnimationFrame: {
11
+ value: polyfillRAF,
12
+ },
13
+ });
14
+ if (!globalThis.window)
15
+ Object.defineProperties(globalThis, {
16
+ window: this.dom.window,
17
+ });
18
+ if (!this.dom.window.requestAnimationFrame)
19
+ Object.defineProperties(this.dom.window, {
20
+ requestAnimationFrame: {
21
+ value: polyfillRAF,
22
+ },
23
+ });
24
+ }
25
+ }
26
+
27
+ module.exports = JSDOMEnvironmentExtended;
@@ -0,0 +1,70 @@
1
+ /* eslint-disable max-len */
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import normalizePath from 'normalize-path';
5
+ import { swcrcConfig } from './swcrc.config.cjs';
6
+ import { findMonoRepoRoot } from './utils.cjs';
7
+
8
+ const __filename = fileURLToPath(import.meta.url); // get the resolved path to the file
9
+ const __dirname = path.dirname(__filename); // get the name of the directory
10
+
11
+ const basePath = (process.env.BASE_PATH || '/').replace(/\/?$/, '/');
12
+
13
+ const getMockFilePath = (fileName) => normalizePath(path.resolve(__dirname, './mocks', fileName));
14
+
15
+ const getNodeModulesPath = (fileName) => {
16
+ const monorepoRoot = findMonoRepoRoot(process.cwd());
17
+ return normalizePath(
18
+ monorepoRoot ? path.join(monorepoRoot, 'node_modules', fileName) : `<rootDir>/node_modules/${fileName}`,
19
+ );
20
+ };
21
+
22
+ const configuredJestConfig = {
23
+ coverageThreshold: {},
24
+ coverageProvider: 'v8',
25
+ moduleDirectories: ['node_modules'],
26
+ moduleNameMapper: {
27
+ d3: '<rootDir>/node_modules/d3/dist/d3.min.js',
28
+ '^d3-(.*)$': '<rootDir>/node_modules/d3-$1/dist/d3-$1.min.js',
29
+ '.*\\webpack-hmr(.[t|j]s)?$': getMockFilePath('webpack-hmr.js'),
30
+ '.*\\.(css|scss)$': getMockFilePath('cssModule.js'),
31
+ '.*\\.(jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga|ico)$':
32
+ getMockFilePath('image.js'),
33
+ '.*\\.svg(?:\\?[a-zA-Z]+)?$': getMockFilePath('svg.js'),
34
+ '.*iframe\\.html(?:\\?[a-zA-Z]+)?$': getMockFilePath('iframe.js'),
35
+ '.*frame\\.html(?:\\?[a-zA-Z]+)?$': getMockFilePath('frame.js'),
36
+ '.*\\.html(?:\\?[a-zA-Z]+)?$': getMockFilePath('html.js'),
37
+ '@elliemae/pui-user-monitoring': getMockFilePath('pui-user-monitoring.js'),
38
+ '@elliemae/pui-app-loader': getMockFilePath('pui-app-loader.js'),
39
+ '@elliemae/pui-diagnostics': getMockFilePath('pui-diagnostics.js'),
40
+ 'react-spring/web': getNodeModulesPath('react-spring/web.cjs.js'),
41
+ 'react-spring/renderprops': getNodeModulesPath('react-spring/renderprops.cjs.js'),
42
+ },
43
+ moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
44
+ setupFilesAfterEnv: [path.resolve(__dirname, './setup-tests.js'), path.resolve(__dirname, './setup-react-env.js')],
45
+ setupFiles: [],
46
+ snapshotSerializers: [],
47
+ testResultsProcessor: 'jest-sonar-reporter',
48
+ resolver: path.resolve(__dirname, './resolver.cjs'),
49
+ transform: {
50
+ '^.+\\.[jt]sx?$': ['@swc/jest', swcrcConfig],
51
+ },
52
+
53
+ transformIgnorePatterns: [
54
+ // eslint-disable-next-line no-useless-escape
55
+ '/node_modules/(?!(\@elliemae\/pui-cli|lodash-es|react-select|react-dates|d3|internmap|delaunator|robust-predicates))/',
56
+ ],
57
+ globals: {
58
+ __webpack_public_path__: '/',
59
+ },
60
+ testEnvironmentOptions: {
61
+ url: `http://localhost:3111${basePath}`,
62
+ resources: 'usable',
63
+ customExportConditions: [''],
64
+ },
65
+ testEnvironment: path.resolve(__dirname, './jest-extended-dom.cjs'),
66
+ watchPlugins: ['jest-watch-typeahead/filename', 'jest-watch-typeahead/testname'],
67
+ prettierPath: null,
68
+ };
69
+
70
+ export const jestConfig = configuredJestConfig;
@@ -0,0 +1,9 @@
1
+ const { jestConfig } = require('./jest.config.cjs');
2
+ exports.jestNodeConfig = {
3
+ ...jestConfig,
4
+ coverage: false,
5
+ testEnvironment: 'node',
6
+ transformIgnorePatterns: [],
7
+ setupFiles: [],
8
+ setupFilesAfterEnv: [],
9
+ };
@@ -0,0 +1,15 @@
1
+ export default {
2
+ defaults: {},
3
+ interceptors: {
4
+ request: {
5
+ use: jest.fn(),
6
+ },
7
+ response: {
8
+ use: jest.fn(),
9
+ },
10
+ },
11
+ create: jest.fn().mockReturnThis(),
12
+ get: jest.fn().mockResolvedValue({ data: {} }),
13
+ post: jest.fn().mockResolvedValue({ data: {} }),
14
+ put: jest.fn().mockResolvedValue({ data: {} }),
15
+ };
@@ -0,0 +1 @@
1
+ export default 'CSS_MODULE';
@@ -0,0 +1,2 @@
1
+ export const filename = './frame.html';
2
+ export default './frame.html';
@@ -0,0 +1 @@
1
+ export default 'HTML_MODULE';
@@ -0,0 +1,2 @@
1
+ export const filename = './iframe.html';
2
+ export default './iframe.html';
@@ -0,0 +1 @@
1
+ export default 'IMAGE_MOCK';
@@ -0,0 +1 @@
1
+ export const load = () => {};
@@ -0,0 +1,3 @@
1
+ export const setCustomUserData = () => {};
2
+ export const setCustomVirtualPageName = () => {};
3
+ export const startVirtualPageMonitoringWithAutoEnd = () => {};
@@ -0,0 +1,3 @@
1
+ export const attach = jest.fn();
2
+ export const detach = jest.fn();
3
+ export const getConfig = jest.fn();
@@ -0,0 +1,2 @@
1
+ export default 'SvgrURL';
2
+ export const ReactComponent = 'div';
@@ -0,0 +1 @@
1
+ export const enableHotReloading = () => {};
@@ -0,0 +1,43 @@
1
+ const resolutions = [
2
+ {
3
+ matcher: /\.jsx?$/i,
4
+ extensions: ['.tsx', '.ts'],
5
+ },
6
+ {
7
+ matcher: /\.mjs$/i,
8
+ extensions: ['.mts'],
9
+ },
10
+ {
11
+ matcher: /\.cjs$/i,
12
+ extensions: ['.cts'],
13
+ },
14
+ ];
15
+
16
+ const resolveConfig = {
17
+ conditionNames: ['import', 'node', 'default'],
18
+ extensions: ['.ts', '.tsx', '.js', '.jsx', '.json', '.node'],
19
+ modules: ['node_modules', 'app', 'lib'],
20
+ };
21
+
22
+ const importResolver = require('enhanced-resolve').create.sync(resolveConfig);
23
+ const requireResolver = require('enhanced-resolve').create.sync({
24
+ ...resolveConfig,
25
+ conditionNames: ['require', 'node', 'default'],
26
+ });
27
+
28
+ module.exports = (request, options) => {
29
+ let resolver = requireResolver;
30
+ if (options.conditions?.includes('import')) {
31
+ resolver = importResolver;
32
+ }
33
+ const resolution = resolutions.find(({ matcher }) => matcher.test(request));
34
+ if (resolution) {
35
+ for (const extension of resolution.extensions) {
36
+ try {
37
+ return resolver(options.basedir, request.replace(resolution.matcher, extension));
38
+ // eslint-disable-next-line no-empty
39
+ } catch {}
40
+ }
41
+ }
42
+ return resolver(options.basedir, request);
43
+ };
@@ -0,0 +1,77 @@
1
+ /* eslint-disable no-console */
2
+ /* eslint-disable max-len */
3
+ import * as React from 'react';
4
+ import 'jest-styled-components';
5
+ global.React = React;
6
+
7
+ // @tanstack/react-virtual relies on DOM APIs that are not available in JSDOM
8
+ // Dimsum uses @tanstack/react-virtual for virtualized lists for performance
9
+ // this mock ensure a standardized behavior for tests when dealing with virtualized lists:
10
+ // - parent size will be 500px height and 400px width regardless of the actual parent size
11
+ // - each item will be sized at 100px height regardless of the actual item size
12
+ // - overscan will be 15 items by default
13
+ // which means any virtualized list in the "first" position will have 20 items rendered
14
+ // (15 overscan + 5 items in the viewport)
15
+ try {
16
+ jest.mock('@tanstack/react-virtual', () => {
17
+ const original = jest.requireActual('@tanstack/react-virtual');
18
+ return {
19
+ ...original,
20
+ useVirtualizer: jest.fn((virtualOpts) => {
21
+ const finalOpts = {
22
+ ...virtualOpts,
23
+ overscan: 15,
24
+ initialRect: {
25
+ height: 500,
26
+ width: 400,
27
+ },
28
+ observeElementRect: (_, cb) => {
29
+ cb({ height: 500, width: 400 });
30
+ },
31
+ measureElement: () => 100,
32
+ };
33
+ return original.useVirtualizer(finalOpts);
34
+ }),
35
+ };
36
+ });
37
+ } catch (e) {
38
+ console.log('Failed to mock @tanstack/react-virtual, test will operate without virtualization mocks');
39
+ }
40
+ // @tanstack/virtual-core relies on browser way to solve racing conditions to set targetWindow and scrollElement properly
41
+ // this mock tries to force default values to window and document.body respectively if something goes wrong racing condition wise
42
+ // all of the features on virtualization are heavely dependent on the specific browser way of operating and browser APIs
43
+ // so this mock may not cover all edge cases, but at least will avoid crashes due to missing window or document references
44
+ // and it will be as unreliable as testin browser specific features in JSDOM has always been.
45
+ try {
46
+ jest.mock('@tanstack/virtual-core', () => {
47
+ const original = jest.requireActual('@tanstack/virtual-core');
48
+ return {
49
+ ...original,
50
+ Virtualizer: class MockedVirtualizer extends original.Virtualizer {
51
+ set targetWindow(value) {
52
+ this._targetWindow = value ?? window;
53
+ }
54
+
55
+ get targetWindow() {
56
+ return this._targetWindow ?? window;
57
+ }
58
+
59
+ set scrollElement(value) {
60
+ this._scrollElement = value ?? document.body;
61
+ }
62
+
63
+ get scrollElement() {
64
+ return this._scrollElement ?? document.body;
65
+ }
66
+
67
+ constructor(...opts) {
68
+ super(...opts);
69
+ }
70
+ },
71
+ };
72
+ });
73
+ } catch (e) {
74
+ console.log(
75
+ 'Failed to mock @tanstack/virtual-core, test will operate without virtualization mocks, it may fail due to missing window or document references',
76
+ );
77
+ }
@@ -0,0 +1,64 @@
1
+ /* eslint-disable max-len */
2
+ // needed for regenerator-runtime
3
+ // (ES7 generator support is required by redux-saga)
4
+ import 'regenerator-runtime/runtime';
5
+ import { webcrypto } from 'node:crypto';
6
+ // import 'core-js/stable'; // Generates a memory leak increasing tests memory consumption and execution time
7
+ import '@testing-library/jest-dom/jest-globals';
8
+ import jestAxe from 'jest-axe';
9
+ import ResizeObserver from 'resize-observer-polyfill';
10
+
11
+ Object.defineProperty(globalThis, 'crypto', {
12
+ value: webcrypto,
13
+ });
14
+
15
+ if (expect) expect.extend(jestAxe.toHaveNoViolations);
16
+
17
+ const addElementToBody = (element) => {
18
+ const documentEle = (window || {}).document;
19
+ if (!documentEle) return null;
20
+ const bodyEle = documentEle.body;
21
+ const newEle = documentEle.createElement(...element);
22
+ if (!newEle) return null;
23
+ bodyEle.appendChild(newEle);
24
+ return newEle;
25
+ };
26
+
27
+ const addRootElement = (id) => {
28
+ const rootEle = addElementToBody('div');
29
+ if (rootEle) rootEle.id = id;
30
+ };
31
+
32
+ addRootElement('root');
33
+
34
+ window.ResizeObserver = ResizeObserver;
35
+
36
+ let showCorejsWarn = false;
37
+ afterAll(() => {
38
+ if (showCorejsWarn) {
39
+ console.warn(
40
+ `IMPORTANT: Try importing core-js/stable in your failed test files and retrying.\nSome tests might have failed because of it`,
41
+ );
42
+ }
43
+ });
44
+
45
+ const originalTest = global.test;
46
+ global.it = (name, fn, timeout = undefined) => {
47
+ const fnWrapper = async () => {
48
+ try {
49
+ await fn();
50
+ } catch (error) {
51
+ // Some tests require core-js/stable, so we retry on failed tests to ensure it didn't fail because of it
52
+ // We avoid importing by default in this setup because core-js/stable has a memory leak which increases memory consumption and tests execution times.
53
+ showCorejsWarn = true;
54
+ throw error;
55
+ }
56
+ };
57
+ originalTest(name, fnWrapper, timeout);
58
+ };
59
+ global.it.skip = originalTest.skip;
60
+ global.it.only = originalTest.only;
61
+
62
+ if (!global.performance.getEntriesByType) {
63
+ global.performance.getEntriesByType = jest.fn(() => []);
64
+ }
@@ -0,0 +1,11 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { merge } = require('lodash');
4
+
5
+ let projectConfig = {};
6
+ const projectPath = path.join(process.cwd(), '.swcrc');
7
+ if (fs.existsSync(projectPath)) {
8
+ projectConfig = JSON.parse(fs.readFileSync(projectPath, 'utf-8'));
9
+ }
10
+ const localConfig = JSON.parse(fs.readFileSync(path.join(__dirname, '.swcrc'), 'utf-8'));
11
+ exports.swcrcConfig = merge(localConfig, projectConfig);
@@ -0,0 +1,30 @@
1
+ const path = require('path');
2
+ const WORKSPACE_DIR_ENV_VAR = 'NPM_CONFIG_WORKSPACE_DIR';
3
+ const WORKSPACE_MANIFEST_FILENAME = 'pnpm-workspace.yaml';
4
+
5
+ const getPNPMWorkspaceLocation = (cwd) => {
6
+ const root = path.parse(cwd).root;
7
+ let currentDir = cwd;
8
+
9
+ while (currentDir && currentDir !== root) {
10
+ // Check for workspace manifest files
11
+ for (const fileName of [WORKSPACE_MANIFEST_FILENAME, 'pnpm-workspace.yml']) {
12
+ const filePath = path.join(currentDir, fileName);
13
+ // eslint-disable-next-line no-undef
14
+ if (require('fs').existsSync(filePath)) {
15
+ return filePath;
16
+ }
17
+ }
18
+ currentDir = path.dirname(currentDir);
19
+ }
20
+ return null;
21
+ };
22
+
23
+ exports.findMonoRepoRoot = (cwd = process.cwd()) => {
24
+ const workspaceManifestDirEnvVar =
25
+ process.env[WORKSPACE_DIR_ENV_VAR] ?? process.env[WORKSPACE_DIR_ENV_VAR.toLowerCase()];
26
+ const workspaceManifestLocation = workspaceManifestDirEnvVar
27
+ ? path.join(workspaceManifestDirEnvVar, 'pnpm-workspace.yaml')
28
+ : getPNPMWorkspaceLocation(cwd);
29
+ return workspaceManifestLocation && path.dirname(workspaceManifestLocation);
30
+ };
@@ -18,9 +18,9 @@ export const config = defineConfig({
18
18
  // ctViteConfig: viteDefineConfig({ logLevel: 'info', plugins: [react()] }),
19
19
  use: {
20
20
  ctPort: 31500,
21
- headless: process.env.CI ? true : false,
21
+ headless: process.env.CI || process.env.PW_HEADLESS ? true : false,
22
22
  launchOptions: {
23
- devtools: process.env.CI ? false : true,
23
+ devtools: process.env.CI || process.env.PW_HEADLESS ? false : true,
24
24
  },
25
25
  },
26
26
  projects: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elliemae/ds-monorepo-devops",
3
- "version": "3.60.0-next.5",
3
+ "version": "3.60.0-next.50",
4
4
  "license": "MIT",
5
5
  "description": "ICE MT - Dimsum - Monorepo Devops",
6
6
  "type": "module",
@@ -42,21 +42,58 @@
42
42
  "reportFile": "tests.xml",
43
43
  "indent": 4
44
44
  },
45
- "scripts": {},
46
45
  "peerDependencies": {
47
- "@elliemae/pui-cli": "catalog:",
46
+ "@swc/core": "~1.13.5",
47
+ "@swc/jest": "~0.2.39",
48
+ "@testing-library/dom": "~10.4.1",
49
+ "@testing-library/jest-dom": "^6.6.3",
50
+ "@testing-library/react": "^16.0.1",
51
+ "@testing-library/react-hooks": "~8.0.1",
52
+ "@testing-library/user-event": "~14.6.1",
48
53
  "arg": "~5.0.2",
49
- "glob": "~10.2.5",
50
- "ignore": "^5.3.0",
51
- "inquirer": "~12.0.0",
52
- "jest": "catalog:"
54
+ "find-up": "~8.0.0",
55
+ "find-up-cli": "~6.0.0",
56
+ "glob": "~13.0.0",
57
+ "ignore": "~7.0.5",
58
+ "inquirer": "~13.2.0",
59
+ "jest": "^30.0.0",
60
+ "jest-environment-jsdom": "^30.0.0",
61
+ "jest-fixed-jsdom": "~0.0.11",
62
+ "jest-sonar-reporter": "~2.0.0",
63
+ "jest-watch-typeahead": "~3.0.1",
64
+ "normalize-path": "~3.0.0",
65
+ "react": "^18.3.1",
66
+ "react-dom": "^18.3.1"
67
+ },
68
+ "devDependencies": {
69
+ "@swc/core": "~1.13.5",
70
+ "@swc/jest": "~0.2.39",
71
+ "@testing-library/dom": "~10.4.1",
72
+ "@testing-library/jest-dom": "^6.6.3",
73
+ "@testing-library/react": "^16.0.1",
74
+ "@testing-library/react-hooks": "~8.0.1",
75
+ "@testing-library/user-event": "~14.6.1",
76
+ "arg": "~5.0.2",
77
+ "find-up": "~8.0.0",
78
+ "find-up-cli": "~6.0.0",
79
+ "glob": "~13.0.0",
80
+ "ignore": "~7.0.5",
81
+ "inquirer": "~13.2.0",
82
+ "jest": "^30.0.0",
83
+ "jest-environment-jsdom": "^30.0.0",
84
+ "jest-fixed-jsdom": "~0.0.11",
85
+ "jest-sonar-reporter": "~2.0.0",
86
+ "jest-watch-typeahead": "~3.0.1",
87
+ "normalize-path": "~3.0.0",
88
+ "react": "^18.3.1",
89
+ "react-dom": "^18.3.1"
53
90
  },
54
91
  "publishConfig": {
55
92
  "access": "public",
56
93
  "typeSafety": false
57
94
  },
58
95
  "dependencies": {
59
- "@playwright/experimental-ct-react": "catalog:"
96
+ "@playwright/experimental-ct-react": "^1.51.1"
60
97
  },
61
- "gitHead": "2c3d9fa1d09e130b4e5f43d9f817c96e10709182"
62
- }
98
+ "scripts": {}
99
+ }
@@ -1,21 +0,0 @@
1
- jest.mock('@tanstack/react-virtual', () => {
2
- const original = jest.requireActual('@tanstack/react-virtual');
3
- return {
4
- ...original,
5
- useVirtualizer: jest.fn((virtualOpts) => {
6
- const finalOpts = {
7
- ...virtualOpts,
8
- overscan: 15,
9
- initialRect: {
10
- height: 500,
11
- width: 400,
12
- },
13
- observeElementRect: (_, cb) => {
14
- cb({ height: 500, width: 400 });
15
- },
16
- measureElement: () => 100,
17
- };
18
- return original.useVirtualizer(finalOpts);
19
- }),
20
- };
21
- });