@cedarjs/testing 0.0.4

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 (70) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +3 -0
  3. package/api/index.js +2 -0
  4. package/api/package.json +4 -0
  5. package/cache/index.js +2 -0
  6. package/cache/package.json +4 -0
  7. package/config/jest/api/RedwoodApiJestEnv.js +31 -0
  8. package/config/jest/api/apiBabelConfig.js +18 -0
  9. package/config/jest/api/globalSetup.js +46 -0
  10. package/config/jest/api/index.js +2 -0
  11. package/config/jest/api/jest-preset.js +67 -0
  12. package/config/jest/api/jest.setup.js +326 -0
  13. package/config/jest/jest-serial-runner.js +13 -0
  14. package/config/jest/web/RedwoodWebJestEnv.js +17 -0
  15. package/config/jest/web/index.js +2 -0
  16. package/config/jest/web/jest-preset.js +89 -0
  17. package/config/jest/web/jest.setup.js +41 -0
  18. package/config/jest/web/resolver.js +37 -0
  19. package/config/jest/web/webBabelConfig.js +3 -0
  20. package/dist/api/apiFunction.d.ts +37 -0
  21. package/dist/api/apiFunction.d.ts.map +1 -0
  22. package/dist/api/apiFunction.js +89 -0
  23. package/dist/api/directUrlHelpers.d.ts +3 -0
  24. package/dist/api/directUrlHelpers.d.ts.map +1 -0
  25. package/dist/api/directUrlHelpers.js +60 -0
  26. package/dist/api/directive.d.ts +51 -0
  27. package/dist/api/directive.d.ts.map +1 -0
  28. package/dist/api/directive.js +77 -0
  29. package/dist/api/index.d.ts +4 -0
  30. package/dist/api/index.d.ts.map +1 -0
  31. package/dist/api/index.js +26 -0
  32. package/dist/api/scenario.d.ts +108 -0
  33. package/dist/api/scenario.d.ts.map +1 -0
  34. package/dist/api/scenario.js +30 -0
  35. package/dist/cache/index.d.ts +50 -0
  36. package/dist/cache/index.d.ts.map +1 -0
  37. package/dist/cache/index.js +104 -0
  38. package/dist/web/MockParamsProvider.d.ts +7 -0
  39. package/dist/web/MockParamsProvider.d.ts.map +1 -0
  40. package/dist/web/MockParamsProvider.js +44 -0
  41. package/dist/web/MockProviders.d.ts +9 -0
  42. package/dist/web/MockProviders.d.ts.map +1 -0
  43. package/dist/web/MockProviders.js +53 -0
  44. package/dist/web/MockRouter.d.ts +14 -0
  45. package/dist/web/MockRouter.d.ts.map +1 -0
  46. package/dist/web/MockRouter.js +48 -0
  47. package/dist/web/customRender.d.ts +6 -0
  48. package/dist/web/customRender.d.ts.map +1 -0
  49. package/dist/web/customRender.js +54 -0
  50. package/dist/web/fileMock.d.ts +9 -0
  51. package/dist/web/fileMock.d.ts.map +1 -0
  52. package/dist/web/fileMock.js +24 -0
  53. package/dist/web/findCellMocks.d.ts +2 -0
  54. package/dist/web/findCellMocks.d.ts.map +1 -0
  55. package/dist/web/findCellMocks.js +45 -0
  56. package/dist/web/global.d.ts +6 -0
  57. package/dist/web/global.d.ts.map +1 -0
  58. package/dist/web/global.js +1 -0
  59. package/dist/web/index.d.ts +7 -0
  60. package/dist/web/index.d.ts.map +1 -0
  61. package/dist/web/index.js +42 -0
  62. package/dist/web/mockAuth.d.ts +29 -0
  63. package/dist/web/mockAuth.d.ts.map +1 -0
  64. package/dist/web/mockAuth.js +89 -0
  65. package/dist/web/mockRequests.d.ts +30 -0
  66. package/dist/web/mockRequests.d.ts.map +1 -0
  67. package/dist/web/mockRequests.js +129 -0
  68. package/package.json +67 -0
  69. package/web/index.js +2 -0
  70. package/web/package.json +4 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Cedar
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # @redwoodjs/testing
2
+
3
+ This package includes Redwood's Jest config.
package/api/index.js ADDED
@@ -0,0 +1,2 @@
1
+ /* eslint-env es6, commonjs */
2
+ module.exports = require('../dist/api')
@@ -0,0 +1,4 @@
1
+ {
2
+ "main": "./index.js",
3
+ "types": "../dist/api/index.d.ts"
4
+ }
package/cache/index.js ADDED
@@ -0,0 +1,2 @@
1
+ /* eslint-env es6, commonjs */
2
+ module.exports = require('../dist/cache')
@@ -0,0 +1,4 @@
1
+ {
2
+ "main": "./index.js",
3
+ "types": "../dist/cache/index.d.ts"
4
+ }
@@ -0,0 +1,31 @@
1
+ const { TestEnvironment } = require('jest-environment-node')
2
+
3
+ class RedwoodApiJestEnvironment extends TestEnvironment {
4
+ constructor(config, context) {
5
+ super(config, context)
6
+ this.testPath = context.testPath
7
+ }
8
+
9
+ async setup() {
10
+ await super.setup()
11
+
12
+ this.global.testPath = this.testPath
13
+ }
14
+
15
+ async teardown() {
16
+ await super.teardown()
17
+ }
18
+
19
+ getVmContext() {
20
+ return super.getVmContext()
21
+ }
22
+
23
+ // async handleTestEvent(event, state) {
24
+ // if (event.name === 'test_start') {
25
+ // // Link to event docs:
26
+ // // https://github.com/facebook/jest/blob/master/packages/jest-types/src/Circus.ts
27
+ // }
28
+ // }
29
+ }
30
+
31
+ module.exports = RedwoodApiJestEnvironment
@@ -0,0 +1,18 @@
1
+ const {
2
+ getApiSideDefaultBabelConfig,
3
+ getApiSideBabelPresets,
4
+ getApiSideBabelPlugins,
5
+ } = require('@cedarjs/babel-config')
6
+
7
+ // Since configFile and babelrc is already passed a level up, cleaning up these keys here.
8
+ // babelrc can not reside inside "extend"ed
9
+ // Ref: packages/testing/config/jest/api/index.js
10
+ const { babelrc: _b, ...defaultBabelConfig } = getApiSideDefaultBabelConfig()
11
+
12
+ module.exports = {
13
+ ...defaultBabelConfig,
14
+ plugins: getApiSideBabelPlugins({ forJest: true }),
15
+ presets: getApiSideBabelPresets({
16
+ presetEnv: true, // jest needs code transpiled
17
+ }),
18
+ }
@@ -0,0 +1,46 @@
1
+ const { getSchema } = require('@prisma/internals')
2
+
3
+ const { getPaths } = require('@cedarjs/project-config')
4
+
5
+ const {
6
+ getDefaultDb,
7
+ checkAndReplaceDirectUrl,
8
+ } = require('../../../dist/api/directUrlHelpers')
9
+
10
+ const rwjsPaths = getPaths()
11
+
12
+ module.exports = async function () {
13
+ if (process.env.SKIP_DB_PUSH !== '1') {
14
+ const process = require('process')
15
+ // Load dotenvs
16
+ require('dotenv-defaults/config')
17
+
18
+ const defaultDb = getDefaultDb(rwjsPaths.base)
19
+
20
+ process.env.DATABASE_URL = process.env.TEST_DATABASE_URL || defaultDb
21
+
22
+ // NOTE: This is a workaround to get the directUrl from the schema
23
+ // Instead of using the schema, we can use the config file
24
+ // const prismaConfig = await getConfig(rwjsPaths.api.dbSchema)
25
+ // and then check for the prismaConfig.datasources[0].directUrl
26
+ const prismaSchema = (await getSchema(rwjsPaths.api.dbSchema)).toString()
27
+
28
+ const directUrlEnvVar = checkAndReplaceDirectUrl(prismaSchema, defaultDb)
29
+
30
+ const command =
31
+ process.env.TEST_DATABASE_STRATEGY === 'reset'
32
+ ? ['prisma', 'migrate', 'reset', '--force', '--skip-seed']
33
+ : ['prisma', 'db', 'push', '--force-reset', '--accept-data-loss']
34
+
35
+ const execa = require('execa')
36
+ execa.sync(`yarn rw`, command, {
37
+ cwd: rwjsPaths.api.base,
38
+ stdio: 'inherit',
39
+ shell: true,
40
+ env: {
41
+ DATABASE_URL: process.env.DATABASE_URL,
42
+ [directUrlEnvVar]: process.env[directUrlEnvVar],
43
+ },
44
+ })
45
+ }
46
+ }
@@ -0,0 +1,2 @@
1
+ // This is for backwards compatibility
2
+ module.exports = require('./jest-preset')
@@ -0,0 +1,67 @@
1
+ // @ts-check
2
+ const path = require('path')
3
+
4
+ const { getApiSideDefaultBabelConfig } = require('@cedarjs/babel-config')
5
+ const { getPaths } = require('@cedarjs/project-config')
6
+
7
+ const rwjsPaths = getPaths()
8
+ const NODE_MODULES_PATH = path.join(rwjsPaths.base, 'node_modules')
9
+ const { babelrc } = getApiSideDefaultBabelConfig()
10
+
11
+ /** @type {import('jest').Config} */
12
+ module.exports = {
13
+ // To make sure other config option which depends on rootDir use
14
+ // correct path, for example, coverageDirectory
15
+ rootDir: rwjsPaths.base,
16
+ roots: [path.join(rwjsPaths.api.src)],
17
+ runner: path.join(__dirname, '../jest-serial-runner.js'),
18
+ testEnvironment: path.join(__dirname, './RedwoodApiJestEnv.js'),
19
+ globals: {
20
+ __RWJS__TEST_IMPORTS: {
21
+ apiSrcPath: rwjsPaths.api.src,
22
+ tearDownCachePath: path.join(
23
+ rwjsPaths.generated.base,
24
+ 'scenarioTeardown.json',
25
+ ),
26
+ dbSchemaPath: rwjsPaths.api.dbSchema,
27
+ },
28
+ },
29
+ sandboxInjectedGlobals: ['__RWJS__TEST_IMPORTS'],
30
+ displayName: {
31
+ color: 'redBright',
32
+ name: 'api',
33
+ },
34
+ collectCoverageFrom: [
35
+ '**/*.{js,jsx,ts,tsx}',
36
+ '!**/node_modules/**',
37
+ '!**/dist/**',
38
+ ],
39
+ coverageDirectory: path.join(rwjsPaths.base, 'coverage'),
40
+ watchPlugins: [
41
+ 'jest-watch-typeahead/filename',
42
+ 'jest-watch-typeahead/testname',
43
+ ],
44
+ // This runs once before all tests
45
+ globalSetup: path.join(__dirname, './globalSetup.js'),
46
+ // Note this setup runs for each test file!
47
+ setupFilesAfterEnv: [path.join(__dirname, './jest.setup.js')],
48
+ moduleNameMapper: {
49
+ // @NOTE: Import @cedarjs/testing in api tests, and it automatically remaps to the api side only
50
+ // This is to prevent web stuff leaking into api, and vice versa
51
+ '^@cedarjs/testing$': path.join(NODE_MODULES_PATH, '@cedarjs/testing/api'),
52
+ },
53
+ transform: {
54
+ '\\.[jt]sx?$': [
55
+ 'babel-jest',
56
+ // When jest runs tests in parallel, it serializes the config before passing down options to babel
57
+ // that's why these must be serializable. So ideally, we should just pass reference to a
58
+ // configFile or "extends" a config. But we need a few other option only at root level, so we'll pass
59
+ // here and remove those keys inside "extend"ed config.
60
+ {
61
+ babelrc, // babelrc can not reside inside "extend"ed config, that's why we have it here
62
+ configFile: path.resolve(__dirname, './apiBabelConfig.js'),
63
+ },
64
+ ],
65
+ },
66
+ testPathIgnorePatterns: ['.scenarios.[jt]s$'],
67
+ }
@@ -0,0 +1,326 @@
1
+ /* eslint-env jest */
2
+ // @ts-check
3
+
4
+ // @NOTE without these imports in the setup file, mockCurrentUser
5
+ // will remain undefined in the user's tests
6
+ // Remember to use specific imports
7
+ const { defineScenario } = require('@cedarjs/testing/dist/api/scenario')
8
+
9
+ // @NOTE we do this because jest.setup.js runs every time in each context
10
+ // while jest-preset runs once. This significantly reduces memory footprint, and testing time
11
+ // The key is to reduce the amount of imports in this file, because the require.cache is not shared between each test context
12
+ const { apiSrcPath, tearDownCachePath, dbSchemaPath } =
13
+ global.__RWJS__TEST_IMPORTS
14
+
15
+ global.defineScenario = defineScenario
16
+
17
+ // Error codes thrown by [MySQL, SQLite, Postgres] when foreign key constraint
18
+ // fails on DELETE
19
+ const FOREIGN_KEY_ERRORS = [1451, 1811, 23503]
20
+ const TEARDOWN_CACHE_PATH = tearDownCachePath
21
+ const DEFAULT_SCENARIO = 'standard'
22
+ let teardownOrder = []
23
+ let originalTeardownOrder = []
24
+
25
+ const deepCopy = (obj) => {
26
+ return JSON.parse(JSON.stringify(obj))
27
+ }
28
+
29
+ const isIdenticalArray = (a, b) => {
30
+ return JSON.stringify(a) === JSON.stringify(b)
31
+ }
32
+
33
+ const configureTeardown = async () => {
34
+ const { getDMMF, getSchema } = require('@prisma/internals')
35
+ const fs = require('fs')
36
+
37
+ // @NOTE prisma utils are available in cli lib/schemaHelpers
38
+ // But avoid importing them, to prevent memory leaks in jest
39
+ const datamodel = await getSchema(dbSchemaPath)
40
+ const schema = await getDMMF({ datamodel })
41
+ const schemaModels = schema.datamodel.models.map((m) => m.dbName || m.name)
42
+
43
+ // check if pre-defined delete order already exists and if so, use it to start
44
+ if (fs.existsSync(TEARDOWN_CACHE_PATH)) {
45
+ teardownOrder = JSON.parse(fs.readFileSync(TEARDOWN_CACHE_PATH).toString())
46
+ }
47
+
48
+ // check the number of models in case we've added/removed since cache was built
49
+ if (teardownOrder.length !== schemaModels.length) {
50
+ teardownOrder = schemaModels
51
+ }
52
+
53
+ // keep a copy of the original order to compare against
54
+ originalTeardownOrder = deepCopy(teardownOrder)
55
+ }
56
+
57
+ let quoteStyle
58
+ // determine what kind of quotes are needed around table names in raw SQL
59
+ const getQuoteStyle = async () => {
60
+ const { getConfig: getPrismaConfig, getSchema } = require('@prisma/internals')
61
+
62
+ // @NOTE prisma utils are available in cli lib/schemaHelpers
63
+ // But avoid importing them, to prevent memory leaks in jest
64
+ const datamodel = await getSchema(dbSchemaPath)
65
+
66
+ if (!quoteStyle) {
67
+ const config = await getPrismaConfig({
68
+ datamodel,
69
+ })
70
+
71
+ switch (config.datasources?.[0]?.provider) {
72
+ case 'mysql':
73
+ quoteStyle = '`'
74
+ break
75
+ default:
76
+ quoteStyle = '"'
77
+ }
78
+ }
79
+
80
+ return quoteStyle
81
+ }
82
+
83
+ const getProjectDb = () => {
84
+ const { db } = require(`${apiSrcPath}/lib/db`)
85
+
86
+ return db
87
+ }
88
+
89
+ /**
90
+ * Wraps "it" or "test", to seed and teardown the scenario after each test
91
+ * This one passes scenario data to the test function
92
+ */
93
+ const buildScenario =
94
+ (itFunc, testPath) =>
95
+ (...args) => {
96
+ let scenarioName, testName, testFunc
97
+
98
+ if (args.length === 3) {
99
+ ;[scenarioName, testName, testFunc] = args
100
+ } else if (args.length === 2) {
101
+ scenarioName = DEFAULT_SCENARIO
102
+ ;[testName, testFunc] = args
103
+ } else {
104
+ throw new Error('scenario() requires 2 or 3 arguments')
105
+ }
106
+
107
+ return itFunc(testName, async () => {
108
+ let { scenario } = loadScenarios(testPath, scenarioName)
109
+
110
+ const scenarioData = await seedScenario(scenario)
111
+ try {
112
+ const result = await testFunc(scenarioData)
113
+
114
+ return result
115
+ } finally {
116
+ // Make sure to cleanup, even if test fails
117
+ if (wasDbUsed()) {
118
+ await teardown()
119
+ }
120
+ }
121
+ })
122
+ }
123
+
124
+ /**
125
+ * This creates a describe() block that will seed the scenario ONCE before all tests in the block
126
+ * Note that you need to use the getScenario() function to get the data.
127
+ */
128
+ const buildDescribeScenario =
129
+ (describeFunc, testPath) =>
130
+ (...args) => {
131
+ let scenarioName, describeBlockName, describeBlock
132
+
133
+ if (args.length === 3) {
134
+ ;[scenarioName, describeBlockName, describeBlock] = args
135
+ } else if (args.length === 2) {
136
+ scenarioName = DEFAULT_SCENARIO
137
+ ;[describeBlockName, describeBlock] = args
138
+ } else {
139
+ throw new Error('describeScenario() requires 2 or 3 arguments')
140
+ }
141
+
142
+ return describeFunc(describeBlockName, () => {
143
+ let scenarioData
144
+ beforeAll(async () => {
145
+ let { scenario } = loadScenarios(testPath, scenarioName)
146
+ scenarioData = await seedScenario(scenario)
147
+ })
148
+
149
+ afterAll(async () => {
150
+ if (wasDbUsed()) {
151
+ await teardown()
152
+ }
153
+ })
154
+
155
+ const getScenario = () => scenarioData
156
+
157
+ describeBlock(getScenario)
158
+ })
159
+ }
160
+
161
+ const teardown = async () => {
162
+ const fs = require('fs')
163
+
164
+ const quoteStyle = await getQuoteStyle()
165
+
166
+ for (const modelName of teardownOrder) {
167
+ try {
168
+ await getProjectDb().$executeRawUnsafe(
169
+ `DELETE FROM ${quoteStyle}${modelName}${quoteStyle}`,
170
+ )
171
+ } catch (e) {
172
+ const match = e.message.match(/Code: `(\d+)`/)
173
+ if (match && FOREIGN_KEY_ERRORS.includes(parseInt(match[1]))) {
174
+ const index = teardownOrder.indexOf(modelName)
175
+ teardownOrder[index] = null
176
+ teardownOrder.push(modelName)
177
+ } else {
178
+ throw e
179
+ }
180
+ }
181
+ }
182
+
183
+ // remove nulls
184
+ teardownOrder = teardownOrder.filter((val) => val)
185
+
186
+ // if the order of delete changed, write out the cached file again
187
+ if (!isIdenticalArray(teardownOrder, originalTeardownOrder)) {
188
+ originalTeardownOrder = deepCopy(teardownOrder)
189
+ fs.writeFileSync(TEARDOWN_CACHE_PATH, JSON.stringify(teardownOrder))
190
+ }
191
+ }
192
+
193
+ const seedScenario = async (scenario) => {
194
+ if (scenario) {
195
+ const scenarios = {}
196
+ for (const [model, namedFixtures] of Object.entries(scenario)) {
197
+ scenarios[model] = {}
198
+ for (const [name, createArgs] of Object.entries(namedFixtures)) {
199
+ if (typeof createArgs === 'function') {
200
+ scenarios[model][name] = await getProjectDb()[model].create(
201
+ createArgs(scenarios),
202
+ )
203
+ } else {
204
+ scenarios[model][name] =
205
+ await getProjectDb()[model].create(createArgs)
206
+ }
207
+ }
208
+ }
209
+ return scenarios
210
+ } else {
211
+ return {}
212
+ }
213
+ }
214
+
215
+ global.scenario = buildScenario(global.it, global.testPath)
216
+ global.scenario.only = buildScenario(global.it.only, global.testPath)
217
+ global.describeScenario = buildDescribeScenario(
218
+ global.describe,
219
+ global.testPath,
220
+ )
221
+ global.describeScenario.only = buildDescribeScenario(
222
+ global.describe.only,
223
+ global.testPath,
224
+ )
225
+
226
+ /**
227
+ *
228
+ * All these hooks run in the VM/Context that the test runs in since we're using "setupAfterEnv".
229
+ * There's a new context for each test-suite i.e. each test file
230
+ *
231
+ * Doing this means if the db isn't used in the current test context,
232
+ * no need to do any of the teardown logic - allowing simple tests to run faster
233
+ * At the same time, if the db is used, disconnecting it in this context prevents connection limit errors.
234
+ * Just disconnecting db in jest-preset is not enough, because
235
+ * the Prisma client is created in a different context.
236
+ */
237
+ const wasDbUsed = () => {
238
+ try {
239
+ const libDbPath = require.resolve(`${apiSrcPath}/lib/db`)
240
+ return Object.keys(require.cache).some((module) => {
241
+ return module === libDbPath
242
+ })
243
+ } catch (e) {
244
+ // If db wasn't resolved, no point trying to perform db resets
245
+ return false
246
+ }
247
+ }
248
+
249
+ // Attempt to emulate the request context isolation behavior
250
+ // This is a little more complicated than it would necessarily need to be
251
+ // but we're following the same pattern as in `@cedarjs/context`
252
+ const mockContextStore = new Map()
253
+ const mockContext = new Proxy(
254
+ {},
255
+ {
256
+ get: (_target, prop) => {
257
+ // Handle toJSON() calls, i.e. JSON.stringify(context)
258
+ if (prop === 'toJSON') {
259
+ return () => mockContextStore.get('context')
260
+ }
261
+ return mockContextStore.get('context')[prop]
262
+ },
263
+ set: (_target, prop, value) => {
264
+ const ctx = mockContextStore.get('context')
265
+ ctx[prop] = value
266
+ return true
267
+ },
268
+ },
269
+ )
270
+ jest.mock('@cedarjs/context', () => {
271
+ return {
272
+ context: mockContext,
273
+ setContext: (newContext) => {
274
+ mockContextStore.set('context', newContext)
275
+ },
276
+ }
277
+ })
278
+ beforeEach(() => {
279
+ mockContextStore.set('context', {})
280
+ })
281
+ global.mockCurrentUser = (currentUser) => {
282
+ mockContextStore.set('context', { currentUser })
283
+ }
284
+
285
+ beforeAll(async () => {
286
+ if (wasDbUsed()) {
287
+ await configureTeardown()
288
+ }
289
+ })
290
+
291
+ afterAll(async () => {
292
+ if (wasDbUsed()) {
293
+ getProjectDb().$disconnect()
294
+ }
295
+ })
296
+
297
+ function loadScenarios(testPath, scenarioName) {
298
+ const path = require('path')
299
+ const testFileDir = path.parse(testPath)
300
+ // e.g. ['comments', 'test'] or ['signup', 'state', 'machine', 'test']
301
+ const testFileNameParts = testFileDir.name.split('.')
302
+ const testFilePath = `${testFileDir.dir}/${testFileNameParts
303
+ .slice(0, testFileNameParts.length - 1)
304
+ .join('.')}.scenarios`
305
+ let allScenarios, scenario
306
+
307
+ try {
308
+ allScenarios = require(testFilePath)
309
+ } catch (e) {
310
+ // ignore error if scenario file not found, otherwise re-throw
311
+ if (e.code !== 'MODULE_NOT_FOUND') {
312
+ throw e
313
+ }
314
+ }
315
+
316
+ if (allScenarios) {
317
+ if (allScenarios[scenarioName]) {
318
+ scenario = allScenarios[scenarioName]
319
+ } else {
320
+ throw new Error(
321
+ `UndefinedScenario: There is no scenario named "${scenarioName}" in ${testFilePath}.{js,ts}`,
322
+ )
323
+ }
324
+ }
325
+ return { scenario }
326
+ }
@@ -0,0 +1,13 @@
1
+ // Originally from https://github.com/gabrieli/jest-serial-runner/blob/master/index.js
2
+ // with fixed module export
3
+
4
+ const TestRunner = require('jest-runner').default
5
+
6
+ class SerialRunner extends TestRunner {
7
+ constructor(...attr) {
8
+ super(...attr)
9
+ this.isSerial = true
10
+ }
11
+ }
12
+
13
+ module.exports = SerialRunner
@@ -0,0 +1,17 @@
1
+ const { TestEnvironment } = require('jest-environment-jsdom')
2
+
3
+ // Due to issue: https://github.com/jsdom/jsdom/issues/2524
4
+ // Fix from: https://github.com/jsdom/jsdom/issues/2524#issuecomment-736672511
5
+ module.exports = class RedwoodWebJestEnv extends TestEnvironment {
6
+ async setup() {
7
+ await super.setup()
8
+ if (typeof this.global.TextEncoder === 'undefined') {
9
+ const { TextEncoder, TextDecoder } = require('util')
10
+ this.global.TextEncoder = TextEncoder
11
+ this.global.TextDecoder = TextDecoder
12
+ }
13
+ if (typeof this.global.crypto.subtle === 'undefined') {
14
+ this.global.crypto.subtle = {} // To make tests work with auth that use WebCrypto like auth0
15
+ }
16
+ }
17
+ }
@@ -0,0 +1,2 @@
1
+ // This is for backwards compatibility
2
+ module.exports = require('./jest-preset')
@@ -0,0 +1,89 @@
1
+ const path = require('path')
2
+
3
+ const { getPaths } = require('@cedarjs/project-config')
4
+
5
+ const rwjsPaths = getPaths()
6
+ const NODE_MODULES_PATH = path.join(rwjsPaths.base, 'node_modules')
7
+
8
+ /** @type {import('jest').Config} */
9
+ module.exports = {
10
+ // To make sure other config option which depends on rootDir always
11
+ // use correct path, for example, coverageDirectory
12
+ rootDir: rwjsPaths.base,
13
+ roots: [path.join(rwjsPaths.web.src)],
14
+ testEnvironment: path.join(__dirname, './RedwoodWebJestEnv.js'),
15
+ displayName: {
16
+ color: 'blueBright',
17
+ name: 'web',
18
+ },
19
+ globals: {
20
+ __RWJS_TESTROOT_DIR: path.join(rwjsPaths.web.src), // used in jest setup to load mocks
21
+ RWJS_ENV: {
22
+ RWJS_API_URL: '',
23
+ RWJS_API_GRAPHQL_URL: '/',
24
+ __REDWOOD__APP_TITLE: 'Redwood App',
25
+ },
26
+ RWJS_DEBUG_ENV: {
27
+ RWJS_SRC_ROOT: rwjsPaths.web.src,
28
+ },
29
+ },
30
+ collectCoverageFrom: [
31
+ '**/*.{js,jsx,ts,tsx}',
32
+ '!**/node_modules/**',
33
+ '!**/dist/**',
34
+ ],
35
+ coverageDirectory: path.join(rwjsPaths.base, 'coverage'),
36
+ watchPlugins: [
37
+ 'jest-watch-typeahead/filename',
38
+ 'jest-watch-typeahead/testname',
39
+ ],
40
+ setupFilesAfterEnv: [path.resolve(__dirname, './jest.setup.js')],
41
+ moduleNameMapper: {
42
+ /**
43
+ * Make sure modules that require different versions of these
44
+ * dependencies end up using the same one.
45
+ */
46
+ '^react$': path.join(NODE_MODULES_PATH, 'react'),
47
+ '^react-dom$': path.join(NODE_MODULES_PATH, 'react-dom'),
48
+ '^@apollo/client/react$': path.join(
49
+ NODE_MODULES_PATH,
50
+ '@apollo/client/react',
51
+ ),
52
+ // We replace imports to "@cedarjs/router" with our own "mock" implementation.
53
+ '^@cedarjs/router$': path.join(
54
+ NODE_MODULES_PATH,
55
+ '@cedarjs/testing/dist/web/MockRouter.js',
56
+ ),
57
+ '^@cedarjs/web$': path.join(NODE_MODULES_PATH, '@cedarjs/web/dist/cjs'),
58
+
59
+ // This allows us to mock `createAuthentication` which is used by auth
60
+ // clients, which in turn lets us mock `useAuth` in tests
61
+ '^@cedarjs/auth$': path.join(
62
+ NODE_MODULES_PATH,
63
+ '@cedarjs/testing/dist/web/mockAuth.js',
64
+ ),
65
+
66
+ // @NOTE: Import @cedarjs/testing in web tests, and it automatically remaps to the web side only
67
+ // This is to prevent web stuff leaking into api, and vice versa
68
+ '^@cedarjs/testing$': path.join(NODE_MODULES_PATH, '@cedarjs/testing/web'),
69
+ '~__REDWOOD__USER_ROUTES_FOR_MOCK': rwjsPaths.web.routes,
70
+ '~__REDWOOD__USER_AUTH_FOR_MOCK': path.join(rwjsPaths.web.src, 'auth'),
71
+ /**
72
+ * Mock out files that aren't particularly useful in tests. See fileMock.js for more info.
73
+ */
74
+ '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga|css)$':
75
+ '@cedarjs/testing/dist/web/fileMock.js',
76
+ },
77
+ transform: {
78
+ '\\.[jt]sx?$': [
79
+ 'babel-jest',
80
+ // When jest runs tests in parallel, it serializes the config before passing down options to babel
81
+ // that's why these must be serializable. Passing the reference to a config instead.
82
+ {
83
+ configFile: path.resolve(__dirname, './webBabelConfig.js'),
84
+ },
85
+ ],
86
+ },
87
+ resolver: path.resolve(__dirname, './resolver.js'),
88
+ testPathIgnorePatterns: ['.(stories|mock).[jt]sx?$'],
89
+ }