@universal-ember/test-support 0.7.2 → 0.8.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.
|
@@ -1 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
interface VisitAllLinksOptions {
|
|
2
|
+
/**
|
|
3
|
+
* How each discovered target is navigated to:
|
|
4
|
+
*
|
|
5
|
+
* - `'visit'` (the default): `visit(target)` directly. One page render per
|
|
6
|
+
* unique target — the cheapest possible full crawl.
|
|
7
|
+
* - `'click'`: return to the page the link was found on and click the
|
|
8
|
+
* actual anchor, exercising the app's link-interception (e.g.
|
|
9
|
+
* `@properLinks`) for every link. Twice the page renders of `'visit'`
|
|
10
|
+
* (each processed link re-renders its source page), so reserve it for
|
|
11
|
+
* apps that need per-link click fidelity.
|
|
12
|
+
*/
|
|
13
|
+
mode?: 'visit' | 'click';
|
|
14
|
+
}
|
|
15
|
+
export declare function visitAllLinks(callback?: (url: string) => void | Promise<void>, knownRedirects?: Record<string, string>, options?: VisitAllLinksOptions): Promise<number>;
|
|
16
|
+
export {};
|
|
@@ -40,7 +40,8 @@ function findInAppLinks() {
|
|
|
40
40
|
return results;
|
|
41
41
|
}
|
|
42
42
|
const assert = QUnit.assert;
|
|
43
|
-
async function visitAllLinks(callback, knownRedirects) {
|
|
43
|
+
async function visitAllLinks(callback, knownRedirects, options) {
|
|
44
|
+
const mode = options?.mode ?? 'visit';
|
|
44
45
|
/**
|
|
45
46
|
* app-relative target paths (without hash)
|
|
46
47
|
*/
|
|
@@ -86,25 +87,44 @@ async function visitAllLinks(callback, knownRedirects) {
|
|
|
86
87
|
assert.ok(true, `${toVisit.href} on page ${returnTo} is not recognized by this app and will be skipped`);
|
|
87
88
|
continue;
|
|
88
89
|
}
|
|
89
|
-
await visit(returnTo);
|
|
90
|
-
const link = find(toVisit.selector);
|
|
91
|
-
assert$1(`link exists via selector \`${toVisit.selector}\``, link);
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* The click navigates by `element.href`, which the browser resolved
|
|
95
|
-
* against the test page's URL (e.g. `/tests`) — NOT against the app's
|
|
96
|
-
* current route the way a production visit would (there, the address bar
|
|
97
|
-
* is the current route). A relative href would therefore navigate
|
|
98
|
-
* somewhere the real app never goes. We already resolved the target
|
|
99
|
-
* against `currentURL()` when the link was encountered, so point the
|
|
100
|
-
* anchor at that; the click then exercises the real
|
|
101
|
-
* properLinks-and-router path with the production URL.
|
|
102
|
-
*/
|
|
103
90
|
if (!toVisit.original.startsWith('/')) {
|
|
104
|
-
console.warn(`[visitAllLinks] Relative href "${toVisit.original}" found on ${returnTo}. ` + `Relative hrefs resolve against the browser's URL rather than the app's current route, ` + `so they only behave in a real full-page visit — they misresolve in this test harness ` + `and under any mount where the address bar isn't the route (embeds, previews). ` + `The crawler
|
|
105
|
-
|
|
91
|
+
console.warn(`[visitAllLinks] Relative href "${toVisit.original}" found on ${returnTo}. ` + `Relative hrefs resolve against the browser's URL rather than the app's current route, ` + `so they only behave in a real full-page visit — they misresolve in this test harness ` + `and under any mount where the address bar isn't the route (embeds, previews). ` + `The crawler navigated to the resolved target instead. ` + `Action: update the source document to link to "${toVisit.href}" directly.`);
|
|
92
|
+
}
|
|
93
|
+
if (mode === 'click') {
|
|
94
|
+
await visit(returnTo);
|
|
95
|
+
const link = find(toVisit.selector);
|
|
96
|
+
assert$1(`link exists via selector \`${toVisit.selector}\``, link);
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* The click navigates by `element.href`, which the browser resolved
|
|
100
|
+
* against the test page's URL (e.g. `/tests`) — NOT against the app's
|
|
101
|
+
* current route the way a production visit would (there, the address
|
|
102
|
+
* bar is the current route). A relative href would therefore navigate
|
|
103
|
+
* somewhere the real app never goes. We already resolved the target
|
|
104
|
+
* against `currentURL()` when the link was encountered, so point the
|
|
105
|
+
* anchor at that; the click then exercises the real
|
|
106
|
+
* properLinks-and-router path with the production URL.
|
|
107
|
+
*/
|
|
108
|
+
if (!toVisit.original.startsWith('/')) {
|
|
109
|
+
link.setAttribute('href', toVisit.href);
|
|
110
|
+
}
|
|
111
|
+
await click(link);
|
|
112
|
+
} else {
|
|
113
|
+
try {
|
|
114
|
+
await visit(nonHashPart ?? toVisit.href);
|
|
115
|
+
} catch (error) {
|
|
116
|
+
// visit() rejects when the route errors (click-mode surfaces the
|
|
117
|
+
// same problem as a URL mismatch via the app's error substate)
|
|
118
|
+
assert.pushResult({
|
|
119
|
+
result: false,
|
|
120
|
+
actual: String(error),
|
|
121
|
+
expected: nonHashPart,
|
|
122
|
+
message: `Navigation was successful: to:${toVisit.original}, from:${returnTo}`
|
|
123
|
+
});
|
|
124
|
+
visited.add(key);
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
106
127
|
}
|
|
107
|
-
await click(link);
|
|
108
128
|
const current = rootURL.replace(/\/$/, '') + '/' + currentURL().replace(/^\//, '');
|
|
109
129
|
const expected = knownRedirects?.[toVisit.href] ?? toVisit.href;
|
|
110
130
|
// currentURL() never includes a #hash, so compare without it
|
|
@@ -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\nexport async function visitAllLinks(\n callback?: (url: string) => void | Promise<void>,\n knownRedirects?: Record<string, string>,\n) {\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 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 bar\n * 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 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 pointed this click at the resolved target instead. ` +\n `Action: update the source document to link to \"${toVisit.href}\" directly.`,\n );\n link.setAttribute('href', toVisit.href);\n }\n\n await click(link);\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","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","link","find","console","warn","setAttribute","click","replace","expected","expectedPath","pushResult","actual","message","add","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\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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"}
|