forked from sanity-io/sanity
-
Notifications
You must be signed in to change notification settings - Fork 0
/
createJestConfig.js
194 lines (176 loc) · 7.95 KB
/
createJestConfig.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
const path = require('path')
const {escapeRegExp, omit} = require('lodash')
const moduleAliases = require('./.module-aliases')
const jestModuleAliases = {
'@sanity/bifur-client': './test/mockBifurClient',
'part:@sanity/base/schema': './test/mockSchema',
'part:@sanity/base/client': './test/mockClient',
'part:@sanity/base/initial-value-templates?': './test/emptyArray',
'config:sanity': './dev/test-studio/sanity.json',
'sanity:css-custom-properties': './test/emptyObject',
'sanity:versions': './test/mockVersions',
}
// note: these can contain regex for matching
const regexMapper = {
'^part:@sanity.*-style$': './test/emptyObject',
'.*\\.css$': './test/emptyObject',
'^all:.*$': './test/emptyArray',
'^config:.*$': './test/undefined',
}
const partPackages = [
'base',
'form-builder',
'desk-tool',
'code-input',
'default-login',
'data-aspects',
]
/**
* Takes a list of webpack-compatible aliases and converts them into jest module
* mappings + handles escaping regexp in paths
* */
const jestify = (aliases) =>
Object.entries(aliases).reduce((acc, [module, target]) => {
acc[`^${escapeRegExp(module)}$`] = target
acc[`^${escapeRegExp(module)}(\\/.*)$`] = `${target}$1`
return acc
}, {})
const resolvePaths = (obj) =>
Object.fromEntries(
Object.entries(obj).map(([module, relativePath]) => [
module,
path.resolve(__dirname, relativePath),
])
)
// note: this is a somewhat naive way to resolve parts.
// there are a few cases that won't work.
const partsAliases = Object.fromEntries(
partPackages.flatMap((i) => {
const packagePath = `./packages/@sanity/${i}`
// eslint-disable-next-line import/no-dynamic-require
const {parts, paths} = require(`${packagePath}/sanity.json`)
return parts.flatMap((part) => {
if (!part.path) return []
if (!part.implements && !part.name) return []
return [part.implements, part.name]
.filter(Boolean)
.map(escapeRegExp)
.filter((escapedPart) => !Object.keys(jestModuleAliases).includes(escapedPart))
.map((partName) => {
return [`^${partName}$`, path.join(packagePath, paths.source, part.path)]
})
})
})
)
// note: these have to be mocked on an individual file level because using
// `jest.mock` on them will replace the target file (and the the source import).
// with this, it's possible for a mock implementation to override multiple other
// optional parts
const optionalParts = {
'^part:@sanity/form-builder/input/image/asset-sources\\?$':
'./test/optionalParts/formBuilderInputImageAssetSources',
'^part:@sanity/base/initial-value-templates\\?$':
'./test/optionalParts/baseInitialValueTemplates',
'^part:@sanity/form-builder/input/legacy-date/schema\\?$':
'./test/optionalParts/formBuilderInputLegacyDateSchema',
'^part:@sanity/base/preview-resolver\\?$': './test/optionalParts/basePreviewResolver',
'^part:@sanity/dashboard/config\\?$': './test/optionalParts/dashboardConfig',
'^part:@sanity/base/login-wrapper\\?$': './test/optionalParts/baseLoginWrapper',
'^part:@sanity/desk-tool/structure\\?$': './test/optionalParts/deskToolStructure',
'^part:@sanity/base/brand-logo\\?$': './test/optionalParts/baseBrandLogo',
'^part:@sanity/form-builder/input/boolean\\?$': './test/optionalParts/formBuilderInputBoolean',
'^part:@sanity/form-builder/input/datetime\\?$': './test/optionalParts/formBuilderInputDatetime',
'^part:@sanity/form-builder/input/email\\?$': './test/optionalParts/formBuilderInputEmail',
'^part:@sanity/form-builder/input/geopoint\\?$': './test/optionalParts/formBuilderInputGeopoint',
'^part:@sanity/form-builder/input/number\\?$': './test/optionalParts/formBuilderInputNumber',
'^part:@sanity/form-builder/input/object\\?$': './test/optionalParts/formBuilderInputObject',
'^part:@sanity/form-builder/input/reference\\?$':
'./test/optionalParts/formBuilderInputReference',
'^part:@sanity/form-builder/input/string\\?$': './test/optionalParts/formBuilderInputString',
'^part:@sanity/form-builder/input/text\\?$': './test/optionalParts/formBuilderInputText',
'^part:@sanity/form-builder/input/url\\?$': './test/optionalParts/formBuilderInputUrl',
'^part:@sanity/form-builder/input/file/asset-sources\\?$':
'./test/optionalParts/formBuilderInputFileAssetSources',
'^part:@sanity/default-layout/studio-hints-config\\?$':
'./test/optionalParts/defaultLayoutStudioHintsConfig',
'^part:@sanity/base/schema\\?$': './test/optionalParts/baseSchema',
'^part:@sanity/base/configure-client\\?$': './test/optionalParts/baseConfigureClient',
'^part:@sanity/components/dialogs/fullscreen-message\\?$':
'./test/optionalParts/componentsDialogsFullscreenMessage',
'^part:@sanity/default-layout/sidecar\\?$': './test/optionalParts/defaultLayoutSidecar',
'^part:@sanity/base/new-document-structure\\?$': './test/optionalParts/baseNewDocumentStructure',
'^part:@sanity/transitional/production-preview/resolve-production-url\\?$':
'./test/optionalParts/transitionalProductionPreviewResolveProductionUrl',
'^part:@sanity/base/client\\?$': './test/optionalParts/baseClient',
'^part:@sanity/base/preview\\?$': './test/optionalParts/basePreview',
'^part:@sanity/form-builder/input-resolver\\?$': './test/optionalParts/formBuilderInputResolver',
'^part:@sanity/desk-tool/filter-fields-fn\\?$': './test/optionalParts/deskToolFilterFieldsFn',
'^part:@sanity/desk-tool/language-select-component\\?$':
'./test/optionalParts/deskToolLanguageSelectComponent',
}
/**
* @param {import('@jest/types').Config.InitialOptions
* | (config: import('@jest/types').Config.InitialOptions) => any} inputConfig
* @returns {import('@jest/types').Config.InitialOptions}
*/
function createProjectConfig({
testMatch = [],
testPathIgnorePatterns = [],
setupFiles = [],
globals = {},
moduleNameMapper: incomingModuleNameMapper = {},
transform = {},
...restOfInputConfig
} = {}) {
const defaultModuleNameMapper = resolvePaths({
...optionalParts,
// > The order in which the mappings are defined matters. Patterns are
// > checked one by one until one fits. The most specific rule should be
// > listed first. This is true for arrays of module names as well.
// https://jestjs.io/docs/configuration#modulenamemapper-objectstring-string--arraystring
//
// See the note below in `moduleNameMapper` about ordering
...jestify({
// note: this spread technique follows the same technique as the
// `moduleNameMapper` below
// jest-specific module aliases first
...jestModuleAliases,
// then match modules from webpack compatible aliases
...omit(moduleAliases, Object.keys(jestModuleAliases)),
}),
// then generic parts mapper
...regexMapper,
// then specific parts implementations
...partsAliases,
})
return {
transform: {
...transform,
'\\.[j|t]sx?$': [
'babel-jest',
// rootMode upwards makes use of the global babel.config.js
{rootMode: 'upward'},
],
},
testMatch: [...testMatch, '<rootDir>/**/*.{test,spec}.{js,ts,tsx}'],
testPathIgnorePatterns: [...testPathIgnorePatterns, '/(node_modules|lib|dist|bin|coverage)/'],
setupFiles: [...setupFiles, path.resolve(__dirname, './test/jest-setup.ts')],
testURL: 'http://localhost:3333',
testEnvironment: 'jsdom',
globals: {__DEV__: false, ...globals},
moduleNameMapper: {
// note: order matters!
//
// the ordering for this _does_ allow for overrides from an incoming
// project configuration. it's kind of weird to see the
// `defaultModuleNameMapper` come last (because the last key in a spread
// takes precedence) however in this case, because the order of the keys
// matter, we leave the incoming moduleNameMapper first, then omit
// duplicate keys from the `defaultModuleNameMapper`
...incomingModuleNameMapper,
...omit(defaultModuleNameMapper, Object.keys(incomingModuleNameMapper)),
},
...restOfInputConfig,
}
}
module.exports = createProjectConfig