@khanacademy/wonder-blocks-clickable 2.1.2

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.
package/src/index.js ADDED
@@ -0,0 +1,15 @@
1
+ // @flow
2
+ import type {
3
+ ChildrenProps,
4
+ ClickableState,
5
+ ClickableRole,
6
+ } from "./components/clickable-behavior.js";
7
+ import Clickable from "./components/clickable.js";
8
+
9
+ export {default as ClickableBehavior} from "./components/clickable-behavior.js";
10
+ export {default as getClickableBehavior} from "./util/get-clickable-behavior.js";
11
+ export {isClientSideUrl} from "./util/is-client-side-url.js";
12
+
13
+ export {Clickable as default};
14
+
15
+ export type {ChildrenProps, ClickableState, ClickableRole};
@@ -0,0 +1,105 @@
1
+ // @flow
2
+ import * as React from "react";
3
+ import {MemoryRouter} from "react-router-dom";
4
+ import ClickableBehavior from "../../components/clickable-behavior.js";
5
+ import getClickableBehavior from "../get-clickable-behavior.js";
6
+
7
+ describe("getClickableBehavior", () => {
8
+ test("Without href, returns ClickableBehavior", () => {
9
+ // Arrange
10
+ const url = undefined;
11
+ const skipClientNav = undefined;
12
+ const router = undefined;
13
+ const expectation = ClickableBehavior;
14
+
15
+ // Act
16
+ const result = getClickableBehavior(url, skipClientNav, router);
17
+
18
+ // Assert
19
+ expect(result).toBe(expectation);
20
+ });
21
+
22
+ describe("with href", () => {
23
+ test("External URL, returns ClickableBehavior", () => {
24
+ // Arrange
25
+ const url = "http://google.com";
26
+ const skipClientNav = undefined;
27
+ const router = <MemoryRouter />;
28
+ const expectation = ClickableBehavior;
29
+
30
+ // Act
31
+ const result = getClickableBehavior(url, skipClientNav, router);
32
+
33
+ // Assert
34
+ expect(result).toBe(expectation);
35
+ });
36
+
37
+ describe("Internal URL", () => {
38
+ describe("skipClientNav is undefined", () => {
39
+ test("No router, returns ClickableBehavior", () => {
40
+ // Arrange
41
+ const url = "/prep/lsat";
42
+ const skipClientNav = undefined;
43
+ const router = undefined;
44
+ const expectation = ClickableBehavior;
45
+
46
+ // Act
47
+ const result = getClickableBehavior(
48
+ url,
49
+ skipClientNav,
50
+ router,
51
+ );
52
+
53
+ // Assert
54
+ expect(result).toBe(expectation);
55
+ });
56
+
57
+ test("Router, returns ClickableBehaviorWithRouter", () => {
58
+ // Arrange
59
+ const url = "/prep/lsat";
60
+ const skipClientNav = undefined;
61
+ const router = <MemoryRouter />;
62
+ const expectation = "withRouter(ClickableBehavior)";
63
+
64
+ // Act
65
+ const result = getClickableBehavior(
66
+ url,
67
+ skipClientNav,
68
+ router,
69
+ );
70
+
71
+ // Assert
72
+ expect(result.displayName).toBe(expectation);
73
+ });
74
+ });
75
+
76
+ test("skipClientNav is false, returns ClickableBehaviorWithRouter", () => {
77
+ // Arrange
78
+ const url = "/prep/lsat";
79
+ const skipClientNav = false;
80
+ const router = <MemoryRouter />;
81
+ const expectation = "withRouter(ClickableBehavior)";
82
+
83
+ // Act
84
+ const result = getClickableBehavior(url, skipClientNav, router);
85
+
86
+ // Assert
87
+ expect(result.displayName).toBe(expectation);
88
+ });
89
+
90
+ test("skipClientNav is true, returns ClickableBehavior", () => {
91
+ // Arrange
92
+ const url = "/prep/lsat";
93
+ const skipClientNav = true;
94
+ const router = <MemoryRouter />;
95
+ const expectation = ClickableBehavior;
96
+
97
+ // Act
98
+ const result = getClickableBehavior(url, skipClientNav, router);
99
+
100
+ // Assert
101
+ expect(result).toBe(expectation);
102
+ });
103
+ });
104
+ });
105
+ });
@@ -0,0 +1,50 @@
1
+ // @flow
2
+ import {isClientSideUrl} from "../is-client-side-url.js";
3
+
4
+ describe("isClientSideUrl", () => {
5
+ test("returns boolean based on the url", () => {
6
+ // external URLs
7
+ expect(
8
+ isClientSideUrl(
9
+ "//khanacademy.zendesk.com/hc/en-us/articles/236355907",
10
+ ),
11
+ ).toEqual(false);
12
+ expect(
13
+ isClientSideUrl(
14
+ "https://khanacademy.zendesk.com/hc/en-us/articles/360007253831",
15
+ ),
16
+ ).toEqual(false);
17
+ expect(isClientSideUrl("http://external.com")).toEqual(false);
18
+ expect(isClientSideUrl("//www.google.com")).toEqual(false);
19
+ expect(isClientSideUrl("//")).toEqual(false);
20
+
21
+ // non-http(s) URLs
22
+ expect(isClientSideUrl("javascript:void(0);")).toEqual(false);
23
+ expect(isClientSideUrl("javascript:void(0)")).toEqual(false);
24
+ expect(isClientSideUrl("mailto:foo@example.com")).toEqual(false);
25
+ expect(isClientSideUrl("tel:+1234567890")).toEqual(false);
26
+ expect(isClientSideUrl("tel:+1234567890")).toEqual(false);
27
+ expect(isClientSideUrl("ms-help://kb12345.htm")).toEqual(false);
28
+ expect(isClientSideUrl("z39.50s://0.0.0.0")).toEqual(false);
29
+
30
+ // HREFs with anchors
31
+ expect(isClientSideUrl("#")).toEqual(false);
32
+ expect(isClientSideUrl("#foo")).toEqual(false);
33
+ expect(isClientSideUrl("/foo#bar")).toEqual(false);
34
+ expect(isClientSideUrl("foo/bar#baz")).toEqual(false);
35
+
36
+ // internal URLs
37
+ expect(isClientSideUrl("/foo//bar")).toEqual(true);
38
+ expect(isClientSideUrl("/coach/dashboard")).toEqual(true);
39
+ expect(isClientSideUrl("/math/early-math/modal/e/addition_1")).toEqual(
40
+ true,
41
+ );
42
+ });
43
+
44
+ test("invalid values for 'href' should return false", () => {
45
+ // $FlowIgnore: testing invalid input
46
+ expect(isClientSideUrl(null)).toEqual(false);
47
+ // $FlowIgnore: testing invalid input
48
+ expect(isClientSideUrl(undefined)).toEqual(false);
49
+ });
50
+ });
@@ -0,0 +1,43 @@
1
+ // @flow
2
+ /**
3
+ * Returns either the default ClickableBehavior or a react-router aware version.
4
+ *
5
+ * The react-router aware version is returned if `router` is a react-router-dom
6
+ * router, `skipClientNav` is not `true`, and `href` is an internal URL.
7
+ *
8
+ * The `router` can be accessed via this.context.router from a component rendered
9
+ * as a descendant of a BrowserRouter.
10
+ * See https://reacttraining.com/react-router/web/guides/basic-components.
11
+ */
12
+ import * as React from "react";
13
+ import {withRouter} from "react-router-dom";
14
+
15
+ import ClickableBehavior from "../components/clickable-behavior.js";
16
+ import {isClientSideUrl} from "./is-client-side-url.js";
17
+
18
+ const ClickableBehaviorWithRouter = withRouter(ClickableBehavior);
19
+
20
+ export default function getClickableBehavior(
21
+ /**
22
+ * The URL to navigate to.
23
+ */
24
+ href?: string,
25
+ /**
26
+ * Should we skip using the react router and go to the page directly.
27
+ */
28
+ skipClientNav?: boolean,
29
+ /**
30
+ * router object added to the React context object by react-router-dom.
31
+ */
32
+ router?: any,
33
+ ): React.ComponentType<React.ElementConfig<typeof ClickableBehavior>> {
34
+ if (router && skipClientNav !== true && href && isClientSideUrl(href)) {
35
+ // We cast to `any` here since the type of ClickableBehaviorWithRouter
36
+ // is slightly different from the return type of this function.
37
+ // TODO(WB-1037): Always return the wrapped version once all routes have
38
+ // been ported to the app-shell in webapp.
39
+ return (ClickableBehaviorWithRouter: any);
40
+ }
41
+
42
+ return ClickableBehavior;
43
+ }
@@ -0,0 +1,16 @@
1
+ // @flow
2
+ /**
3
+ * Returns:
4
+ * - false for hrefs staring with http://, https://, //.
5
+ * - false for '#', 'javascript:...', 'mailto:...', 'tel:...', etc.
6
+ * - true for all other values, e.g. /foo/bar
7
+ */
8
+ export const isClientSideUrl = (href: string): boolean => {
9
+ if (typeof href !== "string") {
10
+ return false;
11
+ }
12
+ return (
13
+ !/^(https?:)?\/\//i.test(href) &&
14
+ !/^([^#]*#[\w-]*|[\w\-.]+:)/.test(href)
15
+ );
16
+ };