Skip to content

Commit

Permalink
Merge branch 'main' into dependabot/npm_and_yarn/store2-2.14.4
Browse files Browse the repository at this point in the history
  • Loading branch information
Tobbe authored Jan 26, 2025
2 parents 9edaeb6 + 49dbc77 commit c173071
Show file tree
Hide file tree
Showing 13 changed files with 300 additions and 35 deletions.
3 changes: 3 additions & 0 deletions .changesets/11380.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
- feat(router): Add option to not reset scroll to the top on navigate/link (#11380) by @guitheengineer

You can now do ``navigate(`?id=${id}`, { scroll: false })`` and ``<Link to={`?id=${id}`} options={{ scroll: false }} />`` to not reset the scroll to the top when navigating.
11 changes: 11 additions & 0 deletions .changesets/11931.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
- feat(cli): Support generating sdls for models with only an id and relation (#11931) by @Tobbe

It's now possible to generate SDL files for models that look like this

```prisma
// This would be seeded with available car brands
model CarBrand {
brand String @id
cars Car[]
}
```
6 changes: 6 additions & 0 deletions docs/docs/router.md
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,9 @@ const SomePage = () => {

The browser keeps track of the browsing history in a stack. By default when you navigate to a new page a new item is pushed to the history stack. But sometimes you want to replace the top item on the stack instead of appending to the stack. This is how you do that in Redwood: `navigate(routes.home(), { replace: true })`. As you can see you need to pass an options object as the second parameter to `navigate` with the option `replace` set to `true`.

By default `navigate` will scroll to the top after navigating to a new route (except for hash param changes), we can prevent this behavior by setting the `scroll` option to false:
`navigate(routes.home(), { scroll: false })`

### back

Going back is as easy as using the `back()` function that's exported from the router.
Expand Down Expand Up @@ -675,6 +678,9 @@ const SomePage = () => <Redirect to={routes.home()} />

In addition to the `to` prop, `<Redirect />` also takes an `options` prop. This is the same as [`navigate()`](#navigate)'s second argument: `navigate(_, { replace: true })`. We can use it to _replace_ the top item of the browser history stack (instead of pushing a new one). This is how you use it to have this effect: `<Redirect to={routes.home()} options={{ replace: true }}/>`.

By default redirect will scroll to the top after navigating to a new route (except for hash param changes), we can prevent this behavior by setting the `scroll` option to false:
`<Redirect to={routes.home()} options={{ scroll: false }}/>`

## Code-splitting

By default, the router will code-split on every Page, creating a separate lazy-loaded bundle for each. When navigating from page to page, the router will wait until the new Page module is loaded before re-rendering, thus preventing the "white-flash" effect.
Expand Down
2 changes: 1 addition & 1 deletion packages/api-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"test:watch": "vitest watch"
},
"dependencies": {
"@fastify/multipart": "8.3.0",
"@fastify/multipart": "8.3.1",
"@fastify/url-data": "5.4.0",
"@redwoodjs/context": "workspace:*",
"@redwoodjs/fastify-web": "workspace:*",
Expand Down
23 changes: 13 additions & 10 deletions packages/cli/src/commands/generate/scaffold/scaffold.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,6 @@ import {
builder as serviceBuilder,
} from '../service/service'

// note a better way to do this is in https://github.com/redwoodjs/redwood/pull/3783/files
const NON_EDITABLE_COLUMNS = ['id', 'createdAt', 'updatedAt']
// Any assets that should not trigger an overwrite error and require a --force
const SKIPPABLE_ASSETS = ['scaffold.css']
const PACKAGE_SET = 'Set'
Expand All @@ -59,12 +57,19 @@ const getIdName = (model) => {
return model.fields.find((field) => field.isId)?.name
}

const filterAutoGeneratedColumnsForScaffold = (column) => {
const isAutoGeneratedColumnForScaffold = (column) => {
const autoGeneratedFunctions = ['now', 'autoincrement']
return !(

const columnDefaultFunction =
typeof column.default === 'object' && 'name' in column.default
? column.default?.name
: ''

return (
column.isId ||
column.isUpdatedAt ||
autoGeneratedFunctions.includes(column?.default?.name)
column.name === 'createdAt' ||
autoGeneratedFunctions.includes(columnDefaultFunction)
)
}

Expand Down Expand Up @@ -400,11 +405,9 @@ const modelRelatedVariables = (model) => {
isRelationalField,
}
})
const editableColumns = columns
.filter((column) => {
return NON_EDITABLE_COLUMNS.indexOf(column.name) === -1
})
.filter(filterAutoGeneratedColumnsForScaffold)
const editableColumns = columns.filter(
(column) => !isAutoGeneratedColumnForScaffold(column),
)
const fieldsToImport = Object.keys(
editableColumns.reduce((accumulator, column) => {
accumulator[column.component] = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1572,6 +1572,66 @@ exports[`with graphql documentations > in typescript mode > creates a single wor
"
`;
exports[`without graphql documentations > in javascript mode > create an sdl file for model with only id and relation 1`] = `
"export const schema = gql\`
type Car {
id: Int!
brand: String!
carBrand: CarBrand!
}
type Query {
cars: [Car!]! @requireAuth
car(id: Int!): Car @requireAuth
}
input CreateCarInput {
brand: String!
}
input UpdateCarInput {
brand: String
}
type Mutation {
createCar(input: CreateCarInput!): Car! @requireAuth
updateCar(id: Int!, input: UpdateCarInput!): Car! @requireAuth
deleteCar(id: Int!): Car! @requireAuth
}
\`
"
`;
exports[`without graphql documentations > in javascript mode > create an sdl file for model with only id and relation 2`] = `
"export const schema = gql\`
type CarBrand {
brand: String!
cars: [Car]!
}
type Query {
carBrands: [CarBrand!]! @requireAuth
carBrand(brand: String!): CarBrand @requireAuth
}
input CreateCarBrandInput {
brand: String!
}
input UpdateCarBrandInput {
brand: String!
}
type Mutation {
createCarBrand(input: CreateCarBrandInput!): CarBrand! @requireAuth
updateCarBrand(brand: String!, input: UpdateCarBrandInput!): CarBrand!
@requireAuth
deleteCarBrand(brand: String!): CarBrand! @requireAuth
}
\`
"
`;
exports[`without graphql documentations > in javascript mode > creates a multi word sdl file 1`] = `
"export const schema = gql\`
type UserProfile {
Expand Down Expand Up @@ -1809,6 +1869,66 @@ exports[`without graphql documentations > in javascript mode > creates a single
"
`;
exports[`without graphql documentations > in typescript mode > create an sdl file for model with only id and relation 1`] = `
"export const schema = gql\`
type Car {
id: Int!
brand: String!
carBrand: CarBrand!
}
type Query {
cars: [Car!]! @requireAuth
car(id: Int!): Car @requireAuth
}
input CreateCarInput {
brand: String!
}
input UpdateCarInput {
brand: String
}
type Mutation {
createCar(input: CreateCarInput!): Car! @requireAuth
updateCar(id: Int!, input: UpdateCarInput!): Car! @requireAuth
deleteCar(id: Int!): Car! @requireAuth
}
\`
"
`;
exports[`without graphql documentations > in typescript mode > create an sdl file for model with only id and relation 2`] = `
"export const schema = gql\`
type CarBrand {
brand: String!
cars: [Car]!
}
type Query {
carBrands: [CarBrand!]! @requireAuth
carBrand(brand: String!): CarBrand @requireAuth
}
input CreateCarBrandInput {
brand: String!
}
input UpdateCarBrandInput {
brand: String!
}
type Mutation {
createCarBrand(input: CreateCarBrandInput!): CarBrand! @requireAuth
updateCarBrand(brand: String!, input: UpdateCarBrandInput!): CarBrand!
@requireAuth
deleteCarBrand(brand: String!): CarBrand! @requireAuth
}
\`
"
`;
exports[`without graphql documentations > in typescript mode > creates a multi word sdl file 1`] = `
"export const schema = gql\`
type UserProfile {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,18 @@ enum Color {
}

model CustomData {
id Int @id @default(autoincrement())
data String
id Int @id @default(autoincrement())
data String
}

// This would be seeded with available car brands
model CarBrand {
brand String @id
cars Car[]
}

model Car {
id Int @id @default(autoincrement())
brand String
carBrand CarBrand @relation(fields: [brand], references: [brand])
}
33 changes: 33 additions & 0 deletions packages/cli/src/commands/generate/sdl/__tests__/sdl.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,37 @@ const itCreatesAnSDLFileWithByteDefinitions = (baseArgs = {}) => {
})
}

const itCreatesAnSslFileForModelWithOnlyIdAndRelation = (baseArgs = {}) => {
test('create an sdl file for model with only id and relation', async () => {
const files = {
...(await sdl.files({
...baseArgs,
name: 'Car',
crud: true,
})),
...(await sdl.files({
...baseArgs,
name: 'CarBrand',
crud: true,
})),
}
const extension = extensionForBaseArgs(baseArgs)

expect(
files[
path.normalize(`/path/to/project/api/src/graphql/cars.sdl.${extension}`)
],
).toMatchSnapshot()
expect(
files[
path.normalize(
`/path/to/project/api/src/graphql/carBrands.sdl.${extension}`,
)
],
).toMatchSnapshot()
})
}

describe('without graphql documentations', () => {
describe('in javascript mode', () => {
const baseArgs = { ...getDefaultArgs(sdl.defaults), tests: true }
Expand All @@ -249,6 +280,7 @@ describe('without graphql documentations', () => {
itCreatesAnSDLFileWithEnumDefinitions(baseArgs)
itCreatesAnSDLFileWithJsonDefinitions(baseArgs)
itCreatesAnSDLFileWithByteDefinitions(baseArgs)
itCreatesAnSslFileForModelWithOnlyIdAndRelation(baseArgs)
})

describe('in typescript mode', () => {
Expand All @@ -267,6 +299,7 @@ describe('without graphql documentations', () => {
itCreatesAnSDLFileWithEnumDefinitions(baseArgs)
itCreatesAnSDLFileWithJsonDefinitions(baseArgs)
itCreatesAnSDLFileWithByteDefinitions(baseArgs)
itCreatesAnSslFileForModelWithOnlyIdAndRelation(baseArgs)
})
})

Expand Down
17 changes: 10 additions & 7 deletions packages/cli/src/commands/generate/sdl/sdl.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,13 @@ const modelFieldToSDL = ({
Bytes: 'Byte',
}

const fieldContent = `${field.name}: ${field.isList ? '[' : ''}${
prismaTypeToGraphqlType[field.type] || field.type
}${field.isList ? ']' : ''}${
(field.isRequired && required) | field.isList ? '!' : ''
}`
const gqlType = prismaTypeToGraphqlType[field.type] || field.type
const type = field.isList ? `[${gqlType}]` : gqlType
// lists and id fields are always required (lists can be empty, that's fine)
const isRequired =
(field.isRequired && required) || field.isList || field.isId
const fieldContent = `${field.name}: ${type}${isRequired ? '!' : ''}`

if (docs) {
return addFieldGraphQLComment(field, fieldContent)
} else {
Expand All @@ -98,7 +100,8 @@ const inputSDL = (model, required, types = {}, docs = false) => {
.filter((field) => {
const idField = model.fields.find((field) => field.isId)

if (idField) {
// Only ignore the id field if it has a default value
if (idField && idField.default) {
ignoredFields.push(idField.name)
}

Expand Down Expand Up @@ -162,7 +165,7 @@ const idName = (model, crud) => {
const sdlFromSchemaModel = async (name, crud, docs = false) => {
const model = await getSchema(name)

// get models for user-defined types referenced
// get models for referenced user-defined types
const types = (
await Promise.all(
model.fields
Expand Down
53 changes: 53 additions & 0 deletions packages/router/src/__tests__/routeScrollReset.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,57 @@ describe('Router scroll reset', () => {

expect(globalThis.scrollTo).not.toHaveBeenCalled()
})

it('when scroll option is false, does NOT reset on location/path change', async () => {
act(() =>
navigate(
// @ts-expect-error - AvailableRoutes built in project only
routes.page2(),
{
scroll: false,
},
),
)

screen.getByText('Page 2')

expect(globalThis.scrollTo).toHaveBeenCalledTimes(0)
})

it('when scroll option is false, does NOT reset on location/path and queryChange change', async () => {
act(() =>
navigate(
// @ts-expect-error - AvailableRoutes built in project only
routes.page2({
tab: 'three',
}),
{
scroll: false,
},
),
)

screen.getByText('Page 2')

expect(globalThis.scrollTo).toHaveBeenCalledTimes(0)
})

it('when scroll option is false, does NOT reset scroll on query params (search) change on the same page', async () => {
act(() =>
// We're staying on page 1, but changing the query params
navigate(
// @ts-expect-error - AvailableRoutes built in project only
routes.page1({
queryParam1: 'foo',
}),
{
scroll: false,
},
),
)

screen.getByText('Page 1')

expect(globalThis.scrollTo).toHaveBeenCalledTimes(0)
})
})
Loading

0 comments on commit c173071

Please sign in to comment.