-
Notifications
You must be signed in to change notification settings - Fork 17
Invalid Links Rule #19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 5 commits
701b469
113477c
a6937fc
2d93670
f4cf99b
f3454bb
a3401d7
d782e0c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| // Jest Snapshot v1, https://goo.gl/fbAQLP | ||
|
|
||
| exports[`data returns the proper object 1`] = ` | ||
| Object { | ||
| "href": null, | ||
| } | ||
| `; | ||
|
|
||
| exports[`form returns the proper object 1`] = ` | ||
| Array [ | ||
| Object { | ||
| "dataKey": "href", | ||
| "disabledIf": [Function], | ||
| "label": "Ensure this link is correct.", | ||
| }, | ||
| Object { | ||
| "checkbox": true, | ||
| "dataKey": "ignore", | ||
| "label": "Ignore this link in the future.", | ||
| }, | ||
| ] | ||
| `; | ||
|
|
||
| exports[`message returns the proper message 1`] = `"This link could not be verified."`; | ||
|
|
||
| exports[`why returns the proper why message 1`] = `"Links should not be broken."`; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| import fetch from "node-fetch" | ||
| import rule from "../valid-links" | ||
|
|
||
| let body, a | ||
|
|
||
| beforeEach(() => { | ||
| body = document.createElement("body") | ||
| a = document.createElement("a") | ||
| body.appendChild(a) | ||
| a.textContent = "Link Text" | ||
| window.fetch = () => Promise.resolve({ ok: true }) | ||
| }) | ||
|
|
||
| describe("test", () => { | ||
| test("returns true if not A element", async () => { | ||
| expect(await rule.test(document.createElement("div"))).toBe(true) | ||
| }) | ||
|
|
||
| test("returns true for valid links", async () => { | ||
| window.fetch = () => Promise.resolve({ ok: true }) | ||
| a.setAttribute("href", "https://www.instructure.com/") | ||
| expect(await rule.test(a)).toBe(true) | ||
| }) | ||
|
|
||
| test("returns false for invalid links", async () => { | ||
| window.fetch = () => Promise.resolve({ ok: false }) | ||
| a.setAttribute("href", "http://something weird") | ||
| expect(await rule.test(a)).toBe(false) | ||
| a.setAttribute("href", "plaintext") | ||
| expect(await rule.test(a)).toBe(false) | ||
| window.fetch = () => Promise.reject(new Error()) | ||
| a.setAttribute("href", "http://something-valid-but-not-reachable/123") | ||
| expect(await rule.test(a)).toBe(false) | ||
| }) | ||
| }) | ||
|
|
||
| describe("data", () => { | ||
| test("returns the proper object", () => { | ||
| expect(rule.data(a)).toMatchSnapshot() | ||
| }) | ||
| }) | ||
|
|
||
| describe("form", () => { | ||
| test("returns the proper object", () => { | ||
| expect(rule.form(a)).toMatchSnapshot() | ||
| }) | ||
| }) | ||
|
|
||
| describe("update", () => { | ||
| test("returns same element", () => { | ||
| expect(rule.update(a, {})).toBe(a) | ||
| }) | ||
| test("does not change href if href does not change", () => { | ||
| const href = "http://example.com" | ||
| a.setAttribute("href", href) | ||
| expect(rule.update(a, { href })).toBe(a) | ||
| }) | ||
| test("changes href if it has been changed", () => { | ||
| const href = "bad" | ||
| a.setAttribute("href", href) | ||
| rule.update(a, { href: "https://www.instructure.com/" }) | ||
| expect(a.getAttribute("href")).toBe("https://www.instructure.com/") | ||
| }) | ||
| test("does not change href if href does not change", () => { | ||
| const href = "http://example.com" | ||
| a.setAttribute("href", href) | ||
| expect( | ||
| rule.update(a, { ignore: true }).getAttribute("data-ignore-a11y-check") | ||
| ).toBe("true") | ||
| }) | ||
| }) | ||
|
|
||
| describe("rootNode", () => { | ||
| test("returns the parentNode of an element", () => { | ||
| expect(rule.rootNode(a).tagName).toBe("BODY") | ||
| }) | ||
| }) | ||
|
|
||
| describe("message", () => { | ||
| test("returns the proper message", () => { | ||
| expect(rule.message()).toMatchSnapshot() | ||
| }) | ||
| }) | ||
|
|
||
| describe("why", () => { | ||
| test("returns the proper why message", () => { | ||
| expect(rule.why()).toMatchSnapshot() | ||
| }) | ||
| }) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| import formatMessage from "../format-message" | ||
|
|
||
| const isValidURL = url => { | ||
| try { | ||
| // the URL constructor is more accurate than regex | ||
| // but not supported in IE. | ||
| new URL(url) | ||
| return true | ||
| } catch (_) { | ||
| // If this does throw, either: | ||
| // 1. the url is invalid | ||
| // 2. the URL constructor is not there. | ||
| // The user will be prompted to check the link manually. | ||
| return false | ||
| } | ||
| } | ||
|
|
||
| const debouncedFetch = (() => { | ||
| let timeout = null | ||
|
|
||
| return (...args) => | ||
| new Promise((resolve, reject) => { | ||
| clearTimeout(timeout) | ||
| timeout = setTimeout(() => { | ||
| fetch(...args) | ||
| .then(resolve) | ||
| .catch(reject) | ||
| }, 500) | ||
| }) | ||
| })() | ||
|
|
||
| export default { | ||
| test: function(elem) { | ||
| return new Promise((resolve, reject) => { | ||
| if (elem.tagName !== "A") return resolve(true) | ||
| const href = elem.getAttribute("href") | ||
|
|
||
| // If url is invalid | ||
| if (!isValidURL(href)) return resolve(false) | ||
|
|
||
| // If url is valid, do a HEAD request. | ||
| debouncedFetch(href, { method: "HEAD" }) | ||
| .then(res => resolve(res.ok)) | ||
| .catch(() => resolve(false)) | ||
|
|
||
| // This will always work in jest because there | ||
|
||
| // node does not check cross-origin requests, | ||
| // but this will cause issues in the browser. | ||
| // | ||
| // In the browser, the request will still fire | ||
| // but will will fail for all links that are | ||
| // not same-origin. | ||
| // | ||
| // For cross-origin urls, the user will be | ||
| // prompted to check the link, or check ignore | ||
| // if they so choose. | ||
| // | ||
| // Workarounds: | ||
| // * Create a service to proxy the request | ||
| // * Do something crazy with web workers | ||
| // * and importScripts (plausible). | ||
| }) | ||
| }, | ||
|
|
||
| data: elem => { | ||
| return { | ||
| href: elem.getAttribute("href") | ||
| } | ||
| }, | ||
|
|
||
| form: () => [ | ||
| { | ||
| label: formatMessage("Ensure this link is correct."), | ||
| dataKey: "href", | ||
| disabledIf: data => data.ignore | ||
| }, | ||
| { | ||
| label: formatMessage("Ignore this link in the future."), | ||
| checkbox: true, | ||
| dataKey: "ignore" | ||
| } | ||
| ], | ||
|
|
||
| update: function(elem, data) { | ||
| const rootElem = elem.parentNode | ||
|
|
||
| if (data.ignore) { | ||
| elem.setAttribute("data-ignore-a11y-check", "true") | ||
| } | ||
|
|
||
| if (data.href !== elem.getAttribute("href")) { | ||
| elem.setAttribute("href", data.href) | ||
| } | ||
|
|
||
| return elem | ||
| }, | ||
|
|
||
| rootNode: function(elem) { | ||
| return elem.parentNode | ||
| }, | ||
|
|
||
| // Note, these messages are poor and should be replaced with | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll get some words for you here :) |
||
| // better text. | ||
| message: () => formatMessage("This link could not be verified."), | ||
|
|
||
| why: () => formatMessage("Links should not be broken."), | ||
|
|
||
| link: " --- fill in --- " | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We already have axios in place. It should be able to handle this same sort of thing without needing an additional library involved.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh sweet. I'll just use that then.