@universal-ember/test-support 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -11,6 +11,23 @@ interface VisitAllLinksOptions {
|
|
|
11
11
|
* apps that need per-link click fidelity.
|
|
12
12
|
*/
|
|
13
13
|
mode?: 'visit' | 'click';
|
|
14
|
+
/**
|
|
15
|
+
* Decides whether a discovered target is crawled at all. Return `false` to
|
|
16
|
+
* skip it — the crawl neither navigates to it nor asserts on it, and its
|
|
17
|
+
* own links are not discovered through it.
|
|
18
|
+
*
|
|
19
|
+
* For links the app deliberately refuses to navigate — e.g. demo links
|
|
20
|
+
* whose route calls `transition.abort()`, or areas under test elsewhere:
|
|
21
|
+
*
|
|
22
|
+
* ```js
|
|
23
|
+
* await visitAllLinks(undefined, undefined, {
|
|
24
|
+
* shouldVisit: (url) => !url.startsWith('/demo-targets/'),
|
|
25
|
+
* });
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* Receives the app-relative target (hash included, as authored).
|
|
29
|
+
*/
|
|
30
|
+
shouldVisit?: (url: string) => boolean;
|
|
14
31
|
}
|
|
15
32
|
export declare function visitAllLinks(callback?: (url: string) => void | Promise<void>, knownRedirects?: Record<string, string>, options?: VisitAllLinksOptions): Promise<number>;
|
|
16
33
|
export {};
|
|
@@ -42,6 +42,7 @@ function findInAppLinks() {
|
|
|
42
42
|
const assert = QUnit.assert;
|
|
43
43
|
async function visitAllLinks(callback, knownRedirects, options) {
|
|
44
44
|
const mode = options?.mode ?? 'visit';
|
|
45
|
+
const shouldVisit = options?.shouldVisit ?? (() => true);
|
|
45
46
|
/**
|
|
46
47
|
* app-relative target paths (without hash)
|
|
47
48
|
*/
|
|
@@ -82,6 +83,7 @@ async function visitAllLinks(callback, knownRedirects, options) {
|
|
|
82
83
|
// appear on every page), which times out on documentation-sized apps.
|
|
83
84
|
const key = nonHashPart;
|
|
84
85
|
if (visited.has(key)) continue;
|
|
86
|
+
if (!shouldVisit(toVisit.href)) continue;
|
|
85
87
|
const result = router.recognize(toVisit.href);
|
|
86
88
|
if (!result) {
|
|
87
89
|
assert.ok(true, `${toVisit.href} on page ${returnTo} is not recognized by this app and will be skipped`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"visit-all.js","sources":["../../src/routing/visit-all.ts"],"sourcesContent":["import { assert as debugAssert } from '@ember/debug';\nimport {\n visit,\n find,\n getContext,\n click,\n currentURL,\n findAll,\n} from '@ember/test-helpers';\nimport QUnit from 'qunit';\nimport { shouldHandle } from 'should-handle-link';\nimport type Owner from '@ember/owner';\nimport type RouterService from '@ember/routing/router-service';\n\ninterface InAppLink {\n href: string;\n original: string;\n selector: string;\n}\n\nfunction findInAppLinks(): InAppLink[] {\n const results: InAppLink[] = [];\n\n const allAnchorsOnThePage = findAll('a');\n\n for (const a of allAnchorsOnThePage) {\n // `findAll('a')` can also match SVG `<a>`, whose `href` is an\n // SVGAnimatedString the router (and shouldHandle) can't work with.\n if (!(a instanceof HTMLAnchorElement)) continue;\n\n const href = a.getAttribute('href');\n\n if (!href) continue;\n\n /**\n * Links the SPA's router never handles are handled natively by the\n * browser instead (new tab/window via `target`, download dialog,\n * `mailto:`/`tel:`, cross-origin, `rel=\"external\"`). Clicking them in a\n * test can't change `currentURL()`, so they'd always be reported as\n * failed navigations.\n *\n * `shouldHandle` is the predicate ember-primitives' @properLinks uses to\n * decide whether the router handles a click, so the crawler visits\n * exactly the set of links the router would. The fabricated click event\n * carries the \"plain left click\" defaults (button 0, no modifier keys).\n */\n if (!shouldHandle(window.location.href, a, new MouseEvent('click'))) {\n continue;\n }\n\n const current = new URL(currentURL(), window.location.origin);\n const url = new URL(href, current);\n const withoutDomain = `${url.pathname}${url.search}${url.hash}`;\n\n results.push({\n href: withoutDomain,\n original: href,\n selector: `a[href=\"${href}\"]`,\n });\n }\n\n return results;\n}\n\nconst assert = QUnit.assert;\n\ninterface VisitAllLinksOptions {\n /**\n * How each discovered target is navigated to:\n *\n * - `'visit'` (the default): `visit(target)` directly. One page render per\n * unique target — the cheapest possible full crawl.\n * - `'click'`: return to the page the link was found on and click the\n * actual anchor, exercising the app's link-interception (e.g.\n * `@properLinks`) for every link. Twice the page renders of `'visit'`\n * (each processed link re-renders its source page), so reserve it for\n * apps that need per-link click fidelity.\n */\n mode?: 'visit' | 'click';\n}\n\nexport async function visitAllLinks(\n callback?: (url: string) => void | Promise<void>,\n knownRedirects?: Record<string, string>,\n options?: VisitAllLinksOptions,\n) {\n const mode = options?.mode ?? 'visit';\n /**\n * app-relative target paths (without hash)\n */\n const visited = new Set();\n let returnTo = '/';\n\n await visit(returnTo);\n\n const inAppLinks = findInAppLinks();\n const queue: (InAppLink | { changeReturnTo: string })[] = [...inAppLinks];\n\n const ctx = getContext() as unknown as { owner: Owner };\n const router = ctx?.owner?.lookup('service:router') as RouterService;\n\n debugAssert(`Could not find the router service`, router);\n\n const rootURL = router.rootURL;\n\n while (queue.length > 0) {\n const toVisit = queue.shift();\n\n debugAssert(`Queue entries cannot be falsey`, toVisit);\n\n if ('changeReturnTo' in toVisit) {\n returnTo = toVisit.changeReturnTo;\n continue;\n }\n\n // In-page links are on the page we're already on.\n // As long as we haven't already encountered an error,\n // this is silly to check.\n if (toVisit.original.startsWith('#')) {\n continue;\n }\n\n const [nonHashPart] = toVisit.href.split('#');\n\n // This was our first page, we've already been here\n if (nonHashPart === '/') {\n continue;\n }\n\n // Keyed on the target alone: this crawl answers \"is every reachable URL\n // visitable?\", so one visit per target suffices. Keying on\n // (current page, target) pairs re-visits every target from every page\n // that links it — quadratic in the size of the app (shared nav links\n // appear on every page), which times out on documentation-sized apps.\n const key = nonHashPart;\n\n if (visited.has(key)) continue;\n\n const result = router.recognize(toVisit.href);\n\n if (!result) {\n assert.ok(\n true,\n `${toVisit.href} on page ${returnTo} is not recognized by this app and will be skipped`,\n );\n continue;\n }\n\n if (!toVisit.original.startsWith('/')) {\n console.warn(\n `[visitAllLinks] Relative href \"${toVisit.original}\" found on ${returnTo}. ` +\n `Relative hrefs resolve against the browser's URL rather than the app's current route, ` +\n `so they only behave in a real full-page visit — they misresolve in this test harness ` +\n `and under any mount where the address bar isn't the route (embeds, previews). ` +\n `The crawler navigated to the resolved target instead. ` +\n `Action: update the source document to link to \"${toVisit.href}\" directly.`,\n );\n }\n\n if (mode === 'click') {\n await visit(returnTo);\n\n const link = find(toVisit.selector);\n\n debugAssert(`link exists via selector \\`${toVisit.selector}\\``, link);\n\n /**\n * The click navigates by `element.href`, which the browser resolved\n * against the test page's URL (e.g. `/tests`) — NOT against the app's\n * current route the way a production visit would (there, the address\n * bar is the current route). A relative href would therefore navigate\n * somewhere the real app never goes. We already resolved the target\n * against `currentURL()` when the link was encountered, so point the\n * anchor at that; the click then exercises the real\n * properLinks-and-router path with the production URL.\n */\n if (!toVisit.original.startsWith('/')) {\n link.setAttribute('href', toVisit.href);\n }\n\n await click(link);\n } else {\n try {\n await visit(nonHashPart ?? toVisit.href);\n } catch (error) {\n // visit() rejects when the route errors (click-mode surfaces the\n // same problem as a URL mismatch via the app's error substate)\n assert.pushResult({\n result: false,\n actual: String(error),\n expected: nonHashPart,\n message: `Navigation was successful: to:${toVisit.original}, from:${returnTo}`,\n });\n visited.add(key);\n continue;\n }\n }\n\n const current =\n rootURL.replace(/\\/$/, '') + '/' + currentURL().replace(/^\\//, '');\n const expected = knownRedirects?.[toVisit.href] ?? toVisit.href;\n // currentURL() never includes a #hash, so compare without it\n const [expectedPath] = expected.split('#');\n\n assert.pushResult({\n result: current.startsWith(expectedPath ?? expected),\n actual: current,\n expected: expectedPath,\n message: `Navigation was successful: to:${toVisit.original}, from:${returnTo}`,\n });\n visited.add(key);\n\n if (callback) {\n await callback(toVisit.href);\n }\n\n const links = findInAppLinks();\n\n queue.push({ changeReturnTo: currentURL() });\n queue.push(...links);\n }\n\n return visited.size;\n}\n"],"names":["findInAppLinks","results","allAnchorsOnThePage","findAll","a","HTMLAnchorElement","href","getAttribute","shouldHandle","window","location","MouseEvent","current","URL","currentURL","origin","url","withoutDomain","pathname","search","hash","push","original","selector","assert","QUnit","visitAllLinks","callback","knownRedirects","options","mode","visited","Set","returnTo","visit","inAppLinks","queue","ctx","getContext","router","owner","lookup","debugAssert","rootURL","length","toVisit","shift","changeReturnTo","startsWith","nonHashPart","split","key","has","result","recognize","ok","console","warn","link","find","setAttribute","click","error","pushResult","actual","String","expected","message","add","replace","expectedPath","links","size"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"visit-all.js","sources":["../../src/routing/visit-all.ts"],"sourcesContent":["import { assert as debugAssert } from '@ember/debug';\nimport {\n visit,\n find,\n getContext,\n click,\n currentURL,\n findAll,\n} from '@ember/test-helpers';\nimport QUnit from 'qunit';\nimport { shouldHandle } from 'should-handle-link';\nimport type Owner from '@ember/owner';\nimport type RouterService from '@ember/routing/router-service';\n\ninterface InAppLink {\n href: string;\n original: string;\n selector: string;\n}\n\nfunction findInAppLinks(): InAppLink[] {\n const results: InAppLink[] = [];\n\n const allAnchorsOnThePage = findAll('a');\n\n for (const a of allAnchorsOnThePage) {\n // `findAll('a')` can also match SVG `<a>`, whose `href` is an\n // SVGAnimatedString the router (and shouldHandle) can't work with.\n if (!(a instanceof HTMLAnchorElement)) continue;\n\n const href = a.getAttribute('href');\n\n if (!href) continue;\n\n /**\n * Links the SPA's router never handles are handled natively by the\n * browser instead (new tab/window via `target`, download dialog,\n * `mailto:`/`tel:`, cross-origin, `rel=\"external\"`). Clicking them in a\n * test can't change `currentURL()`, so they'd always be reported as\n * failed navigations.\n *\n * `shouldHandle` is the predicate ember-primitives' @properLinks uses to\n * decide whether the router handles a click, so the crawler visits\n * exactly the set of links the router would. The fabricated click event\n * carries the \"plain left click\" defaults (button 0, no modifier keys).\n */\n if (!shouldHandle(window.location.href, a, new MouseEvent('click'))) {\n continue;\n }\n\n const current = new URL(currentURL(), window.location.origin);\n const url = new URL(href, current);\n const withoutDomain = `${url.pathname}${url.search}${url.hash}`;\n\n results.push({\n href: withoutDomain,\n original: href,\n selector: `a[href=\"${href}\"]`,\n });\n }\n\n return results;\n}\n\nconst assert = QUnit.assert;\n\ninterface VisitAllLinksOptions {\n /**\n * How each discovered target is navigated to:\n *\n * - `'visit'` (the default): `visit(target)` directly. One page render per\n * unique target — the cheapest possible full crawl.\n * - `'click'`: return to the page the link was found on and click the\n * actual anchor, exercising the app's link-interception (e.g.\n * `@properLinks`) for every link. Twice the page renders of `'visit'`\n * (each processed link re-renders its source page), so reserve it for\n * apps that need per-link click fidelity.\n */\n mode?: 'visit' | 'click';\n\n /**\n * Decides whether a discovered target is crawled at all. Return `false` to\n * skip it — the crawl neither navigates to it nor asserts on it, and its\n * own links are not discovered through it.\n *\n * For links the app deliberately refuses to navigate — e.g. demo links\n * whose route calls `transition.abort()`, or areas under test elsewhere:\n *\n * ```js\n * await visitAllLinks(undefined, undefined, {\n * shouldVisit: (url) => !url.startsWith('/demo-targets/'),\n * });\n * ```\n *\n * Receives the app-relative target (hash included, as authored).\n */\n shouldVisit?: (url: string) => boolean;\n}\n\nexport async function visitAllLinks(\n callback?: (url: string) => void | Promise<void>,\n knownRedirects?: Record<string, string>,\n options?: VisitAllLinksOptions,\n) {\n const mode = options?.mode ?? 'visit';\n const shouldVisit = options?.shouldVisit ?? (() => true);\n /**\n * app-relative target paths (without hash)\n */\n const visited = new Set();\n let returnTo = '/';\n\n await visit(returnTo);\n\n const inAppLinks = findInAppLinks();\n const queue: (InAppLink | { changeReturnTo: string })[] = [...inAppLinks];\n\n const ctx = getContext() as unknown as { owner: Owner };\n const router = ctx?.owner?.lookup('service:router') as RouterService;\n\n debugAssert(`Could not find the router service`, router);\n\n const rootURL = router.rootURL;\n\n while (queue.length > 0) {\n const toVisit = queue.shift();\n\n debugAssert(`Queue entries cannot be falsey`, toVisit);\n\n if ('changeReturnTo' in toVisit) {\n returnTo = toVisit.changeReturnTo;\n continue;\n }\n\n // In-page links are on the page we're already on.\n // As long as we haven't already encountered an error,\n // this is silly to check.\n if (toVisit.original.startsWith('#')) {\n continue;\n }\n\n const [nonHashPart] = toVisit.href.split('#');\n\n // This was our first page, we've already been here\n if (nonHashPart === '/') {\n continue;\n }\n\n // Keyed on the target alone: this crawl answers \"is every reachable URL\n // visitable?\", so one visit per target suffices. Keying on\n // (current page, target) pairs re-visits every target from every page\n // that links it — quadratic in the size of the app (shared nav links\n // appear on every page), which times out on documentation-sized apps.\n const key = nonHashPart;\n\n if (visited.has(key)) continue;\n\n if (!shouldVisit(toVisit.href)) continue;\n\n const result = router.recognize(toVisit.href);\n\n if (!result) {\n assert.ok(\n true,\n `${toVisit.href} on page ${returnTo} is not recognized by this app and will be skipped`,\n );\n continue;\n }\n\n if (!toVisit.original.startsWith('/')) {\n console.warn(\n `[visitAllLinks] Relative href \"${toVisit.original}\" found on ${returnTo}. ` +\n `Relative hrefs resolve against the browser's URL rather than the app's current route, ` +\n `so they only behave in a real full-page visit — they misresolve in this test harness ` +\n `and under any mount where the address bar isn't the route (embeds, previews). ` +\n `The crawler navigated to the resolved target instead. ` +\n `Action: update the source document to link to \"${toVisit.href}\" directly.`,\n );\n }\n\n if (mode === 'click') {\n await visit(returnTo);\n\n const link = find(toVisit.selector);\n\n debugAssert(`link exists via selector \\`${toVisit.selector}\\``, link);\n\n /**\n * The click navigates by `element.href`, which the browser resolved\n * against the test page's URL (e.g. `/tests`) — NOT against the app's\n * current route the way a production visit would (there, the address\n * bar is the current route). A relative href would therefore navigate\n * somewhere the real app never goes. We already resolved the target\n * against `currentURL()` when the link was encountered, so point the\n * anchor at that; the click then exercises the real\n * properLinks-and-router path with the production URL.\n */\n if (!toVisit.original.startsWith('/')) {\n link.setAttribute('href', toVisit.href);\n }\n\n await click(link);\n } else {\n try {\n await visit(nonHashPart ?? toVisit.href);\n } catch (error) {\n // visit() rejects when the route errors (click-mode surfaces the\n // same problem as a URL mismatch via the app's error substate)\n assert.pushResult({\n result: false,\n actual: String(error),\n expected: nonHashPart,\n message: `Navigation was successful: to:${toVisit.original}, from:${returnTo}`,\n });\n visited.add(key);\n continue;\n }\n }\n\n const current =\n rootURL.replace(/\\/$/, '') + '/' + currentURL().replace(/^\\//, '');\n const expected = knownRedirects?.[toVisit.href] ?? toVisit.href;\n // currentURL() never includes a #hash, so compare without it\n const [expectedPath] = expected.split('#');\n\n assert.pushResult({\n result: current.startsWith(expectedPath ?? expected),\n actual: current,\n expected: expectedPath,\n message: `Navigation was successful: to:${toVisit.original}, from:${returnTo}`,\n });\n visited.add(key);\n\n if (callback) {\n await callback(toVisit.href);\n }\n\n const links = findInAppLinks();\n\n queue.push({ changeReturnTo: currentURL() });\n queue.push(...links);\n }\n\n return visited.size;\n}\n"],"names":["findInAppLinks","results","allAnchorsOnThePage","findAll","a","HTMLAnchorElement","href","getAttribute","shouldHandle","window","location","MouseEvent","current","URL","currentURL","origin","url","withoutDomain","pathname","search","hash","push","original","selector","assert","QUnit","visitAllLinks","callback","knownRedirects","options","mode","shouldVisit","visited","Set","returnTo","visit","inAppLinks","queue","ctx","getContext","router","owner","lookup","debugAssert","rootURL","length","toVisit","shift","changeReturnTo","startsWith","nonHashPart","split","key","has","result","recognize","ok","console","warn","link","find","setAttribute","click","error","pushResult","actual","String","expected","message","add","replace","expectedPath","links","size"],"mappings":"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"}
|