path-trie.spec.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  1. import {expect} from 'chai';
  2. import {PathTrie} from './path-trie.js';
  3. import {format} from '@e22m4u/js-format';
  4. import {pathToRegexp} from 'path-to-regexp';
  5. const VALUE = 'myValue1';
  6. const ANOTHER_VALUE = 'myValue2';
  7. describe('PathTrie', function () {
  8. describe('add', function () {
  9. it('requires the first parameter to be a String', function () {
  10. const trie = new PathTrie();
  11. const throwable = v => () => trie.add(v, VALUE);
  12. const error = v =>
  13. format(
  14. 'The first argument of PathTrie.add must be a String, ' +
  15. 'but %s was given.',
  16. v,
  17. );
  18. expect(throwable(10)).to.throw(error('10'));
  19. expect(throwable(0)).to.throw(error('0'));
  20. expect(throwable(true)).to.throw(error('true'));
  21. expect(throwable(false)).to.throw(error('false'));
  22. expect(throwable(null)).to.throw(error('null'));
  23. expect(throwable({})).to.throw(error('Object'));
  24. expect(throwable([])).to.throw(error('Array'));
  25. expect(throwable(undefined)).to.throw(error('undefined'));
  26. throwable('str')();
  27. throwable('')();
  28. });
  29. it('requires the second parameter', function () {
  30. const throwable = v => () => {
  31. const trie = new PathTrie();
  32. trie.add('foo', v);
  33. };
  34. const error = v =>
  35. format(
  36. 'The second argument of PathTrie.add is required, but %s was given.',
  37. v,
  38. );
  39. expect(throwable(undefined)).to.throw(error('undefined'));
  40. expect(throwable(null)).to.throw(error('null'));
  41. throwable('str')();
  42. throwable('')();
  43. throwable(10)();
  44. throwable(0)();
  45. throwable(true)();
  46. throwable(false)();
  47. throwable({})();
  48. throwable([])();
  49. });
  50. it('adds the given value with the path "" to the root node', function () {
  51. const trie = new PathTrie();
  52. expect(trie['_root']).to.be.eql({
  53. token: '',
  54. regexp: undefined,
  55. names: [],
  56. value: undefined,
  57. children: {},
  58. });
  59. trie.add('', VALUE);
  60. expect(trie['_root']).to.be.eql({
  61. token: '',
  62. regexp: undefined,
  63. names: [],
  64. value: VALUE,
  65. children: {},
  66. });
  67. });
  68. it('adds the given value with the path "/" to the root node', function () {
  69. const trie = new PathTrie();
  70. expect(trie['_root']).to.be.eql({
  71. token: '',
  72. regexp: undefined,
  73. names: [],
  74. value: undefined,
  75. children: {},
  76. });
  77. trie.add('/', VALUE);
  78. expect(trie['_root']).to.be.eql({
  79. token: '',
  80. regexp: undefined,
  81. names: [],
  82. value: VALUE,
  83. children: {},
  84. });
  85. });
  86. it('throws an error for the duplicate path "" with a different value', function () {
  87. const trie = new PathTrie();
  88. trie.add('', VALUE);
  89. const throwable = () => trie.add('', ANOTHER_VALUE);
  90. expect(throwable).to.throw(
  91. 'The duplicate path "" has a different value.',
  92. );
  93. });
  94. it('throws an error for the duplicate path "/" with a different value', function () {
  95. const trie = new PathTrie();
  96. trie.add('/', VALUE);
  97. const throwable = () => trie.add('/', ANOTHER_VALUE);
  98. expect(throwable).to.throw(
  99. 'The duplicate path "" has a different value.',
  100. );
  101. });
  102. it('considers paths "" and "/" are the same', function () {
  103. const trie = new PathTrie();
  104. trie.add('', VALUE);
  105. const throwable = () => trie.add('/', ANOTHER_VALUE);
  106. expect(throwable).to.throw(
  107. 'The duplicate path "" has a different value.',
  108. );
  109. });
  110. it('adds multiple nodes by the path which has multiple tokens', function () {
  111. const trie = new PathTrie();
  112. trie.add('foo/bar/baz', VALUE);
  113. expect(trie['_root']).to.be.eql({
  114. token: '',
  115. regexp: undefined,
  116. names: [],
  117. value: undefined,
  118. children: {
  119. foo: {
  120. token: 'foo',
  121. regexp: undefined,
  122. names: [],
  123. value: undefined,
  124. children: {
  125. bar: {
  126. token: 'bar',
  127. regexp: undefined,
  128. names: [],
  129. value: undefined,
  130. children: {
  131. baz: {
  132. token: 'baz',
  133. regexp: undefined,
  134. names: [],
  135. value: VALUE,
  136. children: {},
  137. },
  138. },
  139. },
  140. },
  141. },
  142. },
  143. });
  144. });
  145. it('resolves path parameters in the first node', function () {
  146. const trie = new PathTrie();
  147. trie.add(':date-:time', VALUE);
  148. expect(trie['_root']).to.be.eql({
  149. token: '',
  150. regexp: undefined,
  151. names: [],
  152. value: undefined,
  153. children: {
  154. ':date-:time': {
  155. token: ':date-:time',
  156. regexp: pathToRegexp(':date-:time').regexp,
  157. names: ['date', 'time'],
  158. value: VALUE,
  159. children: {},
  160. },
  161. },
  162. });
  163. });
  164. it('resolves path parameters in the middle node', function () {
  165. const trie = new PathTrie();
  166. trie.add('/foo/:id/bar', VALUE);
  167. expect(trie['_root']).to.be.eql({
  168. token: '',
  169. regexp: undefined,
  170. names: [],
  171. value: undefined,
  172. children: {
  173. foo: {
  174. token: 'foo',
  175. regexp: undefined,
  176. names: [],
  177. value: undefined,
  178. children: {
  179. ':id': {
  180. token: ':id',
  181. regexp: pathToRegexp(':id').regexp,
  182. names: ['id'],
  183. value: undefined,
  184. children: {
  185. bar: {
  186. token: 'bar',
  187. regexp: undefined,
  188. names: [],
  189. value: VALUE,
  190. children: {},
  191. },
  192. },
  193. },
  194. },
  195. },
  196. },
  197. });
  198. });
  199. it('throws an error for unsupported modifiers', function () {
  200. const modifiers = ['?', '*', '+', '{', '}'];
  201. const trie = new PathTrie();
  202. const throwable = v => () => trie.add(v, VALUE);
  203. const error = v =>
  204. format('The symbol %v is not supported in path "/foo/:id%s".', v, v);
  205. modifiers.forEach(m => {
  206. expect(throwable(`/foo/:id${m}`)).to.throw(error(m));
  207. });
  208. });
  209. it('throws an error if no parameter name has specified', function () {
  210. const trie = new PathTrie();
  211. const throwable = () => trie.add('/:', VALUE);
  212. expect(throwable).to.throw(
  213. 'The symbol ":" should be used to define path parameters, ' +
  214. 'but no parameters were found in the path "/:".',
  215. );
  216. });
  217. it('does not overrides value when set another one to the middle', function () {
  218. const trie = new PathTrie();
  219. trie.add('/foo/bar/baz', VALUE);
  220. trie.add('/foo/bar', ANOTHER_VALUE);
  221. expect(trie['_root']).to.be.eql({
  222. token: '',
  223. regexp: undefined,
  224. names: [],
  225. value: undefined,
  226. children: {
  227. foo: {
  228. token: 'foo',
  229. regexp: undefined,
  230. names: [],
  231. value: undefined,
  232. children: {
  233. bar: {
  234. token: 'bar',
  235. regexp: undefined,
  236. names: [],
  237. value: ANOTHER_VALUE,
  238. children: {
  239. baz: {
  240. token: 'baz',
  241. regexp: undefined,
  242. names: [],
  243. value: VALUE,
  244. children: {},
  245. },
  246. },
  247. },
  248. },
  249. },
  250. },
  251. });
  252. });
  253. });
  254. describe('match', function () {
  255. it('requires the first parameter to be a String', function () {
  256. const trie = new PathTrie();
  257. const throwable = v => () => trie.match(v);
  258. const error = v =>
  259. format(
  260. 'The first argument of PathTrie.match must be ' +
  261. 'a String, but %s was given.',
  262. v,
  263. );
  264. expect(throwable(10)).to.throw(error('10'));
  265. expect(throwable(0)).to.throw(error('0'));
  266. expect(throwable(true)).to.throw(error('true'));
  267. expect(throwable(false)).to.throw(error('false'));
  268. expect(throwable(null)).to.throw(error('null'));
  269. expect(throwable({})).to.throw(error('Object'));
  270. expect(throwable([])).to.throw(error('Array'));
  271. expect(throwable(undefined)).to.throw(error('undefined'));
  272. throwable('str')();
  273. throwable('')();
  274. });
  275. it('matches paths "" and "/" or returns undefined', function () {
  276. const trie = new PathTrie();
  277. trie.add('', VALUE);
  278. const res1 = trie.match('');
  279. const res2 = trie.match('/');
  280. const res3 = trie.match('/test');
  281. expect(res1).to.be.eql({value: VALUE, params: {}});
  282. expect(res2).to.be.eql({value: VALUE, params: {}});
  283. expect(res3).to.be.undefined;
  284. });
  285. it('returns undefined if not matched', function () {
  286. const trie = new PathTrie();
  287. trie.add('foo', VALUE);
  288. const res = trie.match('bar');
  289. expect(res).to.be.undefined;
  290. });
  291. it('matches the single token', function () {
  292. const trie = new PathTrie();
  293. trie.add('foo', VALUE);
  294. const res = trie.match('foo');
  295. expect(res).to.be.eql({value: VALUE, params: {}});
  296. });
  297. it('does not respects the prefix "/"', function () {
  298. const trie = new PathTrie();
  299. trie.add('/foo', VALUE);
  300. const res1 = trie.match('/foo');
  301. const res2 = trie.match('foo');
  302. expect(res1).to.be.eql({value: VALUE, params: {}});
  303. expect(res2).to.be.eql({value: VALUE, params: {}});
  304. });
  305. it('does not respects the postfix "/"', function () {
  306. const trie = new PathTrie();
  307. trie.add('/foo', VALUE);
  308. const res1 = trie.match('foo/');
  309. const res2 = trie.match('foo');
  310. expect(res1).to.be.eql({value: VALUE, params: {}});
  311. expect(res2).to.be.eql({value: VALUE, params: {}});
  312. });
  313. it('matches parameters of the first token', function () {
  314. const trie = new PathTrie();
  315. trie.add(':foo-:bar', VALUE);
  316. const res = trie.match('baz-qux');
  317. expect(res).to.be.eql({
  318. value: VALUE,
  319. params: {
  320. foo: 'baz',
  321. bar: 'qux',
  322. },
  323. });
  324. });
  325. it('matches parameters of the first token in the case of multiple tokens', function () {
  326. const trie = new PathTrie();
  327. trie.add(':foo-:bar/test', VALUE);
  328. const res = trie.match('baz-qux/test');
  329. expect(res).to.be.eql({
  330. value: VALUE,
  331. params: {
  332. foo: 'baz',
  333. bar: 'qux',
  334. },
  335. });
  336. });
  337. it('matches parameters of the second token', function () {
  338. const trie = new PathTrie();
  339. trie.add('/test/:foo-:bar', VALUE);
  340. const res = trie.match('/test/baz-qux');
  341. expect(res).to.be.eql({
  342. value: VALUE,
  343. params: {
  344. foo: 'baz',
  345. bar: 'qux',
  346. },
  347. });
  348. });
  349. it('does not match a path which has more tokens than needed', function () {
  350. const trie = new PathTrie();
  351. trie.add('/foo', VALUE);
  352. const res = trie.match('/foo/bar');
  353. expect(res).to.be.undefined;
  354. });
  355. it('does not match a path which has less tokens than needed', function () {
  356. const trie = new PathTrie();
  357. trie.add('/foo/bar', VALUE);
  358. const res = trie.match('/foo');
  359. expect(res).to.be.undefined;
  360. });
  361. });
  362. });