• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

plusnew / router / 14168719779

31 Mar 2025 09:44AM UTC coverage: 83.428% (-8.3%) from 91.712%
14168719779

Pull #349

github

web-flow
Merge 9cf30c16d into 0b7c03408
Pull Request #349: 4.0.0 Nested Routes, Default Handling

182 of 232 branches covered (78.45%)

Branch coverage included in aggregate %.

850 of 1005 new or added lines in 12 files covered. (84.58%)

850 of 1005 relevant lines covered (84.58%)

29.55 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.51
/src/routeHandler.ts
1
import { object } from "./schema";
1✔
2
import { containerHandler, flattenUrlResult, isDefault } from "./schema/util";
1✔
3
import { Tokenizer, state, tokenize } from "./tokenizer";
1✔
4
import { TOKENS } from "./tokenizer";
1✔
5
import type {
1✔
6
  NamespaceTemplate,
1✔
7
  NamespaceToLinkParameter,
1✔
8
  NamespaceToParameter,
1✔
9
  ParameterSpecificationTemplate,
1✔
10
} from "./types";
1✔
11

1✔
12
export function createRootRoute<T extends ParameterSpecificationTemplate>(
1✔
13
  parameterSchema: T,
1✔
14
): Route<{ "/": T }>;
1✔
15
export function createRootRoute<T extends ParameterSpecificationTemplate>(
1✔
16
  segment: string,
1✔
17
  parameterSchema: T,
1✔
18
): Route<{ "/": T }>;
1✔
19
export function createRootRoute<T extends ParameterSpecificationTemplate>(
1✔
20
  segmentOrParameterSchema: string | T,
32✔
21
  parameterSpec?: T,
32✔
22
): Route<{ "/": T }> {
32✔
23
  if (parameterSpec === undefined) {
32✔
24
    return new Route(TOKENS.PATH_SEPERATOR, segmentOrParameterSchema as T);
31✔
25
  }
31✔
26
  return new Route(segmentOrParameterSchema as string, parameterSpec);
1✔
27
}
1✔
28

1✔
29
class Route<T extends NamespaceTemplate> {
1✔
30
  constructor(
1✔
31
    protected namespace: string,
39✔
32
    protected parameterSpec: ParameterSpecificationTemplate,
39✔
33
    protected parentRoute?: Route<any>,
39✔
34
  ) {}
39✔
35
  map<U>(
1✔
36
    url: string,
43✔
37
    cb: (data: {
43✔
38
      parameter: NamespaceToParameter<T>;
43✔
39
      hasChildRouteActive: boolean;
43✔
40
    }) => U,
43✔
41
  ): U | null {
43✔
42
    let isOuterMap = false;
43✔
43
    if (state.index === null) {
43✔
44
      state.index = 0;
36✔
45
      state.tokens = tokenize(url);
36✔
46

36✔
47
      if (
36✔
48
        state.tokens.length === 0 ||
36✔
49
        state.tokens[state.tokens.length - 1].type !== "PATH_SEPERATOR"
36✔
50
      ) {
36!
NEW
51
        state.tokens.push({ type: "PATH_SEPERATOR" });
×
NEW
52
      }
×
53

36✔
54
      isOuterMap = true;
36✔
55
    }
36✔
56

43✔
57
    const tokenizer = new Tokenizer();
43✔
58

43✔
59
    let parameter =
43✔
60
      this.parentRoute === undefined
43✔
61
        ? null
36✔
62
        : this.parentRoute.map(url, ({ parameter }) => parameter);
7✔
63
    let hasChildRouteActive = false;
43✔
64
    let validRoute = this.parentRoute === undefined ? true : parameter !== null;
43✔
65

43✔
66
    if (validRoute) {
43✔
67
      const segmentTokens = tokenize(this.namespace);
43✔
68
      for (const segmentToken of segmentTokens) {
43✔
69
        if (tokenizer.lookahead(segmentToken) === null) {
45✔
70
          validRoute = false;
5✔
71
          break;
5✔
72
        } else {
45✔
73
          tokenizer.eat(segmentToken);
40✔
74
        }
40✔
75
      }
45✔
76

43✔
77
      const hasParameter =
43✔
78
        tokenizer.lookahead({ type: "VALUE_SEPERATOR" }) !== null;
43✔
79

43✔
80
      if (validRoute === true)
43✔
81
        try {
43✔
82
          parameter = {
38✔
83
            ...parameter,
38✔
84
            [this.parentRoute === undefined
38✔
85
              ? TOKENS.PATH_SEPERATOR
36✔
86
              : this.namespace]: handleParameter(this.parameterSpec, tokenizer),
38✔
87
          };
38✔
88
        } catch (error) {
38✔
89
          validRoute = false;
3✔
90
        }
3✔
91

43✔
92
      if (
43✔
93
        validRoute === true &&
43✔
94
        (this.namespace !== TOKENS.PATH_SEPERATOR || hasParameter === true)
35✔
95
      ) {
43✔
96
        if (tokenizer.lookahead({ type: "PATH_SEPERATOR" }) === null) {
23!
NEW
97
          validRoute = false;
×
98
        } else {
23✔
99
          tokenizer.eat({ type: "PATH_SEPERATOR" });
23✔
100
        }
23✔
101
      }
23✔
102

43✔
103
      hasChildRouteActive = tokenizer.isDone() === false;
43✔
104
    }
43✔
105

43✔
106
    if (isOuterMap === true) {
43✔
107
      state.index = null;
36✔
108
    }
36✔
109

43✔
110
    return validRoute === true
43✔
111
      ? cb({
35✔
112
          parameter: parameter as NamespaceToParameter<any>,
35✔
113
          hasChildRouteActive: hasChildRouteActive,
35✔
114
        })
35✔
115
      : null;
8✔
116
  }
43✔
117
  createChildRoute<U extends string, V extends ParameterSpecificationTemplate>(
1✔
118
    namespace: U,
7✔
119
    parameterSpec: V,
7✔
120
  ): Route<T & { [namespace in U]: V }> {
7✔
121
    return new Route(namespace, parameterSpec, this);
7✔
122
  }
7✔
123

1✔
124
  createPath(namespacedParameter: NamespaceToLinkParameter<T>): string {
1✔
125
    let path =
43✔
126
      this.parentRoute === undefined
43✔
127
        ? ""
38✔
128
        : this.parentRoute.createPath(namespacedParameter);
5✔
129

43✔
130
    path += this.namespace;
43✔
131

43✔
132
    path += parameterToUrl(
43✔
133
      this.parameterSpec,
43✔
134
      namespacedParameter[
43✔
135
        this.parentRoute === undefined ? TOKENS.PATH_SEPERATOR : this.namespace
43✔
136
      ],
43✔
137
    );
43✔
138

43✔
139
    if (path.endsWith(TOKENS.PATH_SEPERATOR) === false) {
43✔
140
      path += TOKENS.PATH_SEPERATOR;
28✔
141
    }
28✔
142

41✔
143
    return path;
41✔
144
  }
43✔
145
}
1✔
146

1✔
147
function parameterToUrl(
43✔
148
  parameterSpec: ParameterSpecificationTemplate,
43✔
149
  parameter: object,
43✔
150
) {
43✔
151
  return Object.entries(parameter).reduce((accumulator, [name, value]) => {
43✔
152
    if (isDefault(parameterSpec[name], value)) {
40✔
153
      return accumulator;
7✔
154
    }
7✔
155
    const urlResult = parameterSpec[name].toUrl(value);
33✔
156
    if (urlResult === "") {
40!
NEW
157
      return accumulator;
×
NEW
158
    }
✔
159
    return `${accumulator}${flattenUrlResult(name, urlResult).reduce(
31✔
160
      (accumulator, [name, value]) =>
31✔
161
        `${accumulator}${TOKENS.VALUE_SEPERATOR}${name}${TOKENS.VALUE_ASSIGNMENT}${value}`,
29✔
162
      "",
31✔
163
    )}`;
31✔
164
  }, "");
43✔
165
}
43✔
166

1✔
167
function handleParameter<T extends ParameterSpecificationTemplate>(
38✔
168
  parameterSpecification: T,
38✔
169
  tokenizer: Tokenizer,
38✔
170
) {
38✔
171
  let hasValues = true;
38✔
172
  const valueSeperator = tokenizer.lookahead({ type: "VALUE_SEPERATOR" });
38✔
173
  if (valueSeperator === null) {
38✔
174
    hasValues = false;
14✔
175
  } else {
38✔
176
    tokenizer.eat({ type: "VALUE_SEPERATOR" });
24✔
177
  }
24✔
178
  const result = containerHandler(
38✔
179
    object(parameterSpecification),
38✔
180
    tokenizer,
38✔
181
    hasValues,
38✔
182
  );
38✔
183

38✔
184
  return result;
38✔
185
}
38✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc