diff --git a/packages/storybook/src/plugins/mock-router.ts b/packages/storybook/src/plugins/mock-router.ts index 60f4c67bf5..1043006947 100644 --- a/packages/storybook/src/plugins/mock-router.ts +++ b/packages/storybook/src/plugins/mock-router.ts @@ -8,7 +8,6 @@ export function mockRouter(): PluginOption { if (id.includes('src')) { code = code.replace( "'@cedarjs/router'", - // TODO(storybook): Use the mock router from @cedarjs/testing instead "'storybook-framework-cedarjs/dist/mocks/MockRouter'", ) } diff --git a/packages/testing/build.mts b/packages/testing/build.mts index 3169cac85e..6427862176 100644 --- a/packages/testing/build.mts +++ b/packages/testing/build.mts @@ -1,6 +1,6 @@ import fs from 'node:fs' -import { build, buildEsm, defaultBuildOptions } from '@cedarjs/framework-tools' +import { buildCjs, buildEsm } from '@cedarjs/framework-tools' import { generateTypesCjs, generateTypesEsm, @@ -10,21 +10,7 @@ import { await buildEsm() await generateTypesEsm() -await build({ - buildOptions: { - ...defaultBuildOptions, - tsconfig: 'tsconfig.cjs.json', - outdir: 'dist/cjs', - logOverride: { - // This feels a bit dangerous, I wish I could do this with a comment - // inside the file where I need this for greater control, but I haven't - // found a way to do that yet. - // This is to silence the CJS warning for `import.meta.glob` and - // `import.meta.dirname` - 'empty-import-meta': 'silent', - }, - }, -}) +await buildCjs() await generateTypesCjs() await insertCommonJsPackageJson({ buildFileUrl: import.meta.url, @@ -85,21 +71,3 @@ fs.writeFileSync( configJestWebJestSetupPath, webJestSetupFile.replaceAll('await import', 'require'), ) - -// ./src/web/globRoutesImporter.ts contains `import.meta.glob`. This is not -// supported in CJS. And for CJS we don't really use this, but it does get -// imported and executed, so we need to mock it. esbuild will just make -// `import.meta` be an empty object, but that's not quite enough for what we -// need here, so I extend it a bit more. -const globRoutesImporterBuildPath = './dist/cjs/web/globRoutesImporter.js' -const globRoutesImporterFile = fs.readFileSync( - globRoutesImporterBuildPath, - 'utf-8', -) -fs.writeFileSync( - globRoutesImporterBuildPath, - globRoutesImporterFile.replaceAll( - 'const import_meta = {};', - 'const import_meta = { glob: () => ({ "routes.tsx": () => null }) };', - ), -) diff --git a/packages/testing/package.json b/packages/testing/package.json index 7c263fdefc..f66eeae16a 100644 --- a/packages/testing/package.json +++ b/packages/testing/package.json @@ -63,10 +63,6 @@ "default": "./dist/cjs/api/index.js" } }, - "./api/vitest": { - "types": "./dist/api/vitest/index.d.ts", - "default": "./dist/api/vitest/index.js" - }, "./cache": { "import": { "types": "./dist/cache/index.d.ts", @@ -86,20 +82,6 @@ "types": "./dist/cjs/web/index.d.ts", "default": "./dist/cjs/web/index.js" } - }, - "./web/MockRouter.js": { - "import": { - "types": "./dist/web/MockRouter.d.ts", - "default": "./dist/web/MockRouter.js" - }, - "require": { - "types": "./dist/cjs/web/MockRouter.d.ts", - "default": "./dist/cjs/web/MockRouter.js" - } - }, - "./web/vitest": { - "types": "./dist/web/vitest/index.d.ts", - "default": "./dist/web/vitest/index.js" } }, "files": [ @@ -113,7 +95,7 @@ "build": "tsx ./build.mts && yarn build:types", "build:pack": "yarn pack -o cedarjs-testing.tgz", "build:types": "tsc --build --verbose ./tsconfig.build.json", - "build:types-cjs": "tsc --build --verbose ./tsconfig.cjs.json", + "build:types-cjs": "tsc --build --verbose tsconfig.cjs.json", "build:watch": "nodemon --watch src --ext 'js,jsx,ts,tsx' --ignore dist --exec 'yarn build'", "check:attw": "yarn rw-fwtools-attw", "check:package": "concurrently npm:check:attw yarn:publint", @@ -143,7 +125,6 @@ "jest-watch-typeahead": "2.2.2", "msw": "1.3.5", "ts-toolbelt": "9.6.0", - "unplugin-auto-import": "19.3.0", "whatwg-fetch": "3.6.20" }, "devDependencies": { diff --git a/packages/testing/src/api/mockContext.ts b/packages/testing/src/api/mockContext.ts deleted file mode 100644 index 54343cd4a6..0000000000 --- a/packages/testing/src/api/mockContext.ts +++ /dev/null @@ -1,46 +0,0 @@ -// TODO: See if we can use `GlobalContext` instead of `any` here to more -// closely match the production context. -// https://github.com/cedarjs/cedar/pull/355#discussion_r2264851576 -const mockContextStore = new Map() -const mockContext = new Proxy( - {}, - { - get: (_target, prop) => { - // Handle toJSON() calls, i.e. JSON.stringify(context) - if (prop === 'toJSON') { - return () => mockContextStore.get('context') - } - - const ctx = mockContextStore.get('context') - - if (!ctx) { - return undefined - } - - return ctx[prop] - }, - set: (_target, prop, value) => { - const ctx = mockContextStore.get('context') - - if (!ctx) { - return false - } - - ctx[prop] = value - - return true - }, - }, -) - -// eslint-disable-next-line @typescript-eslint/no-empty-object-type -export interface GlobalContext extends Record {} - -export const context = mockContext - -export const setContext = (newContext: GlobalContext): GlobalContext => { - mockContextStore.set('context', newContext) - // TODO: See if this should be `newContext` instead - // https://github.com/cedarjs/cedar/pull/355#discussion_r2264851567 - return mockContext -} diff --git a/packages/testing/src/api/vitest/CedarApiVitestEnv.ts b/packages/testing/src/api/vitest/CedarApiVitestEnv.ts deleted file mode 100644 index c933d0286f..0000000000 --- a/packages/testing/src/api/vitest/CedarApiVitestEnv.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { getSchema } from '@prisma/internals' -import 'dotenv-defaults/config.js' -import execa from 'execa' -import type { Environment } from 'vitest/environments' - -import { getPaths } from '@cedarjs/project-config' - -import { getDefaultDb, checkAndReplaceDirectUrl } from '../directUrlHelpers.js' - -const CedarApiVitestEnvironment: Environment = { - name: 'cedar-api', - transformMode: 'ssr', - - async setup() { - if (process.env.SKIP_DB_PUSH === '1') { - return { - teardown() {}, - } - } - - const cedarPaths = getPaths() - const defaultDb = getDefaultDb(cedarPaths.base) - - process.env.DATABASE_URL = process.env.TEST_DATABASE_URL || defaultDb - - // NOTE: This is a workaround to get the directUrl from the schema - // Instead of using the schema, we can use the config file - // const prismaConfig = await getConfig(rwjsPaths.api.dbSchema) - // and then check for the prismaConfig.datasources[0].directUrl - const prismaSchema = (await getSchema(cedarPaths.api.dbSchema)).toString() - - const directUrlEnvVar = checkAndReplaceDirectUrl(prismaSchema, defaultDb) - - const command = - process.env.TEST_DATABASE_STRATEGY === 'reset' - ? ['prisma', 'migrate', 'reset', '--force', '--skip-seed'] - : ['prisma', 'db', 'push', '--force-reset', '--accept-data-loss'] - - const directUrlDefinition = directUrlEnvVar - ? { [directUrlEnvVar]: process.env[directUrlEnvVar] } - : {} - - execa.sync(`yarn rw`, command, { - cwd: cedarPaths.api.base, - stdio: 'inherit', - shell: true, - env: { - DATABASE_URL: process.env.DATABASE_URL, - ...directUrlDefinition, - }, - }) - - return { - teardown() {}, - } - }, -} - -export default CedarApiVitestEnvironment diff --git a/packages/testing/src/api/vitest/index.ts b/packages/testing/src/api/vitest/index.ts deleted file mode 100644 index 95a6a2063f..0000000000 --- a/packages/testing/src/api/vitest/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { autoImportsPlugin } from './vite-plugin-auto-import.js' -export { cedarVitestApiConfigPlugin } from './vite-plugin-cedar-vitest-api-config.js' -export { trackDbImportsPlugin } from './vite-plugin-track-db-imports.js' diff --git a/packages/testing/src/api/vitest/vite-plugin-auto-import.ts b/packages/testing/src/api/vitest/vite-plugin-auto-import.ts deleted file mode 100644 index 2817e33b9c..0000000000 --- a/packages/testing/src/api/vitest/vite-plugin-auto-import.ts +++ /dev/null @@ -1,36 +0,0 @@ -import autoImport from 'unplugin-auto-import/vite' - -export function autoImportsPlugin() { - return autoImport({ - // targets to transform - include: [ - /\.[tj]sx?$/, // .ts, .tsx, .js, .jsx - ], - - // global imports to register - imports: [ - // import { mockContext, mockHttpEvent, mockSignedWebhook } from '@cedarjs/testing/api'; - { - '@cedarjs/testing/api': [ - 'mockContext', - 'mockHttpEvent', - 'mockSignedWebhook', - ], - }, - // import { gql } from 'graphql-tag' - { - 'graphql-tag': ['gql'], - }, - // import { context } from '@cedarjs/context' - { - '@cedarjs/context': ['context'], - }, - ], - - // We provide our mocking types elsewhere and so don't need this plugin to - // generate them. - // TODO: Maybe we should have it at least generate the types for the gql - // import? (Or do we already provide that some other way?) - dts: false, - }) -} diff --git a/packages/testing/src/api/vitest/vite-plugin-cedar-vitest-api-config.ts b/packages/testing/src/api/vitest/vite-plugin-cedar-vitest-api-config.ts deleted file mode 100644 index d8f102d930..0000000000 --- a/packages/testing/src/api/vitest/vite-plugin-cedar-vitest-api-config.ts +++ /dev/null @@ -1,35 +0,0 @@ -import path from 'node:path' - -import type { Plugin } from 'vite' - -import { getEnvVarDefinitions, getPaths } from '@cedarjs/project-config' - -export function cedarVitestApiConfigPlugin(): Plugin { - return { - name: 'cedar-vitest-plugin', - config: () => { - return { - define: getEnvVarDefinitions(), - ssr: { - noExternal: ['@cedarjs/testing'], - }, - resolve: { - alias: { - src: getPaths().api.src, - }, - }, - test: { - environment: path.join(import.meta.dirname, 'CedarApiVitestEnv.js'), - // fileParallelism: false, - // fileParallelism doesn't work with vitest projects (which is what - // we're using in the root vitest.config.ts). As a workaround we set - // poolOptions instead, which also shouldn't work, but was suggested - // by Vitest team member AriPerkkio (Hiroshi's answer didn't work). - // https://github.com/vitest-dev/vitest/discussions/7416 - poolOptions: { forks: { singleFork: true } }, - setupFiles: [path.join(import.meta.dirname, 'vitest-api.setup.js')], - }, - } - }, - } -} diff --git a/packages/testing/src/api/vitest/vite-plugin-track-db-imports.ts b/packages/testing/src/api/vitest/vite-plugin-track-db-imports.ts deleted file mode 100644 index fa32f7f127..0000000000 --- a/packages/testing/src/api/vitest/vite-plugin-track-db-imports.ts +++ /dev/null @@ -1,33 +0,0 @@ -import type { Plugin } from 'vite' - -export function trackDbImportsPlugin(): Plugin { - return { - name: 'db-import-tracker', - transform(code, id) { - // This regex and code content check could potentially match other files. - // It's very unlikely, but it is possible. For now this is good enough - if ( - id.match(/\/api\/src\/lib\/db\.(js|ts)$/) && - code.includes('PrismaClient') - ) { - // Inserting the code last (instead of at the top) works nicer with - // sourcemaps - return ( - code + - ` - ;if (typeof globalThis !== "undefined") { - globalThis.__cedarjs_db_imported__ = true; - } else { - throw new Error( - "vite-plugin-track-db-imports: globalThis is undefined. " + - "This is an error with CedarJS" - ); - } - ` - ) - } - - return code - }, - } -} diff --git a/packages/testing/src/api/vitest/vitest-api.setup.ts b/packages/testing/src/api/vitest/vitest-api.setup.ts deleted file mode 100644 index cada8e3d20..0000000000 --- a/packages/testing/src/api/vitest/vitest-api.setup.ts +++ /dev/null @@ -1,413 +0,0 @@ -import fs from 'node:fs' -import path from 'node:path' - -import { afterAll, beforeEach, it, describe, vi, beforeAll } from 'vitest' - -import { getPaths } from '@cedarjs/project-config' -import { defineScenario } from '@cedarjs/testing/api' -import type { DefineScenario } from '@cedarjs/testing/api' - -// Attempt to emulate the request context isolation behavior -// This is a little more complicated than it would necessarily need to be -// but we're following the same pattern as in `@cedarjs/context` -const mockContextStore = vi.hoisted(() => new Map()) -const mockContext = vi.hoisted( - () => - new Proxy( - {}, - { - get: (_target, prop) => { - // Handle toJSON() calls, i.e. JSON.stringify(context) - if (prop === 'toJSON') { - return () => mockContextStore.get('context') - } - - return mockContextStore.get('context')[prop] - }, - set: (_target, prop, value) => { - const ctx = mockContextStore.get('context') - ctx[prop] = value - - return true - }, - }, - ), -) - -vi.mock('@cedarjs/context', () => { - return { - context: mockContext, - setContext: (newContext: unknown) => { - mockContextStore.set('context', newContext) - }, - } -}) - -beforeEach(() => { - mockContextStore.set('context', {}) -}) - -declare global { - // eslint-disable-next-line no-var - var mockCurrentUser: (currentUser: Record | null) => void -} - -globalThis.mockCurrentUser = (currentUser: Record | null) => { - mockContextStore.set('context', { currentUser }) -} - -// ==================================== -// Scenario support -// ==================================== - -declare global { - // eslint-disable-next-line no-var - var defineScenario: DefineScenario - // eslint-disable-next-line no-var - var __cedarjs_db_imported__: boolean -} - -globalThis.defineScenario = defineScenario - -const cedarPaths = getPaths() - -// Error codes thrown by [MySQL, SQLite, Postgres] when foreign key constraint -// fails on DELETE -const FOREIGN_KEY_ERRORS = [1451, 1811, 23503] -const TEARDOWN_CACHE_PATH = path.join( - cedarPaths.generated.base, - 'scenarioTeardown.json', -) -const DEFAULT_SCENARIO = 'standard' -let teardownOrder: (string | null)[] = [] -let originalTeardownOrder: string[] = [] - -type It = typeof it | typeof it.only -type Describe = typeof describe | typeof describe.only -type TestFunc = (scenarioData: any) => any -type DescribeBlock = (getScenario: () => any) => any - -/** - * Wraps "it" or "test", to seed and teardown the scenario after each test - * This one passes scenario data to the test function - */ -function buildScenario(itFunc: It) { - return ( - ...args: - | [scenarioName: string, testName: string, testFunc: TestFunc] - | [testName: string, testFunc: TestFunc] - ) => { - let scenarioName: string - let testName: string - let testFunc: TestFunc - - if (args.length === 3) { - ;[scenarioName, testName, testFunc] = args - } else if (args.length === 2) { - scenarioName = DEFAULT_SCENARIO - ;[testName, testFunc] = args - } else { - throw new Error('scenario() requires 2 or 3 arguments') - } - - return itFunc(testName, async (ctx) => { - const testPath = ctx.task.file.filepath - const { scenario } = await loadScenarios(testPath, scenarioName) - - const scenarioData = await seedScenario(scenario) - try { - const result = await testFunc(scenarioData) - - return result - } finally { - // Make sure to cleanup, even if test fails - await teardown() - } - }) - } -} - -/** - * This creates a describe() block that will seed the scenario ONCE before all tests in the block - * Note that you need to use the getScenario() function to get the data. - */ -function buildDescribeScenario(describeFunc: Describe) { - return ( - ...args: [string, string, DescribeBlock] | [string, DescribeBlock] - ) => { - let scenarioName: string - let describeBlockName: string - let describeBlock: DescribeBlock - - if (args.length === 3) { - ;[scenarioName, describeBlockName, describeBlock] = args - } else if (args.length === 2) { - scenarioName = DEFAULT_SCENARIO - ;[describeBlockName, describeBlock] = args - } else { - throw new Error('describeScenario() requires 2 or 3 arguments') - } - - return describeFunc(describeBlockName, () => { - let scenarioData: Record - - beforeAll(async (ctx) => { - const testPath = ctx.file.filepath - const { scenario } = await loadScenarios(testPath, scenarioName) - scenarioData = await seedScenario(scenario) - }) - - afterAll(async () => { - await teardown() - }) - - const getScenario = () => scenarioData - - describeBlock(getScenario) - }) - } -} - -async function configureTeardown() { - if (!wasDbImported()) { - return - } - - const { getDMMF, getSchema } = await import('@prisma/internals') - - // @NOTE prisma utils are available in cli lib/schemaHelpers - // But avoid importing them, to prevent memory leaks in jest - const datamodel = await getSchema(cedarPaths.api.dbSchema) - const schema = await getDMMF({ datamodel }) - const schemaModels = schema.datamodel.models.map((m) => { - return m.dbName || m.name - }) - - // check if pre-defined delete order already exists and if so, use it to start - if (fs.existsSync(TEARDOWN_CACHE_PATH)) { - teardownOrder = JSON.parse(fs.readFileSync(TEARDOWN_CACHE_PATH).toString()) - } - - // check the number of models in case we've added or removed any models since - // cache was built - if (teardownOrder.length !== schemaModels.length) { - teardownOrder = schemaModels - } - - // keep a copy of the original order to compare against - originalTeardownOrder = deepCopy(teardownOrder) -} - -beforeAll(async () => { - await configureTeardown() -}) - -afterAll(() => { - // afterAll runs after all the tests in a single test file, and then for the - // next test file the code that's injected in src/lib/db.ts in the user's - // project will set this to `true` again if it's imported - globalThis.__cedarjs_db_imported__ = false -}) - -async function teardown() { - if (!wasDbImported()) { - return - } - - const quoteStyle = await getQuoteStyle() - const projectDb = await getProjectDb() - - for (const modelName of teardownOrder) { - try { - const query = `DELETE FROM ${quoteStyle}${modelName}${quoteStyle}` - await projectDb.$executeRawUnsafe(query) - } catch (e) { - console.error('teardown error\n', e) - const match = isErrorWithMessage(e) && e.message.match(/Code: `(\d+)`/) - - if (match && FOREIGN_KEY_ERRORS.includes(parseInt(match[1]))) { - const index = teardownOrder.indexOf(modelName) - teardownOrder[index] = null - teardownOrder.push(modelName) - } else { - throw e - } - } - } - - // remove nulls - teardownOrder = teardownOrder.filter((val) => val) - - // if the order of delete changed, write out the cached file again - if (!isIdenticalArray(teardownOrder, originalTeardownOrder)) { - originalTeardownOrder = deepCopy(teardownOrder) - fs.writeFileSync(TEARDOWN_CACHE_PATH, JSON.stringify(teardownOrder)) - } -} - -const seedScenario = async (scenario: Record) => { - if (scenario) { - const scenarios: Record = {} - - const projectDb = await getProjectDb() - - for (const [model, namedFixtures] of Object.entries(scenario)) { - scenarios[model] = {} - - for (const [name, createArgs] of Object.entries(namedFixtures)) { - if (typeof createArgs === 'function') { - scenarios[model][name] = await projectDb[model].create( - createArgs(scenarios), - ) - } else { - scenarios[model][name] = await projectDb[model].create(createArgs) - } - } - } - - return scenarios - } else { - return {} - } -} - -async function loadScenarios(testPath: string, scenarioName: string) { - const testFileDir = path.parse(testPath) - // e.g. ['comments', 'test'] or ['signup', 'state', 'machine', 'test'] - const testFileNameParts = testFileDir.name.split('.') - const testFilePath = `${testFileDir.dir}/${testFileNameParts - .slice(0, testFileNameParts.length - 1) - .join('.')}.scenarios` - let allScenarios: Record | undefined - let scenario: any - - try { - allScenarios = await import(testFilePath) - } catch (e) { - // ignore error if scenario file not found, otherwise re-throw - if (isErrorWithCode(e)) { - if (e instanceof Error) { - throw e - } else { - console.error('unexpected error type', e) - // eslint-disable-next-line - throw e - } - } - } - - if (allScenarios) { - if (allScenarios[scenarioName]) { - scenario = allScenarios[scenarioName] - } else { - throw new Error( - `UndefinedScenario: There is no scenario named "${scenarioName}" in ` + - `${testFilePath}.{js,ts}`, - ) - } - } - return { scenario } -} - -/** - * All these hooks run in the VM/Context that the test runs in since we're using - * "setupAfterEnv". - * There's a new context for each test-suite i.e. each test file - * - * Doing this means if the db isn't used in the current test context, no need to - * do any of the teardown logic - allowing simple tests to run faster - * At the same time, if the db is used, disconnecting it in this context - * prevents connection limit errors. - * Just disconnecting db in jest-preset is not enough, because the Prisma client - * is created in a different context. - */ -const wasDbImported = () => { - return Boolean(globalThis.__cedarjs_db_imported__) -} - -let quoteStyle: string -// determine what kind of quotes are needed around table names in raw SQL -async function getQuoteStyle() { - const { getConfig: getPrismaConfig, getSchema } = await import( - '@prisma/internals' - ) - - // @NOTE prisma utils are available in cli lib/schemaHelpers - // But avoid importing them, to prevent memory leaks in jest - const datamodel = await getSchema(cedarPaths.api.dbSchema) - - if (!quoteStyle) { - const config = await getPrismaConfig({ - datamodel, - }) - - switch (config.datasources?.[0]?.provider) { - case 'mysql': - quoteStyle = '`' - break - default: - quoteStyle = '"' - } - } - - return quoteStyle -} - -async function getProjectDb() { - const libDb = await import(`${cedarPaths.api.lib}/db`) - - return libDb.db -} - -function isIdenticalArray(a: unknown[], b: unknown[]) { - return JSON.stringify(a) === JSON.stringify(b) -} - -function deepCopy(obj: unknown[]) { - return JSON.parse(JSON.stringify(obj)) -} - -function isErrorWithMessage(e: unknown): e is { message: string } { - return ( - !!e && - typeof e === 'object' && - 'message' in e && - typeof e.message === 'string' - ) -} - -function isErrorWithCode(e: unknown): e is { code: string } { - return ( - !!e && typeof e === 'object' && 'code' in e && typeof e.code === 'string' - ) -} - -// These types are still in `global.d.ts` to work with Jest -// -// interface GlobalScenario { -// (...args: [string, string, TestFunc] | [string, TestFunc]): ReturnType -// only?: ( -// ...args: [string, string, TestFunc] | [string, TestFunc] -// ) => ReturnType -// } - -// interface DescribeScenario { -// ( -// ...args: [string, string, DescribeBlock] | [string, DescribeBlock] -// ): ReturnType -// only?: ( -// ...args: [string, string, DescribeBlock] | [string, DescribeBlock] -// ) => ReturnType -// } - -// declare global { -// // eslint-disable-next-line no-var -// var scenario: GlobalScenario -// // eslint-disable-next-line no-var -// var describeScenario: DescribeScenario -// } - -globalThis.scenario = buildScenario(it) -globalThis.scenario.only = buildScenario(it.only) -globalThis.describeScenario = buildDescribeScenario(describe) -globalThis.describeScenario.only = buildDescribeScenario(describe.only) diff --git a/packages/testing/src/web/MockProviders.tsx b/packages/testing/src/web/MockProviders.tsx index fdff96138a..e480b43b94 100644 --- a/packages/testing/src/web/MockProviders.tsx +++ b/packages/testing/src/web/MockProviders.tsx @@ -1,10 +1,13 @@ +/** + * NOTE: This module should not contain any nodejs functionality, + * because it's also used by Storybook in the browser. + */ import React from 'react' import { LocationProvider } from '@cedarjs/router' import { RedwoodProvider } from '@cedarjs/web' import { RedwoodApolloProvider } from '@cedarjs/web/apollo' -import { UserRoutes as VitestUserRoutes } from './globRoutesImporter.js' import { useAuth } from './mockAuth.js' import { MockParamsProvider } from './MockParamsProvider.js' @@ -27,8 +30,7 @@ try { UserRoutes = () => <> } -// TODO(pc): see if there are props we want to allow to be passed into our mock -// provider (e.g. AuthProviderProps) +// TODO(pc): see if there are props we want to allow to be passed into our mock provider (e.g. AuthProviderProps) export const MockProviders: React.FunctionComponent<{ children: React.ReactNode }> = ({ children }) => { @@ -36,7 +38,6 @@ export const MockProviders: React.FunctionComponent<{ - {children} diff --git a/packages/testing/src/web/MockRouter.tsx b/packages/testing/src/web/MockRouter.tsx index 5eeed8fd31..0130c21be3 100644 --- a/packages/testing/src/web/MockRouter.tsx +++ b/packages/testing/src/web/MockRouter.tsx @@ -1,11 +1,3 @@ -// For Vitest we use a plugin to swap out imports from `@cedarjs/router` to this -// file. @see ../vitest/cedarJsRoutesImportTransformPlugin.ts -// -// For Jest we overwrite the default `Router` export using jest-preset. So every -// import of @cedarjs/router will import this Router instead -// -// It's therefore important to reexport everything that we *don't* want to mock. - import type React from 'react' import { flattenAll } from '@cedarjs/router/dist/react-util' @@ -15,12 +7,14 @@ import { flattenAll } from '@cedarjs/router/dist/react-util' import { isValidRoute } from '@cedarjs/router/dist/route-validators' import type { RouterProps } from '@cedarjs/router/dist/router' import { replaceParams } from '@cedarjs/router/dist/util' - export * from '@cedarjs/router/dist/index' export const routes: { [routeName: string]: () => string } = {} /** + * We overwrite the default `Router` export (see jest-preset). So every import + * of @cedarjs/router will import this Router instead + * * This router populates the `routes.()` utility object. */ export const Router: React.FC = ({ children }) => { diff --git a/packages/testing/src/web/globRoutesImporter.ts b/packages/testing/src/web/globRoutesImporter.ts deleted file mode 100644 index 6598a872fb..0000000000 --- a/packages/testing/src/web/globRoutesImporter.ts +++ /dev/null @@ -1,52 +0,0 @@ -/// - -// TODO(storybook): Use this for Storybook as well - -// We're building this file with esbuild, which doesn't understand the vite- -// specific `import.meta.glob` feature. So it'll just leave it as is, which is -// actually exactly what we want. We can't evaluate the glob import when -// building the framework, because at that time we have no idea what user -// project this will be used in or what routes it will have. So instead I tell -// vite to process this file when building the user's project. I do that by -// including @cedarjs/testing in `noExternal` in the default vite config (see -// lib/getMergedConfig.ts in the vite package) - -// We want to find the user's Routes file in web/src/Routes.{tsx,jsx} -// When running tests from the root of the user's project, vite will see the -// path as `/src/Routes.tsx`. When running the tests from the web/ directory, -// vite will see the path as `/Routes.tsx` -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore - Silence the TS error on this line for CJS builds -const defaultExports = import.meta.glob( - ['/src/Routes.{tsx,jsx}', '/Routes.{tsx,jsx}'], - { - import: 'default', - eager: true, - }, -) -const routesFileName = Object.keys(defaultExports)[0] - -if (!routesFileName) { - throw new Error('@cedarjs/testing: No routes found') -} - -const routesFunction = defaultExports[routesFileName] - -if (typeof routesFunction !== 'function') { - throw new Error( - '@cedarjs/testing: Routes file does not export a React component', - ) -} - -/** - * All the routes the user has defined - * - * We render this in the `` component to populate the `routes` - * import from `@cedarjs/router` to make sure code like - * `Home` works in tests - * - * The final piece to this puzzle is to realize that the user's Routes file - * imports `@cedarjs/router`, which we replace to import from '@cedarjs/testing' - * instead using a vite plugin that we only run for vitest and storybook - */ -export const UserRoutes = routesFunction as React.FC diff --git a/packages/testing/src/web/vitest/index.ts b/packages/testing/src/web/vitest/index.ts deleted file mode 100644 index 62f593972b..0000000000 --- a/packages/testing/src/web/vitest/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { cedarJsRouterImportTransformPlugin } from './vite-plugin-cedarjs-router-import-transform.js' -export { createAuthImportTransformPlugin } from './vite-plugin-create-auth-import-transform.js' -export { autoImportsPlugin } from './vite-plugin-auto-import.js' diff --git a/packages/testing/src/web/vitest/vite-plugin-auto-import.ts b/packages/testing/src/web/vitest/vite-plugin-auto-import.ts deleted file mode 100644 index 36797a1929..0000000000 --- a/packages/testing/src/web/vitest/vite-plugin-auto-import.ts +++ /dev/null @@ -1,25 +0,0 @@ -import autoImport from 'unplugin-auto-import/vite' - -export function autoImportsPlugin() { - return autoImport({ - // targets to transform - include: [ - /\.[tj]sx?$/, // .ts, .tsx, .js, .jsx - ], - - // global imports to register - imports: [ - { - '@cedarjs/testing/web': [ - 'mockGraphQLQuery', - 'mockGraphQLMutation', - 'mockCurrentUser', - ], - }, - ], - - // We provide our mocking types elsewhere and so don't need this plugin to - // generate them. - dts: false, - }) -} diff --git a/packages/testing/src/web/vitest/vite-plugin-cedarjs-router-import-transform.ts b/packages/testing/src/web/vitest/vite-plugin-cedarjs-router-import-transform.ts deleted file mode 100644 index b123aa5049..0000000000 --- a/packages/testing/src/web/vitest/vite-plugin-cedarjs-router-import-transform.ts +++ /dev/null @@ -1,24 +0,0 @@ -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore - Silence the TS error on this line for CJS builds -import type { PluginOption } from 'vite' - -/** - * Replace `@cedarjs/router` imports with imports of - * `@cedarjs/testing/web/MockRouter.js` instead - */ -export function cedarJsRouterImportTransformPlugin(): PluginOption { - return { - name: 'cedarjs-router-import-transform', - enforce: 'pre', - transform(code: string, id: string) { - if (id.includes('/web/src')) { - code = code.replace( - /['"]@cedarjs\/router['"]/, - "'@cedarjs/testing/web/MockRouter.js'", - ) - } - - return code - }, - } -} diff --git a/packages/testing/src/web/vitest/vite-plugin-create-auth-import-transform.ts b/packages/testing/src/web/vitest/vite-plugin-create-auth-import-transform.ts deleted file mode 100644 index 819394a54f..0000000000 --- a/packages/testing/src/web/vitest/vite-plugin-create-auth-import-transform.ts +++ /dev/null @@ -1,28 +0,0 @@ -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore - Silence the TS error on this line for CJS builds -import type { PluginOption } from 'vite' - -export function createAuthImportTransformPlugin(): PluginOption { - return { - name: 'create-auth-import-transform', - enforce: 'pre', - transform(code: string, id: string) { - if (id.endsWith('/web/src/auth.ts') || id.endsWith('/web/src/auth.js')) { - // Remove any existing import of `createAuth` without affecting anything - // else. - // This regex defines 4 capture groups, where the second is `createAuth` - // and the third is an (optional) comma for subsequent named imports — - // we want to remove those two. - code = code.replace( - /(^\s*import\s*{[^}]*?)(\bcreateAuth\b)(,?)([^}]*})/m, - '$1$4', - ) - // Add import to mocked `createAuth` at the top of the file. - code = - "import { createAuthentication as createAuth } from '@cedarjs/testing/auth'\n" + - code - } - return code - }, - } -} diff --git a/packages/testing/tsconfig.cjs.json b/packages/testing/tsconfig.cjs.json index 233539bd2c..a660cecf11 100644 --- a/packages/testing/tsconfig.cjs.json +++ b/packages/testing/tsconfig.cjs.json @@ -3,13 +3,5 @@ "compilerOptions": { "outDir": "dist/cjs", "tsBuildInfoFile": "./tsconfig.cjs.tsbuildinfo" - }, - "exclude": [ - "dist", - "node_modules", - "**/__tests__", - "**/__mocks__", - "**/*.test.*", - "src/api/vitest" - ] + } } diff --git a/packages/vite/package.json b/packages/vite/package.json index ec0b4d95b0..34aa00e618 100644 --- a/packages/vite/package.json +++ b/packages/vite/package.json @@ -21,10 +21,6 @@ "default": "./dist/cjs/index.js" } }, - "./api": { - "types": "./dist/api/vite-plugin-cedar-vitest-api-preset.d.ts", - "default": "./dist/api/vite-plugin-cedar-vitest-api-preset.js" - }, "./client": { "require": "./dist/cjs/client.js", "import": "./dist/client.js" @@ -70,7 +66,6 @@ "@cedarjs/internal": "workspace:*", "@cedarjs/project-config": "workspace:*", "@cedarjs/server-store": "workspace:*", - "@cedarjs/testing": "workspace:*", "@cedarjs/web": "workspace:*", "@swc/core": "1.13.3", "@vitejs/plugin-react": "4.3.4", diff --git a/packages/vite/src/api/vite-plugin-cedar-vitest-api-preset.ts b/packages/vite/src/api/vite-plugin-cedar-vitest-api-preset.ts deleted file mode 100644 index 79db805299..0000000000 --- a/packages/vite/src/api/vite-plugin-cedar-vitest-api-preset.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { - autoImportsPlugin, - cedarVitestApiConfigPlugin, - trackDbImportsPlugin, -} from '@cedarjs/testing/api/vitest' - -import { cedarjsDirectoryNamedImportPlugin } from '../plugins/vite-plugin-cedarjs-directory-named-import.js' - -export function cedarVitestPreset() { - return [ - cedarVitestApiConfigPlugin(), - autoImportsPlugin(), - cedarjsDirectoryNamedImportPlugin(), - trackDbImportsPlugin(), - ] -} diff --git a/packages/vite/src/index.ts b/packages/vite/src/index.ts index 60a7dce7af..f4d25123f0 100644 --- a/packages/vite/src/index.ts +++ b/packages/vite/src/index.ts @@ -3,11 +3,6 @@ import type { PluginOption } from 'vite' import { getWebSideDefaultBabelConfig } from '@cedarjs/babel-config' import { getConfig } from '@cedarjs/project-config' -import { - autoImportsPlugin, - cedarJsRouterImportTransformPlugin, - createAuthImportTransformPlugin, -} from '@cedarjs/testing/web/vitest' import { cedarCellTransform } from './plugins/vite-plugin-cedar-cell.js' import { cedarEntryInjectionPlugin } from './plugins/vite-plugin-cedar-entry-injection.js' @@ -29,14 +24,10 @@ export { cedarTransformJsAsJsx } from './plugins/vite-plugin-jsx-loader.js' export { cedarMergedConfig } from './plugins/vite-plugin-merged-config.js' export { cedarSwapApolloProvider } from './plugins/vite-plugin-swap-apollo-provider.js' -type PluginOptions = { - mode?: string | undefined -} - /** * Pre-configured vite plugin, with required config for CedarJS apps. */ -export function cedar({ mode }: PluginOptions = {}): PluginOption[] { +export function cedar(): PluginOption[] { const rwConfig = getConfig() const rscEnabled = rwConfig.experimental?.rsc?.enabled @@ -60,9 +51,6 @@ export function cedar({ mode }: PluginOptions = {}): PluginOption[] { } return [ - mode === 'test' && cedarJsRouterImportTransformPlugin(), - mode === 'test' && createAuthImportTransformPlugin(), - mode === 'test' && autoImportsPlugin(), cedarNodePolyfills(), cedarHtmlEnvPlugin(), cedarEntryInjectionPlugin(), diff --git a/packages/vite/src/lib/getMergedConfig.ts b/packages/vite/src/lib/getMergedConfig.ts index 6f319af527..8110f6e67d 100644 --- a/packages/vite/src/lib/getMergedConfig.ts +++ b/packages/vite/src/lib/getMergedConfig.ts @@ -1,8 +1,8 @@ import path from 'node:path' import type { InputOption } from 'rollup' +import type { ConfigEnv, UserConfig } from 'vite' import { mergeConfig } from 'vite' -import type { ConfigEnv, ViteUserConfig } from 'vitest/config' import type { Config, Paths } from '@cedarjs/project-config' import { @@ -19,7 +19,7 @@ import { * build */ export function getMergedConfig(rwConfig: Config, rwPaths: Paths) { - return (userConfig: ViteUserConfig, env: ConfigEnv): ViteUserConfig => { + return (userConfig: UserConfig, env: ConfigEnv): UserConfig => { let apiHost = process.env.REDWOOD_API_HOST apiHost ??= rwConfig.api.host apiHost ??= process.env.NODE_ENV === 'production' ? '0.0.0.0' : '[::]' @@ -35,7 +35,7 @@ export function getMergedConfig(rwConfig: Config, rwPaths: Paths) { apiPort = rwConfig.api.port } - const defaultRwViteConfig: ViteUserConfig = { + const defaultRwViteConfig: UserConfig = { root: rwPaths.web.src, // @MARK: when we have these aliases, the warnings from the FE server go // away BUT, if you have imports like this: @@ -145,15 +145,6 @@ export function getMergedConfig(rwConfig: Config, rwPaths: Paths) { }, }, }, - ssr: { - // `@cedarjs/testing` is not externalized in order to support - // `import.meta.glob`, which we use in one of the files in the package - noExternal: env.mode === 'test' ? ['@cedarjs/testing'] : [], - }, - test: { - globals: false, - environment: 'jsdom', - }, } return mergeConfig(defaultRwViteConfig, userConfig) diff --git a/packages/vite/tsconfig.build.json b/packages/vite/tsconfig.build.json index 5380b45b53..c5ad0e00c5 100644 --- a/packages/vite/tsconfig.build.json +++ b/packages/vite/tsconfig.build.json @@ -13,7 +13,6 @@ { "path": "../project-config" }, { "path": "../router/tsconfig.build.json" }, { "path": "../server-store" }, - { "path": "../testing/tsconfig.build.json" }, { "path": "../web/tsconfig.build.json" } ] } diff --git a/packages/vite/tsconfig.json b/packages/vite/tsconfig.json index 0b25aa9ec7..4f59899bb5 100644 --- a/packages/vite/tsconfig.json +++ b/packages/vite/tsconfig.json @@ -15,7 +15,6 @@ { "path": "../project-config" }, { "path": "../router/tsconfig.build.json" }, { "path": "../server-store" }, - { "path": "../testing/tsconfig.build.json" }, { "path": "../web/tsconfig.build.json" } ] } diff --git a/yarn.lock b/yarn.lock index ec2dba7e08..9eadea8c0d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3601,7 +3601,6 @@ __metadata: ts-toolbelt: "npm:9.6.0" tsx: "npm:4.20.3" typescript: "npm:5.6.2" - unplugin-auto-import: "npm:19.3.0" vitest: "npm:3.2.4" whatwg-fetch: "npm:3.6.20" languageName: unknown @@ -3636,7 +3635,6 @@ __metadata: "@cedarjs/internal": "workspace:*" "@cedarjs/project-config": "workspace:*" "@cedarjs/server-store": "workspace:*" - "@cedarjs/testing": "workspace:*" "@cedarjs/web": "workspace:*" "@hyrious/esbuild-plugin-commonjs": "npm:0.2.6" "@swc/core": "npm:1.13.3"