testing.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603
  1. /**
  2. * @license Angular v8.1.0
  3. * (c) 2010-2019 Google LLC. https://angular.io/
  4. * License: MIT
  5. */
  6. import { EventEmitter, Injectable, InjectionToken, Inject, Optional } from '@angular/core';
  7. import { LocationStrategy } from '@angular/common';
  8. import { Subject } from 'rxjs';
  9. /**
  10. * @fileoverview added by tsickle
  11. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  12. */
  13. /**
  14. * A spy for {\@link Location} that allows tests to fire simulated location events.
  15. *
  16. * \@publicApi
  17. */
  18. class SpyLocation {
  19. constructor() {
  20. this.urlChanges = [];
  21. this._history = [new LocationState('', '', null)];
  22. this._historyIndex = 0;
  23. /**
  24. * \@internal
  25. */
  26. this._subject = new EventEmitter();
  27. /**
  28. * \@internal
  29. */
  30. this._baseHref = '';
  31. /**
  32. * \@internal
  33. */
  34. this._platformStrategy = (/** @type {?} */ (null));
  35. /**
  36. * \@internal
  37. */
  38. this._platformLocation = (/** @type {?} */ (null));
  39. /**
  40. * \@internal
  41. */
  42. this._urlChangeListeners = [];
  43. }
  44. /**
  45. * @param {?} url
  46. * @return {?}
  47. */
  48. setInitialPath(url) { this._history[this._historyIndex].path = url; }
  49. /**
  50. * @param {?} url
  51. * @return {?}
  52. */
  53. setBaseHref(url) { this._baseHref = url; }
  54. /**
  55. * @return {?}
  56. */
  57. path() { return this._history[this._historyIndex].path; }
  58. /**
  59. * @return {?}
  60. */
  61. getState() { return this._history[this._historyIndex].state; }
  62. /**
  63. * @param {?} path
  64. * @param {?=} query
  65. * @return {?}
  66. */
  67. isCurrentPathEqualTo(path, query = '') {
  68. /** @type {?} */
  69. const givenPath = path.endsWith('/') ? path.substring(0, path.length - 1) : path;
  70. /** @type {?} */
  71. const currPath = this.path().endsWith('/') ? this.path().substring(0, this.path().length - 1) : this.path();
  72. return currPath == givenPath + (query.length > 0 ? ('?' + query) : '');
  73. }
  74. /**
  75. * @param {?} pathname
  76. * @return {?}
  77. */
  78. simulateUrlPop(pathname) {
  79. this._subject.emit({ 'url': pathname, 'pop': true, 'type': 'popstate' });
  80. }
  81. /**
  82. * @param {?} pathname
  83. * @return {?}
  84. */
  85. simulateHashChange(pathname) {
  86. // Because we don't prevent the native event, the browser will independently update the path
  87. this.setInitialPath(pathname);
  88. this.urlChanges.push('hash: ' + pathname);
  89. this._subject.emit({ 'url': pathname, 'pop': true, 'type': 'hashchange' });
  90. }
  91. /**
  92. * @param {?} url
  93. * @return {?}
  94. */
  95. prepareExternalUrl(url) {
  96. if (url.length > 0 && !url.startsWith('/')) {
  97. url = '/' + url;
  98. }
  99. return this._baseHref + url;
  100. }
  101. /**
  102. * @param {?} path
  103. * @param {?=} query
  104. * @param {?=} state
  105. * @return {?}
  106. */
  107. go(path, query = '', state = null) {
  108. path = this.prepareExternalUrl(path);
  109. if (this._historyIndex > 0) {
  110. this._history.splice(this._historyIndex + 1);
  111. }
  112. this._history.push(new LocationState(path, query, state));
  113. this._historyIndex = this._history.length - 1;
  114. /** @type {?} */
  115. const locationState = this._history[this._historyIndex - 1];
  116. if (locationState.path == path && locationState.query == query) {
  117. return;
  118. }
  119. /** @type {?} */
  120. const url = path + (query.length > 0 ? ('?' + query) : '');
  121. this.urlChanges.push(url);
  122. this._subject.emit({ 'url': url, 'pop': false });
  123. }
  124. /**
  125. * @param {?} path
  126. * @param {?=} query
  127. * @param {?=} state
  128. * @return {?}
  129. */
  130. replaceState(path, query = '', state = null) {
  131. path = this.prepareExternalUrl(path);
  132. /** @type {?} */
  133. const history = this._history[this._historyIndex];
  134. if (history.path == path && history.query == query) {
  135. return;
  136. }
  137. history.path = path;
  138. history.query = query;
  139. history.state = state;
  140. /** @type {?} */
  141. const url = path + (query.length > 0 ? ('?' + query) : '');
  142. this.urlChanges.push('replace: ' + url);
  143. }
  144. /**
  145. * @return {?}
  146. */
  147. forward() {
  148. if (this._historyIndex < (this._history.length - 1)) {
  149. this._historyIndex++;
  150. this._subject.emit({ 'url': this.path(), 'state': this.getState(), 'pop': true });
  151. }
  152. }
  153. /**
  154. * @return {?}
  155. */
  156. back() {
  157. if (this._historyIndex > 0) {
  158. this._historyIndex--;
  159. this._subject.emit({ 'url': this.path(), 'state': this.getState(), 'pop': true });
  160. }
  161. }
  162. /**
  163. * @param {?} fn
  164. * @return {?}
  165. */
  166. onUrlChange(fn) {
  167. this._urlChangeListeners.push(fn);
  168. this.subscribe((/**
  169. * @param {?} v
  170. * @return {?}
  171. */
  172. v => { this._notifyUrlChangeListeners(v.url, v.state); }));
  173. }
  174. /**
  175. * \@internal
  176. * @param {?=} url
  177. * @param {?=} state
  178. * @return {?}
  179. */
  180. _notifyUrlChangeListeners(url = '', state) {
  181. this._urlChangeListeners.forEach((/**
  182. * @param {?} fn
  183. * @return {?}
  184. */
  185. fn => fn(url, state)));
  186. }
  187. /**
  188. * @param {?} onNext
  189. * @param {?=} onThrow
  190. * @param {?=} onReturn
  191. * @return {?}
  192. */
  193. subscribe(onNext, onThrow, onReturn) {
  194. return this._subject.subscribe({ next: onNext, error: onThrow, complete: onReturn });
  195. }
  196. /**
  197. * @param {?} url
  198. * @return {?}
  199. */
  200. normalize(url) { return (/** @type {?} */ (null)); }
  201. }
  202. SpyLocation.decorators = [
  203. { type: Injectable }
  204. ];
  205. class LocationState {
  206. /**
  207. * @param {?} path
  208. * @param {?} query
  209. * @param {?} state
  210. */
  211. constructor(path, query, state) {
  212. this.path = path;
  213. this.query = query;
  214. this.state = state;
  215. }
  216. }
  217. /**
  218. * @fileoverview added by tsickle
  219. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  220. */
  221. /**
  222. * A mock implementation of {\@link LocationStrategy} that allows tests to fire simulated
  223. * location events.
  224. *
  225. * \@publicApi
  226. */
  227. class MockLocationStrategy extends LocationStrategy {
  228. constructor() {
  229. super();
  230. this.internalBaseHref = '/';
  231. this.internalPath = '/';
  232. this.internalTitle = '';
  233. this.urlChanges = [];
  234. /**
  235. * \@internal
  236. */
  237. this._subject = new EventEmitter();
  238. this.stateChanges = [];
  239. }
  240. /**
  241. * @param {?} url
  242. * @return {?}
  243. */
  244. simulatePopState(url) {
  245. this.internalPath = url;
  246. this._subject.emit(new _MockPopStateEvent(this.path()));
  247. }
  248. /**
  249. * @param {?=} includeHash
  250. * @return {?}
  251. */
  252. path(includeHash = false) { return this.internalPath; }
  253. /**
  254. * @param {?} internal
  255. * @return {?}
  256. */
  257. prepareExternalUrl(internal) {
  258. if (internal.startsWith('/') && this.internalBaseHref.endsWith('/')) {
  259. return this.internalBaseHref + internal.substring(1);
  260. }
  261. return this.internalBaseHref + internal;
  262. }
  263. /**
  264. * @param {?} ctx
  265. * @param {?} title
  266. * @param {?} path
  267. * @param {?} query
  268. * @return {?}
  269. */
  270. pushState(ctx, title, path, query) {
  271. // Add state change to changes array
  272. this.stateChanges.push(ctx);
  273. this.internalTitle = title;
  274. /** @type {?} */
  275. const url = path + (query.length > 0 ? ('?' + query) : '');
  276. this.internalPath = url;
  277. /** @type {?} */
  278. const externalUrl = this.prepareExternalUrl(url);
  279. this.urlChanges.push(externalUrl);
  280. }
  281. /**
  282. * @param {?} ctx
  283. * @param {?} title
  284. * @param {?} path
  285. * @param {?} query
  286. * @return {?}
  287. */
  288. replaceState(ctx, title, path, query) {
  289. // Reset the last index of stateChanges to the ctx (state) object
  290. this.stateChanges[(this.stateChanges.length || 1) - 1] = ctx;
  291. this.internalTitle = title;
  292. /** @type {?} */
  293. const url = path + (query.length > 0 ? ('?' + query) : '');
  294. this.internalPath = url;
  295. /** @type {?} */
  296. const externalUrl = this.prepareExternalUrl(url);
  297. this.urlChanges.push('replace: ' + externalUrl);
  298. }
  299. /**
  300. * @param {?} fn
  301. * @return {?}
  302. */
  303. onPopState(fn) { this._subject.subscribe({ next: fn }); }
  304. /**
  305. * @return {?}
  306. */
  307. getBaseHref() { return this.internalBaseHref; }
  308. /**
  309. * @return {?}
  310. */
  311. back() {
  312. if (this.urlChanges.length > 0) {
  313. this.urlChanges.pop();
  314. this.stateChanges.pop();
  315. /** @type {?} */
  316. const nextUrl = this.urlChanges.length > 0 ? this.urlChanges[this.urlChanges.length - 1] : '';
  317. this.simulatePopState(nextUrl);
  318. }
  319. }
  320. /**
  321. * @return {?}
  322. */
  323. forward() { throw 'not implemented'; }
  324. /**
  325. * @return {?}
  326. */
  327. getState() { return this.stateChanges[(this.stateChanges.length || 1) - 1]; }
  328. }
  329. MockLocationStrategy.decorators = [
  330. { type: Injectable }
  331. ];
  332. /** @nocollapse */
  333. MockLocationStrategy.ctorParameters = () => [];
  334. class _MockPopStateEvent {
  335. /**
  336. * @param {?} newUrl
  337. */
  338. constructor(newUrl) {
  339. this.newUrl = newUrl;
  340. this.pop = true;
  341. this.type = 'popstate';
  342. }
  343. }
  344. /**
  345. * @fileoverview added by tsickle
  346. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  347. */
  348. /**
  349. * Parser from https://tools.ietf.org/html/rfc3986#appendix-B
  350. * ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
  351. * 12 3 4 5 6 7 8 9
  352. *
  353. * Example: http://www.ics.uci.edu/pub/ietf/uri/#Related
  354. *
  355. * Results in:
  356. *
  357. * $1 = http:
  358. * $2 = http
  359. * $3 = //www.ics.uci.edu
  360. * $4 = www.ics.uci.edu
  361. * $5 = /pub/ietf/uri/
  362. * $6 = <undefined>
  363. * $7 = <undefined>
  364. * $8 = #Related
  365. * $9 = Related
  366. * @type {?}
  367. */
  368. const urlParse = /^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/;
  369. /**
  370. * @param {?} urlStr
  371. * @param {?} baseHref
  372. * @return {?}
  373. */
  374. function parseUrl(urlStr, baseHref) {
  375. /** @type {?} */
  376. const verifyProtocol = /^((http[s]?|ftp):\/\/)/;
  377. /** @type {?} */
  378. let serverBase;
  379. // URL class requires full URL. If the URL string doesn't start with protocol, we need to add
  380. // an arbitrary base URL which can be removed afterward.
  381. if (!verifyProtocol.test(urlStr)) {
  382. serverBase = 'http://empty.com/';
  383. }
  384. /** @type {?} */
  385. let parsedUrl;
  386. try {
  387. parsedUrl = new URL(urlStr, serverBase);
  388. }
  389. catch (e) {
  390. /** @type {?} */
  391. const result = urlParse.exec(serverBase || '' + urlStr);
  392. if (!result) {
  393. throw new Error(`Invalid URL: ${urlStr} with base: ${baseHref}`);
  394. }
  395. /** @type {?} */
  396. const hostSplit = result[4].split(':');
  397. parsedUrl = {
  398. protocol: result[1],
  399. hostname: hostSplit[0],
  400. port: hostSplit[1] || '',
  401. pathname: result[5],
  402. search: result[6],
  403. hash: result[8],
  404. };
  405. }
  406. if (parsedUrl.pathname && parsedUrl.pathname.indexOf(baseHref) === 0) {
  407. parsedUrl.pathname = parsedUrl.pathname.substring(baseHref.length);
  408. }
  409. return {
  410. hostname: !serverBase && parsedUrl.hostname || '',
  411. protocol: !serverBase && parsedUrl.protocol || '',
  412. port: !serverBase && parsedUrl.port || '',
  413. pathname: parsedUrl.pathname || '/',
  414. search: parsedUrl.search || '',
  415. hash: parsedUrl.hash || '',
  416. };
  417. }
  418. /**
  419. * Provider for mock platform location config
  420. *
  421. * \@publicApi
  422. * @type {?}
  423. */
  424. const MOCK_PLATFORM_LOCATION_CONFIG = new InjectionToken('MOCK_PLATFORM_LOCATION_CONFIG');
  425. /**
  426. * Mock implementation of URL state.
  427. *
  428. * \@publicApi
  429. */
  430. class MockPlatformLocation {
  431. /**
  432. * @param {?=} config
  433. */
  434. constructor(config) {
  435. this.baseHref = '';
  436. this.hashUpdate = new Subject();
  437. this.urlChanges = [{ hostname: '', protocol: '', port: '', pathname: '/', search: '', hash: '', state: null }];
  438. if (config) {
  439. this.baseHref = config.appBaseHref || '';
  440. /** @type {?} */
  441. const parsedChanges = this.parseChanges(null, config.startUrl || 'http://<empty>/', this.baseHref);
  442. this.urlChanges[0] = Object.assign({}, parsedChanges);
  443. }
  444. }
  445. /**
  446. * @return {?}
  447. */
  448. get hostname() { return this.urlChanges[0].hostname; }
  449. /**
  450. * @return {?}
  451. */
  452. get protocol() { return this.urlChanges[0].protocol; }
  453. /**
  454. * @return {?}
  455. */
  456. get port() { return this.urlChanges[0].port; }
  457. /**
  458. * @return {?}
  459. */
  460. get pathname() { return this.urlChanges[0].pathname; }
  461. /**
  462. * @return {?}
  463. */
  464. get search() { return this.urlChanges[0].search; }
  465. /**
  466. * @return {?}
  467. */
  468. get hash() { return this.urlChanges[0].hash; }
  469. /**
  470. * @return {?}
  471. */
  472. get state() { return this.urlChanges[0].state; }
  473. /**
  474. * @return {?}
  475. */
  476. getBaseHrefFromDOM() { return this.baseHref; }
  477. /**
  478. * @param {?} fn
  479. * @return {?}
  480. */
  481. onPopState(fn) {
  482. // No-op: a state stack is not implemented, so
  483. // no events will ever come.
  484. }
  485. /**
  486. * @param {?} fn
  487. * @return {?}
  488. */
  489. onHashChange(fn) { this.hashUpdate.subscribe(fn); }
  490. /**
  491. * @return {?}
  492. */
  493. get href() {
  494. /** @type {?} */
  495. let url = `${this.protocol}//${this.hostname}${this.port ? ':' + this.port : ''}`;
  496. url += `${this.pathname === '/' ? '' : this.pathname}${this.search}${this.hash}`;
  497. return url;
  498. }
  499. /**
  500. * @return {?}
  501. */
  502. get url() { return `${this.pathname}${this.search}${this.hash}`; }
  503. /**
  504. * @private
  505. * @param {?} state
  506. * @param {?} url
  507. * @param {?=} baseHref
  508. * @return {?}
  509. */
  510. parseChanges(state, url, baseHref = '') {
  511. // When the `history.state` value is stored, it is always copied.
  512. state = JSON.parse(JSON.stringify(state));
  513. return Object.assign({}, parseUrl(url, baseHref), { state });
  514. }
  515. /**
  516. * @param {?} state
  517. * @param {?} title
  518. * @param {?} newUrl
  519. * @return {?}
  520. */
  521. replaceState(state, title, newUrl) {
  522. const { pathname, search, state: parsedState, hash } = this.parseChanges(state, newUrl);
  523. this.urlChanges[0] = Object.assign({}, this.urlChanges[0], { pathname, search, hash, state: parsedState });
  524. }
  525. /**
  526. * @param {?} state
  527. * @param {?} title
  528. * @param {?} newUrl
  529. * @return {?}
  530. */
  531. pushState(state, title, newUrl) {
  532. const { pathname, search, state: parsedState, hash } = this.parseChanges(state, newUrl);
  533. this.urlChanges.unshift(Object.assign({}, this.urlChanges[0], { pathname, search, hash, state: parsedState }));
  534. }
  535. /**
  536. * @return {?}
  537. */
  538. forward() { throw new Error('Not implemented'); }
  539. /**
  540. * @return {?}
  541. */
  542. back() {
  543. /** @type {?} */
  544. const oldUrl = this.url;
  545. /** @type {?} */
  546. const oldHash = this.hash;
  547. this.urlChanges.shift();
  548. /** @type {?} */
  549. const newHash = this.hash;
  550. if (oldHash !== newHash) {
  551. scheduleMicroTask((/**
  552. * @return {?}
  553. */
  554. () => this.hashUpdate.next((/** @type {?} */ ({
  555. type: 'hashchange', state: null, oldUrl, newUrl: this.url
  556. })))));
  557. }
  558. }
  559. /**
  560. * @return {?}
  561. */
  562. getState() { return this.state; }
  563. }
  564. MockPlatformLocation.decorators = [
  565. { type: Injectable }
  566. ];
  567. /** @nocollapse */
  568. MockPlatformLocation.ctorParameters = () => [
  569. { type: undefined, decorators: [{ type: Inject, args: [MOCK_PLATFORM_LOCATION_CONFIG,] }, { type: Optional }] }
  570. ];
  571. /**
  572. * @param {?} cb
  573. * @return {?}
  574. */
  575. function scheduleMicroTask(cb) {
  576. Promise.resolve(null).then(cb);
  577. }
  578. /**
  579. * @fileoverview added by tsickle
  580. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  581. */
  582. /**
  583. * @fileoverview added by tsickle
  584. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  585. */
  586. /**
  587. * @fileoverview added by tsickle
  588. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  589. */
  590. /**
  591. * Generated bundle index. Do not edit.
  592. */
  593. export { SpyLocation, MockLocationStrategy, MOCK_PLATFORM_LOCATION_CONFIG, MockPlatformLocation };
  594. //# sourceMappingURL=testing.js.map