Skip to content

Commit

Permalink
Merge branch 'feat/api-skip-prebuild-try-2' of github.com:dac09/redwo…
Browse files Browse the repository at this point in the history
…od into feat/api-skip-prebuild-try-2

* 'feat/api-skip-prebuild-try-2' of github.com:dac09/redwood:
  chore(router): Miniscule fixes
  chore(router): Move useMatch to its own file (redwoodjs#9770)
  Allow GraphQL documents to be typed with a TypedDocumentNode for fully-typed result and variables objects (redwoodjs#9619)
  chore(ci): retry detectChanges on error, and await result (redwoodjs#9772)
  • Loading branch information
dac09 committed Dec 29, 2023
2 parents 2377778 + ef5ddb1 commit fa52105
Show file tree
Hide file tree
Showing 12 changed files with 196 additions and 179 deletions.
18 changes: 12 additions & 6 deletions .github/actions/detect-changes/detectChanges.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { hasCodeChanges } from './cases/code_changes.mjs'
import { rscChanged } from './cases/rsc.mjs'
import { ssrChanged } from './cases/ssr.mjs'

const getPrNumber = (githubRef) => {
const getPrNumber = () => {
// Example GITHUB_REF refs/pull/9544/merge
const result = /refs\/pull\/(\d+)\/merge/g.exec(process.env.GITHUB_REF)

Expand Down Expand Up @@ -48,7 +48,7 @@ async function getChangedFiles(page = 1, retries = 0) {
const githubToken = process.env.GITHUB_TOKEN
const url = `https://api.github.com/repos/redwoodjs/redwood/pulls/${prNumber}/files?per_page=100&page=${page}`
let resp
let files
let files = []

try {
resp = await fetch(url, {
Expand All @@ -59,6 +59,12 @@ async function getChangedFiles(page = 1, retries = 0) {
},
})

if (!resp.ok) {
console.log()
console.error('Response not ok')
console.log('resp', resp)
}

const json = await resp.json()
files = json.map((file) => file.filename) || []
} catch (e) {
Expand All @@ -70,8 +76,8 @@ async function getChangedFiles(page = 1, retries = 0) {

return []
} else {
await new Promise((resolve) => setTimeout(resolve, 3000))
getChangedFiles(page, ++retries)
await new Promise((resolve) => setTimeout(resolve, 3000 * retries))
files = await getChangedFiles(page, ++retries)
}
}

Expand Down Expand Up @@ -103,8 +109,8 @@ async function main() {

if (changedFiles.length === 0) {
console.log(
'No changed files found. Something must have gone wrong. Fall back to ' +
'running all tests.'
'No changed files found. Something must have gone wrong. Falling back ' +
'to running all tests.'
)
core.setOutput('onlydocs', false)
core.setOutput('rsc', true)
Expand Down
3 changes: 1 addition & 2 deletions packages/internal/src/__tests__/clientPreset.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,13 @@ describe('Generate client preset', () => {

const { clientPresetFiles } = await generateClientPreset()

expect(clientPresetFiles).toHaveLength(6)
expect(clientPresetFiles).toHaveLength(5)
const expectedEndings = [
'/fragment-masking.ts',
'/index.ts',
'/gql.ts',
'/graphql.ts',
'/persisted-documents.json',
'/types.d.ts',
]

const foundEndings = expectedEndings.filter((expectedEnding) =>
Expand Down
21 changes: 0 additions & 21 deletions packages/internal/src/generate/clientPreset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,27 +32,6 @@ export const generateClientPreset = async () => {
schema: getPaths().generated.schema,
documents: documentsGlob,
generates: {
// should be graphql.d.ts
[`${getPaths().web.base}/types/types.d.ts`]: {
plugins: ['typescript', 'typescript-operations', 'add'],
config: {
enumsAsTypes: true,
content: 'import { Prisma } from "@prisma/client"',
placement: 'prepend',
scalars: {
// We need these, otherwise these scalars are mapped to any
BigInt: 'number',
// @Note: DateTime fields can be valid Date-strings, or the Date object in the api side. They're always strings on the web side.
DateTime: 'string',
Date: 'string',
JSON: 'Prisma.JsonValue',
JSONObject: 'Prisma.JsonObject',
Time: 'string',
},
namingConvention: 'keep', // to allow camelCased query names
omitOperationSuffix: true,
},
},
[`${getPaths().web.src}/graphql/`]: {
preset: 'client',
presetConfig: {
Expand Down
77 changes: 1 addition & 76 deletions packages/router/src/__tests__/links.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ import React from 'react'

import { render } from '@testing-library/react'

import { NavLink, useMatch, Link } from '../links'
import { NavLink } from '../links'
import { LocationProvider } from '../location'
import { flattenSearchParams } from '../util'

function createDummyLocation(pathname: string, search = '') {
return {
Expand Down Expand Up @@ -279,77 +278,3 @@ describe('<NavLink />', () => {
expect(getByText(/Dunder Mifflin/)).not.toHaveClass('activeTest')
})
})

describe('useMatch', () => {
const MyLink = ({
to,
...rest
}: React.ComponentPropsWithoutRef<typeof Link>) => {
const [pathname, queryString] = to.split('?')
const matchInfo = useMatch(pathname, {
searchParams: flattenSearchParams(queryString),
})

return (
<Link
to={to}
style={{ color: matchInfo.match ? 'green' : 'red' }}
{...rest}
/>
)
}

it('returns a match on the same pathname', () => {
const mockLocation = createDummyLocation('/dunder-mifflin')

const { getByText } = render(
<LocationProvider location={mockLocation}>
<MyLink to="/dunder-mifflin">Dunder Mifflin</MyLink>
</LocationProvider>
)

expect(getByText(/Dunder Mifflin/)).toHaveStyle('color: green')
})

it('returns a match on the same pathname with search parameters', () => {
const mockLocation = createDummyLocation(
'/search-params',
'?page=1&tab=main'
)

const { getByText } = render(
<LocationProvider location={mockLocation}>
<MyLink to={`/search-params?tab=main&page=1`}>Dunder Mifflin</MyLink>
</LocationProvider>
)

expect(getByText(/Dunder Mifflin/)).toHaveStyle('color: green')
})

it('does NOT receive active class on different path', () => {
const mockLocation = createDummyLocation('/staples')

const { getByText } = render(
<LocationProvider location={mockLocation}>
<MyLink to="/dunder-mifflin">Dunder Mifflin</MyLink>
</LocationProvider>
)

expect(getByText(/Dunder Mifflin/)).toHaveStyle('color: red')
})

it('does NOT receive active class on the same pathname with different parameters', () => {
const mockLocation = createDummyLocation(
'/search-params',
'?tab=main&page=1'
)

const { getByText } = render(
<LocationProvider location={mockLocation}>
<MyLink to={`/search-params?page=2&tab=main`}>Dunder Mifflin</MyLink>
</LocationProvider>
)

expect(getByText(/Dunder Mifflin/)).toHaveStyle('color: red')
})
})
100 changes: 100 additions & 0 deletions packages/router/src/__tests__/useMatch.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import React from 'react'

import { render } from '@testing-library/react'

import { Link } from '../links'
import { LocationProvider } from '../location'
import { useMatch } from '../useMatch'
import { flattenSearchParams } from '../util'

function createDummyLocation(pathname: string, search = '') {
return {
pathname,
hash: '',
host: '',
hostname: '',
href: '',
ancestorOrigins: null,
assign: () => null,
reload: () => null,
replace: () => null,
origin: '',
port: '',
protocol: '',
search,
}
}

describe('useMatch', () => {
const MyLink = ({
to,
...rest
}: React.ComponentPropsWithoutRef<typeof Link>) => {
const [pathname, queryString] = to.split('?')
const matchInfo = useMatch(pathname, {
searchParams: flattenSearchParams(queryString),
})

return (
<Link
to={to}
style={{ color: matchInfo.match ? 'green' : 'red' }}
{...rest}
/>
)
}

it('returns a match on the same pathname', () => {
const mockLocation = createDummyLocation('/dunder-mifflin')

const { getByText } = render(
<LocationProvider location={mockLocation}>
<MyLink to="/dunder-mifflin">Dunder Mifflin</MyLink>
</LocationProvider>
)

expect(getByText(/Dunder Mifflin/)).toHaveStyle('color: green')
})

it('returns a match on the same pathname with search parameters', () => {
const mockLocation = createDummyLocation(
'/search-params',
'?page=1&tab=main'
)

const { getByText } = render(
<LocationProvider location={mockLocation}>
<MyLink to={`/search-params?tab=main&page=1`}>Dunder Mifflin</MyLink>
</LocationProvider>
)

expect(getByText(/Dunder Mifflin/)).toHaveStyle('color: green')
})

it('does NOT receive active class on different path', () => {
const mockLocation = createDummyLocation('/staples')

const { getByText } = render(
<LocationProvider location={mockLocation}>
<MyLink to="/dunder-mifflin">Dunder Mifflin</MyLink>
</LocationProvider>
)

expect(getByText(/Dunder Mifflin/)).toHaveStyle('color: red')
})

it('does NOT receive active class on the same pathname with different parameters', () => {
const mockLocation = createDummyLocation(
'/search-params',
'?tab=main&page=1'
)

const { getByText } = render(
<LocationProvider location={mockLocation}>
<MyLink to={`/search-params?page=2&tab=main`}>Dunder Mifflin</MyLink>
</LocationProvider>
)

expect(getByText(/Dunder Mifflin/)).toHaveStyle('color: red')
})
})
3 changes: 2 additions & 1 deletion packages/router/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// latter of which has closely inspired some of this code).

export { navigate, back } from './history'
export { Link, NavLink, useMatch, Redirect } from './links'
export { Link, NavLink, Redirect } from './links'
export { useLocation, LocationProvider } from './location'
export {
usePageLoadingContext,
Expand All @@ -20,6 +20,7 @@ export { default as RouteFocus } from './route-focus'
export * from './route-focus'
export * from './useRouteName'
export * from './useRoutePaths'
export * from './useMatch'

export { parseSearch, getRouteRegexAndParams, matchPath } from './util'

Expand Down
66 changes: 4 additions & 62 deletions packages/router/src/links.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,67 +2,9 @@ import { forwardRef, useEffect } from 'react'

import type { NavigateOptions } from './history'
import { navigate } from './history'
import { useLocation } from './location'
import { flattenSearchParams, matchPath } from './util'

type FlattenSearchParams = ReturnType<typeof flattenSearchParams>
type UseMatchOptions = {
searchParams?: FlattenSearchParams
matchSubPaths?: boolean
}

/**
* Returns an object of { match: boolean; params: Record<string, unknown>; }
* if the path matches the current location match will be true.
* Params will be an object of the matched params, if there are any.
*
* Provide searchParams options to match the current location.search
*
* This is useful for components that need to know "active" state, e.g.
* <NavLink>.
*
* Examples:
*
* Match search params key existence
* const match = useMatch('/about', { searchParams: ['category', 'page'] })
*
* Match search params key and value
* const match = useMatch('/items', { searchParams: [{page: 2}, {category: 'book'}] })
*
* Mix match
* const match = useMatch('/list', { searchParams: [{page: 2}, 'gtm'] })
*
* Match sub paths
* const match = useMatch('/product', { matchSubPaths: true })
*
*/
const useMatch = (pathname: string, options?: UseMatchOptions) => {
const location = useLocation()
if (!location) {
return { match: false }
}

if (options?.searchParams) {
const locationParams = new URLSearchParams(location.search)
const hasUnmatched = options.searchParams.some((param) => {
if (typeof param === 'string') {
return !locationParams.has(param)
} else {
return Object.keys(param).some(
(key) => param[key] != locationParams.get(key)
)
}
})

if (hasUnmatched) {
return { match: false }
}
}

return matchPath(pathname, location.pathname, {
matchSubPaths: options?.matchSubPaths,
})
}
import { useMatch } from './useMatch'
import type { FlattenSearchParams } from './util'
import { flattenSearchParams } from './util'

interface LinkProps {
to: string
Expand Down Expand Up @@ -187,4 +129,4 @@ const Redirect = ({ to, options }: RedirectProps) => {
return null
}

export { Link, NavLink, useMatch, Redirect }
export { Link, NavLink, Redirect }
Loading

0 comments on commit fa52105

Please sign in to comment.