diff --git a/__fixtures__/fragment-test-project/.redwood/schema.graphql b/__fixtures__/fragment-test-project/.redwood/schema.graphql new file mode 100644 index 000000000000..1cd2ea849879 --- /dev/null +++ b/__fixtures__/fragment-test-project/.redwood/schema.graphql @@ -0,0 +1,102 @@ +""" +Use to check whether or not a user is authenticated and is associated +with an optional set of roles. +""" +directive @requireAuth(roles: [String]) on FIELD_DEFINITION + +"""Use to skip authentication checks and allow public access.""" +directive @skipAuth on FIELD_DEFINITION + +scalar BigInt + +scalar Date + +scalar DateTime + +type Fruit implements Grocery { + id: ID! + + """Seedless is only for fruits""" + isSeedless: Boolean + name: String! + nutrients: String + price: Int! + quantity: Int! + region: String! + + """Ripeness is only for fruits""" + ripenessIndicators: String + stall: Stall! +} + +union Groceries = Fruit | Vegetable + +interface Grocery { + id: ID! + name: String! + nutrients: String + price: Int! + quantity: Int! + region: String! + stall: Stall! +} + +scalar JSON + +scalar JSONObject + +"""About the Redwood queries.""" +type Query { + fruitById(id: ID!): Fruit + fruits: [Fruit!]! + groceries: [Groceries!]! + + """Fetches the Redwood root schema.""" + redwood: Redwood + stallById(id: ID!): Stall + stalls: [Stall!]! + vegetableById(id: ID!): Vegetable + vegetables: [Vegetable!]! +} + +""" +The RedwoodJS Root Schema + +Defines details about RedwoodJS such as the current user and version information. +""" +type Redwood { + """The current user.""" + currentUser: JSON + + """The version of Prisma.""" + prismaVersion: String + + """The version of Redwood.""" + version: String +} + +type Stall { + fruits: [Fruit] + id: ID! + name: String! + stallNumber: String! + vegetables: [Vegetable] +} + +scalar Time + +type Vegetable implements Grocery { + id: ID! + + """Pickled is only for vegetables""" + isPickled: Boolean + name: String! + nutrients: String + price: Int! + quantity: Int! + region: String! + stall: Stall! + + """Veggie Family is only for vegetables""" + vegetableFamily: String +} \ No newline at end of file diff --git a/__fixtures__/fragment-test-project/README.md b/__fixtures__/fragment-test-project/README.md new file mode 100644 index 000000000000..861af7d9e65e --- /dev/null +++ b/__fixtures__/fragment-test-project/README.md @@ -0,0 +1,4 @@ +# Project to Test GraphQL Fragments + +* Has unions and interfaces to test: https://www.apollographql.com/docs/react/data/fragments/#using-fragments-with-unions-and-interfaces +* Generates "possible types" for Apollo Client, see: https://www.apollographql.com/docs/react/data/fragments/#defining-possibletypes-manually diff --git a/__fixtures__/fragment-test-project/api/db/schema.prisma b/__fixtures__/fragment-test-project/api/db/schema.prisma new file mode 100644 index 000000000000..20b2df54078c --- /dev/null +++ b/__fixtures__/fragment-test-project/api/db/schema.prisma @@ -0,0 +1,36 @@ +datasource db { + provider = "sqlite" + url = env("DATABASE_URL") + directUrl = env("DIRECT_URL") +} + +generator client { + provider = "prisma-client-js" + binaryTargets = "native" +} + +model Stall { + id String @id @default(cuid()) + name String + stallNumber String @unique + produce Produce[] +} + +model Produce { + id String @id @default(cuid()) + name String @unique + quantity Int + price Int + nutrients String? + region String + /// Available only for fruits + isSeedless Boolean? + /// Available only for fruits + ripenessIndicators String? + /// Available only for vegetables + vegetableFamily String? + /// Available only for vegetables + isPickled Boolean? + stall Stall @relation(fields: [stallId], references: [id], onDelete: Cascade) + stallId String +} diff --git a/__fixtures__/fragment-test-project/api/jest.config.js b/__fixtures__/fragment-test-project/api/jest.config.js new file mode 100644 index 000000000000..932fc82dce93 --- /dev/null +++ b/__fixtures__/fragment-test-project/api/jest.config.js @@ -0,0 +1,8 @@ +// More info at https://redwoodjs.com/docs/project-configuration-dev-test-build + +const config = { + rootDir: '../', + preset: '@redwoodjs/testing/config/jest/api', +} + +module.exports = config diff --git a/__fixtures__/fragment-test-project/api/package.json b/__fixtures__/fragment-test-project/api/package.json new file mode 100644 index 000000000000..4a05d2980285 --- /dev/null +++ b/__fixtures__/fragment-test-project/api/package.json @@ -0,0 +1,9 @@ +{ + "name": "api", + "version": "0.0.0", + "private": true, + "dependencies": { + "@redwoodjs/api": "6.2.0", + "@redwoodjs/graphql-server": "6.2.0" + } +} diff --git a/__fixtures__/fragment-test-project/api/server.config.js b/__fixtures__/fragment-test-project/api/server.config.js new file mode 100644 index 000000000000..73dca9225a3e --- /dev/null +++ b/__fixtures__/fragment-test-project/api/server.config.js @@ -0,0 +1,52 @@ +/** + * This file allows you to configure the Fastify Server settings + * used by the RedwoodJS dev server. + * + * It also applies when running RedwoodJS with `yarn rw serve`. + * + * For the Fastify server options that you can set, see: + * https://www.fastify.io/docs/latest/Reference/Server/#factory + * + * Examples include: logger settings, timeouts, maximum payload limits, and more. + * + * Note: This configuration does not apply in a serverless deploy. + */ + +/** @type {import('fastify').FastifyServerOptions} */ +const config = { + requestTimeout: 15_000, + logger: { + // Note: If running locally using `yarn rw serve` you may want to adjust + // the default non-development level to `info` + level: process.env.NODE_ENV === 'development' ? 'debug' : 'warn', + }, +} + +/** + * You can also register Fastify plugins and additional routes for the API and Web sides + * in the configureFastify function. + * + * This function has access to the Fastify instance and options, such as the side + * (web, api, or proxy) that is being configured and other settings like the apiRootPath + * of the functions endpoint. + * + * Note: This configuration does not apply in a serverless deploy. + */ + +/** @type {import('@redwoodjs/api-server/dist/types').FastifySideConfigFn} */ +const configureFastify = async (fastify, options) => { + if (options.side === 'api') { + fastify.log.trace({ custom: { options } }, 'Configuring api side') + } + + if (options.side === 'web') { + fastify.log.trace({ custom: { options } }, 'Configuring web side') + } + + return fastify +} + +module.exports = { + config, + configureFastify, +} diff --git a/__fixtures__/fragment-test-project/api/src/.keep b/__fixtures__/fragment-test-project/api/src/.keep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/__fixtures__/fragment-test-project/api/src/directives/requireAuth/requireAuth.test.ts b/__fixtures__/fragment-test-project/api/src/directives/requireAuth/requireAuth.test.ts new file mode 100644 index 000000000000..0f01aa367a85 --- /dev/null +++ b/__fixtures__/fragment-test-project/api/src/directives/requireAuth/requireAuth.test.ts @@ -0,0 +1,18 @@ +import { mockRedwoodDirective, getDirectiveName } from '@redwoodjs/testing/api' + +import requireAuth from './requireAuth' + +describe('requireAuth directive', () => { + it('declares the directive sdl as schema, with the correct name', () => { + expect(requireAuth.schema).toBeTruthy() + expect(getDirectiveName(requireAuth.schema)).toBe('requireAuth') + }) + + it('requireAuth has stub implementation. Should not throw when current user', () => { + // If you want to set values in context, pass it through e.g. + // mockRedwoodDirective(requireAuth, { context: { currentUser: { id: 1, name: 'Lebron McGretzky' } }}) + const mockExecution = mockRedwoodDirective(requireAuth, { context: {} }) + + expect(mockExecution).not.toThrowError() + }) +}) diff --git a/__fixtures__/fragment-test-project/api/src/directives/requireAuth/requireAuth.ts b/__fixtures__/fragment-test-project/api/src/directives/requireAuth/requireAuth.ts new file mode 100644 index 000000000000..77b31a70ae07 --- /dev/null +++ b/__fixtures__/fragment-test-project/api/src/directives/requireAuth/requireAuth.ts @@ -0,0 +1,25 @@ +import gql from 'graphql-tag' + +import type { ValidatorDirectiveFunc } from '@redwoodjs/graphql-server' +import { createValidatorDirective } from '@redwoodjs/graphql-server' + +import { requireAuth as applicationRequireAuth } from 'src/lib/auth' + +export const schema = gql` + """ + Use to check whether or not a user is authenticated and is associated + with an optional set of roles. + """ + directive @requireAuth(roles: [String]) on FIELD_DEFINITION +` + +type RequireAuthValidate = ValidatorDirectiveFunc<{ roles?: string[] }> + +const validate: RequireAuthValidate = ({ directiveArgs }) => { + const { roles } = directiveArgs + applicationRequireAuth({ roles }) +} + +const requireAuth = createValidatorDirective(schema, validate) + +export default requireAuth diff --git a/__fixtures__/fragment-test-project/api/src/directives/skipAuth/skipAuth.test.ts b/__fixtures__/fragment-test-project/api/src/directives/skipAuth/skipAuth.test.ts new file mode 100644 index 000000000000..88d99a56eab2 --- /dev/null +++ b/__fixtures__/fragment-test-project/api/src/directives/skipAuth/skipAuth.test.ts @@ -0,0 +1,10 @@ +import { getDirectiveName } from '@redwoodjs/testing/api' + +import skipAuth from './skipAuth' + +describe('skipAuth directive', () => { + it('declares the directive sdl as schema, with the correct name', () => { + expect(skipAuth.schema).toBeTruthy() + expect(getDirectiveName(skipAuth.schema)).toBe('skipAuth') + }) +}) diff --git a/__fixtures__/fragment-test-project/api/src/directives/skipAuth/skipAuth.ts b/__fixtures__/fragment-test-project/api/src/directives/skipAuth/skipAuth.ts new file mode 100644 index 000000000000..e85b94ae8b89 --- /dev/null +++ b/__fixtures__/fragment-test-project/api/src/directives/skipAuth/skipAuth.ts @@ -0,0 +1,16 @@ +import gql from 'graphql-tag' + +import { createValidatorDirective } from '@redwoodjs/graphql-server' + +export const schema = gql` + """ + Use to skip authentication checks and allow public access. + """ + directive @skipAuth on FIELD_DEFINITION +` + +const skipAuth = createValidatorDirective(schema, () => { + return +}) + +export default skipAuth diff --git a/__fixtures__/fragment-test-project/api/src/functions/graphql.ts b/__fixtures__/fragment-test-project/api/src/functions/graphql.ts new file mode 100644 index 000000000000..f395c3b0f852 --- /dev/null +++ b/__fixtures__/fragment-test-project/api/src/functions/graphql.ts @@ -0,0 +1,19 @@ +import { createGraphQLHandler } from '@redwoodjs/graphql-server' + +import directives from 'src/directives/**/*.{js,ts}' +import sdls from 'src/graphql/**/*.sdl.{js,ts}' +import services from 'src/services/**/*.{js,ts}' + +import { db } from 'src/lib/db' +import { logger } from 'src/lib/logger' + +export const handler = createGraphQLHandler({ + loggerConfig: { logger, options: {} }, + directives, + sdls, + services, + onException: () => { + // Disconnect from your database with an unhandled exception. + db.$disconnect() + }, +}) diff --git a/__fixtures__/fragment-test-project/api/src/graphql/.keep b/__fixtures__/fragment-test-project/api/src/graphql/.keep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/__fixtures__/fragment-test-project/api/src/graphql/groceries.sdl.ts b/__fixtures__/fragment-test-project/api/src/graphql/groceries.sdl.ts new file mode 100644 index 000000000000..0870d7daeb54 --- /dev/null +++ b/__fixtures__/fragment-test-project/api/src/graphql/groceries.sdl.ts @@ -0,0 +1,49 @@ +export const schema = gql` + interface Grocery { + id: ID! + name: String! + quantity: Int! + price: Int! + nutrients: String + stall: Stall! + region: String! + } + + type Fruit implements Grocery { + id: ID! + name: String! + quantity: Int! + price: Int! + nutrients: String + stall: Stall! + region: String! + "Seedless is only for fruits" + isSeedless: Boolean + "Ripeness is only for fruits" + ripenessIndicators: String + } + + type Vegetable implements Grocery { + id: ID! + name: String! + quantity: Int! + price: Int! + nutrients: String + stall: Stall! + region: String! + "Veggie Family is only for vegetables" + vegetableFamily: String + "Pickled is only for vegetables" + isPickled: Boolean + } + + union Groceries = Fruit | Vegetable + + type Query { + groceries: [Groceries!]! @skipAuth + fruits: [Fruit!]! @skipAuth + fruitById(id: ID!): Fruit @skipAuth + vegetables: [Vegetable!]! @skipAuth + vegetableById(id: ID!): Vegetable @skipAuth + } +` diff --git a/__fixtures__/fragment-test-project/api/src/graphql/stalls.sdl.ts b/__fixtures__/fragment-test-project/api/src/graphql/stalls.sdl.ts new file mode 100644 index 000000000000..ff159aa2df9a --- /dev/null +++ b/__fixtures__/fragment-test-project/api/src/graphql/stalls.sdl.ts @@ -0,0 +1,14 @@ +export const schema = gql` + type Stall { + id: ID! + stallNumber: String! + name: String! + fruits: [Fruit] + vegetables: [Vegetable] + } + + type Query { + stalls: [Stall!]! @skipAuth + stallById(id: ID!): Stall @skipAuth + } +` diff --git a/__fixtures__/fragment-test-project/api/src/lib/auth.ts b/__fixtures__/fragment-test-project/api/src/lib/auth.ts new file mode 100644 index 000000000000..f98fe93a960c --- /dev/null +++ b/__fixtures__/fragment-test-project/api/src/lib/auth.ts @@ -0,0 +1,25 @@ +/** + * Once you are ready to add authentication to your application + * you'll build out requireAuth() with real functionality. For + * now we just return `true` so that the calls in services + * have something to check against, simulating a logged + * in user that is allowed to access that service. + * + * See https://redwoodjs.com/docs/authentication for more info. + */ +export const isAuthenticated = () => { + return true +} + +export const hasRole = ({ roles }) => { + return roles !== undefined +} + +// This is used by the redwood directive +// in ./api/src/directives/requireAuth + +// Roles are passed in by the requireAuth directive if you have auth setup +// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars +export const requireAuth = ({ roles }) => { + return isAuthenticated() +} diff --git a/__fixtures__/fragment-test-project/api/src/lib/db.ts b/__fixtures__/fragment-test-project/api/src/lib/db.ts new file mode 100644 index 000000000000..6743f0c11d9a --- /dev/null +++ b/__fixtures__/fragment-test-project/api/src/lib/db.ts @@ -0,0 +1,21 @@ +// See https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-client/constructor +// for options. + +import { PrismaClient } from '@prisma/client' + +import { emitLogLevels, handlePrismaLogging } from '@redwoodjs/api/logger' + +import { logger } from './logger' + +/* + * Instance of the Prisma Client + */ +export const db = new PrismaClient({ + log: emitLogLevels(['info', 'warn', 'error', 'query']), +}) + +handlePrismaLogging({ + db, + logger, + logLevels: ['info', 'warn', 'error', 'query'], +}) diff --git a/__fixtures__/fragment-test-project/api/src/lib/logger.ts b/__fixtures__/fragment-test-project/api/src/lib/logger.ts new file mode 100644 index 000000000000..150a309767c5 --- /dev/null +++ b/__fixtures__/fragment-test-project/api/src/lib/logger.ts @@ -0,0 +1,17 @@ +import { createLogger } from '@redwoodjs/api/logger' + +/** + * Creates a logger with RedwoodLoggerOptions + * + * These extend and override default LoggerOptions, + * can define a destination like a file or other supported pino log transport stream, + * and sets whether or not to show the logger configuration settings (defaults to false) + * + * @param RedwoodLoggerOptions + * + * RedwoodLoggerOptions have + * @param {options} LoggerOptions - defines how to log, such as redaction and format + * @param {string | DestinationStream} destination - defines where to log, such as a transport stream or file + * @param {boolean} showConfig - whether to display logger configuration on initialization + */ +export const logger = createLogger({}) diff --git a/__fixtures__/fragment-test-project/api/src/services/.keep b/__fixtures__/fragment-test-project/api/src/services/.keep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/__fixtures__/fragment-test-project/api/src/services/fruit.ts b/__fixtures__/fragment-test-project/api/src/services/fruit.ts new file mode 100644 index 000000000000..b94ff9c3796d --- /dev/null +++ b/__fixtures__/fragment-test-project/api/src/services/fruit.ts @@ -0,0 +1,29 @@ +import type { QueryResolvers } from 'types/graphql' + +import { db } from 'src/lib/db' +import { logger } from 'src/lib/logger' + +const isFruit = { isSeedless: { not: null }, ripenessIndicators: { not: null } } + +export const fruits: QueryResolvers['fruits'] = async () => { + const result = await db.produce.findMany({ + where: { ...isFruit }, + include: { stall: true }, + orderBy: { name: 'asc' }, + }) + + logger.debug({ result }, 'frroooooots') + + return result +} + +export const fruitById: QueryResolvers['fruitById'] = async ({ id }) => { + const result = await db.produce.findUnique({ + where: { id, ...isFruit }, + include: { stall: true }, + }) + + logger.debug({ result }, 'frroot') + + return result +} diff --git a/__fixtures__/fragment-test-project/api/src/services/groceries.ts b/__fixtures__/fragment-test-project/api/src/services/groceries.ts new file mode 100644 index 000000000000..8a34942d27a3 --- /dev/null +++ b/__fixtures__/fragment-test-project/api/src/services/groceries.ts @@ -0,0 +1,30 @@ +import { db } from 'src/lib/db' + +const isFruit = (grocery) => { + return grocery.isSeedless !== null && grocery.ripenessIndicators !== null +} + +export const groceries = async () => { + const result = await db.produce.findMany({ + include: { stall: true }, + orderBy: { name: 'asc' }, + }) + + const avail = result.map((grocery) => { + if (isFruit) { + return { + ...grocery, + __typename: 'Fruit', + __resolveType: 'Fruit', + } + } else { + return { + ...grocery, + __typename: 'Vegetable', + __resolveType: 'Vegetable', + } + } + }) + + return avail +} diff --git a/__fixtures__/fragment-test-project/api/src/services/stalls.ts b/__fixtures__/fragment-test-project/api/src/services/stalls.ts new file mode 100644 index 000000000000..aa7d417fd10c --- /dev/null +++ b/__fixtures__/fragment-test-project/api/src/services/stalls.ts @@ -0,0 +1,21 @@ +import type { QueryResolvers } from 'types/graphql' + +import { db } from 'src/lib/db' + +export const stalls: QueryResolvers['stalls'] = async () => { + const result = await db.stall.findMany({ + include: { produce: true }, + orderBy: { name: 'asc' }, + }) + + return result +} + +export const stallById: QueryResolvers['stallById'] = async ({ id }) => { + const result = await db.stall.findUnique({ + where: { id }, + include: { produce: true }, + }) + + return result +} diff --git a/__fixtures__/fragment-test-project/api/src/services/vegetables.ts b/__fixtures__/fragment-test-project/api/src/services/vegetables.ts new file mode 100644 index 000000000000..7caf39d47fea --- /dev/null +++ b/__fixtures__/fragment-test-project/api/src/services/vegetables.ts @@ -0,0 +1,22 @@ +import type { QueryResolvers } from 'types/graphql' + +import { db } from 'src/lib/db' + +const isVegetable = { vegetableFamily: { not: null }, isPickled: { not: null } } + +export const vegetables: QueryResolvers['vegetables'] = async () => { + return await db.produce.findMany({ + where: { ...isVegetable }, + include: { stall: true }, + orderBy: { name: 'asc' }, + }) +} + +export const vegetableById: QueryResolvers['vegetableById'] = async ({ + id, +}) => { + return await db.produce.findUnique({ + where: { id, ...isVegetable }, + include: { stall: true }, + }) +} diff --git a/__fixtures__/fragment-test-project/api/tsconfig.json b/__fixtures__/fragment-test-project/api/tsconfig.json new file mode 100644 index 000000000000..fcbbf9872e43 --- /dev/null +++ b/__fixtures__/fragment-test-project/api/tsconfig.json @@ -0,0 +1,36 @@ +{ + "compilerOptions": { + "noEmit": true, + "allowJs": true, + "esModuleInterop": true, + "target": "esnext", + "module": "esnext", + "moduleResolution": "node", + "skipLibCheck": false, + "baseUrl": "./", + "rootDirs": [ + "./src", + "../.redwood/types/mirror/api/src" + ], + "paths": { + "src/*": [ + "./src/*", + "../.redwood/types/mirror/api/src/*" + ], + "types/*": ["./types/*", "../types/*"], + "@redwoodjs/testing": ["../node_modules/@redwoodjs/testing/api"] + }, + "typeRoots": [ + "../node_modules/@types", + "./node_modules/@types" + ], + "types": ["jest"], + "jsx": "react-jsx" + }, + "include": [ + "src", + "../.redwood/types/includes/all-*", + "../.redwood/types/includes/api-*", + "../types" + ] +} diff --git a/__fixtures__/fragment-test-project/graphql.config.js b/__fixtures__/fragment-test-project/graphql.config.js new file mode 100644 index 000000000000..e6c0ef53af71 --- /dev/null +++ b/__fixtures__/fragment-test-project/graphql.config.js @@ -0,0 +1,6 @@ +const { getPaths } = require('@redwoodjs/internal') + +module.exports = { + schema: getPaths().generated.schema, + documents: './web/src/**/!(*.d).{ts,tsx,js,jsx}', +} diff --git a/__fixtures__/fragment-test-project/redwood.toml b/__fixtures__/fragment-test-project/redwood.toml new file mode 100644 index 000000000000..11270a20be22 --- /dev/null +++ b/__fixtures__/fragment-test-project/redwood.toml @@ -0,0 +1,16 @@ +# This file contains the configuration settings for your Redwood app. +# This file is also what makes your Redwood app a Redwood app. +# If you remove it and try to run `yarn rw dev`, you'll get an error. +# +# For the full list of options, see the "App Configuration: redwood.toml" doc: +# https://redwoodjs.com/docs/app-configuration-redwood-toml + +[web] + title = "Redwood App" + port = 8910 + apiUrl = "/.redwood/functions" # you can customise graphql and dbauth urls individually too: see https://redwoodjs.com/docs/app-configuration-redwood-toml#api-paths + includeEnvironmentVariables = [] # any ENV vars that should be available to the web side, see https://redwoodjs.com/docs/environment-variables#web +[api] + port = 8911 +[browser] + open = true diff --git a/__fixtures__/fragment-test-project/scripts/.keep b/__fixtures__/fragment-test-project/scripts/.keep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/__fixtures__/fragment-test-project/web/.editorconfig b/__fixtures__/fragment-test-project/web/.editorconfig new file mode 100644 index 000000000000..ae10a5cce3b2 --- /dev/null +++ b/__fixtures__/fragment-test-project/web/.editorconfig @@ -0,0 +1,10 @@ +# editorconfig.org +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/__fixtures__/fragment-test-project/web/jest.config.js b/__fixtures__/fragment-test-project/web/jest.config.js new file mode 100644 index 000000000000..0e54869ebdcb --- /dev/null +++ b/__fixtures__/fragment-test-project/web/jest.config.js @@ -0,0 +1,8 @@ +// More info at https://redwoodjs.com/docs/project-configuration-dev-test-build + +const config = { + rootDir: '../', + preset: '@redwoodjs/testing/config/jest/web', +} + +module.exports = config diff --git a/__fixtures__/fragment-test-project/web/package.json b/__fixtures__/fragment-test-project/web/package.json new file mode 100644 index 000000000000..1bffdb2f6902 --- /dev/null +++ b/__fixtures__/fragment-test-project/web/package.json @@ -0,0 +1,24 @@ +{ + "name": "web", + "version": "0.0.0", + "private": true, + "browserslist": { + "development": [ + "last 1 version" + ], + "production": [ + "defaults" + ] + }, + "dependencies": { + "@redwoodjs/forms": "6.2.0", + "@redwoodjs/router": "6.2.0", + "@redwoodjs/web": "6.2.0", + "prop-types": "15.8.1", + "react": "18.2.0", + "react-dom": "18.2.0" + }, + "devDependencies": { + "@redwoodjs/vite": "6.2.0" + } +} diff --git a/__fixtures__/fragment-test-project/web/src/App.tsx b/__fixtures__/fragment-test-project/web/src/App.tsx new file mode 100644 index 000000000000..f60aa1dfc22a --- /dev/null +++ b/__fixtures__/fragment-test-project/web/src/App.tsx @@ -0,0 +1,24 @@ +import { FatalErrorBoundary, RedwoodProvider } from '@redwoodjs/web' +import { RedwoodApolloProvider } from '@redwoodjs/web/apollo' + +import introspection from 'src/graphql/possibleTypes' +import FatalErrorPage from 'src/pages/FatalErrorPage' +import Routes from 'src/Routes' + +const App = () => ( + + + + + + + +) + +export default App diff --git a/__fixtures__/fragment-test-project/web/src/Routes.tsx b/__fixtures__/fragment-test-project/web/src/Routes.tsx new file mode 100644 index 000000000000..71fc2e0f7098 --- /dev/null +++ b/__fixtures__/fragment-test-project/web/src/Routes.tsx @@ -0,0 +1,21 @@ +// In this file, all Page components from 'src/pages` are auto-imported. Nested +// directories are supported, and should be uppercase. Each subdirectory will be +// prepended onto the component name. +// +// Examples: +// +// 'src/pages/HomePage/HomePage.js' -> HomePage +// 'src/pages/Admin/BooksPage/BooksPage.js' -> AdminBooksPage + +import { Router, Route } from '@redwoodjs/router' + +const Routes = () => { + return ( + + + + + ) +} + +export default Routes diff --git a/__fixtures__/fragment-test-project/web/src/components/.keep b/__fixtures__/fragment-test-project/web/src/components/.keep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/__fixtures__/fragment-test-project/web/src/components/Card/Card.tsx b/__fixtures__/fragment-test-project/web/src/components/Card/Card.tsx new file mode 100644 index 000000000000..8894a447b29c --- /dev/null +++ b/__fixtures__/fragment-test-project/web/src/components/Card/Card.tsx @@ -0,0 +1,9 @@ +const Card = ({ children }) => { + return ( +
+ {children} +
+ ) +} + +export default Card diff --git a/__fixtures__/fragment-test-project/web/src/components/Fruit.tsx b/__fixtures__/fragment-test-project/web/src/components/Fruit.tsx new file mode 100644 index 000000000000..8cdbb8f4bdd7 --- /dev/null +++ b/__fixtures__/fragment-test-project/web/src/components/Fruit.tsx @@ -0,0 +1,39 @@ +import type { Fruit } from 'types/graphql' + +import { registerFragment } from '@redwoodjs/web/apollo' + +import Card from 'src/components/Card/Card' +import Stall from 'src/components/Stall' + +const { useRegisteredFragment } = registerFragment( + gql` + fragment Fruit_info on Fruit { + id + name + isSeedless + ripenessIndicators + stall { + ...Stall_info + } + } + ` +) + +const Fruit = ({ id }: { id: string }) => { + const { data: fruit, complete } = useRegisteredFragment(id) + + console.log(fruit) + + return ( + complete && ( + +

Fruit Name: {fruit.name}

+

Seeds? {fruit.isSeedless ? 'Yes' : 'No'}

+

Ripeness: {fruit.ripenessIndicators}

+ +
+ ) + ) +} + +export default Fruit diff --git a/__fixtures__/fragment-test-project/web/src/components/Produce.tsx b/__fixtures__/fragment-test-project/web/src/components/Produce.tsx new file mode 100644 index 000000000000..fe3798c80578 --- /dev/null +++ b/__fixtures__/fragment-test-project/web/src/components/Produce.tsx @@ -0,0 +1,30 @@ +import type { Produce } from 'types/graphql' + +import { registerFragment } from '@redwoodjs/web/apollo' + +import Card from 'src/components/Card/Card' + +const { useRegisteredFragment } = registerFragment( + gql` + fragment Produce_info on Produce { + id + name + } + ` +) + +const Produce = ({ id }: { id: string }) => { + const { data, complete } = useRegisteredFragment(id) + + console.log('>>>>>>>>>>>Produce', data) + + return ( + complete && ( + +

Produce Name: {data.name}

+
+ ) + ) +} + +export default Produce diff --git a/__fixtures__/fragment-test-project/web/src/components/Stall.tsx b/__fixtures__/fragment-test-project/web/src/components/Stall.tsx new file mode 100644 index 000000000000..3244714500bd --- /dev/null +++ b/__fixtures__/fragment-test-project/web/src/components/Stall.tsx @@ -0,0 +1,28 @@ +import type { Stall } from 'types/graphql' + +import { registerFragment } from '@redwoodjs/web/apollo' + +const { useRegisteredFragment } = registerFragment( + gql` + fragment Stall_info on Stall { + id + name + } + ` +) + +const Stall = ({ id }: { id: string }) => { + const { data, complete } = useRegisteredFragment(id) + + console.log(data) + + return ( + complete && ( +
+

Stall Name: {data.name}

+
+ ) + ) +} + +export default Stall diff --git a/__fixtures__/fragment-test-project/web/src/components/Vegetable.tsx b/__fixtures__/fragment-test-project/web/src/components/Vegetable.tsx new file mode 100644 index 000000000000..461cb9377f48 --- /dev/null +++ b/__fixtures__/fragment-test-project/web/src/components/Vegetable.tsx @@ -0,0 +1,39 @@ +import type { Vegetable } from 'types/graphql' + +import { registerFragment } from '@redwoodjs/web/apollo' + +import Card from 'src/components/Card/Card' +import Stall from 'src/components/Stall' + +const { useRegisteredFragment } = registerFragment( + gql` + fragment Vegetable_info on Vegetable { + id + name + vegetableFamily + isPickled + stall { + ...Stall_info + } + } + ` +) + +const Vegetable = ({ id }: { id: string }) => { + const { data: vegetable, complete } = useRegisteredFragment(id) + + console.log(vegetable) + + return ( + complete && ( + +

Vegetable Name: {vegetable.name}

+

Pickled? {vegetable.isPickled ? 'Yes' : 'No'}

+

Family: {vegetable.vegetableFamily}

+ +
+ ) + ) +} + +export default Vegetable diff --git a/__fixtures__/fragment-test-project/web/src/entry.client.tsx b/__fixtures__/fragment-test-project/web/src/entry.client.tsx new file mode 100644 index 000000000000..ffee44f85869 --- /dev/null +++ b/__fixtures__/fragment-test-project/web/src/entry.client.tsx @@ -0,0 +1,17 @@ +import { hydrateRoot, createRoot } from 'react-dom/client' + +import App from './App' +/** + * When `#redwood-app` isn't empty then it's very likely that you're using + * prerendering. So React attaches event listeners to the existing markup + * rather than replacing it. + * https://reactjs.org/docs/react-dom-client.html#hydrateroot + */ +const redwoodAppElement = document.getElementById('redwood-app') + +if (redwoodAppElement.children?.length > 0) { + hydrateRoot(redwoodAppElement, ) +} else { + const root = createRoot(redwoodAppElement) + root.render() +} diff --git a/__fixtures__/fragment-test-project/web/src/graphql/possibleTypes.ts b/__fixtures__/fragment-test-project/web/src/graphql/possibleTypes.ts new file mode 100644 index 000000000000..79a376225982 --- /dev/null +++ b/__fixtures__/fragment-test-project/web/src/graphql/possibleTypes.ts @@ -0,0 +1,20 @@ + + export interface PossibleTypesResultData { + possibleTypes: { + [key: string]: string[] + } + } + const result: PossibleTypesResultData = { + "possibleTypes": { + "Groceries": [ + "Fruit", + "Vegetable" + ], + "Grocery": [ + "Fruit", + "Vegetable" + ] + } +}; + export default result; + \ No newline at end of file diff --git a/__fixtures__/fragment-test-project/web/src/index.css b/__fixtures__/fragment-test-project/web/src/index.css new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/__fixtures__/fragment-test-project/web/src/index.html b/__fixtures__/fragment-test-project/web/src/index.html new file mode 100644 index 000000000000..e240b8eb8c54 --- /dev/null +++ b/__fixtures__/fragment-test-project/web/src/index.html @@ -0,0 +1,15 @@ + + + + + + + + + + + +
+ + + diff --git a/__fixtures__/fragment-test-project/web/src/layouts/.keep b/__fixtures__/fragment-test-project/web/src/layouts/.keep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/__fixtures__/fragment-test-project/web/src/pages/FatalErrorPage/FatalErrorPage.tsx b/__fixtures__/fragment-test-project/web/src/pages/FatalErrorPage/FatalErrorPage.tsx new file mode 100644 index 000000000000..b2bb436f8ed0 --- /dev/null +++ b/__fixtures__/fragment-test-project/web/src/pages/FatalErrorPage/FatalErrorPage.tsx @@ -0,0 +1,57 @@ +// This page will be rendered when an error makes it all the way to the top of the +// application without being handled by a Javascript catch statement or React error +// boundary. +// +// You can modify this page as you wish, but it is important to keep things simple to +// avoid the possibility that it will cause its own error. If it does, Redwood will +// still render a generic error page, but your users will prefer something a bit more +// thoughtful :) + +// This import will be automatically removed when building for production +import { DevFatalErrorPage } from '@redwoodjs/web/dist/components/DevFatalErrorPage' + +export default DevFatalErrorPage || + (() => ( +
+