Skip to content

Commit

Permalink
Add react example to readme, improve UrlBuilder to accept additionalS…
Browse files Browse the repository at this point in the history
…tring for url concatenation
  • Loading branch information
tomas-light committed Jan 14, 2023
1 parent fdcaf2e commit ea51af4
Show file tree
Hide file tree
Showing 12 changed files with 116 additions and 19 deletions.
51 changes: 51 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
* [Installation](#install)
* [How to use](#usage)
* [Using with react-router](#react-router)
* [Customization](#customization)
* [FactoryConfig](#config)
* [Creating strategies](#strategies)
Expand Down Expand Up @@ -36,6 +37,56 @@ routes.user.userId().relativeUrl(); // ':userId'
routes.user.userId('18').private_info.url(); // '/user/18/private-info'
```

### <a name="react-router"></a> Using with react-router

```tsx
import { createNiceWebRoutes } from 'nice-web-routes';
import { Navigate, Route, Routes } from 'react-router-dom';

const appRoutes = createNiceWebRoutes({
auth: {
login: {},
registration: {},
},
profile: {
userId: () => ({}),
settings: {},
edit: {
personal: {},
career: {},
},
},
});

const App = () => (
<Routes>
<Route index element={<Navigate to={appRoutes.auth.relativeUrl()} replace />} />

<Route path={appRoutes.auth.url('/*')}> {/* '/auth/*' */}
<Route index element={<Navigate to={appRoutes.auth.login.relativeUrl()} replace />} />

<Route path={appRoutes.auth.login.relativeUrl()} element={<LoginDisplay />} />
<Route path={appRoutes.auth.registration.relativeUrl()} element={<RegistrationDisplay />} />
</Route>

<Route path={appRoutes.profile.url('/*')}> {/* '/profile/*' */}
<Route index element={<MyProfileDisplay />} />

<Route path={appRoutes.profile.userId().relativeUrl()} element={<UserProfile />} />
<Route path={appRoutes.profile.settings.relativeUrl()} element={<SettingsDisplay />} />

<Route path={appRoutes.profile.edit.relativeUrl('/*')}> {/* 'edit/*' */}
<Route index element={<ProfileSettings />} />

<Route path={appRoutes.profile.edit.career.relativeUrl()} element={<EditCareerDisplay />} />
<Route path={appRoutes.profile.edit.personal.relativeUrl()} element={<EditPersonalInformationDisplay />} />
</Route>
</Route>
</Routes>
);

```

### <a name="customization"></a> Customization

You can customize routes creating by using `configureNiceWebRoutesCreating` and passing `FactoryConfig`:
Expand Down
7 changes: 6 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,9 @@ export * from './configureNiceWebRoutesCreating';
export * from './createNiceWebRoutes';
export * from './strategies';
export * from './types';
export { UrlBuilder, DefaultUrlBuilder } from './utils';
export {
UrlBuilder,
DefaultUrlBuilder,
joinRouteSegments,
snakeCaseToDashCase,
} from './utils';
2 changes: 1 addition & 1 deletion src/makeSearchUrl.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { joinRouteSegments } from './joinRouteSegments';
import { joinRouteSegments } from './utils/joinRouteSegments';
import { snakeCaseToDashCase, UrlBuilderConstructor } from './utils';

function makeSearchUrl(
Expand Down
13 changes: 6 additions & 7 deletions src/strategies/createObjectNiceWebRoutes.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { defaultSegmentValueGetter } from '../defaultSegmentValueGetter';
import { joinRouteSegments } from '../joinRouteSegments';
import { joinRouteSegments } from '../utils/joinRouteSegments';
import {
NICE_WEB_ROUTE_URLS_KEYS,
NiceWebRoutesDescription,
Expand Down Expand Up @@ -38,15 +38,15 @@ export const createObjectNiceWebRoutes: CreatingStrategy = (config = {}) =>

const node = {
url: function <Search extends Record<string, string>>(
searchParams?: Search
searchParams?: Search | string
) {
return new UrlBuilderImpl()
.addPathnameIfExists(routePath)
.addSearchParamsIfExists(searchParams)
.build();
},
relativeUrl: function () {
return currentSegmentName;
relativeUrl: function (additionalString: string = '') {
return currentSegmentName + additionalString;
},
} as NiceWebRoutesNode<
DescriptionShape,
Expand All @@ -69,8 +69,7 @@ export const createObjectNiceWebRoutes: CreatingStrategy = (config = {}) =>
let segment: string;
if (snakeTransformation.disableForSegmentName) {
segment = descriptionSegment;
}
else {
} else {
segment = snakeCaseToDashCase(descriptionSegment);
}

Expand All @@ -84,7 +83,7 @@ export const createObjectNiceWebRoutes: CreatingStrategy = (config = {}) =>
}

if (typeof descriptionSegmentValue === 'function') {
const parametrizedRoute: ParametrizedNiceWebRoute<any> = value => {
const parametrizedRoute: ParametrizedNiceWebRoute<any> = (value) => {
const description = descriptionSegmentValue();

let segmentValue = getSegmentValue(descriptionSegment, value);
Expand Down
8 changes: 4 additions & 4 deletions src/strategies/createProxyNiceWebRoutes.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { defaultSegmentValueGetter } from '../defaultSegmentValueGetter';
import { joinRouteSegments } from '../joinRouteSegments';
import { joinRouteSegments } from '../utils/joinRouteSegments';
import {
NICE_WEB_ROUTE_URLS_KEYS,
NiceWebRoutesDescription,
Expand Down Expand Up @@ -40,15 +40,15 @@ export const createProxyNiceWebRoutes: CreatingStrategy = (config = {}) =>
const proxy = new Proxy<ProxyType>(
{
url: function <Search extends Record<string, string>>(
searchParams?: Search
searchParams?: Search | string
) {
return new UrlBuilderImpl()
.addPathnameIfExists(routePath)
.addSearchParamsIfExists(searchParams)
.build();
},
relativeUrl: function () {
return currentSegmentName;
relativeUrl: function (additionalString: string = '') {
return currentSegmentName + additionalString;
},
},
{
Expand Down
6 changes: 6 additions & 0 deletions src/strategies/creatingStrategies.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ describe.each(testTable)('%s creating strategy', (strategyName) => {
test('if relative url on second level equals "statistic"', () => {
expect(routes.users.statistic.relativeUrl()).toBe('statistic');
});
test('if full url on second level with additional string equals "/users/statistic/*"', () => {
expect(routes.users.statistic.url('/*')).toBe('/users/statistic/*');
});
test('if full url on second level with additional string equals "/users/statistic/*"', () => {
expect(routes.users.statistic.relativeUrl('/*')).toBe('statistic/*');
});

test('if full url of parametrized route on first level WITHOUT value equals "/:article"', () => {
expect(routes.article().url()).toBe('/:article');
Expand Down
6 changes: 4 additions & 2 deletions src/types/NiceWebRouteUrls.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
export type NiceWebRouteUrls = {
url: <Search extends Record<string, string>>(searchParams?: Search) => string;
relativeUrl: () => string;
url: <Search extends Record<string, string>>(
searchParams?: Search | string
) => string;
relativeUrl: (additionalString?: string) => string;
};

export const NICE_WEB_ROUTE_URLS_KEYS: (keyof NiceWebRouteUrls)[] = [
Expand Down
16 changes: 16 additions & 0 deletions src/utils/DefaultUrlBuilder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,19 @@ test('if nullable search params will ignored', () => {
.build();
expect(url).toBe('/?one=1&three=three');
});

test('if additional string was passed in search params', () => {
const builder = new DefaultUrlBuilder();
const url = builder.addSearchParamsIfExists('*').build();
expect(url).toBe('/*');
});

test('if additional string was passed in search params with other data', () => {
const builder = new DefaultUrlBuilder();
const url = builder
.addPathnameIfExists('some-url')
.addPathnameIfExists('asd')
.addSearchParamsIfExists('*')
.build();
expect(url).toBe('/some-url/asd*');
});
19 changes: 16 additions & 3 deletions src/utils/DefaultUrlBuilder.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,30 @@
import { isNullOrUndefined } from './isNullOrUndefined';
import { joinRouteSegments } from './joinRouteSegments';
import { UrlBuilder } from './UrlBuilder';

class DefaultUrlBuilder implements UrlBuilder {
private pathname = '';
private searchParams: Record<string, string> = {};
private additionalString = '';

addPathnameIfExists(pathname?: string) {
if (!pathname) {
return this;
}
this.pathname += pathname;
this.pathname = joinRouteSegments(this.pathname, pathname);
return this;
}

addSearchParamsIfExists(searchParams?: Record<string, string>) {
addSearchParamsIfExists(searchParams?: Record<string, string> | string) {
if (!searchParams) {
return this;
}

if (typeof searchParams === 'string') {
this.additionalString = searchParams;
return this;
}

this.searchParams = {
...this.searchParams,
...searchParams,
Expand All @@ -36,7 +44,12 @@ class DefaultUrlBuilder implements UrlBuilder {
});

// remove extra part in url
return url.toString().substring(urlBase.length);
let builtUrl = url.toString().substring(urlBase.length);
if (this.additionalString) {
builtUrl += this.additionalString;
}

return builtUrl;
}
}

Expand Down
4 changes: 3 additions & 1 deletion src/utils/UrlBuilder.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
export interface UrlBuilder {
addPathnameIfExists(pathname?: string): UrlBuilder;

addSearchParamsIfExists(searchParams?: Record<string, string>): UrlBuilder;
addSearchParamsIfExists(
searchParams?: Record<string, string> | string
): UrlBuilder;

build(): string;
}
Expand Down
1 change: 1 addition & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './isNullOrUndefined';
export * from './snakeCaseToDashCase';
export * from './DefaultUrlBuilder';
export * from './UrlBuilder';
export * from './joinRouteSegments';
2 changes: 2 additions & 0 deletions src/joinRouteSegments.ts → src/utils/joinRouteSegments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ function joinRouteSegments(...segments: string[]) {
const routeSegmentSeparator = '/';

return segments
.flatMap((segment) => segment.split(routeSegmentSeparator))
.map((segment) => {
if (segment.endsWith(routeSegmentSeparator)) {
return segment.slice(0, segment.length - 2);
}
return segment;
})
.filter((segment) => segment !== '')
.join(routeSegmentSeparator);
}

Expand Down

0 comments on commit ea51af4

Please sign in to comment.