Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Named route path interpolation and hook navigation fixes #8

Merged
merged 36 commits into from
Dec 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
a4eba2c
chore(deps): dev latest
maxholman Dec 5, 2022
8b52bf2
feat: children as a function has been removed, use component prop ins…
maxholman Dec 5, 2022
49a1b62
chore: renamed RouteParams type
maxholman Dec 5, 2022
9ae855b
fix: suffix was not stripped with interpolation of params with traili…
maxholman Dec 5, 2022
c1912bc
chore: initial commit
maxholman Dec 5, 2022
8e64218
chore: lint
maxholman Dec 5, 2022
8244a4c
chore: eslint block65 config latest alpha
maxholman Dec 5, 2022
d35346a
chore: lint
maxholman Dec 5, 2022
0f6ea76
fix: improve types to disallow children + component, or component on …
maxholman Dec 5, 2022
0be5883
chore(des): latest dev deps
maxholman Dec 25, 2022
2a5b649
refactor split components into separate files
maxholman Dec 25, 2022
8ab6216
feat: improve types and inference from paths
maxholman Dec 25, 2022
e2bac76
chore: redundant code
maxholman Dec 25, 2022
3878e07
chore: update exports
maxholman Dec 25, 2022
89f26eb
chore(deps): latest dev deps
maxholman Dec 25, 2022
5061d9e
refactor: avoid dep cycle
maxholman Dec 25, 2022
8aea4b8
chore: vite
maxholman Dec 25, 2022
b57befe
chore: stick with node16 reso
maxholman Dec 25, 2022
ac5b0f1
chore: vite plugin react is not compatible with node16 reso, so expec…
maxholman Dec 25, 2022
9d185c7
chore: es2022
maxholman Dec 25, 2022
ad383b3
chore: update imports
maxholman Dec 25, 2022
706dd13
chore: prevent form submission when clicking buttons
maxholman Dec 25, 2022
18be4fa
chore: improve tests
maxholman Dec 25, 2022
bc2ed04
chore: compile everything
maxholman Dec 25, 2022
fc835c1
chore: type improvements
maxholman Dec 26, 2022
15a5f91
feat: wrap fragments in an anchor
maxholman Dec 26, 2022
fae3971
feat: export `LinkChildProps` for consumer to know what `Link` child …
maxholman Dec 26, 2022
32cf1f6
chore: fix garbage var naming and text
maxholman Dec 26, 2022
9f255ab
refactor: dedupe popstate event name
maxholman Dec 26, 2022
db2386a
fix: programmatic navigation via effect within router children when u…
maxholman Dec 26, 2022
398f5b0
feat: more consistent return values for programmatic navigation, rega…
maxholman Dec 26, 2022
9f47552
chore: code cleanup
maxholman Dec 26, 2022
96cd5c8
chore: remove unused import
maxholman Dec 26, 2022
ddd5575
chore: lint
maxholman Dec 26, 2022
00332e0
chore: lint
maxholman Dec 26, 2022
3e60fe0
ci: fix broken build recipe
maxholman Dec 26, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
module.exports = {
root: true,
extends: '@block65/eslint-config/react',
extends: [
'@block65/eslint-config/typescript',
'@block65/eslint-config/react',
],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json',
Expand Down
8 changes: 4 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,20 @@ distclean: clean
.PHONY: test
test: node_modules
NODE_OPTIONS=--experimental-vm-modules yarn jest
$(MAKE) build/index.js
$(MAKE) build/main.js

.PRECIOUS: yarn.lock
node_modules: yarn.lock package.json
yarn install

.PHONY: dev
dev: node_modules webpack.config.js
yarn webpack -o build --mode=development -w
dev: node_modules
yarn vite dev

.PHONY: types
types: node_modules
yarn tsc --emitDeclarationOnly --removeComments false

build/main.js: node_modules $(SRCS)
yarn vite build
NODE_ENV=production yarn vite build
npx bundlesize
52 changes: 8 additions & 44 deletions __tests__/__snapshots__/link.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -7,66 +7,30 @@ exports[`basic 1`] = `
>
test as string
</a>
<a
href="https://invalid.example.com"
rel="no-opener noreferrer"
>
cross origin link
</a>
<a
href="/login"
>
test as fragment
</a>
<a
data-linkylink="true"
href="/login"
>
<span
data-linkylink="true"
>
test nested in an FC
</span>
</a>
<a
href="/login"
>
this will not work properly
</a>
<a
href="/login"
>
plnk plonk
this text should be in a component that has a href
</a>
this text will not be in a component that has a href :sadface:
<a
href="/login"
id="123"
>
Hello!
This anchor should have a href!
</a>
this text will not be in a component that has a href :sadface:
<a
href="/login"
>
<div
data-compycomp="true"
>
Once
<div
data-compycomp="true"
>
Twice
<div
data-compycomp="true"
>
Three times a lady
</div>
</div>
</div>
</a>
<a
href="/login"
href="https://invalid.example.com"
rel="no-opener noreferrer"
>
this will also not work properly
cross origin link
</a>
,
</DocumentFragment>
`;
52 changes: 52 additions & 0 deletions __tests__/interpolate.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import '@testing-library/jest-dom';
import { interpolate } from '../lib/named-route.js';

// type PathPart = `/${string}`;
// type Path = `${PathPart}${PathPart}?` | `${PathPart}${PathPart}*`;

test('custom route', async () => {
expect(interpolate('/test', {})).toBe('/test');

expect(
interpolate('/foo/:foo', {
foo: 'oof',
}),
).toBe('/foo/oof');

expect(
interpolate('/foo/:foo?', {
foo: 'oof',
}),
).toBe('/foo/oof');

expect(interpolate('/foo/:foo?', {})).toBe('/foo');
expect(
interpolate('/foo/:foo?/bar/:bar', {
bar: 'rab',
}),
).toBe('/foo/bar/rab');

expect(
interpolate('/foo/:foo*', {
foo: 'oof',
}),
).toBe('/foo/oof');

expect(
interpolate('/foo/:foo+', {
foo: 'oof',
}),
).toBe('/foo/oof');

expect(
interpolate('/foo/:foo*', {
foo: 'oof/rab/zab',
}),
).toBe('/foo/oof/rab/zab');
expect(
interpolate('/set/:set/q/:id?', {
set: 'BTT',
id: 'YES!',
}),
).toBe('/set/BTT/q/YES!');
});
47 changes: 19 additions & 28 deletions __tests__/link.test.tsx
Original file line number Diff line number Diff line change
@@ -1,64 +1,55 @@
import '@testing-library/jest-dom';
import { render } from '@testing-library/react';
import type { AnchorHTMLAttributes, FC, PropsWithChildren } from 'react';
import type { FC, PropsWithChildren } from 'react';
import { FormattedMessage, IntlProvider } from 'react-intl';
import type { LinkChildProps } from '../lib/components/Link.js';
import { namedRoute } from '../lib/named-route.js';
import { Link, Router } from '../src/index.js';

const LinkyLink: FC<
PropsWithChildren<AnchorHTMLAttributes<HTMLAnchorElement>>
> = (props) => <span data-linkylink {...props} />;
const ComponentThatTakesProps: FC<PropsWithChildren<LinkChildProps>> = (
props,
) => <a data-linkylink {...props} />;

const CompyComp: FC<PropsWithChildren> = ({ children, ...props }) => (
<div data-compycomp {...props}>
{children}
</div>
const ComponentThatIgnoresProps: FC<PropsWithChildren> = ({ children }) => (
<>{children}</>
);

const BrokeyBroke: FC<PropsWithChildren> = ({ children }) => <>{children}</>;

test('basic', async () => {
const login = namedRoute('/login');

const { asFragment } = render(
<IntlProvider locale="en" onError={() => {}}>
<Router>
<Link dest={login.build()}>test as string</Link>
<Link dest="https://invalid.example.com">cross origin link</Link>
<Link dest={login.build()}>
<>test as fragment</>
</Link>
<Link dest={login.build()}>
<LinkyLink>test nested in an FC</LinkyLink>
<ComponentThatTakesProps>
this text should be in a component that has a href
</ComponentThatTakesProps>
</Link>

<Link dest={login.build()}>
<FormattedMessage
id="broken"
description="this will not work properly"
defaultMessage="this will not work properly"
description="broken"
defaultMessage="this text will not be in a component that has a href :sadface:"
/>
</Link>

<Link dest={login.build()}>plnk plonk</Link>

<Link dest={login.build()}>
<a id="123">Hello!</a>
<a id="123">This anchor should have a href!</a>
</Link>

<Link dest={login.build()}>
<CompyComp>
Once
<CompyComp>
Twice
<CompyComp>Three times a lady</CompyComp>
</CompyComp>
</CompyComp>
</Link>
<Link dest={login.build()}>
<BrokeyBroke>this will also not work properly</BrokeyBroke>
<ComponentThatIgnoresProps>
this text will not be in a component that has a href :sadface:
</ComponentThatIgnoresProps>
</Link>

<Link dest="https://invalid.example.com">cross origin link</Link>
</Router>
,
</IntlProvider>,
);

Expand Down
20 changes: 8 additions & 12 deletions __tests__/navigate.test.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { FC, useEffect } from 'react';
import { Route } from '../lib/components.js';
import { namedRoute } from '../lib/named-route.js';
import { Router, useNavigate } from '../lib/router.js';
import { Routes } from '../lib/routes.js';
import { Route, Routes } from '../lib/routes.js';
import { LocationDisplay } from './index.test.js';

const usersView = namedRoute('/users/:userId');
Expand All @@ -18,6 +17,7 @@ const Buttons: FC = () => {
{users.map((userId) => (
<li key={userId}>
<button
type="button"
data-testid={`button-${userId}`}
onClick={() => {
const dest = usersView.build({ params: { userId } });
Expand Down Expand Up @@ -46,18 +46,18 @@ const NavigationInsideEffect: FC = () => {
};

test('nav with clicks', async () => {
// const { debug } =
render(
<Router pathname="/users/test1">
<LocationDisplay />
<Routes>
<Route path={usersView.path}>
{(params) => (
<Route
path={usersView.path}
component={(params) => (
<h1 data-testid={`heading-${params.userId}`}>
You are user {params.userId}
</h1>
)}
</Route>
/>

<Route>
<h1>fail</h1>
Expand All @@ -68,8 +68,6 @@ test('nav with clicks', async () => {
</Router>,
);

// debug();

await waitFor(() => screen.getByTestId('heading-test1'));

fireEvent.click(screen.getByTestId('button-alice'));
Expand All @@ -92,11 +90,9 @@ test.only('programmatic navigation with hooks', async () => {
await waitFor(() => {
// global location
expect(location.href).toBe('http://localhost/pickles?foo=bar');
});

await waitFor(() =>
expect(screen.getByTestId('location-display')).toHaveTextContent(
'http://localhost/pickles?foo=bar',
),
);
);
});
});
29 changes: 17 additions & 12 deletions __tests__/nested.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@ import '@testing-library/jest-dom';
import { render, screen, waitFor } from '@testing-library/react';
import type { FC } from 'react';
import { namedRoute } from '../lib/named-route.js';
import { Route, RouteComponentProps, Router, Routes } from '../src/index.js';
import { Route, Router, Routes } from '../src/index.js';
import { LocationDisplay } from './index.test.js';

test('wildcard routes + nested', async () => {
const userRoot = namedRoute('/users');
const userView = namedRoute('/users/blah/:userId');

const ComponentWithUserId: FC<RouteComponentProps<{ userId: string }>> = ({
params: { userId },
}) => <>userId = {userId}</>;
const ComponentWithUserId: FC<{ userId: string }> = ({ userId }) => (
<>userId = {userId}</>
);

const ParamlessComponent: FC = () => <>I am a Paramless Component</>;

Expand All @@ -23,14 +23,17 @@ test('wildcard routes + nested', async () => {
<h1>inside userRoot</h1>
<Routes>
<Route path={userRoot.path}>this should not display</Route>
<Route wildcard path={userView.path}>
{(params) => (
<Route
wildcard
path={userView.path}
component={(params) => (
<h1 data-testid="users">You are user {params.userId}</h1>
)}
</Route>
/>
<Route path={userView.path} component={ComponentWithUserId} />
<Route component={ParamlessComponent} />
<Route>{() => <ParamlessComponent />}</Route>
<Route>
<ParamlessComponent />
</Route>
<Route>
<h1>fail</h1>
</Route>
Expand Down Expand Up @@ -60,11 +63,13 @@ test('default route + wildcard routes + nested', async () => {
<Routes>
<Route>
<Routes>
<Route wildcard path={userView.path}>
{(params) => (
<Route
wildcard
path={userView.path}
component={(params) => (
<h1 data-testid="users">You are user {params.userId}</h1>
)}
</Route>
/>
<Route>
<h1>fail</h1>
</Route>
Expand Down
Loading