-
Notifications
You must be signed in to change notification settings - Fork 5.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add type tests for Expo Router
- Loading branch information
1 parent
04a8359
commit b253fc4
Showing
8 changed files
with
1,156 additions
and
201 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 2 additions & 0 deletions
2
packages/@expo/cli/src/start/server/type-generation/__typetests__/fixtures/.gitignore
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
** | ||
!.gitignore |
49 changes: 49 additions & 0 deletions
49
packages/@expo/cli/src/start/server/type-generation/__typetests__/generateFixtures.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import { writeFile } from 'fs/promises'; | ||
import { join } from 'path'; | ||
|
||
import { getTemplateString } from '../routes'; | ||
|
||
type Fixture = { | ||
staticRoutes: string[]; | ||
dynamicRoutes: string[]; | ||
dynamicRouteTemplates: string[]; | ||
}; | ||
|
||
const fixtures: Record<string, Fixture> = { | ||
basic: { | ||
staticRoutes: ['/apple', '/banana'], | ||
dynamicRoutes: [ | ||
'/colors/${SingleRoutePart<T>}', | ||
'/animals/${CatchAllRoutePart<T>}', | ||
'/mix/${SingleRoutePart<T>}/${SingleRoutePart<T>}/${CatchAllRoutePart<T>}', | ||
], | ||
dynamicRouteTemplates: [ | ||
'/colors/[color]', | ||
'/animals/[...animal]', | ||
'/mix/[fruit]/[color]/[...animals]', | ||
], | ||
}, | ||
}; | ||
|
||
export default async function () { | ||
await Promise.all( | ||
Object.entries(fixtures).map(async ([key, value]) => { | ||
const template = getTemplateString( | ||
new Set(value.staticRoutes), | ||
new Set(value.dynamicRoutes), | ||
new Set(value.dynamicRouteTemplates) | ||
) | ||
// The Template produces a global module .d.ts declaration | ||
// These replacements turn it into a local module | ||
.replace(/declare module "expo-router" {|(^}\\Z)/, '') | ||
.replaceAll(/export function/g, 'export declare function') | ||
.replaceAll(/export const/g, 'export declare const') | ||
// Remove the last `}` | ||
.slice(0, -2); | ||
|
||
return writeFile(join(__dirname, './fixtures/', key + '.ts'), template); | ||
}) | ||
); | ||
|
||
console.log('done'); | ||
} |
154 changes: 154 additions & 0 deletions
154
packages/@expo/cli/src/start/server/type-generation/__typetests__/route.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
import { expectType, expectError } from 'tsd-lite'; | ||
|
||
import { useGlobalSearchParams, useSegments, useRouter, useSearchParams } from './fixtures/basic'; | ||
|
||
// eslint-disable-next-line react-hooks/rules-of-hooks | ||
const router = useRouter(); | ||
|
||
describe('router.push()', () => { | ||
// router.push will return void when the type matches, otherwise it should error | ||
|
||
describe('href', () => { | ||
it('will error on non-urls', () => { | ||
expectError(router.push('should-error')); | ||
}); | ||
|
||
it('can accept an absolute url', () => { | ||
expectType<void>(router.push('/apple')); | ||
expectType<void>(router.push('/banana')); | ||
}); | ||
|
||
it('can accept a ANY relative url', () => { | ||
// We only type-check absolute urls | ||
expectType<void>(router.push('./this/work/but/is/not/valid')); | ||
}); | ||
|
||
it('works for dynamic urls', () => { | ||
expectType<void>(router.push('/colors/blue')); | ||
expectError(router.push('/colors/blue/test')); | ||
|
||
expectType<void>(router.push('/animals/bear')); | ||
expectType<void>(router.push('/animals/bear/cat/dog')); | ||
|
||
// This will fail because it is missing params | ||
expectError(router.push('/mix/apple')); | ||
expectError(router.push('/mix/apple/cat')); | ||
expectType<void>(router.push('/mix/apple/blue/cat/dog')); | ||
}); | ||
|
||
it('can accept any external url', () => { | ||
expectType<void>(router.push('http://expo.dev')); | ||
}); | ||
}); | ||
|
||
describe('HrefObject', () => { | ||
it('will error on non-urls', () => { | ||
expectError(router.push({ pathname: 'should-error' })); | ||
}); | ||
|
||
it('can accept an absolute url', () => { | ||
expectType<void>(router.push({ pathname: '/apple' })); | ||
expectType<void>(router.push({ pathname: '/banana' })); | ||
}); | ||
|
||
it('can accept a ANY relative url', () => { | ||
// We only type-check absolute urls | ||
expectType<void>(router.push({ pathname: './this/work/but/is/not/valid' })); | ||
}); | ||
|
||
it('works for dynamic urls', () => { | ||
expectType<void>( | ||
router.push({ | ||
pathname: '/colors/[color]', | ||
params: { color: 'blue' }, | ||
}) | ||
); | ||
}); | ||
|
||
it('requires a valid pathname', () => { | ||
expectError( | ||
router.push({ | ||
pathname: '/colors/[invalid]', | ||
params: { color: 'blue' }, | ||
}) | ||
); | ||
}); | ||
|
||
it('requires a valid param', () => { | ||
expectError( | ||
router.push({ | ||
pathname: '/colors/[color]', | ||
params: { invalid: 'blue' }, | ||
}) | ||
); | ||
}); | ||
|
||
it('works for catch all routes', () => { | ||
expectType<void>( | ||
router.push({ | ||
pathname: '/animals/[...animal]', | ||
params: { animal: ['cat', 'dog'] }, | ||
}) | ||
); | ||
}); | ||
|
||
it('requires an array for catch all routes', () => { | ||
expectError( | ||
router.push({ | ||
pathname: '/animals/[...animal]', | ||
params: { animal: 'cat' }, | ||
}) | ||
); | ||
}); | ||
|
||
it('works for mixed routes', () => { | ||
expectType<void>( | ||
router.push({ | ||
pathname: '/mix/[fruit]/[color]/[...animals]', | ||
params: { color: 'red', fruit: 'apple', animals: ['cat', 'dog'] }, | ||
}) | ||
); | ||
}); | ||
|
||
it('requires all params in mixed routes', () => { | ||
expectError( | ||
router.push({ | ||
pathname: '/mix/[fruit]/[color]/[...animals]', | ||
params: { color: 'red', animals: ['cat', 'dog'] }, | ||
}) | ||
); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('useSearchParams', () => { | ||
expectType<Record<'color', string>>(useSearchParams<'/colors/[color]'>()); | ||
expectType<Record<'color', string>>(useSearchParams<Record<'color', string>>()); | ||
expectError(useSearchParams<'/invalid'>()); | ||
expectError(useSearchParams<Record<'custom', string>>()); | ||
}); | ||
|
||
describe('useGlobalSearchParams', () => { | ||
expectType<Record<'color', string>>(useGlobalSearchParams<'/colors/[color]'>()); | ||
expectType<Record<'color', string>>(useGlobalSearchParams<Record<'color', string>>()); | ||
expectError(useGlobalSearchParams<'/invalid'>()); | ||
expectError(useGlobalSearchParams<Record<'custom', string>>()); | ||
}); | ||
|
||
describe('useSegments', () => { | ||
it('can accept an absolute url', () => { | ||
expectType<['apple']>(useSegments<'/apple'>()); | ||
}); | ||
|
||
it('only accepts valid possible urls', () => { | ||
expectError(useSegments<'/invalid'>()); | ||
}); | ||
|
||
it('can accept an array of segments', () => { | ||
expectType<['apple']>(useSegments<['apple']>()); | ||
}); | ||
|
||
it('only accepts valid possible segments', () => { | ||
expectError(useSegments<['invalid segment']>()); | ||
}); | ||
}); |
Oops, something went wrong.