Skip to content

Commit

Permalink
Add tests and documentation for patterns
Browse files Browse the repository at this point in the history
  • Loading branch information
mchudy committed Sep 15, 2020
1 parent a10b56a commit 7f2f084
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 8 deletions.
43 changes: 40 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,16 @@ const routes = createRouting({
} as const);

routes.products(); // '/products'
routes.products.pattern // '/products'

routes.users({ userId: '10' }) // '/users/10'
routes.users.pattern // '/users/:userId([0-9]+)

routes.items({}, { filter: 'new' }) // '/items?filter=new'
routes.items.pattern // '/items'

routes.items.item({ itemId: '12d66718-e47c-4a2a-ad5b-8897def2f6a7' }) // '/items/12d66718-e47c-4a2a-ad5b-8897def2f6a7'
routes.items.item.pattern // `/items/:itemId(${uuidRegex})`
```
## Usage
Expand Down Expand Up @@ -86,7 +93,7 @@ There are some predefined convenience parameter types provided:
### Query string
Query string parameters can be specified by interpolating `query` function inside a segment string. The `query` function
expects an object where keys are names of parameters and values specify whether those params are required in a path.
expects an object where keys are names of parameters and values specify whether those params are required in the path.
```js
segment`/product${query({
Expand All @@ -95,9 +102,9 @@ segment`/product${query({
})}`;
```
The above segment defines a path which expects a `productId` URL param and an optional `details` URL param.
The above segment defines a path which expects the `productId` URL param and the optional `details` URL param.
When creating a route query strings can be passed in a second argument:
When creating a route query strings can be passed in the second argument:
```js
routes.products(
Expand All @@ -111,6 +118,36 @@ routes.products(
which will return `/product?details=false&productId=10`.
### Patterns
While creating a routing, alongside path string generators, patterns for those paths compatible with
[path-to-regexp](https://github.com/pillarjs/path-to-regexp) are generated. You can access them via the `pattern`
property:
```
routes.products.pattern
```
Those patterns are useful for integration with routing libraries which support
[path-to-regexp](https://github.com/pillarjs/path-to-regexp)-style syntax e.g. in case of React Router DOM:
```jsx
<Route exact component={ProductsPage} path={routes.products.pattern} />
```
and in case of Vue Router:
```js
const router = new VueRouter({
routes: [
{
path: routes.products.pattern,
component: ProductsPage,
},
],
});
```
### Nested routes
Routes can be nested by providing an optional `children` property to segments:
Expand Down
30 changes: 25 additions & 5 deletions __tests__/routing.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@ describe("createRouting", () => {
expect(route).toEqual("/products");
});

it("returns correct pattern for a simple route", () => {
const routes = createRouting({
products: segment`/products`,
} as const);

const pattern = routes.products.pattern;

expect(pattern).toEqual("/products");
});

it("creates nested routes", () => {
const routes = createRouting({
products: {
Expand All @@ -28,7 +38,7 @@ describe("createRouting", () => {
expect(nestedRoute).toEqual("/products/create");
});

it("creates nested routes with params", () => {
describe("nested routes", () => {
const routes = createRouting({
products: {
...segment`/products/${number("productId")}`,
Expand All @@ -38,10 +48,20 @@ describe("createRouting", () => {
},
} as const);

const mainRoute = routes.products({ productId: "2" });
const nestedRoute = routes.products.edit({ productId: "2" });
it("creates nested routes with params", () => {
const mainRoute = routes.products({ productId: "2" });
const nestedRoute = routes.products.edit({ productId: "2" });

expect(mainRoute).toEqual("/products/2");
expect(nestedRoute).toEqual("/products/2/edit");
});

it("returns correct patterns", () => {
const mainPattern = routes.products.pattern;
const nestedPattern = routes.products.edit.pattern;

expect(mainRoute).toEqual("/products/2");
expect(nestedRoute).toEqual("/products/2/edit");
expect(mainPattern).toEqual("/products/:productId([0-9]+)");
expect(nestedPattern).toEqual("/products/:productId([0-9]+)/edit");
});
});
});
34 changes: 34 additions & 0 deletions __tests__/segments/arg.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,38 @@ describe("arg segment", () => {

expect(() => routes.product({ productId: "123" })).toThrow();
});

it("returns the correct path pattern when required", () => {
const routes = createRouting({
product: segment`/product/${arg("productId")}`,
} as const);

const pattern = routes.product.pattern;

expect(pattern).toEqual("/product/:productId");
});

it("returns the correct path pattern when optional", () => {
const routes = createRouting({
product: segment`/product/${arg("productId", {
optional: true,
})}`,
} as const);

const pattern = routes.product.pattern;

expect(pattern).toEqual("/product/:productId?");
});

it("returns the correct path pattern for custom regexes", () => {
const routes = createRouting({
product: segment`/product/${arg("productId", {
pattern: /[0-9]{2}/.source,
})}`,
} as const);

const pattern = routes.product.pattern;

expect(pattern).toEqual("/product/:productId([0-9]{2})");
});
});
20 changes: 20 additions & 0 deletions __tests__/segments/number.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,24 @@ describe("number segment", () => {

expect(() => routes.product({ productId: "aaa" })).toThrow();
});

it("returns the correct pattern when required", () => {
const routes = createRouting({
product: segment`/product/${number("productId")}`,
} as const);

const pattern = routes.product.pattern;

expect(pattern).toEqual("/product/:productId([0-9]+)");
});

it("returns the correct pattern when optional", () => {
const routes = createRouting({
product: segment`/product/${number("productId", true)}`,
} as const);

const pattern = routes.product.pattern;

expect(pattern).toEqual("/product/:productId([0-9]+)?");
});
});
10 changes: 10 additions & 0 deletions __tests__/segments/query.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,14 @@ describe("query segment", () => {

expect(route).toEqual(`/product/1?filter=value`);
});

it("ignores query params in the pattern", () => {
const routes = createRouting({
product: segment`/product${query({ productId: true })}`,
} as const);

const pattern = routes.product.pattern;

expect(pattern).toEqual("/product");
});
});
11 changes: 11 additions & 0 deletions __tests__/segments/uuid.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,15 @@ describe("uuid segment", () => {

expect(() => routes.product({ productId: "3d368832-0bc-4fcc-bf75-5bd8794c1ad3" })).toThrow();
});

it("returns correct pattern", () => {
const uuidPattern = "[0-9a-fA-F]{8}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{12}";
const routes = createRouting({
product: segment`/product/${uuid("productId")}`,
} as const);

const pattern = routes.product.pattern;

expect(pattern).toEqual(`/product/:productId(${uuidPattern})`);
});
});

0 comments on commit 7f2f084

Please sign in to comment.