├── .editorconfig
├── .github
└── ISSUE_TEMPLATE
│ ├── config.yml
│ └── custom.md
├── .gitignore
├── .storybook
├── main.js
├── manager.js
└── rtwTheme.js
├── README.md
├── nwb.config.js
├── package.json
├── src
├── index.d.ts
├── index.js
└── utils.js
├── stories
├── 0-Follow.stories.js
├── 1-Hashtag.stories.js
├── 2-Mention.stories.js
├── 3-Share.stories.js
├── 4-Timeline.stories.js
├── 5-Tweet.stories.js
└── 6-Misc.stories.js
└── tests
├── .eslintrc
└── index.test.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig
2 | # See: http://editorconfig.org
3 |
4 | # top-most EditorConfig file
5 | root = true
6 |
7 | # Match all files
8 | # Unix-style newlines with a newline ending every file,
9 | # 4-space indentation
10 | [*]
11 | end_of_line = lf
12 | insert_final_newline = true
13 | charset = utf-8
14 | indent_style = space
15 | indent_size = 2
16 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 | contact_links:
3 | - name: 🤔 Questions and Help
4 | url: https://twittercommunity.com
5 | about: This issue tracker is not for support questions. Please use twittercommunity.com or the Discussions tab.
6 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/custom.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug Report or Feature Request
3 | about: 'Bugs or feature requests specifically for react-twitter-widgets'
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | *READ BEFORE OPENING AN ISSUE*
11 |
12 | react-twitter-widgets is a thin wrapper around the official Twitter for Websites factory functions.
13 |
14 | This is remote code that can and has changed on a whim. Bugs are often introduced.
15 |
16 | Do not open issues here for bugs or questions related to the actual widget. For that, you can use https://twittercommunity.com/.
17 |
18 | Open an issue *if it occurs only with react-twitter-widgets and not the vanilla factory functions* (use Codepen or similar to test).
19 |
20 | Delete this entire message before writing your issue.
21 |
22 | Thank you.
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /coverage
2 | /es
3 | /lib
4 | /node_modules
5 | /umd
6 | npm-debug.log*
7 | package-lock.json
8 |
--------------------------------------------------------------------------------
/.storybook/main.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | stories: ["../stories/**/*.stories.js"],
3 | addons: ["@storybook/addon-storysource"],
4 | };
5 |
--------------------------------------------------------------------------------
/.storybook/manager.js:
--------------------------------------------------------------------------------
1 | import { addons } from "@storybook/addons";
2 | import rtwTheme from "./rtwTheme";
3 |
4 | addons.setConfig({
5 | theme: rtwTheme
6 | });
7 |
--------------------------------------------------------------------------------
/.storybook/rtwTheme.js:
--------------------------------------------------------------------------------
1 | import { create } from "@storybook/theming/create";
2 |
3 | export default create({
4 | brandTitle: "react-twitter-widgets",
5 | brandUrl: "https://github.com/andrewsuzuki/react-twitter-widgets"
6 | });
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-twitter-widgets
2 |
3 | [](https://badge.fury.io/js/react-twitter-widgets)
4 | [](https://badge.fury.io/js/react-twitter-widgets)
5 | [](https://github.com/andrewsuzuki/react-twitter-widgets/issues)
6 |
7 | Quick and easy Twitter widgets for React.
8 |
9 | Available widgets: `Timeline`, `Share`, `Follow`, `Hashtag`, `Mention`, `Tweet`.
10 |
11 | See below for usage.
12 |
13 | ## Demo
14 |
15 | **[Storybook / Live Demo](https://andrewsuzuki.github.io/react-twitter-widgets/)**
16 |
17 | ## Installation
18 |
19 | ```
20 | npm install --save react-twitter-widgets
21 | ```
22 |
23 | ## Example
24 |
25 | ```javascript
26 | import { Timeline } from 'react-twitter-widgets'
27 |
28 | // Tweet (without options)
29 |
30 |
31 | // Timeline (with options)
32 |
41 | ```
42 |
43 | ## Usage
44 |
45 | [**🔗 Official Twitter Documentation**](https://developer.twitter.com/en/docs/twitter-for-websites/javascript-api/guides/scripting-factory-functions)
46 |
47 | Available widgets: `Timeline`, `Share`, `Follow`, `Hashtag`, `Mention`, `Tweet`
48 |
49 | **`Timeline`** requires a `dataSource` object prop. The source type can be `profile`, `list`, or `url`. They each require their own co-fields; see Twitter documentation. NOTE that `collection`, `likes`, and `moments` will be [deprecated](https://twittercommunity.com/t/removing-support-for-embedded-like-collection-and-moment-timelines/150313) on June 23, 2021.
50 |
51 | **`Share`** requires a `url` prop.
52 |
53 | **`Follow`** and **`Mention`** require a `username` prop. NOTE that the Twitter
54 | documentation now refers to this as _screenName_.
55 |
56 | **`Hashtag`** requires a `hashtag` prop.
57 |
58 | **`Tweet`** requires a `tweetId` prop. Ex. `'511181794914627584'`
59 |
60 | ### Common Props
61 |
62 | All widgets accept these props.
63 |
64 | - `options` (object)
65 | - To learn more about the available options, refer to the Twitter documentation. There are four options that are common to all widgets (`lang`, `dnt`, `related`, and `via`). There are further options for button widgets, tweet buttons, Timeline, and Tweet.
66 | - `onLoad` (function)
67 | - Called every time the widget is loaded. A widget will reload if its props change.
68 | - `renderError` (function)
69 | - Render prop. Rendered if widget cannot be loaded (no internet connection, screenName not found, bad props, etc).
70 | - Example: `renderError={(_err) =>
Could not load timeline
}`
71 |
72 | ### Lazy vs. Eager Loading
73 |
74 | By default, the remote Twitter library will be lazy-loaded when the first widget renders. To instead load it eagerly, call `eagerLoadTwitterLibrary`.
75 |
76 | ```js
77 | import { eagerLoadTwitterLibrary } from "react-twitter-widgets";
78 | eagerLoadTwitterLibrary();
79 | ```
80 |
81 | ## Further Information
82 |
83 | - This library loads the remote _Twitter for Websites_ library.
84 | - Twitter widgets are only loaded in the browser. A blank div will be rendered during SSR.
85 |
86 | ## Contributing
87 |
88 | 1. Fork it!
89 | 2. Create your feature branch: `git checkout -b my-new-feature`
90 | 3. Commit your changes: `git commit -am 'Add some feature'`
91 | 4. Push to the branch: `git push origin my-new-feature`
92 | 5. Submit a pull request
93 |
94 | ## Credits
95 |
96 | - Andrew Suzuki - @andrewsuzuki - [andrewsuzuki.com](http://andrewsuzuki.com)
97 |
98 | ## License
99 |
100 | MIT
101 |
--------------------------------------------------------------------------------
/nwb.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | type: 'react-component',
3 | npm: {
4 | esModules: true,
5 | umd: {
6 | global: 'ReactTwitterWidgets',
7 | externals: {
8 | react: 'React'
9 | }
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-twitter-widgets",
3 | "version": "1.11.0",
4 | "description": "Twitter widgets as React components",
5 | "author": "Andrew Suzuki",
6 | "license": "MIT",
7 | "bugs": {
8 | "url": "https://github.com/andrewsuzuki/react-twitter-widgets/issues"
9 | },
10 | "homepage": "https://github.com/andrewsuzuki/react-twitter-widgets",
11 | "main": "lib/index.js",
12 | "module": "es/index.js",
13 | "types": "src/index.d.ts",
14 | "files": [
15 | "src/index.d.ts",
16 | "es",
17 | "lib",
18 | "umd"
19 | ],
20 | "dependencies": {
21 | "loadjs": "^4.2.0"
22 | },
23 | "devDependencies": {
24 | "@babel/core": "^7.10.2",
25 | "@storybook/addon-storysource": "^5.3.19",
26 | "@storybook/addons": "^5.3.19",
27 | "@storybook/react": "^5.3.19",
28 | "@storybook/storybook-deployer": "^2.8.6",
29 | "@storybook/theming": "^5.3.19",
30 | "@types/react": "^16.9.35",
31 | "babel-loader": "^8.1.0",
32 | "nwb": "0.25.x",
33 | "react": "^16.13.1",
34 | "react-dom": "^16.13.1"
35 | },
36 | "peerDependencies": {
37 | "react": "^16.8.0 || ^17 || ^18"
38 | },
39 | "scripts": {
40 | "build": "nwb build-react-component",
41 | "clean": "nwb clean-module",
42 | "prepublishOnly": "npm run build",
43 | "start": "npm run storybook",
44 | "test": "nwb test-react",
45 | "test:coverage": "nwb test-react --coverage",
46 | "test:watch": "nwb test-react --server",
47 | "storybook": "start-storybook -p 6006",
48 | "build-storybook": "build-storybook",
49 | "deploy-storybook-ghpages": "storybook-to-ghpages"
50 | },
51 | "repository": {
52 | "type": "git",
53 | "url": "git+https://github.com/andrewsuzuki/react-twitter-widgets.git"
54 | },
55 | "keywords": [
56 | "react",
57 | "component",
58 | "twitter",
59 | "embed",
60 | "widgets",
61 | "twttr",
62 | "timeline",
63 | "feed",
64 | "share",
65 | "tweet",
66 | "share",
67 | "follow",
68 | "hashtag",
69 | "button",
70 | "mention"
71 | ]
72 | }
73 |
--------------------------------------------------------------------------------
/src/index.d.ts:
--------------------------------------------------------------------------------
1 | import { FunctionComponent, ReactNode } from 'react'
2 |
3 | declare module 'react-twitter-widgets' {
4 | export type onLoad = () => any;
5 | export type renderError = (error: Error) => ReactNode;
6 |
7 | export interface FollowProps {
8 | username: string;
9 | options?: Object;
10 | onLoad?: onLoad;
11 | renderError?: renderError;
12 | }
13 | export const Follow: FunctionComponent;
14 |
15 | export interface HashtagProps {
16 | hashtag: string;
17 | options?: Object;
18 | onLoad?: onLoad;
19 | renderError?: renderError;
20 | }
21 | export const Hashtag: FunctionComponent;
22 |
23 | export interface MentionProps {
24 | username: string;
25 | options?: Object;
26 | onLoad?: onLoad;
27 | renderError?: renderError;
28 | }
29 | export const Mention: FunctionComponent;
30 |
31 | export interface ShareProps {
32 | url: string;
33 | options?: Object;
34 | onLoad?: onLoad;
35 | renderError?: renderError;
36 | }
37 | export const Share: FunctionComponent;
38 |
39 | export interface TimelineProps {
40 | dataSource: object;
41 | options?: Object;
42 | onLoad?: onLoad;
43 | renderError?: renderError;
44 | }
45 | export const Timeline: FunctionComponent;
46 |
47 | export interface TweetProps {
48 | tweetId: string;
49 | options?: Object;
50 | onLoad?: onLoad;
51 | renderError?: renderError;
52 | }
53 | export const Tweet: FunctionComponent;
54 | }
55 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React, { useRef, useEffect, useState } from "react";
2 | import {
3 | canUseDOM,
4 | useShallowCompareMemoize,
5 | removeChildrenWithAttribute,
6 | twWidgetFactory,
7 | cloneShallow
8 | } from "./utils";
9 |
10 | export { loadTwitterLibrary as eagerLoadTwitterLibrary } from "./utils";
11 |
12 | const childDivIdentifyingAttribute = "twdiv";
13 |
14 | function useTwitterWidget(factoryFunctionName, primaryArg, options, onLoad) {
15 | const [error, setError] = useState(null);
16 |
17 | const ref = useRef(null);
18 |
19 | // noop if ssr
20 | if (!canUseDOM) {
21 | return { ref, error };
22 | }
23 |
24 | // Make deps for useEffect.
25 | // options, and possibly primaryArg, are objects that should be compared (shallow).
26 | // There currently aren't any nested arrays or objects, so they
27 | // can be cloned in a shallow manner.
28 | // NOTE onLoad is used in useCallback, but it is not listed as a dependency.
29 | // Listing it would likely cause unnecessary loads. The latest onLoad should be
30 | // used regardless, since it will not be called unless the other dependencies
31 | // change, so it works out.
32 | const deps = [
33 | factoryFunctionName,
34 | useShallowCompareMemoize(primaryArg),
35 | useShallowCompareMemoize(options)
36 | ];
37 |
38 | useEffect(() => {
39 | // Reset error
40 | setError(null);
41 |
42 | // Protect against race conditions
43 | // (set to true in cleanup function;
44 | // checked if canceled in async loadWidget)
45 | let isCanceled = false;
46 |
47 | if (ref.current) {
48 | removeChildrenWithAttribute(ref.current, childDivIdentifyingAttribute);
49 |
50 | if (!ref || !ref.current) {
51 | return;
52 | }
53 |
54 | const childEl = document.createElement("div");
55 | childEl.setAttribute(childDivIdentifyingAttribute, "yes");
56 | ref.current.appendChild(childEl);
57 |
58 | twWidgetFactory()
59 | .then(wf => {
60 | // primaryArg (possibly an object) and options must be cloned
61 | // since twitter mutates them (gah!).
62 | // There currently aren't any nested arrays or objects, so they
63 | // can be cloned in a shallow manner.
64 | return wf[factoryFunctionName](
65 | cloneShallow(primaryArg),
66 | childEl,
67 | cloneShallow(options)
68 | );
69 | })
70 | .then(resultMaybe => {
71 | // Twitter returns undefined if widget creation fails.
72 | // However, if deps are stale (isCanceled), suppress error (likely race condition).
73 | if (!resultMaybe && !isCanceled) {
74 | throw new Error(
75 | "Twitter could not create widget. If it is a Timeline or " +
76 | "Tweet, ensure the screenName/tweetId exists."
77 | );
78 | }
79 |
80 | if (!ref || !ref.current) {
81 | return;
82 | }
83 |
84 | if (isCanceled) {
85 | if (childEl) {
86 | childEl.remove();
87 | }
88 | return;
89 | }
90 |
91 | if (onLoad) {
92 | onLoad();
93 | }
94 | })
95 | .catch(e => {
96 | console.error(e);
97 | setError(e);
98 | });
99 | }
100 |
101 | return () => {
102 | isCanceled = true;
103 | };
104 | }, deps);
105 |
106 | return { ref, error };
107 | }
108 |
109 | export const Follow = ({ username, options, onLoad, renderError }) => {
110 | const { ref, error } = useTwitterWidget(
111 | "createFollowButton",
112 | username,
113 | options,
114 | onLoad
115 | );
116 | return {error && renderError && renderError(error)}
;
117 | };
118 |
119 | export const Hashtag = ({ hashtag, options, onLoad, renderError }) => {
120 | const { ref, error } = useTwitterWidget(
121 | "createHashtagButton",
122 | hashtag,
123 | options,
124 | onLoad
125 | );
126 | return {error && renderError && renderError(error)}
;
127 | };
128 |
129 | export const Mention = ({ username, options, onLoad, renderError }) => {
130 | const { ref, error } = useTwitterWidget(
131 | "createMentionButton",
132 | username,
133 | options,
134 | onLoad
135 | );
136 | return {error && renderError && renderError(error)}
;
137 | };
138 |
139 | export const Share = ({ url, options, onLoad, renderError }) => {
140 | const { ref, error } = useTwitterWidget(
141 | "createShareButton",
142 | url,
143 | options,
144 | onLoad
145 | );
146 | return {error && renderError && renderError(error)}
;
147 | };
148 |
149 | export const Timeline = ({ dataSource, options, onLoad, renderError }) => {
150 | const { ref, error } = useTwitterWidget(
151 | "createTimeline",
152 | dataSource,
153 | options,
154 | onLoad
155 | );
156 | return {error && renderError && renderError(error)}
;
157 | };
158 |
159 | export const Tweet = ({ tweetId, options, onLoad, renderError }) => {
160 | const { ref, error } = useTwitterWidget(
161 | "createTweet",
162 | tweetId,
163 | options,
164 | onLoad
165 | );
166 | return {error && renderError && renderError(error)}
;
167 | };
168 |
--------------------------------------------------------------------------------
/src/utils.js:
--------------------------------------------------------------------------------
1 | import loadjs from "loadjs";
2 | import { useRef } from "react";
3 |
4 | const twScriptUrl = "https://platform.twitter.com/widgets.js";
5 | const twScriptWindowFieldName = "twttr";
6 | const twScriptName = twScriptWindowFieldName;
7 |
8 | export const canUseDOM = !!(
9 | typeof window !== "undefined" &&
10 | window.document &&
11 | window.document.createElement
12 | );
13 |
14 | export function loadTwitterLibrary() {
15 | if (!loadjs.isDefined(twScriptName)) {
16 | loadjs(twScriptUrl, twScriptName);
17 | }
18 | }
19 |
20 | export function twWidgetFactory() {
21 | return new Promise((resolve, reject) => {
22 | const rejectWithError = () =>
23 | reject(new Error("Could not load remote twitter widgets js"));
24 |
25 | loadTwitterLibrary();
26 |
27 | loadjs.ready(twScriptName, {
28 | success: () => {
29 | // Ensure loaded
30 | const twttr = window[twScriptWindowFieldName];
31 | if (!twttr || !twttr.widgets) {
32 | rejectWithError();
33 | }
34 | resolve(twttr.widgets);
35 | },
36 | error: rejectWithError
37 | });
38 | });
39 | }
40 |
41 | export function removeChildrenWithAttribute(node, attribute) {
42 | if (node) {
43 | node.querySelectorAll("*").forEach(child => {
44 | if (child.hasAttribute(attribute)) {
45 | child.remove();
46 | }
47 | });
48 | }
49 | }
50 |
51 | function is(x, y) {
52 | if (x === y) {
53 | return x !== 0 || y !== 0 || 1 / x === 1 / y;
54 | } else {
55 | return x !== x && y !== y;
56 | }
57 | }
58 | function isShallowEqual(objA, objB) {
59 | if (is(objA, objB)) {
60 | return true;
61 | }
62 |
63 | if (
64 | typeof objA !== "object" ||
65 | objA === null ||
66 | typeof objB !== "object" ||
67 | objB === null
68 | ) {
69 | return false;
70 | }
71 |
72 | const keysA = Object.keys(objA);
73 | const keysB = Object.keys(objB);
74 |
75 | if (keysA.length !== keysB.length) {
76 | return false;
77 | }
78 |
79 | for (let i = 0; i < keysA.length; i++) {
80 | if (
81 | !Object.prototype.hasOwnProperty.call(objB, keysA[i]) ||
82 | !is(objA[keysA[i]], objB[keysA[i]])
83 | ) {
84 | return false;
85 | }
86 | }
87 |
88 | return true;
89 | }
90 |
91 | export function useShallowCompareMemoize(value) {
92 | const ref = useRef();
93 | if (!isShallowEqual(value, ref.current)) {
94 | ref.current = value;
95 | }
96 | return ref.current;
97 | }
98 |
99 | export function cloneShallow(value) {
100 | return typeof value === "object" ? Object.assign({}, value) : value;
101 | }
102 |
--------------------------------------------------------------------------------
/stories/0-Follow.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Follow } from "../src";
3 |
4 | export default {
5 | title: "Follow",
6 | component: Follow
7 | };
8 |
9 | export const Basic = () => ;
10 |
11 | export const Large = () => (
12 |
13 | );
14 |
15 | export const Japanese = () => (
16 |
17 | );
18 |
19 | export const DoNotTrack = () => (
20 |
21 | );
22 |
--------------------------------------------------------------------------------
/stories/1-Hashtag.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Hashtag } from "../src";
3 |
4 | export default {
5 | title: "Hashtag",
6 | component: Hashtag
7 | };
8 |
9 | export const Basic = () => ;
10 |
11 | export const Large = () => (
12 |
13 | );
14 |
15 | export const Japanese = () => (
16 |
17 | );
18 |
19 | export const DoNotTrack = () => (
20 |
21 | );
22 |
--------------------------------------------------------------------------------
/stories/2-Mention.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Mention } from "../src";
3 |
4 | export default {
5 | title: "Mention",
6 | component: Mention
7 | };
8 |
9 | export const Basic = () => ;
10 |
11 | export const DefaultText = () => (
12 |
13 | );
14 |
15 | export const DefaultHashtags = () => (
16 |
17 | );
18 |
19 | export const Large = () => (
20 |
21 | );
22 |
23 | export const Japanese = () => (
24 |
25 | );
26 |
27 | export const DoNotTrack = () => (
28 |
29 | );
30 |
--------------------------------------------------------------------------------
/stories/3-Share.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Share } from "../src";
3 |
4 | export default {
5 | title: "Share",
6 | component: Share
7 | };
8 |
9 | export const Basic = () => ;
10 |
11 | export const DefaultText = () => (
12 |
13 | );
14 |
15 | export const DefaultHashtags = () => (
16 |
20 | );
21 |
22 | export const Large = () => (
23 |
24 | );
25 |
26 | export const Japanese = () => (
27 |
28 | );
29 |
30 | export const DoNotTrack = () => (
31 |
32 | );
33 |
--------------------------------------------------------------------------------
/stories/4-Timeline.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Timeline } from "../src";
3 |
4 | export default {
5 | title: "Timeline",
6 | component: Timeline
7 | };
8 |
9 | export const ProfileBasic = () => (
10 |
11 | );
12 |
13 | export const ProfileLoadingError = () => (
14 |
17 | "Could not load timeline! ...Your custom component here"
18 | }
19 | />
20 | );
21 |
22 | export const ProfileSmaller = () => (
23 |
27 | );
28 |
29 | export const ProfileDarkTheme = () => (
30 |
34 | );
35 |
36 | export const ProfileBorderColor = () => (
37 |
41 | );
42 |
43 | export const ProfileNoHeaderOrFooter = () => (
44 |
48 | );
49 |
50 | export const ProfileDoNotTrack = () => (
51 |
55 | );
56 |
57 | export const ProfileJapanese = () => (
58 |
62 | );
63 |
64 | export const ListBasic = () => (
65 |
72 | );
73 |
74 | export const UrlBasic = () => (
75 |
78 | );
79 |
--------------------------------------------------------------------------------
/stories/5-Tweet.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Tweet } from "../src";
3 |
4 | export default {
5 | title: "Tweet",
6 | component: Tweet
7 | };
8 |
9 | export const Basic = () => ;
10 |
11 | export const TweetLoadingError = () => (
12 | "Could not load tweet! ...Your custom component here"}
15 | />
16 | );
17 |
18 | export const Smaller = () => (
19 |
20 | );
21 |
22 | export const AlignRight = () => (
23 |
27 | );
28 |
29 | export const DarkTheme = () => (
30 |
31 | );
32 |
--------------------------------------------------------------------------------
/stories/6-Misc.stories.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { Timeline } from "../src";
3 |
4 | export default {
5 | title: "Misc"
6 | };
7 |
8 | export const QuickUpdatesTest = () => {
9 | const [height, setHeight] = useState(400);
10 |
11 | return (
12 | <>
13 |
14 | react-twitter-widgets protects against race conditions with many
15 | successive prop updates. Drag the slider to send many successive updates
16 | to timeline height.
17 |
18 |
19 | setHeight(e.target.value)}
25 | />
26 |
27 | {height}px
28 |
29 |
33 | >
34 | );
35 | };
36 |
--------------------------------------------------------------------------------
/tests/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "mocha": true
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/tests/index.test.js:
--------------------------------------------------------------------------------
1 | // import expect from "expect";
2 | import React from "react";
3 | import { render, unmountComponentAtNode } from "react-dom";
4 | // import TestUtils from "react-dom/test-utils";
5 |
6 | import {
7 | // Follow,
8 | // Hashtag,
9 | // Mention,
10 | // Share,
11 | // Timeline,
12 | Tweet,
13 | } from "src/";
14 |
15 | describe("Tweet", () => {
16 | let node;
17 |
18 | beforeEach(() => {
19 | node = document.createElement("div");
20 | document.body.appendChild(node); // required for twitter library
21 | });
22 |
23 | afterEach(() => {
24 | unmountComponentAtNode(node);
25 | });
26 |
27 | it("loads tweet widget", () => {
28 | render( , node, () => {
29 | // ...
30 | });
31 | });
32 | });
33 |
--------------------------------------------------------------------------------