Compare commits
246 Commits
devtools-v
...
v16.10.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d346d92084 | ||
|
|
3694a3b5e9 | ||
|
|
4bb0e96b4b | ||
|
|
fa1a326227 | ||
|
|
57bf275fbd | ||
|
|
7c3bd08b37 | ||
|
|
a06d181af0 | ||
|
|
0d8c0cd096 | ||
|
|
d6d83d7060 | ||
|
|
d0ebde77f6 | ||
|
|
32e5c97d11 | ||
|
|
ebc299fc2f | ||
|
|
793f176dad | ||
|
|
68a87eee54 | ||
|
|
18d2e0c03e | ||
|
|
8b580a89d6 | ||
|
|
c5e7190ed4 | ||
|
|
911104a129 | ||
|
|
bce2ac63a9 | ||
|
|
9b3cde9b62 | ||
|
|
1a6294d3e2 | ||
|
|
1758b3f7ba | ||
|
|
013b7ad117 | ||
|
|
0a527707cd | ||
|
|
a87d245fc2 | ||
|
|
312b462d54 | ||
|
|
70754f10d4 | ||
|
|
d7f6dd5a80 | ||
|
|
cef47cbc01 | ||
|
|
57a5805a9f | ||
|
|
08b51aa384 | ||
|
|
b5cebedfbe | ||
|
|
35bf9d27a3 | ||
|
|
fd870e6b6a | ||
|
|
4ddcb8e134 | ||
|
|
924a305780 | ||
|
|
a5df18a9e5 | ||
|
|
f818af9b03 | ||
|
|
6ecfa90eb0 | ||
|
|
18cb59050b | ||
|
|
d862f0ea56 | ||
|
|
d1c2555861 | ||
|
|
70dcdd265d | ||
|
|
e8c7ddeef2 | ||
|
|
8f1533f4d8 | ||
|
|
7c802de797 | ||
|
|
901139c291 | ||
|
|
f40ceb0010 | ||
|
|
2f1e8c5f78 | ||
|
|
8e0c574122 | ||
|
|
3af05de1aa | ||
|
|
494300b366 | ||
|
|
9691eb273a | ||
|
|
b8d079b413 | ||
|
|
cdbfa5044b | ||
|
|
45b6443c90 | ||
|
|
45898d0be0 | ||
|
|
87eaa90ef8 | ||
|
|
0c0b30b8c7 | ||
|
|
137ea783b1 | ||
|
|
d6f6b951e1 | ||
|
|
b4b8a349a3 | ||
|
|
a7dabcb60a | ||
|
|
4b0b556dcf | ||
|
|
fb39f62925 | ||
|
|
ba932a5ad9 | ||
|
|
35a202d0e7 | ||
|
|
3717c25a7e | ||
|
|
0a2215cc0e | ||
|
|
8f03109cd2 | ||
|
|
efa780d0ab | ||
|
|
4290967d4c | ||
|
|
f09854a9e8 | ||
|
|
776d1c69b9 | ||
|
|
3a49dff386 | ||
|
|
2e75000f40 | ||
|
|
56114a4b22 | ||
|
|
ae724be7be | ||
|
|
ab4951fc03 | ||
|
|
b0a8a3e041 | ||
|
|
e04f4259c4 | ||
|
|
8a6cd3cd12 | ||
|
|
d51f062d03 | ||
|
|
85c7211014 | ||
|
|
788036c7ed | ||
|
|
c93038fabe | ||
|
|
2c98af77c3 | ||
|
|
2ce5801c25 | ||
|
|
709baf1fec | ||
|
|
4ef6387d6e | ||
|
|
c317fc273b | ||
|
|
41a78cd85c | ||
|
|
2400400788 | ||
|
|
ba6bb0fccf | ||
|
|
fd3e8cb0ae | ||
|
|
38c03ce006 | ||
|
|
4905590e1e | ||
|
|
35f447ddbf | ||
|
|
440cbf2ee5 | ||
|
|
cc2492ccf1 | ||
|
|
031eba789f | ||
|
|
f26fe8c0a7 | ||
|
|
9444c876d5 | ||
|
|
b260bef398 | ||
|
|
2f15881859 | ||
|
|
9044bb0fa3 | ||
|
|
21d79ce040 | ||
|
|
206d61f722 | ||
|
|
61836fba2a | ||
|
|
e11bf42cea | ||
|
|
962dfc2c33 | ||
|
|
ff006451ad | ||
|
|
040ca0fad7 | ||
|
|
d96f478f8a | ||
|
|
79e46b6778 | ||
|
|
8d7c733f1f | ||
|
|
9ce8711d5a | ||
|
|
e86146e714 | ||
|
|
c66edb9f8b | ||
|
|
9ff60ff16b | ||
|
|
7126a37bf4 | ||
|
|
539640d89f | ||
|
|
af032764a9 | ||
|
|
f705e2bac7 | ||
|
|
77bb102398 | ||
|
|
92f094d86d | ||
|
|
46f912fd57 | ||
|
|
2c1e6bf619 | ||
|
|
f962feb882 | ||
|
|
ac6193687f | ||
|
|
16329bd954 | ||
|
|
4e544cffee | ||
|
|
f61138e068 | ||
|
|
980112b146 | ||
|
|
8a7c2e50f1 | ||
|
|
557d472fe3 | ||
|
|
37fcd04681 | ||
|
|
bd79be9b68 | ||
|
|
996acf9036 | ||
|
|
34aaec6f90 | ||
|
|
01fb68b9bf | ||
|
|
1b585f630b | ||
|
|
0f3e82f3c4 | ||
|
|
b8390310b1 | ||
|
|
b438699d36 | ||
|
|
ada5991422 | ||
|
|
08ce280cbc | ||
|
|
b034ac6d38 | ||
|
|
f512537754 | ||
|
|
cb15f18dc1 | ||
|
|
bc8b15332b | ||
|
|
7153dd516f | ||
|
|
33d439f8fd | ||
|
|
fb316787cb | ||
|
|
8e1434e80e | ||
|
|
49b0f87d10 | ||
|
|
8c684bf7e1 | ||
|
|
9a016c0c2b | ||
|
|
f55b4f8e28 | ||
|
|
4ef269606c | ||
|
|
892ca8137e | ||
|
|
896c993ada | ||
|
|
e3cc42be97 | ||
|
|
177f357d9d | ||
|
|
a48593a8d3 | ||
|
|
ee4806f47a | ||
|
|
9d4fd7a249 | ||
|
|
4c1514495b | ||
|
|
13a93e7b82 | ||
|
|
c00a920640 | ||
|
|
0da7bd0604 | ||
|
|
a39d9c3dff | ||
|
|
1e3b0b520e | ||
|
|
84b492f344 | ||
|
|
fc80772078 | ||
|
|
0f6e3cd61c | ||
|
|
474b650cac | ||
|
|
2f03aa6eed | ||
|
|
16c3408638 | ||
|
|
05f5192e81 | ||
|
|
bf9415834f | ||
|
|
6dda816102 | ||
|
|
79bda69d88 | ||
|
|
8a01b50fc3 | ||
|
|
3ed289b3b1 | ||
|
|
0672829053 | ||
|
|
2559111c21 | ||
|
|
c433fbb593 | ||
|
|
507f0fb372 | ||
|
|
66c9fedc34 | ||
|
|
fe943c339d | ||
|
|
2e549efae5 | ||
|
|
4da836af71 | ||
|
|
3ad50710a7 | ||
|
|
833f206348 | ||
|
|
efa5dbe7a5 | ||
|
|
da0a47bec3 | ||
|
|
69aafbf4df | ||
|
|
2843a1556e | ||
|
|
aa56fe34ac | ||
|
|
c80678c760 | ||
|
|
2d68bd0960 | ||
|
|
96eb703bbf | ||
|
|
56f93a7f38 | ||
|
|
dce430ad92 | ||
|
|
6ae6a7c020 | ||
|
|
5441b094a4 | ||
|
|
56d1b0fb59 | ||
|
|
01b1e7e2cf | ||
|
|
d2456c7572 | ||
|
|
14c2eab7c3 | ||
|
|
e89c19d16c | ||
|
|
3c6a219466 | ||
|
|
4697f5b379 | ||
|
|
d97af798d2 | ||
|
|
95ca079556 | ||
|
|
21e793fb4f | ||
|
|
95ffd3ccf8 | ||
|
|
2935d6a18d | ||
|
|
ce65df7092 | ||
|
|
f66a20f439 | ||
|
|
69b2ecc531 | ||
|
|
024bac4263 | ||
|
|
74f4a3f972 | ||
|
|
f616613059 | ||
|
|
454157dd66 | ||
|
|
49399aa3e7 | ||
|
|
5e043adba7 | ||
|
|
c1d3f7f1a9 | ||
|
|
2bcc6c6d04 | ||
|
|
527fc4a63f | ||
|
|
c100cc7b31 | ||
|
|
545de6f02e | ||
|
|
6f86294e68 | ||
|
|
d0dcbe5594 | ||
|
|
0763c48ed8 | ||
|
|
732f3a6ef1 | ||
|
|
600c57a9b9 | ||
|
|
db9e5c9715 | ||
|
|
9b5985b3c1 | ||
|
|
ebd1f5ddb0 | ||
|
|
85fbe3be3f | ||
|
|
a9304e79d4 | ||
|
|
6edff8f5e1 | ||
|
|
7ce229d3b0 | ||
|
|
a6aba5a587 |
@@ -158,6 +158,16 @@ jobs:
|
||||
- *run_yarn
|
||||
- run: yarn test-build --maxWorkers=2
|
||||
|
||||
test_build_devtools:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace: *attach_workspace
|
||||
- *restore_yarn_cache
|
||||
- *run_yarn
|
||||
- run: yarn test-build-devtools --maxWorkers=2
|
||||
|
||||
test_dom_fixtures:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
@@ -231,6 +241,9 @@ workflows:
|
||||
- test_build_prod:
|
||||
requires:
|
||||
- build
|
||||
- test_build_devtools:
|
||||
requires:
|
||||
- build
|
||||
- test_dom_fixtures:
|
||||
requires:
|
||||
- build
|
||||
|
||||
@@ -142,10 +142,13 @@ module.exports = {
|
||||
],
|
||||
|
||||
globals: {
|
||||
SharedArrayBuffer: true,
|
||||
|
||||
spyOnDev: true,
|
||||
spyOnDevAndProd: true,
|
||||
spyOnProd: true,
|
||||
__PROFILE__: true,
|
||||
__UMD__: true,
|
||||
trustedTypes: true,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -41,6 +41,10 @@
|
||||
* Warn in Strict Mode if effects are scheduled outside an `act()` call. ([@threepointone](https://github.com/threepointone) in [#15763](https://github.com/facebook/react/pull/15763) and [#16041](https://github.com/facebook/react/pull/16041))
|
||||
* Warn when using `act` from the wrong renderer. ([@threepointone](https://github.com/threepointone) in [#15756](https://github.com/facebook/react/pull/15756))
|
||||
|
||||
### ESLint Plugin: React Hooks
|
||||
|
||||
* Report Hook calls at the top level as a violation. ([gaearon](https://github.com/gaearon) in [#16455](https://github.com/facebook/react/pull/16455))
|
||||
|
||||
## 16.8.6 (March 27, 2019)
|
||||
|
||||
### React DOM
|
||||
|
||||
@@ -1,3 +1,76 @@
|
||||
# Code of Conduct
|
||||
|
||||
Facebook has adopted a Code of Conduct that we expect project participants to adhere to. Please [read the full text](https://code.fb.com/codeofconduct/) so that you can understand what actions will and will not be tolerated.
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to make participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all project spaces, and it also applies when
|
||||
an individual is representing the project or its community in public spaces.
|
||||
Examples of representing a project or community include using an official
|
||||
project e-mail address, posting via an official social media account, or acting
|
||||
as an appointed representative at an online or offline event. Representation of
|
||||
a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at <opensource-conduct@fb.com>. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
<br/><br/>
|
||||
Use the <code>server</code> script instead:
|
||||
<br/><br/>
|
||||
<code>node ./fixtures/regression/server.js</code><br/>
|
||||
<code>open <a href="http://localhost:3000/fixtures/regression/14.9.html">http://localhost:3000/fixtures/regression/14.9.html</a></code>
|
||||
<code>node ./fixtures/devtools/regression/server.js</code><br/>
|
||||
<code>open <a href="http://localhost:3000/14.9.html">http://localhost:3000/14.9.html</a></code>
|
||||
</div>
|
||||
|
||||
<script src="shared.js" type="text/babel"></script>
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
<br/><br/>
|
||||
Use the <code>server</code> script instead:
|
||||
<br/><br/>
|
||||
<code>node ./fixtures/regression/server.js</code><br/>
|
||||
<code>open <a href="http://localhost:3000/fixtures/regression/15.0.html">http://localhost:3000/fixtures/regression/15.0.html</a></code>
|
||||
<code>node ./fixtures/devtools/regression/server.js</code><br/>
|
||||
<code>open <a href="http://localhost:3000/15.0.html">http://localhost:3000/15.0.html</a></code>
|
||||
</div>
|
||||
|
||||
<script src="shared.js" type="text/babel"></script>
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
<br/><br/>
|
||||
Use the <code>server</code> script instead:
|
||||
<br/><br/>
|
||||
<code>node ./fixtures/regression/server.js</code><br/>
|
||||
<code>open <a href="http://localhost:3000/fixtures/regression/15.1.html">http://localhost:3000/fixtures/regression/15.1.html</a></code>
|
||||
<code>node ./fixtures/devtools/regression/server.js</code><br/>
|
||||
<code>open <a href="http://localhost:3000/15.1.html">http://localhost:3000/15.1.html</a></code>
|
||||
</div>
|
||||
|
||||
<script src="shared.js" type="text/babel"></script>
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
<br/><br/>
|
||||
Use the <code>server</code> script instead:
|
||||
<br/><br/>
|
||||
<code>node ./fixtures/regression/server.js</code><br/>
|
||||
<code>open <a href="http://localhost:3000/fixtures/regression/15.2.html">http://localhost:3000/fixtures/regression/15.2.html</a></code>
|
||||
<code>node ./fixtures/devtools/regression/server.js</code><br/>
|
||||
<code>open <a href="http://localhost:3000/15.2.html">http://localhost:3000/15.2.html</a></code>
|
||||
</div>
|
||||
|
||||
<script src="shared.js" type="text/babel"></script>
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
<br/><br/>
|
||||
Use the <code>server</code> script instead:
|
||||
<br/><br/>
|
||||
<code>node ./fixtures/regression/server.js</code><br/>
|
||||
<code>open <a href="http://localhost:3000/fixtures/regression/15.3.html">http://localhost:3000/fixtures/regression/15.3.html</a></code>
|
||||
<code>node ./fixtures/devtools/regression/server.js</code><br/>
|
||||
<code>open <a href="http://localhost:3000/15.3.html">http://localhost:3000/15.3.html</a></code>
|
||||
</div>
|
||||
|
||||
<script src="shared.js" type="text/babel"></script>
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
<br/><br/>
|
||||
Use the <code>server</code> script instead:
|
||||
<br/><br/>
|
||||
<code>node ./fixtures/regression/server.js</code><br/>
|
||||
<code>open <a href="http://localhost:3000/fixtures/regression/15.4.html">http://localhost:3000/fixtures/regression/15.4.html</a></code>
|
||||
<code>node ./fixtures/devtools/regression/server.js</code><br/>
|
||||
<code>open <a href="http://localhost:3000/15.4.html">http://localhost:3000/15.4.html</a></code>
|
||||
</div>
|
||||
|
||||
<script src="shared.js" type="text/babel"></script>
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
<br/><br/>
|
||||
Use the <code>server</code> script instead:
|
||||
<br/><br/>
|
||||
<code>node ./fixtures/regression/server.js</code><br/>
|
||||
<code>open <a href="http://localhost:3000/fixtures/regression/15.5.html">http://localhost:3000/fixtures/regression/15.5.html</a></code>
|
||||
<code>node ./fixtures/devtools/regression/server.js</code><br/>
|
||||
<code>open <a href="http://localhost:3000/15.5.html">http://localhost:3000/15.5.html</a></code>
|
||||
</div>
|
||||
|
||||
<script src="shared.js" type="text/babel"></script>
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
<br/><br/>
|
||||
Use the <code>server</code> script instead:
|
||||
<br/><br/>
|
||||
<code>node ./fixtures/regression/server.js</code><br/>
|
||||
<code>open <a href="http://localhost:3000/fixtures/regression/15.6.html">http://localhost:3000/fixtures/regression/15.6.html</a></code>
|
||||
<code>node ./fixtures/devtools/regression/server.js</code><br/>
|
||||
<code>open <a href="http://localhost:3000/15.6.html">http://localhost:3000/15.6.html</a></code>
|
||||
</div>
|
||||
|
||||
<script src="shared.js" type="text/babel"></script>
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
<br/><br/>
|
||||
Use the <code>server</code> script instead:
|
||||
<br/><br/>
|
||||
<code>node ./fixtures/regression/server.js</code><br/>
|
||||
<code>open <a href="http://localhost:3000/fixtures/regression/16.0.html">http://localhost:3000/fixtures/regression/16.0.html</a></code>
|
||||
<code>node ./fixtures/devtools/regression/server.js</code><br/>
|
||||
<code>open <a href="http://localhost:3000/16.0.html">http://localhost:3000/16.0.html</a></code>
|
||||
</div>
|
||||
|
||||
<script src="shared.js" type="text/babel"></script>
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
<br/><br/>
|
||||
Use the <code>server</code> script instead:
|
||||
<br/><br/>
|
||||
<code>node ./fixtures/regression/server.js</code><br/>
|
||||
<code>open <a href="http://localhost:3000/fixtures/regression/16.1.html">http://localhost:3000/fixtures/regression/16.1.html</a></code>
|
||||
<code>node ./fixtures/devtools/regression/server.js</code><br/>
|
||||
<code>open <a href="http://localhost:3000/16.1.html">http://localhost:3000/16.1.html</a></code>
|
||||
</div>
|
||||
|
||||
<script src="shared.js" type="text/babel"></script>
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
<br/><br/>
|
||||
Use the <code>server</code> script instead:
|
||||
<br/><br/>
|
||||
<code>node ./fixtures/regression/server.js</code><br/>
|
||||
<code>open <a href="http://localhost:3000/fixtures/regression/16.2.html">http://localhost:3000/fixtures/regression/16.2.html</a></code>
|
||||
<code>node ./fixtures/devtools/regression/server.js</code><br/>
|
||||
<code>open <a href="http://localhost:3000/16.2.html">http://localhost:3000/16.2.html</a></code>
|
||||
</div>
|
||||
|
||||
<script src="shared.js" type="text/babel"></script>
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
<br/><br/>
|
||||
Use the <code>server</code> script instead:
|
||||
<br/><br/>
|
||||
<code>node ./fixtures/regression/server.js</code><br/>
|
||||
<code>open <a href="http://localhost:3000/fixtures/regression/16.3.html">http://localhost:3000/fixtures/regression/16.3.html</a></code>
|
||||
<code>node ./fixtures/devtools/regression/server.js</code><br/>
|
||||
<code>open <a href="http://localhost:3000/16.3.html">http://localhost:3000/16.3.html</a></code>
|
||||
</div>
|
||||
|
||||
<script src="shared.js" type="text/babel"></script>
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
<br/><br/>
|
||||
Use the <code>server</code> script instead:
|
||||
<br/><br/>
|
||||
<code>node ./fixtures/regression/server.js</code><br/>
|
||||
<code>open <a href="http://localhost:3000/fixtures/regression/16.4.html">http://localhost:3000/fixtures/regression/16.4.html</a></code>
|
||||
<code>node ./fixtures/devtools/regression/server.js</code><br/>
|
||||
<code>open <a href="http://localhost:3000/16.4.html">http://localhost:3000/16.4.html</a></code>
|
||||
</div>
|
||||
|
||||
<script src="shared.js" type="text/babel"></script>
|
||||
|
||||
@@ -25,8 +25,8 @@
|
||||
<br/><br/>
|
||||
Use the <code>server</code> script instead:
|
||||
<br/><br/>
|
||||
<code>node ./fixtures/regression/server.js</code><br/>
|
||||
<code>open <a href="http://localhost:3000/fixtures/regression/16.5.html">http://localhost:3000/fixtures/regression/16.5.html</a></code>
|
||||
<code>node ./fixtures/devtools/regression/server.js</code><br/>
|
||||
<code>open <a href="http://localhost:3000/16.5.html">http://localhost:3000/16.5.html</a></code>
|
||||
</div>
|
||||
|
||||
<script src="shared.js" type="text/babel"></script>
|
||||
|
||||
@@ -26,8 +26,8 @@
|
||||
<br/><br/>
|
||||
Use the <code>server</code> script instead:
|
||||
<br/><br/>
|
||||
<code>node ./fixtures/regression/server.js</code><br/>
|
||||
<code>open <a href="http://localhost:3000/fixtures/regression/16.6.html">http://localhost:3000/fixtures/regression/16.6.html</a></code>
|
||||
<code>node ./fixtures/devtools/regression/server.js</code><br/>
|
||||
<code>open <a href="http://localhost:3000/16.6.html">http://localhost:3000/16.6.html</a></code>
|
||||
</div>
|
||||
|
||||
<script src="shared.js" type="text/babel"></script>
|
||||
|
||||
@@ -26,8 +26,8 @@
|
||||
<br/><br/>
|
||||
Use the <code>server</code> script instead:
|
||||
<br/><br/>
|
||||
<code>node ./fixtures/regression/server.js</code><br/>
|
||||
<code>open <a href="http://localhost:3000/fixtures/regression/16.7.html">http://localhost:3000/fixtures/regression/16.7.html</a></code>
|
||||
<code>node ./fixtures/devtools/regression/server.js</code><br/>
|
||||
<code>open <a href="http://localhost:3000/16.7.html">http://localhost:3000/16.7.html</a></code>
|
||||
</div>
|
||||
|
||||
<script src="shared.js" type="text/babel"></script>
|
||||
|
||||
@@ -26,8 +26,8 @@
|
||||
<br/><br/>
|
||||
Use the <code>server</code> script instead:
|
||||
<br/><br/>
|
||||
<code>node ./fixtures/regression/server.js</code><br/>
|
||||
<code>open <a href="http://localhost:3000/fixtures/regression/canary.html">http://localhost:3000/fixtures/regression/canary.html</a></code>
|
||||
<code>node ./fixtures/devtools/regression/server.js</code><br/>
|
||||
<code>open <a href="http://localhost:3000/canary.html">http://localhost:3000/canary.html</a></code>
|
||||
</div>
|
||||
|
||||
<script src="shared.js" type="text/babel"></script>
|
||||
|
||||
@@ -26,8 +26,8 @@
|
||||
<br/><br/>
|
||||
Use the <code>server</code> script instead:
|
||||
<br/><br/>
|
||||
<code>node ./fixtures/regression/server.js</code><br/>
|
||||
<code>open <a href="http://localhost:3000/fixtures/regression/next.html">http://localhost:3000/fixtures/regression/next.html</a></code>
|
||||
<code>node ./fixtures/devtools/regression/server.js</code><br/>
|
||||
<code>open <a href="http://localhost:3000/next.html">http://localhost:3000/next.html</a></code>
|
||||
</div>
|
||||
|
||||
<script src="shared.js" type="text/babel"></script>
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
|
||||
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
|
||||
<script src="https://unpkg.com/immutable@4.0.0-rc.12/dist/immutable.js"></script>
|
||||
|
||||
<!-- Don't use this in production: -->
|
||||
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
|
||||
@@ -255,6 +256,33 @@
|
||||
);
|
||||
}
|
||||
|
||||
const set = new Set(['abc', 123]);
|
||||
const map = new Map([['name', 'Brian'], ['food', 'sushi']]);
|
||||
const setOfSets = new Set([new Set(['a', 'b', 'c']), new Set([1, 2, 3])]);
|
||||
const mapOfMaps = new Map([['first', map], ['second', map]]);
|
||||
const typedArray = Int8Array.from([100, -100, 0]);
|
||||
const immutable = Immutable.fromJS({
|
||||
a: [{ hello: 'there' }, 'fixed', true],
|
||||
b: 123,
|
||||
c: {
|
||||
'1': 'xyz',
|
||||
xyz: 1,
|
||||
},
|
||||
});
|
||||
|
||||
function UnserializableProps() {
|
||||
return (
|
||||
<ChildComponent
|
||||
map={map}
|
||||
set={set}
|
||||
mapOfMaps={mapOfMaps}
|
||||
setOfSets={setOfSets}
|
||||
typedArray={typedArray}
|
||||
immutable={immutable}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function ChildComponent(props: any) {
|
||||
return null;
|
||||
}
|
||||
@@ -264,6 +292,7 @@
|
||||
<Fragment>
|
||||
<SimpleValues />
|
||||
<ObjectProps />
|
||||
<UnserializableProps />
|
||||
<CustomObject />
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import Fixture from '../../Fixture';
|
||||
import FixtureSet from '../../FixtureSet';
|
||||
import TestCase from '../../TestCase';
|
||||
|
||||
@@ -39,6 +40,44 @@ export default class TextAreaFixtures extends React.Component {
|
||||
<textarea placeholder="Hello, world" />
|
||||
</div>
|
||||
</TestCase>
|
||||
|
||||
<TestCase
|
||||
title="Required Textareas"
|
||||
affectedBrowsers="Firefox"
|
||||
relatedIssues="16402">
|
||||
<TestCase.Steps>
|
||||
<li>View this test in Firefox</li>
|
||||
</TestCase.Steps>
|
||||
|
||||
<TestCase.ExpectedResult>
|
||||
You should{' '}
|
||||
<b>
|
||||
<i>not</i>
|
||||
</b>{' '}
|
||||
see a red aura on initial page load, indicating the textarea is
|
||||
invalid.
|
||||
<br />
|
||||
This aura looks roughly like:
|
||||
<textarea style={{boxShadow: '0 0 1px 1px red', marginLeft: 8}} />
|
||||
</TestCase.ExpectedResult>
|
||||
|
||||
<Fixture>
|
||||
<form className="control-box">
|
||||
<fieldset>
|
||||
<legend>Empty value prop string</legend>
|
||||
<textarea value="" required={true} />
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>No value prop</legend>
|
||||
<textarea required={true} />
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Empty defaultValue prop string</legend>
|
||||
<textarea required={true} defaultValue="" />
|
||||
</fieldset>
|
||||
</form>
|
||||
</Fixture>
|
||||
</TestCase>
|
||||
</FixtureSet>
|
||||
);
|
||||
}
|
||||
|
||||
11
package.json
11
package.json
@@ -12,6 +12,7 @@
|
||||
"@babel/plugin-proposal-class-properties": "^7.0.0",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.0.0",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
|
||||
"@babel/plugin-syntax-jsx": "^7.2.0",
|
||||
"@babel/plugin-transform-arrow-functions": "^7.0.0",
|
||||
"@babel/plugin-transform-async-to-generator": "^7.0.0",
|
||||
"@babel/plugin-transform-block-scoped-functions": "^7.0.0",
|
||||
@@ -43,14 +44,14 @@
|
||||
"coveralls": "^2.11.6",
|
||||
"create-react-class": "^15.6.3",
|
||||
"cross-env": "^5.1.1",
|
||||
"danger": "^3.0.4",
|
||||
"danger": "^9.1.8",
|
||||
"error-stack-parser": "^2.0.2",
|
||||
"eslint": "^6.1.0",
|
||||
"eslint-config-fbjs": "^1.1.1",
|
||||
"eslint-plugin-babel": "^5.3.0",
|
||||
"eslint-plugin-flowtype": "^2.25.0",
|
||||
"eslint-plugin-jest": "^22.15.0",
|
||||
"eslint-plugin-no-for-of-loops": "^1.0.0",
|
||||
"eslint-plugin-babel": "^5.3.0",
|
||||
"eslint-plugin-react": "^6.7.1",
|
||||
"eslint-plugin-react-internal": "link:./scripts/eslint-rules",
|
||||
"fbjs-scripts": "^0.8.3",
|
||||
@@ -61,8 +62,8 @@
|
||||
"google-closure-compiler": "20190301.0.0",
|
||||
"gzip-size": "^3.0.0",
|
||||
"jasmine-check": "^1.0.0-rc.0",
|
||||
"jest": "^23.1.0",
|
||||
"jest-diff": "^23.0.1",
|
||||
"jest": "^23",
|
||||
"jest-diff": "^23",
|
||||
"jest-snapshot-serializer-raw": "^1.1.0",
|
||||
"minimatch": "^3.0.4",
|
||||
"minimist": "^1.2.0",
|
||||
@@ -108,6 +109,8 @@
|
||||
"test-prod-build": "yarn test-build-prod",
|
||||
"test-build": "cross-env NODE_ENV=development jest --config ./scripts/jest/config.build.js",
|
||||
"test-build-prod": "cross-env NODE_ENV=production jest --config ./scripts/jest/config.build.js",
|
||||
"test-build-devtools": "cross-env NODE_ENV=development jest --config ./scripts/jest/config.build-devtools.js",
|
||||
"debug-test-build-devtools": "cross-env NODE_ENV=development node --inspect-brk node_modules/.bin/jest --config ./scripts/jest/config.build-devtools.js",
|
||||
"test-dom-fixture": "cd fixtures/dom && yarn && yarn prestart && yarn test",
|
||||
"flow": "node ./scripts/tasks/flow.js",
|
||||
"flow-ci": "node ./scripts/tasks/flow-ci.js",
|
||||
|
||||
5
packages/babel-plugin-react-jsx/README.md
Normal file
5
packages/babel-plugin-react-jsx/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
This package is intended to eventually replace the current `@babel/plugin-transform-react-jsx`, changing the JSX transform from targeting `React.createElement(type, props, children)` to `React.jsx(types, props, key)`.
|
||||
|
||||
https://github.com/reactjs/rfcs/blob/createlement-rfc/text/0000-create-element-changes.md
|
||||
|
||||
**This is experimental and not intended to be used directly.**
|
||||
@@ -0,0 +1,392 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
/* eslint-disable quotes */
|
||||
'use strict';
|
||||
|
||||
const babel = require('@babel/core');
|
||||
const codeFrame = require('@babel/code-frame');
|
||||
const {wrap} = require('jest-snapshot-serializer-raw');
|
||||
|
||||
function transform(input, options) {
|
||||
return wrap(
|
||||
babel.transform(input, {
|
||||
configFile: false,
|
||||
plugins: [
|
||||
'@babel/plugin-syntax-jsx',
|
||||
'@babel/plugin-transform-arrow-functions',
|
||||
...(options && options.development
|
||||
? [
|
||||
'@babel/plugin-transform-react-jsx-source',
|
||||
'@babel/plugin-transform-react-jsx-self',
|
||||
]
|
||||
: []),
|
||||
[
|
||||
'./packages/babel-plugin-react-jsx',
|
||||
{
|
||||
development: __DEV__,
|
||||
useBuiltIns: true,
|
||||
useCreateElement: true,
|
||||
...options,
|
||||
},
|
||||
],
|
||||
],
|
||||
}).code
|
||||
);
|
||||
}
|
||||
|
||||
describe('transform react to jsx', () => {
|
||||
it('fragment with no children', () => {
|
||||
expect(transform(`var x = <></>`)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('React.Fragment to set keys and source', () => {
|
||||
expect(
|
||||
transform(`var x = <React.Fragment key='foo'><div /></React.Fragment>`, {
|
||||
development: true,
|
||||
})
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('normal fragments not to set key and source', () => {
|
||||
expect(
|
||||
transform(`var x = <><div /></>`, {
|
||||
development: true,
|
||||
})
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should properly handle comments adjacent to children', () => {
|
||||
expect(
|
||||
transform(`
|
||||
var x = (
|
||||
<div>
|
||||
{/* A comment at the beginning */}
|
||||
{/* A second comment at the beginning */}
|
||||
<span>
|
||||
{/* A nested comment */}
|
||||
</span>
|
||||
{/* A sandwiched comment */}
|
||||
<br />
|
||||
{/* A comment at the end */}
|
||||
{/* A second comment at the end */}
|
||||
</div>
|
||||
);
|
||||
`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('adds appropriate new lines when using spread attribute', () => {
|
||||
expect(transform(`<Component {...props} sound="moo" />`)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('arrow functions', () => {
|
||||
expect(
|
||||
transform(`
|
||||
var foo = function () {
|
||||
return () => <this />;
|
||||
};
|
||||
|
||||
var bar = function () {
|
||||
return () => <this.foo />;
|
||||
};
|
||||
|
||||
`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('assignment', () => {
|
||||
expect(
|
||||
transform(`var div = <Component {...props} foo="bar" />`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('concatenates adjacent string literals', () => {
|
||||
expect(
|
||||
transform(`
|
||||
var x =
|
||||
<div>
|
||||
foo
|
||||
{"bar"}
|
||||
baz
|
||||
<div>
|
||||
buz
|
||||
bang
|
||||
</div>
|
||||
qux
|
||||
{null}
|
||||
quack
|
||||
</div>
|
||||
`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should allow constructor as prop', () => {
|
||||
expect(transform(`<Component constructor="foo" />;`)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should allow deeper js namespacing', () => {
|
||||
expect(
|
||||
transform(`<Namespace.DeepNamespace.Component />;`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should allow elements as attributes', () => {
|
||||
expect(transform(`<div attr=<div /> />`)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should allow js namespacing', () => {
|
||||
expect(transform(`<Namespace.Component />;`)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should allow nested fragments', () => {
|
||||
expect(
|
||||
transform(`
|
||||
<div>
|
||||
< >
|
||||
<>
|
||||
<span>Hello</span>
|
||||
<span>world</span>
|
||||
</>
|
||||
<>
|
||||
<span>Goodbye</span>
|
||||
<span>world</span>
|
||||
</>
|
||||
</>
|
||||
</div>
|
||||
`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should avoid wrapping in extra parens if not needed', () => {
|
||||
expect(
|
||||
transform(`
|
||||
var x = <div>
|
||||
<Component />
|
||||
</div>;
|
||||
|
||||
var x = <div>
|
||||
{props.children}
|
||||
</div>;
|
||||
|
||||
var x = <Composite>
|
||||
{props.children}
|
||||
</Composite>;
|
||||
|
||||
var x = <Composite>
|
||||
<Composite2 />
|
||||
</Composite>;
|
||||
`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should convert simple tags', () => {
|
||||
expect(transform(`var x = <div></div>;`)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should convert simple text', () => {
|
||||
expect(transform(`var x = <div>text</div>;`)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should disallow spread children', () => {
|
||||
let _error;
|
||||
const code = `<div>{...children}</div>;`;
|
||||
try {
|
||||
transform(code);
|
||||
} catch (error) {
|
||||
_error = error;
|
||||
}
|
||||
expect(_error).toEqual(
|
||||
new SyntaxError(
|
||||
'undefined: Spread children are not supported in React.' +
|
||||
'\n' +
|
||||
codeFrame.codeFrameColumns(
|
||||
code,
|
||||
{start: {line: 1, column: 6}},
|
||||
{highlightCode: true}
|
||||
)
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it('should escape xhtml jsxattribute', () => {
|
||||
expect(
|
||||
transform(`
|
||||
<div id="wôw" />;
|
||||
<div id="\w" />;
|
||||
<div id="w < w" />;
|
||||
`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should escape xhtml jsxtext', () => {
|
||||
/* eslint-disable no-irregular-whitespace */
|
||||
expect(
|
||||
transform(`
|
||||
<div>wow</div>;
|
||||
<div>wôw</div>;
|
||||
|
||||
<div>w & w</div>;
|
||||
<div>w & w</div>;
|
||||
|
||||
<div>w w</div>;
|
||||
<div>this should not parse as unicode: \u00a0</div>;
|
||||
<div>this should parse as nbsp: </div>;
|
||||
<div>this should parse as unicode: {'\u00a0 '}</div>;
|
||||
|
||||
<div>w < w</div>;
|
||||
`)
|
||||
).toMatchSnapshot();
|
||||
/*eslint-enable */
|
||||
});
|
||||
|
||||
it('should handle attributed elements', () => {
|
||||
expect(
|
||||
transform(`
|
||||
var HelloMessage = React.createClass({
|
||||
render: function() {
|
||||
return <div>Hello {this.props.name}</div>;
|
||||
}
|
||||
});
|
||||
|
||||
React.render(<HelloMessage name={
|
||||
<span>
|
||||
Sebastian
|
||||
</span>
|
||||
} />, mountNode);
|
||||
`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should handle has own property correctly', () => {
|
||||
expect(
|
||||
transform(`<hasOwnProperty>testing</hasOwnProperty>;`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should have correct comma in nested children', () => {
|
||||
expect(
|
||||
transform(`
|
||||
var x = <div>
|
||||
<div><br /></div>
|
||||
<Component>{foo}<br />{bar}</Component>
|
||||
<br />
|
||||
</div>;
|
||||
`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should insert commas after expressions before whitespace', () => {
|
||||
expect(
|
||||
transform(`
|
||||
var x =
|
||||
<div
|
||||
attr1={
|
||||
"foo" + "bar"
|
||||
}
|
||||
attr2={
|
||||
"foo" + "bar" +
|
||||
|
||||
"baz" + "bug"
|
||||
}
|
||||
attr3={
|
||||
"foo" + "bar" +
|
||||
"baz" + "bug"
|
||||
}
|
||||
attr4="baz">
|
||||
</div>
|
||||
`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should not add quotes to identifier names', () => {
|
||||
expect(
|
||||
transform(`var e = <F aaa new const var default foo-bar/>;`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should not strip nbsp even couple with other whitespace', () => {
|
||||
expect(transform(`<div> </div>;`)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should not strip tags with a single child of nbsp', () => {
|
||||
expect(transform(`<div> </div>;`)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should properly handle comments between props', () => {
|
||||
expect(
|
||||
transform(`
|
||||
var x = (
|
||||
<div
|
||||
/* a multi-line
|
||||
comment */
|
||||
attr1="foo">
|
||||
<span // a double-slash comment
|
||||
attr2="bar"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should quote jsx attributes', () => {
|
||||
expect(
|
||||
transform(`<button data-value='a value'>Button</button>`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should support xml namespaces if flag', () => {
|
||||
expect(
|
||||
transform('<f:image n:attr />', {throwIfNamespace: false})
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should throw error namespaces if not flag', () => {
|
||||
let _error;
|
||||
const code = `<f:image />`;
|
||||
try {
|
||||
transform(code);
|
||||
} catch (error) {
|
||||
_error = error;
|
||||
}
|
||||
expect(_error).toEqual(
|
||||
new SyntaxError(
|
||||
"undefined: Namespace tags are not supported by default. React's " +
|
||||
"JSX doesn't support namespace tags. You can turn on the " +
|
||||
"'throwIfNamespace' flag to bypass this warning." +
|
||||
'\n' +
|
||||
codeFrame.codeFrameColumns(
|
||||
code,
|
||||
{start: {line: 1, column: 2}},
|
||||
{highlightCode: true}
|
||||
)
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it('should transform known hyphenated tags', () => {
|
||||
expect(transform(`<font-face />`)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('wraps props in react spread for first spread attributes', () => {
|
||||
expect(transform(`<Component {...x} y={2} z />`)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('wraps props in react spread for last spread attributes', () => {
|
||||
expect(transform(`<Component y={2} z { ... x } />`)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('wraps props in react spread for middle spread attributes', () => {
|
||||
expect(transform(`<Component y={2} { ... x } z />`)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('useBuiltIns false uses extend instead of Object.assign', () => {
|
||||
expect(
|
||||
transform(`<Component y={2} {...x} />`, {useBuiltIns: false})
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,483 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
/* eslint-disable quotes */
|
||||
'use strict';
|
||||
|
||||
const babel = require('@babel/core');
|
||||
const codeFrame = require('@babel/code-frame');
|
||||
const {wrap} = require('jest-snapshot-serializer-raw');
|
||||
|
||||
function transform(input, options) {
|
||||
return wrap(
|
||||
babel.transform(input, {
|
||||
configFile: false,
|
||||
plugins: [
|
||||
'@babel/plugin-syntax-jsx',
|
||||
'@babel/plugin-transform-arrow-functions',
|
||||
...(options && options.development
|
||||
? [
|
||||
'@babel/plugin-transform-react-jsx-source',
|
||||
'@babel/plugin-transform-react-jsx-self',
|
||||
]
|
||||
: []),
|
||||
[
|
||||
'./packages/babel-plugin-react-jsx',
|
||||
{
|
||||
useBuiltIns: true,
|
||||
useCreateElement: false,
|
||||
...options,
|
||||
},
|
||||
],
|
||||
],
|
||||
}).code
|
||||
);
|
||||
}
|
||||
|
||||
describe('transform react to jsx', () => {
|
||||
it('fragment with no children', () => {
|
||||
expect(transform(`var x = <></>`)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('fragments', () => {
|
||||
expect(transform(`var x = <><div /></>`)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('fragments to set keys', () => {
|
||||
expect(
|
||||
transform(`var x = <React.Fragment key="foo"></React.Fragment>`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('React.fragment to set keys and source', () => {
|
||||
expect(
|
||||
transform(`var x = <React.Fragment key='foo'></React.Fragment>`, {
|
||||
development: true,
|
||||
})
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('fragments in dev mode (no key and source)', () => {
|
||||
expect(
|
||||
transform(`var x = <><div /></>`, {
|
||||
development: true,
|
||||
})
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('nonStatic children', () => {
|
||||
expect(
|
||||
transform(
|
||||
`var x = (
|
||||
<div>
|
||||
{[<span key={'0'} />, <span key={'1'} />]}
|
||||
</div>
|
||||
);
|
||||
`,
|
||||
{
|
||||
development: true,
|
||||
}
|
||||
)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('static children', () => {
|
||||
expect(
|
||||
transform(
|
||||
`var x = (
|
||||
<div>
|
||||
<span />
|
||||
{[<span key={'0'} />, <span key={'1'} />]}
|
||||
</div>
|
||||
);
|
||||
`,
|
||||
{
|
||||
development: true,
|
||||
}
|
||||
)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('uses jsxDEV instead of jsx in dev mode', () => {
|
||||
expect(
|
||||
transform(`var x = <span propOne="one">Hi</span>`, {development: true})
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('properly passes in source and self', () => {
|
||||
expect(
|
||||
transform(`var x = <div />;`, {development: true})
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should properly handle potentially null variables', () => {
|
||||
expect(
|
||||
transform(`
|
||||
var foo = null;
|
||||
var x = <div {...foo} />;
|
||||
`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('properly handles keys', () => {
|
||||
expect(
|
||||
transform(`var x = (
|
||||
<div>
|
||||
<div key="1" />
|
||||
<div key="2" meow="wolf" />
|
||||
<div key="3" />
|
||||
</div>
|
||||
);`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('uses createElement when the key comes after a spread', () => {
|
||||
expect(
|
||||
transform(`var x = (
|
||||
<div {...props} key="1" foo="bar" />
|
||||
);`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('uses jsx when the key comes before a spread', () => {
|
||||
expect(
|
||||
transform(`var x = (
|
||||
<div key="1" {...props} foo="bar" />
|
||||
);`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should properly handle comments adjacent to children', () => {
|
||||
expect(
|
||||
transform(`
|
||||
var x = (
|
||||
<div>
|
||||
{/* A comment at the beginning */}
|
||||
{/* A second comment at the beginning */}
|
||||
<span>
|
||||
{/* A nested comment */}
|
||||
</span>
|
||||
{/* A sandwiched comment */}
|
||||
<br />
|
||||
{/* A comment at the end */}
|
||||
{/* A second comment at the end */}
|
||||
</div>
|
||||
);
|
||||
`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('adds appropriate new lines when using spread attribute', () => {
|
||||
expect(transform(`<Component {...props} sound="moo" />`)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('arrow functions', () => {
|
||||
expect(
|
||||
transform(`
|
||||
var foo = function () {
|
||||
return () => <this />;
|
||||
};
|
||||
|
||||
var bar = function () {
|
||||
return () => <this.foo />;
|
||||
};
|
||||
|
||||
`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('assignment', () => {
|
||||
expect(
|
||||
transform(`var div = <Component {...props} foo="bar" />`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('concatenates adjacent string literals', () => {
|
||||
expect(
|
||||
transform(`
|
||||
var x =
|
||||
<div>
|
||||
foo
|
||||
{"bar"}
|
||||
baz
|
||||
<div>
|
||||
buz
|
||||
bang
|
||||
</div>
|
||||
qux
|
||||
{null}
|
||||
quack
|
||||
</div>
|
||||
`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should allow constructor as prop', () => {
|
||||
expect(transform(`<Component constructor="foo" />;`)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should allow deeper js namespacing', () => {
|
||||
expect(
|
||||
transform(`<Namespace.DeepNamespace.Component />;`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should allow elements as attributes', () => {
|
||||
expect(transform(`<div attr=<div /> />`)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should allow js namespacing', () => {
|
||||
expect(transform(`<Namespace.Component />;`)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should allow nested fragments', () => {
|
||||
expect(
|
||||
transform(`
|
||||
<div>
|
||||
< >
|
||||
<>
|
||||
<span>Hello</span>
|
||||
<span>world</span>
|
||||
</>
|
||||
<>
|
||||
<span>Goodbye</span>
|
||||
<span>world</span>
|
||||
</>
|
||||
</>
|
||||
</div>
|
||||
`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should avoid wrapping in extra parens if not needed', () => {
|
||||
expect(
|
||||
transform(`
|
||||
var x = <div>
|
||||
<Component />
|
||||
</div>;
|
||||
|
||||
var x = <div>
|
||||
{props.children}
|
||||
</div>;
|
||||
|
||||
var x = <Composite>
|
||||
{props.children}
|
||||
</Composite>;
|
||||
|
||||
var x = <Composite>
|
||||
<Composite2 />
|
||||
</Composite>;
|
||||
`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should convert simple tags', () => {
|
||||
expect(transform(`var x = <div></div>;`)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should convert simple text', () => {
|
||||
expect(transform(`var x = <div>text</div>;`)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should disallow spread children', () => {
|
||||
let _error;
|
||||
const code = `<div>{...children}</div>;`;
|
||||
try {
|
||||
transform(code);
|
||||
} catch (error) {
|
||||
_error = error;
|
||||
}
|
||||
expect(_error).toEqual(
|
||||
new SyntaxError(
|
||||
'undefined: Spread children are not supported in React.' +
|
||||
'\n' +
|
||||
codeFrame.codeFrameColumns(
|
||||
code,
|
||||
{start: {line: 1, column: 6}},
|
||||
{highlightCode: true}
|
||||
)
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it('should escape xhtml jsxattribute', () => {
|
||||
expect(
|
||||
transform(`
|
||||
<div id="wôw" />;
|
||||
<div id="\w" />;
|
||||
<div id="w < w" />;
|
||||
`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should escape xhtml jsxtext', () => {
|
||||
/* eslint-disable no-irregular-whitespace */
|
||||
expect(
|
||||
transform(`
|
||||
<div>wow</div>;
|
||||
<div>wôw</div>;
|
||||
|
||||
<div>w & w</div>;
|
||||
<div>w & w</div>;
|
||||
|
||||
<div>w w</div>;
|
||||
<div>this should not parse as unicode: \u00a0</div>;
|
||||
<div>this should parse as nbsp: </div>;
|
||||
<div>this should parse as unicode: {'\u00a0 '}</div>;
|
||||
|
||||
<div>w < w</div>;
|
||||
`)
|
||||
).toMatchSnapshot();
|
||||
/*eslint-enable */
|
||||
});
|
||||
|
||||
it('should handle attributed elements', () => {
|
||||
expect(
|
||||
transform(`
|
||||
var HelloMessage = React.createClass({
|
||||
render: function() {
|
||||
return <div>Hello {this.props.name}</div>;
|
||||
}
|
||||
});
|
||||
|
||||
React.render(<HelloMessage name={
|
||||
<span>
|
||||
Sebastian
|
||||
</span>
|
||||
} />, mountNode);
|
||||
`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should handle has own property correctly', () => {
|
||||
expect(
|
||||
transform(`<hasOwnProperty>testing</hasOwnProperty>;`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should have correct comma in nested children', () => {
|
||||
expect(
|
||||
transform(`
|
||||
var x = <div>
|
||||
<div><br /></div>
|
||||
<Component>{foo}<br />{bar}</Component>
|
||||
<br />
|
||||
</div>;
|
||||
`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should insert commas after expressions before whitespace', () => {
|
||||
expect(
|
||||
transform(`
|
||||
var x =
|
||||
<div
|
||||
attr1={
|
||||
"foo" + "bar"
|
||||
}
|
||||
attr2={
|
||||
"foo" + "bar" +
|
||||
|
||||
"baz" + "bug"
|
||||
}
|
||||
attr3={
|
||||
"foo" + "bar" +
|
||||
"baz" + "bug"
|
||||
}
|
||||
attr4="baz">
|
||||
</div>
|
||||
`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should not add quotes to identifier names', () => {
|
||||
expect(
|
||||
transform(`var e = <F aaa new const var default foo-bar/>;`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should not strip nbsp even couple with other whitespace', () => {
|
||||
expect(transform(`<div> </div>;`)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should not strip tags with a single child of nbsp', () => {
|
||||
expect(transform(`<div> </div>;`)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should properly handle comments between props', () => {
|
||||
expect(
|
||||
transform(`
|
||||
var x = (
|
||||
<div
|
||||
/* a multi-line
|
||||
comment */
|
||||
attr1="foo">
|
||||
<span // a double-slash comment
|
||||
attr2="bar"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should quote jsx attributes', () => {
|
||||
expect(
|
||||
transform(`<button data-value='a value'>Button</button>`)
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should support xml namespaces if flag', () => {
|
||||
expect(
|
||||
transform('<f:image n:attr />', {throwIfNamespace: false})
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should throw error namespaces if not flag', () => {
|
||||
let _error;
|
||||
const code = `<f:image />`;
|
||||
try {
|
||||
transform(code);
|
||||
} catch (error) {
|
||||
_error = error;
|
||||
}
|
||||
expect(_error).toEqual(
|
||||
new SyntaxError(
|
||||
"undefined: Namespace tags are not supported by default. React's " +
|
||||
"JSX doesn't support namespace tags. You can turn on the " +
|
||||
"'throwIfNamespace' flag to bypass this warning." +
|
||||
'\n' +
|
||||
codeFrame.codeFrameColumns(
|
||||
code,
|
||||
{start: {line: 1, column: 2}},
|
||||
{highlightCode: true}
|
||||
)
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it('should transform known hyphenated tags', () => {
|
||||
expect(transform(`<font-face />`)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('wraps props in react spread for first spread attributes', () => {
|
||||
expect(transform(`<Component {...x} y={2} z />`)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('wraps props in react spread for last spread attributes', () => {
|
||||
expect(transform(`<Component y={2} z { ... x } />`)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('wraps props in react spread for middle spread attributes', () => {
|
||||
expect(transform(`<Component y={2} { ... x } z />`)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('useBuiltIns false uses extend instead of Object.assign', () => {
|
||||
expect(
|
||||
transform(`<Component y={2} {...x} />`, {useBuiltIns: false})
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,213 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`transform react to jsx React.Fragment to set keys and source 1`] = `
|
||||
var _jsxFileName = "";
|
||||
var x = React.createElement(React.Fragment, {
|
||||
key: "foo",
|
||||
__source: {
|
||||
fileName: _jsxFileName,
|
||||
lineNumber: 1
|
||||
},
|
||||
__self: this
|
||||
}, React.createElement("div", {
|
||||
__source: {
|
||||
fileName: _jsxFileName,
|
||||
lineNumber: 1
|
||||
},
|
||||
__self: this
|
||||
}));
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx adds appropriate new lines when using spread attribute 1`] = `
|
||||
React.createElement(Component, Object.assign({}, props, {
|
||||
sound: "moo"
|
||||
}));
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx arrow functions 1`] = `
|
||||
var foo = function () {
|
||||
var _this = this;
|
||||
|
||||
return function () {
|
||||
return React.createElement(_this, null);
|
||||
};
|
||||
};
|
||||
|
||||
var bar = function () {
|
||||
var _this2 = this;
|
||||
|
||||
return function () {
|
||||
return React.createElement(_this2.foo, null);
|
||||
};
|
||||
};
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx assignment 1`] = `
|
||||
var div = React.createElement(Component, Object.assign({}, props, {
|
||||
foo: "bar"
|
||||
}));
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx concatenates adjacent string literals 1`] = `var x = React.createElement("div", null, "foo", "bar", "baz", React.createElement("div", null, "buz bang"), "qux", null, "quack");`;
|
||||
|
||||
exports[`transform react to jsx fragment with no children 1`] = `var x = React.createElement(React.Fragment, null);`;
|
||||
|
||||
exports[`transform react to jsx normal fragments not to set key and source 1`] = `
|
||||
var _jsxFileName = "";
|
||||
var x = React.createElement(React.Fragment, null, React.createElement("div", {
|
||||
__source: {
|
||||
fileName: _jsxFileName,
|
||||
lineNumber: 1
|
||||
},
|
||||
__self: this
|
||||
}));
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx should allow constructor as prop 1`] = `
|
||||
React.createElement(Component, {
|
||||
constructor: "foo"
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx should allow deeper js namespacing 1`] = `React.createElement(Namespace.DeepNamespace.Component, null);`;
|
||||
|
||||
exports[`transform react to jsx should allow elements as attributes 1`] = `
|
||||
React.createElement("div", {
|
||||
attr: React.createElement("div", null)
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx should allow js namespacing 1`] = `React.createElement(Namespace.Component, null);`;
|
||||
|
||||
exports[`transform react to jsx should allow nested fragments 1`] = `React.createElement("div", null, React.createElement(React.Fragment, null, React.createElement(React.Fragment, null, React.createElement("span", null, "Hello"), React.createElement("span", null, "world")), React.createElement(React.Fragment, null, React.createElement("span", null, "Goodbye"), React.createElement("span", null, "world"))));`;
|
||||
|
||||
exports[`transform react to jsx should avoid wrapping in extra parens if not needed 1`] = `
|
||||
var x = React.createElement("div", null, React.createElement(Component, null));
|
||||
var x = React.createElement("div", null, props.children);
|
||||
var x = React.createElement(Composite, null, props.children);
|
||||
var x = React.createElement(Composite, null, React.createElement(Composite2, null));
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx should convert simple tags 1`] = `var x = React.createElement("div", null);`;
|
||||
|
||||
exports[`transform react to jsx should convert simple text 1`] = `var x = React.createElement("div", null, "text");`;
|
||||
|
||||
exports[`transform react to jsx should escape xhtml jsxattribute 1`] = `
|
||||
React.createElement("div", {
|
||||
id: "w\\xF4w"
|
||||
});
|
||||
React.createElement("div", {
|
||||
id: "w"
|
||||
});
|
||||
React.createElement("div", {
|
||||
id: "w < w"
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx should escape xhtml jsxtext 1`] = `
|
||||
React.createElement("div", null, "wow");
|
||||
React.createElement("div", null, "w\\xF4w");
|
||||
React.createElement("div", null, "w & w");
|
||||
React.createElement("div", null, "w & w");
|
||||
React.createElement("div", null, "w \\xA0 w");
|
||||
React.createElement("div", null, "this should not parse as unicode: \\xA0");
|
||||
React.createElement("div", null, "this should parse as nbsp: \\xA0 ");
|
||||
React.createElement("div", null, "this should parse as unicode: ", ' ');
|
||||
React.createElement("div", null, "w < w");
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx should handle attributed elements 1`] = `
|
||||
var HelloMessage = React.createClass({
|
||||
render: function () {
|
||||
return React.createElement("div", null, "Hello ", this.props.name);
|
||||
}
|
||||
});
|
||||
React.render(React.createElement(HelloMessage, {
|
||||
name: React.createElement("span", null, "Sebastian")
|
||||
}), mountNode);
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx should handle has own property correctly 1`] = `React.createElement("hasOwnProperty", null, "testing");`;
|
||||
|
||||
exports[`transform react to jsx should have correct comma in nested children 1`] = `var x = React.createElement("div", null, React.createElement("div", null, React.createElement("br", null)), React.createElement(Component, null, foo, React.createElement("br", null), bar), React.createElement("br", null));`;
|
||||
|
||||
exports[`transform react to jsx should insert commas after expressions before whitespace 1`] = `
|
||||
var x = React.createElement("div", {
|
||||
attr1: "foo" + "bar",
|
||||
attr2: "foo" + "bar" + "baz" + "bug",
|
||||
attr3: "foo" + "bar" + "baz" + "bug",
|
||||
attr4: "baz"
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx should not add quotes to identifier names 1`] = `
|
||||
var e = React.createElement(F, {
|
||||
aaa: true,
|
||||
new: true,
|
||||
const: true,
|
||||
var: true,
|
||||
default: true,
|
||||
"foo-bar": true
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx should not strip nbsp even couple with other whitespace 1`] = `React.createElement("div", null, "\\xA0 ");`;
|
||||
|
||||
exports[`transform react to jsx should not strip tags with a single child of nbsp 1`] = `React.createElement("div", null, "\\xA0");`;
|
||||
|
||||
exports[`transform react to jsx should properly handle comments adjacent to children 1`] = `var x = React.createElement("div", null, React.createElement("span", null), React.createElement("br", null));`;
|
||||
|
||||
exports[`transform react to jsx should properly handle comments between props 1`] = `
|
||||
var x = React.createElement("div", {
|
||||
/* a multi-line
|
||||
comment */
|
||||
attr1: "foo"
|
||||
}, React.createElement("span", {
|
||||
// a double-slash comment
|
||||
attr2: "bar"
|
||||
}));
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx should quote jsx attributes 1`] = `
|
||||
React.createElement("button", {
|
||||
"data-value": "a value"
|
||||
}, "Button");
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx should support xml namespaces if flag 1`] = `
|
||||
React.createElement("f:image", {
|
||||
"n:attr": true
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx should transform known hyphenated tags 1`] = `React.createElement("font-face", null);`;
|
||||
|
||||
exports[`transform react to jsx useBuiltIns false uses extend instead of Object.assign 1`] = `
|
||||
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
|
||||
|
||||
React.createElement(Component, _extends({
|
||||
y: 2
|
||||
}, x));
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx wraps props in react spread for first spread attributes 1`] = `
|
||||
React.createElement(Component, Object.assign({}, x, {
|
||||
y: 2,
|
||||
z: true
|
||||
}));
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx wraps props in react spread for last spread attributes 1`] = `
|
||||
React.createElement(Component, Object.assign({
|
||||
y: 2,
|
||||
z: true
|
||||
}, x));
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx wraps props in react spread for middle spread attributes 1`] = `
|
||||
React.createElement(Component, Object.assign({
|
||||
y: 2
|
||||
}, x, {
|
||||
z: true
|
||||
}));
|
||||
`;
|
||||
@@ -0,0 +1,374 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`transform react to jsx React.fragment to set keys and source 1`] = `
|
||||
var _jsxFileName = "";
|
||||
var x = React.jsxDEV(React.Fragment, {}, "foo", false, {
|
||||
fileName: _jsxFileName,
|
||||
lineNumber: 1
|
||||
}, this);
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx adds appropriate new lines when using spread attribute 1`] = `
|
||||
React.jsx(Component, Object.assign({}, props, {
|
||||
sound: "moo"
|
||||
}));
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx arrow functions 1`] = `
|
||||
var foo = function () {
|
||||
var _this = this;
|
||||
|
||||
return function () {
|
||||
return React.jsx(_this, {});
|
||||
};
|
||||
};
|
||||
|
||||
var bar = function () {
|
||||
var _this2 = this;
|
||||
|
||||
return function () {
|
||||
return React.jsx(_this2.foo, {});
|
||||
};
|
||||
};
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx assignment 1`] = `
|
||||
var div = React.jsx(Component, Object.assign({}, props, {
|
||||
foo: "bar"
|
||||
}));
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx concatenates adjacent string literals 1`] = `
|
||||
var x = React.jsxs("div", {
|
||||
children: ["foo", "bar", "baz", React.jsx("div", {
|
||||
children: "buz bang"
|
||||
}), "qux", null, "quack"]
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx fragment with no children 1`] = `var x = React.jsx(React.Fragment, {});`;
|
||||
|
||||
exports[`transform react to jsx fragments 1`] = `
|
||||
var x = React.jsx(React.Fragment, {
|
||||
children: React.jsx("div", {})
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx fragments in dev mode (no key and source) 1`] = `
|
||||
var _jsxFileName = "";
|
||||
var x = React.jsxDEV(React.Fragment, {
|
||||
children: React.jsxDEV("div", {}, undefined, false, {
|
||||
fileName: _jsxFileName,
|
||||
lineNumber: 1
|
||||
}, this)
|
||||
}, undefined, false);
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx fragments to set keys 1`] = `var x = React.jsx(React.Fragment, {}, "foo");`;
|
||||
|
||||
exports[`transform react to jsx nonStatic children 1`] = `
|
||||
var _jsxFileName = "";
|
||||
var x = React.jsxDEV("div", {
|
||||
children: [React.jsxDEV("span", {}, '0', false, {
|
||||
fileName: _jsxFileName,
|
||||
lineNumber: 3
|
||||
}, this), React.jsxDEV("span", {}, '1', false, {
|
||||
fileName: _jsxFileName,
|
||||
lineNumber: 3
|
||||
}, this)]
|
||||
}, undefined, false, {
|
||||
fileName: _jsxFileName,
|
||||
lineNumber: 2
|
||||
}, this);
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx properly handles keys 1`] = `
|
||||
var x = React.jsxs("div", {
|
||||
children: [React.jsx("div", {}, "1"), React.jsx("div", {
|
||||
meow: "wolf"
|
||||
}, "2"), React.jsx("div", {}, "3")]
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx properly passes in source and self 1`] = `
|
||||
var _jsxFileName = "";
|
||||
var x = React.jsxDEV("div", {}, undefined, false, {
|
||||
fileName: _jsxFileName,
|
||||
lineNumber: 1
|
||||
}, this);
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx should allow constructor as prop 1`] = `
|
||||
React.jsx(Component, {
|
||||
constructor: "foo"
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx should allow deeper js namespacing 1`] = `React.jsx(Namespace.DeepNamespace.Component, {});`;
|
||||
|
||||
exports[`transform react to jsx should allow elements as attributes 1`] = `
|
||||
React.jsx("div", {
|
||||
attr: React.jsx("div", {})
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx should allow js namespacing 1`] = `React.jsx(Namespace.Component, {});`;
|
||||
|
||||
exports[`transform react to jsx should allow nested fragments 1`] = `
|
||||
React.jsx("div", {
|
||||
children: React.jsxs(React.Fragment, {
|
||||
children: [React.jsxs(React.Fragment, {
|
||||
children: [React.jsx("span", {
|
||||
children: "Hello"
|
||||
}), React.jsx("span", {
|
||||
children: "world"
|
||||
})]
|
||||
}), React.jsxs(React.Fragment, {
|
||||
children: [React.jsx("span", {
|
||||
children: "Goodbye"
|
||||
}), React.jsx("span", {
|
||||
children: "world"
|
||||
})]
|
||||
})]
|
||||
})
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx should avoid wrapping in extra parens if not needed 1`] = `
|
||||
var x = React.jsx("div", {
|
||||
children: React.jsx(Component, {})
|
||||
});
|
||||
var x = React.jsx("div", {
|
||||
children: props.children
|
||||
});
|
||||
var x = React.jsx(Composite, {
|
||||
children: props.children
|
||||
});
|
||||
var x = React.jsx(Composite, {
|
||||
children: React.jsx(Composite2, {})
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx should convert simple tags 1`] = `var x = React.jsx("div", {});`;
|
||||
|
||||
exports[`transform react to jsx should convert simple text 1`] = `
|
||||
var x = React.jsx("div", {
|
||||
children: "text"
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx should escape xhtml jsxattribute 1`] = `
|
||||
React.jsx("div", {
|
||||
id: "w\\xF4w"
|
||||
});
|
||||
React.jsx("div", {
|
||||
id: "w"
|
||||
});
|
||||
React.jsx("div", {
|
||||
id: "w < w"
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx should escape xhtml jsxtext 1`] = `
|
||||
React.jsx("div", {
|
||||
children: "wow"
|
||||
});
|
||||
React.jsx("div", {
|
||||
children: "w\\xF4w"
|
||||
});
|
||||
React.jsx("div", {
|
||||
children: "w & w"
|
||||
});
|
||||
React.jsx("div", {
|
||||
children: "w & w"
|
||||
});
|
||||
React.jsx("div", {
|
||||
children: "w \\xA0 w"
|
||||
});
|
||||
React.jsx("div", {
|
||||
children: "this should not parse as unicode: \\xA0"
|
||||
});
|
||||
React.jsx("div", {
|
||||
children: "this should parse as nbsp: \\xA0 "
|
||||
});
|
||||
React.jsxs("div", {
|
||||
children: ["this should parse as unicode: ", ' ']
|
||||
});
|
||||
React.jsx("div", {
|
||||
children: "w < w"
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx should handle attributed elements 1`] = `
|
||||
var HelloMessage = React.createClass({
|
||||
render: function () {
|
||||
return React.jsxs("div", {
|
||||
children: ["Hello ", this.props.name]
|
||||
});
|
||||
}
|
||||
});
|
||||
React.render(React.jsx(HelloMessage, {
|
||||
name: React.jsx("span", {
|
||||
children: "Sebastian"
|
||||
})
|
||||
}), mountNode);
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx should handle has own property correctly 1`] = `
|
||||
React.jsx("hasOwnProperty", {
|
||||
children: "testing"
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx should have correct comma in nested children 1`] = `
|
||||
var x = React.jsxs("div", {
|
||||
children: [React.jsx("div", {
|
||||
children: React.jsx("br", {})
|
||||
}), React.jsxs(Component, {
|
||||
children: [foo, React.jsx("br", {}), bar]
|
||||
}), React.jsx("br", {})]
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx should insert commas after expressions before whitespace 1`] = `
|
||||
var x = React.jsx("div", {
|
||||
attr1: "foo" + "bar",
|
||||
attr2: "foo" + "bar" + "baz" + "bug",
|
||||
attr3: "foo" + "bar" + "baz" + "bug",
|
||||
attr4: "baz"
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx should not add quotes to identifier names 1`] = `
|
||||
var e = React.jsx(F, {
|
||||
aaa: true,
|
||||
new: true,
|
||||
const: true,
|
||||
var: true,
|
||||
default: true,
|
||||
"foo-bar": true
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx should not strip nbsp even couple with other whitespace 1`] = `
|
||||
React.jsx("div", {
|
||||
children: "\\xA0 "
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx should not strip tags with a single child of nbsp 1`] = `
|
||||
React.jsx("div", {
|
||||
children: "\\xA0"
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx should properly handle comments adjacent to children 1`] = `
|
||||
var x = React.jsxs("div", {
|
||||
children: [React.jsx("span", {}), React.jsx("br", {})]
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx should properly handle comments between props 1`] = `
|
||||
var x = React.jsx("div", {
|
||||
/* a multi-line
|
||||
comment */
|
||||
attr1: "foo",
|
||||
children: React.jsx("span", {
|
||||
// a double-slash comment
|
||||
attr2: "bar"
|
||||
})
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx should properly handle potentially null variables 1`] = `
|
||||
var foo = null;
|
||||
var x = React.jsx("div", Object.assign({}, foo));
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx should quote jsx attributes 1`] = `
|
||||
React.jsx("button", {
|
||||
"data-value": "a value",
|
||||
children: "Button"
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx should support xml namespaces if flag 1`] = `
|
||||
React.jsx("f:image", {
|
||||
"n:attr": true
|
||||
});
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx should transform known hyphenated tags 1`] = `React.jsx("font-face", {});`;
|
||||
|
||||
exports[`transform react to jsx static children 1`] = `
|
||||
var _jsxFileName = "";
|
||||
var x = React.jsxDEV("div", {
|
||||
children: [React.jsxDEV("span", {}, undefined, false, {
|
||||
fileName: _jsxFileName,
|
||||
lineNumber: 3
|
||||
}, this), [React.jsxDEV("span", {}, '0', false, {
|
||||
fileName: _jsxFileName,
|
||||
lineNumber: 4
|
||||
}, this), React.jsxDEV("span", {}, '1', false, {
|
||||
fileName: _jsxFileName,
|
||||
lineNumber: 4
|
||||
}, this)]]
|
||||
}, undefined, true, {
|
||||
fileName: _jsxFileName,
|
||||
lineNumber: 2
|
||||
}, this);
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx useBuiltIns false uses extend instead of Object.assign 1`] = `
|
||||
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
|
||||
|
||||
React.jsx(Component, _extends({
|
||||
y: 2
|
||||
}, x));
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx uses createElement when the key comes after a spread 1`] = `
|
||||
var x = React.createElement("div", Object.assign({}, props, {
|
||||
key: "1",
|
||||
foo: "bar"
|
||||
}));
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx uses jsx when the key comes before a spread 1`] = `
|
||||
var x = React.jsx("div", Object.assign({}, props, {
|
||||
foo: "bar"
|
||||
}), "1");
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx uses jsxDEV instead of jsx in dev mode 1`] = `
|
||||
var _jsxFileName = "";
|
||||
var x = React.jsxDEV("span", {
|
||||
propOne: "one",
|
||||
children: "Hi"
|
||||
}, undefined, false, {
|
||||
fileName: _jsxFileName,
|
||||
lineNumber: 1
|
||||
}, this);
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx wraps props in react spread for first spread attributes 1`] = `
|
||||
React.jsx(Component, Object.assign({}, x, {
|
||||
y: 2,
|
||||
z: true
|
||||
}));
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx wraps props in react spread for last spread attributes 1`] = `
|
||||
React.jsx(Component, Object.assign({
|
||||
y: 2,
|
||||
z: true
|
||||
}, x));
|
||||
`;
|
||||
|
||||
exports[`transform react to jsx wraps props in react spread for middle spread attributes 1`] = `
|
||||
React.jsx(Component, Object.assign({
|
||||
y: 2
|
||||
}, x, {
|
||||
z: true
|
||||
}));
|
||||
`;
|
||||
3
packages/babel-plugin-react-jsx/index.js
Normal file
3
packages/babel-plugin-react-jsx/index.js
Normal file
@@ -0,0 +1,3 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = require('./src/TransformJSXToReactBabelPlugin');
|
||||
7
packages/babel-plugin-react-jsx/npm/index.js
Normal file
7
packages/babel-plugin-react-jsx/npm/index.js
Normal file
@@ -0,0 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports = require('./cjs/react-jsx-babel.production.min.js');
|
||||
} else {
|
||||
module.exports = require('./cjs/react-jsx-babel.development.js');
|
||||
}
|
||||
18
packages/babel-plugin-react-jsx/package.json
Normal file
18
packages/babel-plugin-react-jsx/package.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "babel-plugin-react-jsx",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"description": "@babel/plugin-transform-react-jsx",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"esutils": "^2.0.0"
|
||||
|
||||
},
|
||||
"files": [
|
||||
"README.md",
|
||||
"index.js",
|
||||
"build-info.json",
|
||||
"cjs/",
|
||||
"umd/"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,611 @@
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2014-present Sebastian McKenzie and other contributors
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
// © 2019 GitHub, Inc.
|
||||
'use strict';
|
||||
|
||||
const esutils = require('esutils');
|
||||
|
||||
function helper(babel, opts) {
|
||||
const {types: t} = babel;
|
||||
|
||||
const visitor = {};
|
||||
|
||||
visitor.JSXNamespacedName = function(path, state) {
|
||||
const throwIfNamespace =
|
||||
state.opts.throwIfNamespace === undefined
|
||||
? true
|
||||
: !!state.opts.throwIfNamespace;
|
||||
if (throwIfNamespace) {
|
||||
throw path.buildCodeFrameError(
|
||||
`Namespace tags are not supported by default. React's JSX doesn't support namespace tags. \
|
||||
You can turn on the 'throwIfNamespace' flag to bypass this warning.`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
visitor.JSXSpreadChild = function(path) {
|
||||
throw path.buildCodeFrameError(
|
||||
'Spread children are not supported in React.',
|
||||
);
|
||||
};
|
||||
|
||||
visitor.JSXElement = {
|
||||
exit(path, file) {
|
||||
let callExpr;
|
||||
if (file.opts.useCreateElement || shouldUseCreateElement(path)) {
|
||||
callExpr = buildCreateElementCall(path, file);
|
||||
} else {
|
||||
callExpr = buildJSXElementCall(path, file);
|
||||
}
|
||||
|
||||
if (callExpr) {
|
||||
path.replaceWith(t.inherits(callExpr, path.node));
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
visitor.JSXFragment = {
|
||||
exit(path, file) {
|
||||
if (opts.compat) {
|
||||
throw path.buildCodeFrameError(
|
||||
'Fragment tags are only supported in React 16 and up.',
|
||||
);
|
||||
}
|
||||
let callExpr;
|
||||
if (file.opts.useCreateElement) {
|
||||
callExpr = buildCreateElementFragmentCall(path, file);
|
||||
} else {
|
||||
callExpr = buildJSXFragmentCall(path, file);
|
||||
}
|
||||
|
||||
if (callExpr) {
|
||||
path.replaceWith(t.inherits(callExpr, path.node));
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
return visitor;
|
||||
|
||||
function convertJSXIdentifier(node, parent) {
|
||||
if (t.isJSXIdentifier(node)) {
|
||||
if (node.name === 'this' && t.isReferenced(node, parent)) {
|
||||
return t.thisExpression();
|
||||
} else if (esutils.keyword.isIdentifierNameES6(node.name)) {
|
||||
node.type = 'Identifier';
|
||||
} else {
|
||||
return t.stringLiteral(node.name);
|
||||
}
|
||||
} else if (t.isJSXMemberExpression(node)) {
|
||||
return t.memberExpression(
|
||||
convertJSXIdentifier(node.object, node),
|
||||
convertJSXIdentifier(node.property, node),
|
||||
);
|
||||
} else if (t.isJSXNamespacedName(node)) {
|
||||
/**
|
||||
* If there is flag "throwIfNamespace"
|
||||
* print XMLNamespace like string literal
|
||||
*/
|
||||
return t.stringLiteral(`${node.namespace.name}:${node.name.name}`);
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
function convertAttributeValue(node) {
|
||||
if (t.isJSXExpressionContainer(node)) {
|
||||
return node.expression;
|
||||
} else {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
function convertAttribute(node) {
|
||||
const value = convertAttributeValue(node.value || t.booleanLiteral(true));
|
||||
|
||||
if (t.isStringLiteral(value) && !t.isJSXExpressionContainer(node.value)) {
|
||||
value.value = value.value.replace(/\n\s+/g, ' ');
|
||||
|
||||
// "raw" JSXText should not be used from a StringLiteral because it needs to be escaped.
|
||||
if (value.extra && value.extra.raw) {
|
||||
delete value.extra.raw;
|
||||
}
|
||||
}
|
||||
|
||||
if (t.isJSXNamespacedName(node.name)) {
|
||||
node.name = t.stringLiteral(
|
||||
node.name.namespace.name + ':' + node.name.name.name,
|
||||
);
|
||||
} else if (esutils.keyword.isIdentifierNameES6(node.name.name)) {
|
||||
node.name.type = 'Identifier';
|
||||
} else {
|
||||
node.name = t.stringLiteral(node.name.name);
|
||||
}
|
||||
|
||||
return t.inherits(t.objectProperty(node.name, value), node);
|
||||
}
|
||||
|
||||
// We want to use React.createElement, even in the case of
|
||||
// jsx, for <div {...props} key={key} /> to distinguish it
|
||||
// from <div key={key} {...props} />. This is an intermediary
|
||||
// step while we deprecate key spread from props. Afterwards,
|
||||
// we will remove createElement entirely
|
||||
function shouldUseCreateElement(path) {
|
||||
const openingPath = path.get('openingElement');
|
||||
const attributes = openingPath.node.attributes;
|
||||
|
||||
let seenPropsSpread = false;
|
||||
for (let i = 0; i < attributes.length; i++) {
|
||||
const attr = attributes[i];
|
||||
if (
|
||||
seenPropsSpread &&
|
||||
t.isJSXAttribute(attr) &&
|
||||
attr.name.name === 'key'
|
||||
) {
|
||||
return true;
|
||||
} else if (t.isJSXSpreadAttribute(attr)) {
|
||||
seenPropsSpread = true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Builds JSX into:
|
||||
// Production: React.jsx(type, arguments, key)
|
||||
// Development: React.jsxDEV(type, arguments, key, isStaticChildren, source, self)
|
||||
function buildJSXElementCall(path, file) {
|
||||
if (opts.filter && !opts.filter(path.node, file)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const openingPath = path.get('openingElement');
|
||||
openingPath.parent.children = t.react.buildChildren(openingPath.parent);
|
||||
|
||||
const tagExpr = convertJSXIdentifier(
|
||||
openingPath.node.name,
|
||||
openingPath.node,
|
||||
);
|
||||
const args = [];
|
||||
|
||||
let tagName;
|
||||
if (t.isIdentifier(tagExpr)) {
|
||||
tagName = tagExpr.name;
|
||||
} else if (t.isLiteral(tagExpr)) {
|
||||
tagName = tagExpr.value;
|
||||
}
|
||||
|
||||
const state = {
|
||||
tagExpr: tagExpr,
|
||||
tagName: tagName,
|
||||
args: args,
|
||||
};
|
||||
|
||||
if (opts.pre) {
|
||||
opts.pre(state, file);
|
||||
}
|
||||
|
||||
let attribs = [];
|
||||
let key;
|
||||
let source;
|
||||
let self;
|
||||
|
||||
// for React.jsx, key, __source (dev), and __self (dev) is passed in as
|
||||
// a separate argument rather than in the args object. We go through the
|
||||
// props and filter out these three keywords so we can pass them in
|
||||
// as separate arguments later
|
||||
for (let i = 0; i < openingPath.node.attributes.length; i++) {
|
||||
const attr = openingPath.node.attributes[i];
|
||||
if (t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name)) {
|
||||
if (attr.name.name === 'key') {
|
||||
key = convertAttribute(attr).value;
|
||||
} else if (attr.name.name === '__source') {
|
||||
source = convertAttribute(attr).value;
|
||||
} else if (attr.name.name === '__self') {
|
||||
self = convertAttribute(attr).value;
|
||||
} else {
|
||||
attribs.push(attr);
|
||||
}
|
||||
} else {
|
||||
attribs.push(attr);
|
||||
}
|
||||
}
|
||||
|
||||
if (attribs.length || path.node.children.length) {
|
||||
attribs = buildJSXOpeningElementAttributes(
|
||||
attribs,
|
||||
file,
|
||||
path.node.children,
|
||||
);
|
||||
} else {
|
||||
// attributes should never be null
|
||||
attribs = t.objectExpression([]);
|
||||
}
|
||||
|
||||
args.push(attribs);
|
||||
|
||||
if (!file.opts.development) {
|
||||
if (key !== undefined) {
|
||||
args.push(key);
|
||||
}
|
||||
} else {
|
||||
// isStaticChildren, __source, and __self are only used in development
|
||||
args.push(
|
||||
key === undefined ? t.identifier('undefined') : key,
|
||||
t.booleanLiteral(path.node.children.length > 1),
|
||||
source === undefined ? t.identifier('undefined') : source,
|
||||
self === undefined ? t.identifier('undefined') : self,
|
||||
);
|
||||
}
|
||||
|
||||
if (opts.post) {
|
||||
opts.post(state, file);
|
||||
}
|
||||
return (
|
||||
state.call ||
|
||||
t.callExpression(
|
||||
path.node.children.length > 1 ? state.staticCallee : state.callee,
|
||||
args,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Builds props for React.jsx. This function adds children into the props
|
||||
// and ensures that props is always an object
|
||||
function buildJSXOpeningElementAttributes(attribs, file, children) {
|
||||
let _props = [];
|
||||
const objs = [];
|
||||
|
||||
const useBuiltIns = file.opts.useBuiltIns || false;
|
||||
if (typeof useBuiltIns !== 'boolean') {
|
||||
throw new Error(
|
||||
'transform-react-jsx currently only accepts a boolean option for ' +
|
||||
'useBuiltIns (defaults to false)',
|
||||
);
|
||||
}
|
||||
|
||||
while (attribs.length) {
|
||||
const prop = attribs.shift();
|
||||
if (t.isJSXSpreadAttribute(prop)) {
|
||||
_props = pushProps(_props, objs);
|
||||
objs.push(prop.argument);
|
||||
} else {
|
||||
_props.push(convertAttribute(prop));
|
||||
}
|
||||
}
|
||||
|
||||
// In React.JSX, children is no longer a separate argument, but passed in
|
||||
// through the argument object
|
||||
if (children && children.length > 0) {
|
||||
if (children.length === 1) {
|
||||
_props.push(t.objectProperty(t.identifier('children'), children[0]));
|
||||
} else {
|
||||
_props.push(
|
||||
t.objectProperty(
|
||||
t.identifier('children'),
|
||||
t.arrayExpression(children),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pushProps(_props, objs);
|
||||
|
||||
if (objs.length === 1) {
|
||||
// only one object
|
||||
if (!t.isObjectExpression(objs[0])) {
|
||||
// if the prop object isn't an object, use Object.assign or _extends
|
||||
// to ensure that the prop will always be an object (as opposed to a variable
|
||||
// that could be null at some point)
|
||||
const expressionHelper = useBuiltIns
|
||||
? t.memberExpression(t.identifier('Object'), t.identifier('assign'))
|
||||
: file.addHelper('extends');
|
||||
|
||||
attribs = t.callExpression(expressionHelper, [
|
||||
t.objectExpression([]),
|
||||
objs[0],
|
||||
]);
|
||||
} else {
|
||||
attribs = objs[0];
|
||||
}
|
||||
} else {
|
||||
// looks like we have multiple objects
|
||||
if (!t.isObjectExpression(objs[0])) {
|
||||
objs.unshift(t.objectExpression([]));
|
||||
}
|
||||
|
||||
const expressionHelper = useBuiltIns
|
||||
? t.memberExpression(t.identifier('Object'), t.identifier('assign'))
|
||||
: file.addHelper('extends');
|
||||
|
||||
// spread it
|
||||
attribs = t.callExpression(expressionHelper, objs);
|
||||
}
|
||||
|
||||
return attribs;
|
||||
}
|
||||
|
||||
// Builds JSX Fragment <></> into
|
||||
// Production: React.jsx(type, arguments)
|
||||
// Development: React.jsxDEV(type, { children})
|
||||
function buildJSXFragmentCall(path, file) {
|
||||
if (opts.filter && !opts.filter(path.node, file)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const openingPath = path.get('openingElement');
|
||||
openingPath.parent.children = t.react.buildChildren(openingPath.parent);
|
||||
|
||||
const args = [];
|
||||
const tagName = null;
|
||||
const tagExpr = file.get('jsxFragIdentifier')();
|
||||
|
||||
const state = {
|
||||
tagExpr: tagExpr,
|
||||
tagName: tagName,
|
||||
args: args,
|
||||
};
|
||||
|
||||
if (opts.pre) {
|
||||
opts.pre(state, file);
|
||||
}
|
||||
|
||||
let childrenNode;
|
||||
if (path.node.children.length > 0) {
|
||||
if (path.node.children.length === 1) {
|
||||
childrenNode = path.node.children[0];
|
||||
} else {
|
||||
childrenNode = t.arrayExpression(path.node.children);
|
||||
}
|
||||
}
|
||||
|
||||
args.push(
|
||||
t.objectExpression(
|
||||
childrenNode !== undefined
|
||||
? [t.objectProperty(t.identifier('children'), childrenNode)]
|
||||
: [],
|
||||
),
|
||||
);
|
||||
|
||||
if (file.opts.development) {
|
||||
args.push(
|
||||
t.identifier('undefined'),
|
||||
t.booleanLiteral(path.node.children.length > 1),
|
||||
);
|
||||
}
|
||||
|
||||
if (opts.post) {
|
||||
opts.post(state, file);
|
||||
}
|
||||
|
||||
return (
|
||||
state.call ||
|
||||
t.callExpression(
|
||||
path.node.children.length > 1 ? state.staticCallee : state.callee,
|
||||
args,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Builds JSX into:
|
||||
// Production: React.createElement(type, arguments, children)
|
||||
// Development: React.createElement(type, arguments, children, source, self)
|
||||
function buildCreateElementCall(path, file) {
|
||||
if (opts.filter && !opts.filter(path.node, file)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const openingPath = path.get('openingElement');
|
||||
openingPath.parent.children = t.react.buildChildren(openingPath.parent);
|
||||
|
||||
const tagExpr = convertJSXIdentifier(
|
||||
openingPath.node.name,
|
||||
openingPath.node,
|
||||
);
|
||||
const args = [];
|
||||
|
||||
let tagName;
|
||||
if (t.isIdentifier(tagExpr)) {
|
||||
tagName = tagExpr.name;
|
||||
} else if (t.isLiteral(tagExpr)) {
|
||||
tagName = tagExpr.value;
|
||||
}
|
||||
|
||||
const state = {
|
||||
tagExpr: tagExpr,
|
||||
tagName: tagName,
|
||||
args: args,
|
||||
};
|
||||
|
||||
if (opts.pre) {
|
||||
opts.pre(state, file);
|
||||
}
|
||||
|
||||
let attribs = openingPath.node.attributes;
|
||||
if (attribs.length) {
|
||||
attribs = buildCreateElementOpeningElementAttributes(attribs, file);
|
||||
} else {
|
||||
attribs = t.nullLiteral();
|
||||
}
|
||||
|
||||
args.push(attribs, ...path.node.children);
|
||||
|
||||
if (opts.post) {
|
||||
opts.post(state, file);
|
||||
}
|
||||
|
||||
return state.call || t.callExpression(state.oldCallee, args);
|
||||
}
|
||||
|
||||
function pushProps(_props, objs) {
|
||||
if (!_props.length) {
|
||||
return _props;
|
||||
}
|
||||
|
||||
objs.push(t.objectExpression(_props));
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* The logic for this is quite terse. It's because we need to
|
||||
* support spread elements. We loop over all attributes,
|
||||
* breaking on spreads, we then push a new object containing
|
||||
* all prior attributes to an array for later processing.
|
||||
*/
|
||||
function buildCreateElementOpeningElementAttributes(attribs, file) {
|
||||
let _props = [];
|
||||
const objs = [];
|
||||
|
||||
const useBuiltIns = file.opts.useBuiltIns || false;
|
||||
if (typeof useBuiltIns !== 'boolean') {
|
||||
throw new Error(
|
||||
'transform-react-jsx currently only accepts a boolean option for ' +
|
||||
'useBuiltIns (defaults to false)',
|
||||
);
|
||||
}
|
||||
|
||||
while (attribs.length) {
|
||||
const prop = attribs.shift();
|
||||
if (t.isJSXSpreadAttribute(prop)) {
|
||||
_props = pushProps(_props, objs);
|
||||
objs.push(prop.argument);
|
||||
} else {
|
||||
const attr = convertAttribute(prop);
|
||||
_props.push(attr);
|
||||
}
|
||||
}
|
||||
|
||||
pushProps(_props, objs);
|
||||
|
||||
if (objs.length === 1) {
|
||||
// only one object
|
||||
attribs = objs[0];
|
||||
} else {
|
||||
// looks like we have multiple objects
|
||||
if (!t.isObjectExpression(objs[0])) {
|
||||
objs.unshift(t.objectExpression([]));
|
||||
}
|
||||
|
||||
const expressionHelper = useBuiltIns
|
||||
? t.memberExpression(t.identifier('Object'), t.identifier('assign'))
|
||||
: file.addHelper('extends');
|
||||
|
||||
// spread it
|
||||
attribs = t.callExpression(expressionHelper, objs);
|
||||
}
|
||||
|
||||
return attribs;
|
||||
}
|
||||
|
||||
function buildCreateElementFragmentCall(path, file) {
|
||||
if (opts.filter && !opts.filter(path.node, file)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const openingPath = path.get('openingElement');
|
||||
openingPath.parent.children = t.react.buildChildren(openingPath.parent);
|
||||
|
||||
const args = [];
|
||||
const tagName = null;
|
||||
const tagExpr = file.get('jsxFragIdentifier')();
|
||||
|
||||
const state = {
|
||||
tagExpr: tagExpr,
|
||||
tagName: tagName,
|
||||
args: args,
|
||||
};
|
||||
|
||||
if (opts.pre) {
|
||||
opts.pre(state, file);
|
||||
}
|
||||
|
||||
// no attributes are allowed with <> syntax
|
||||
args.push(t.nullLiteral(), ...path.node.children);
|
||||
|
||||
if (opts.post) {
|
||||
opts.post(state, file);
|
||||
}
|
||||
|
||||
return state.call || t.callExpression(state.oldCallee, args);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = function(babel) {
|
||||
const {types: t} = babel;
|
||||
|
||||
const createIdentifierParser = id => () => {
|
||||
return id
|
||||
.split('.')
|
||||
.map(name => t.identifier(name))
|
||||
.reduce((object, property) => t.memberExpression(object, property));
|
||||
};
|
||||
|
||||
const visitor = helper(babel, {
|
||||
pre(state) {
|
||||
const tagName = state.tagName;
|
||||
const args = state.args;
|
||||
if (t.react.isCompatTag(tagName)) {
|
||||
args.push(t.stringLiteral(tagName));
|
||||
} else {
|
||||
args.push(state.tagExpr);
|
||||
}
|
||||
},
|
||||
|
||||
post(state, pass) {
|
||||
state.callee = pass.get('jsxIdentifier')();
|
||||
state.staticCallee = pass.get('jsxStaticIdentifier')();
|
||||
state.oldCallee = pass.get('oldJSXIdentifier')();
|
||||
},
|
||||
});
|
||||
|
||||
visitor.Program = {
|
||||
enter(path, state) {
|
||||
state.set(
|
||||
'oldJSXIdentifier',
|
||||
createIdentifierParser('React.createElement'),
|
||||
);
|
||||
state.set(
|
||||
'jsxIdentifier',
|
||||
createIdentifierParser(
|
||||
state.opts.development ? 'React.jsxDEV' : 'React.jsx',
|
||||
),
|
||||
);
|
||||
state.set(
|
||||
'jsxStaticIdentifier',
|
||||
createIdentifierParser(
|
||||
state.opts.development ? 'React.jsxDEV' : 'React.jsxs',
|
||||
),
|
||||
);
|
||||
state.set('jsxFragIdentifier', createIdentifierParser('React.Fragment'));
|
||||
},
|
||||
};
|
||||
|
||||
visitor.JSXAttribute = function(path) {
|
||||
if (t.isJSXElement(path.node.value)) {
|
||||
path.node.value = t.jsxExpressionContainer(path.node.value);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
name: 'transform-react-jsx',
|
||||
visitor,
|
||||
};
|
||||
};
|
||||
@@ -19,8 +19,17 @@ ESLintTester.setDefaultConfig({
|
||||
},
|
||||
});
|
||||
|
||||
const eslintTester = new ESLintTester();
|
||||
eslintTester.run('react-hooks', ReactHooksESLintRule, {
|
||||
// ***************************************************
|
||||
// For easier local testing, you can add to any case:
|
||||
// {
|
||||
// skip: true,
|
||||
// --or--
|
||||
// only: true,
|
||||
// ...
|
||||
// }
|
||||
// ***************************************************
|
||||
|
||||
const tests = {
|
||||
valid: [
|
||||
`
|
||||
// Valid because components can use hooks.
|
||||
@@ -223,21 +232,20 @@ eslintTester.run('react-hooks', ReactHooksESLintRule, {
|
||||
(class {i() { useState(); }});
|
||||
`,
|
||||
`
|
||||
// Currently valid although we *could* consider these invalid.
|
||||
// It doesn't make a lot of difference because it would crash early.
|
||||
// Valid because they're not matching use[A-Z].
|
||||
fooState();
|
||||
use();
|
||||
_use();
|
||||
useState();
|
||||
_useState();
|
||||
use42();
|
||||
useHook();
|
||||
use_hook();
|
||||
React.useState();
|
||||
`,
|
||||
`
|
||||
// Regression test for the popular "history" library
|
||||
const {createHistory, useBasename} = require('history-2.1.2');
|
||||
const browserHistory = useBasename(createHistory)({
|
||||
// This is grey area.
|
||||
// Currently it's valid (although React.useCallback would fail here).
|
||||
// We could also get stricter and disallow it, just like we did
|
||||
// with non-namespace use*() top-level calls.
|
||||
const History = require('history-2.1.2');
|
||||
const browserHistory = History.useBasename(History.createHistory)({
|
||||
basename: '/',
|
||||
});
|
||||
`,
|
||||
@@ -669,8 +677,63 @@ eslintTester.run('react-hooks', ReactHooksESLintRule, {
|
||||
conditionalError('useState'),
|
||||
],
|
||||
},
|
||||
{
|
||||
code: `
|
||||
// Invalid because it's dangerous and might not warn otherwise.
|
||||
// This *must* be invalid.
|
||||
function useHook({ bar }) {
|
||||
let foo1 = bar && useState();
|
||||
let foo2 = bar || useState();
|
||||
let foo3 = bar ?? useState();
|
||||
}
|
||||
`,
|
||||
errors: [
|
||||
conditionalError('useState'),
|
||||
conditionalError('useState'),
|
||||
// TODO: ideally this *should* warn, but ESLint
|
||||
// doesn't plan full support for ?? until it advances.
|
||||
// conditionalError('useState'),
|
||||
],
|
||||
},
|
||||
{
|
||||
code: `
|
||||
// Invalid because it's dangerous.
|
||||
// Normally, this would crash, but not if you use inline requires.
|
||||
// This *must* be invalid.
|
||||
// It's expected to have some false positives, but arguably
|
||||
// they are confusing anyway due to the use*() convention
|
||||
// already being associated with Hooks.
|
||||
useState();
|
||||
if (foo) {
|
||||
const foo = React.useCallback(() => {});
|
||||
}
|
||||
useCustomHook();
|
||||
`,
|
||||
errors: [
|
||||
topLevelError('useState'),
|
||||
topLevelError('React.useCallback'),
|
||||
topLevelError('useCustomHook'),
|
||||
],
|
||||
},
|
||||
{
|
||||
code: `
|
||||
// Technically this is a false positive.
|
||||
// We *could* make it valid (and it used to be).
|
||||
//
|
||||
// However, top-level Hook-like calls can be very dangerous
|
||||
// in environments with inline requires because they can mask
|
||||
// the runtime error by accident.
|
||||
// So we prefer to disallow it despite the false positive.
|
||||
|
||||
const {createHistory, useBasename} = require('history-2.1.2');
|
||||
const browserHistory = useBasename(createHistory)({
|
||||
basename: '/',
|
||||
});
|
||||
`,
|
||||
errors: [topLevelError('useBasename')],
|
||||
},
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
function conditionalError(hook, hasPreviousFinalizer = false) {
|
||||
return {
|
||||
@@ -708,3 +771,42 @@ function genericError(hook) {
|
||||
'Hook function.',
|
||||
};
|
||||
}
|
||||
|
||||
function topLevelError(hook) {
|
||||
return {
|
||||
message:
|
||||
`React Hook "${hook}" cannot be called at the top level. React Hooks ` +
|
||||
'must be called in a React function component or a custom React ' +
|
||||
'Hook function.',
|
||||
};
|
||||
}
|
||||
|
||||
// For easier local testing
|
||||
if (!process.env.CI) {
|
||||
let only = [];
|
||||
let skipped = [];
|
||||
[...tests.valid, ...tests.invalid].forEach(t => {
|
||||
if (t.skip) {
|
||||
delete t.skip;
|
||||
skipped.push(t);
|
||||
}
|
||||
if (t.only) {
|
||||
delete t.only;
|
||||
only.push(t);
|
||||
}
|
||||
});
|
||||
const predicate = t => {
|
||||
if (only.length > 0) {
|
||||
return only.indexOf(t) !== -1;
|
||||
}
|
||||
if (skipped.length > 0) {
|
||||
return skipped.indexOf(t) === -1;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
tests.valid = tests.valid.filter(predicate);
|
||||
tests.invalid = tests.invalid.filter(predicate);
|
||||
}
|
||||
|
||||
const eslintTester = new ESLintTester();
|
||||
eslintTester.run('react-hooks', ReactHooksESLintRule, tests);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "eslint-plugin-react-hooks",
|
||||
"description": "ESLint rules for React Hooks",
|
||||
"version": "1.7.0",
|
||||
"version": "2.0.1",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/facebook/react.git",
|
||||
|
||||
@@ -432,9 +432,12 @@ export default {
|
||||
'React Hook function.';
|
||||
context.report({node: hook, message});
|
||||
} else if (codePathNode.type === 'Program') {
|
||||
// For now, ignore if it's in top level scope.
|
||||
// We could warn here but there are false positives related
|
||||
// configuring libraries like `history`.
|
||||
// These are dangerous if you have inline requires enabled.
|
||||
const message =
|
||||
`React Hook "${context.getSource(hook)}" cannot be called ` +
|
||||
'at the top level. React Hooks must be called in a ' +
|
||||
'React function component or a custom React Hook function.';
|
||||
context.report({node: hook, message});
|
||||
} else {
|
||||
// Assume in all other cases the user called a hook in some
|
||||
// random function callback. This should usually be true for
|
||||
|
||||
@@ -22,6 +22,7 @@ import type {ReactSyntheticEvent} from './ReactSyntheticEventType';
|
||||
import type {Fiber} from 'react-reconciler/src/ReactFiber';
|
||||
import type {AnyNativeEvent} from './PluginModuleType';
|
||||
import type {TopLevelType} from './TopLevelEventTypes';
|
||||
import type {EventSystemFlags} from 'legacy-events/EventSystemFlags';
|
||||
|
||||
function isInteractive(tag) {
|
||||
return (
|
||||
@@ -131,6 +132,7 @@ export function getListener(inst: Fiber, registrationName: string) {
|
||||
*/
|
||||
function extractPluginEvents(
|
||||
topLevelType: TopLevelType,
|
||||
eventSystemFlags: EventSystemFlags,
|
||||
targetInst: null | Fiber,
|
||||
nativeEvent: AnyNativeEvent,
|
||||
nativeEventTarget: EventTarget,
|
||||
@@ -142,6 +144,7 @@ function extractPluginEvents(
|
||||
if (possiblePlugin) {
|
||||
const extractedEvents = possiblePlugin.extractEvents(
|
||||
topLevelType,
|
||||
eventSystemFlags,
|
||||
targetInst,
|
||||
nativeEvent,
|
||||
nativeEventTarget,
|
||||
@@ -156,12 +159,14 @@ function extractPluginEvents(
|
||||
|
||||
export function runExtractedPluginEventsInBatch(
|
||||
topLevelType: TopLevelType,
|
||||
eventSystemFlags: EventSystemFlags,
|
||||
targetInst: null | Fiber,
|
||||
nativeEvent: AnyNativeEvent,
|
||||
nativeEventTarget: EventTarget,
|
||||
) {
|
||||
const events = extractPluginEvents(
|
||||
topLevelType,
|
||||
eventSystemFlags,
|
||||
targetInst,
|
||||
nativeEvent,
|
||||
nativeEventTarget,
|
||||
|
||||
@@ -14,3 +14,4 @@ export const RESPONDER_EVENT_SYSTEM = 1 << 1;
|
||||
export const IS_PASSIVE = 1 << 2;
|
||||
export const IS_ACTIVE = 1 << 3;
|
||||
export const PASSIVE_NOT_SUPPORTED = 1 << 4;
|
||||
export const IS_REPLAYED = 1 << 5;
|
||||
|
||||
@@ -13,6 +13,7 @@ import type {
|
||||
ReactSyntheticEvent,
|
||||
} from './ReactSyntheticEventType';
|
||||
import type {TopLevelType} from './TopLevelEventTypes';
|
||||
import type {EventSystemFlags} from 'legacy-events/EventSystemFlags';
|
||||
|
||||
export type EventTypes = {[key: string]: DispatchConfig};
|
||||
|
||||
@@ -24,6 +25,7 @@ export type PluginModule<NativeEvent> = {
|
||||
eventTypes: EventTypes,
|
||||
extractEvents: (
|
||||
topLevelType: TopLevelType,
|
||||
eventSystemFlags: EventSystemFlags,
|
||||
targetInst: null | Fiber,
|
||||
nativeTarget: NativeEvent,
|
||||
nativeEventTarget: EventTarget,
|
||||
|
||||
@@ -9,7 +9,9 @@ import {
|
||||
needsStateRestore,
|
||||
restoreStateIfNeeded,
|
||||
} from './ReactControlledComponent';
|
||||
|
||||
import {enableFlareAPI} from 'shared/ReactFeatureFlags';
|
||||
import {invokeGuardedCallbackAndCatchFirstError} from 'shared/ReactErrorUtils';
|
||||
|
||||
// Used as a way to call batchedUpdates when we don't have a reference to
|
||||
// the renderer. Such as when we're dispatching events or if third party
|
||||
@@ -28,6 +30,7 @@ let flushDiscreteUpdatesImpl = function() {};
|
||||
let batchedEventUpdatesImpl = batchedUpdatesImpl;
|
||||
|
||||
let isInsideEventHandler = false;
|
||||
let isBatchingEventUpdates = false;
|
||||
|
||||
function finishEventHandler() {
|
||||
// Here we wait until all updates have propagated, which is important
|
||||
@@ -60,20 +63,32 @@ export function batchedUpdates(fn, bookkeeping) {
|
||||
}
|
||||
|
||||
export function batchedEventUpdates(fn, a, b) {
|
||||
if (isInsideEventHandler) {
|
||||
if (isBatchingEventUpdates) {
|
||||
// If we are currently inside another batch, we need to wait until it
|
||||
// fully completes before restoring state.
|
||||
return fn(a, b);
|
||||
}
|
||||
isInsideEventHandler = true;
|
||||
isBatchingEventUpdates = true;
|
||||
try {
|
||||
return batchedEventUpdatesImpl(fn, a, b);
|
||||
} finally {
|
||||
isInsideEventHandler = false;
|
||||
isBatchingEventUpdates = false;
|
||||
finishEventHandler();
|
||||
}
|
||||
}
|
||||
|
||||
// This is for the React Flare event system
|
||||
export function executeUserEventHandler(fn: any => void, value: any): void {
|
||||
const previouslyInEventHandler = isInsideEventHandler;
|
||||
try {
|
||||
isInsideEventHandler = true;
|
||||
const type = typeof value === 'object' && value !== null ? value.type : '';
|
||||
invokeGuardedCallbackAndCatchFirstError(type, fn, undefined, value);
|
||||
} finally {
|
||||
isInsideEventHandler = previouslyInEventHandler;
|
||||
}
|
||||
}
|
||||
|
||||
export function discreteUpdates(fn, a, b, c) {
|
||||
const prevIsInsideEventHandler = isInsideEventHandler;
|
||||
isInsideEventHandler = true;
|
||||
|
||||
@@ -504,6 +504,7 @@ const ResponderEventPlugin = {
|
||||
*/
|
||||
extractEvents: function(
|
||||
topLevelType,
|
||||
eventSystemFlags,
|
||||
targetInst,
|
||||
nativeEvent,
|
||||
nativeEventTarget,
|
||||
@@ -514,7 +515,7 @@ const ResponderEventPlugin = {
|
||||
if (trackedTouchCount >= 0) {
|
||||
trackedTouchCount -= 1;
|
||||
} else {
|
||||
console.error(
|
||||
console.warn(
|
||||
'Ended a touch event which was not counted in `trackedTouchCount`.',
|
||||
);
|
||||
return null;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
'use strict';
|
||||
|
||||
const {HostComponent} = require('shared/ReactWorkTags');
|
||||
const {PLUGIN_EVENT_SYSTEM} = require('legacy-events/EventSystemFlags');
|
||||
|
||||
let EventBatching;
|
||||
let EventPluginUtils;
|
||||
@@ -313,6 +314,7 @@ const run = function(config, hierarchyConfig, nativeEventConfig) {
|
||||
// Trigger the event
|
||||
const extractedEvents = ResponderEventPlugin.extractEvents(
|
||||
nativeEventConfig.topLevelType,
|
||||
PLUGIN_EVENT_SYSTEM,
|
||||
nativeEventConfig.targetInst,
|
||||
nativeEventConfig.nativeEvent,
|
||||
nativeEventConfig.target,
|
||||
|
||||
2
packages/react-art/src/ReactART.js
vendored
2
packages/react-art/src/ReactART.js
vendored
@@ -66,7 +66,7 @@ class Surface extends React.Component {
|
||||
|
||||
this._surface = Mode.Surface(+width, +height, this._tagRef);
|
||||
|
||||
this._mountNode = createContainer(this._surface, LegacyRoot, false);
|
||||
this._mountNode = createContainer(this._surface, LegacyRoot, false, null);
|
||||
updateContainer(this.props.children, this._mountNode, this);
|
||||
}
|
||||
|
||||
|
||||
1
packages/react-art/src/ReactARTHostConfig.js
vendored
1
packages/react-art/src/ReactARTHostConfig.js
vendored
@@ -432,7 +432,6 @@ export function mountResponderInstance(
|
||||
props: Object,
|
||||
state: Object,
|
||||
instance: Object,
|
||||
rootContainerInstance: Object,
|
||||
) {
|
||||
throw new Error('Not yet implemented.');
|
||||
}
|
||||
|
||||
@@ -12,20 +12,17 @@ This is similar requiring the `react-devtools` package, but provides several con
|
||||
|
||||
```js
|
||||
const { connectToDevTools } = require("react-devtools-core");
|
||||
connectToDevTools({
|
||||
// Config options
|
||||
});
|
||||
|
||||
connectToDevTools(config);
|
||||
```
|
||||
|
||||
Run `connectToDevTools()` in the same context as React to set up a connection to DevTools.
|
||||
Be sure to run this function *before* importing e.g. `react`, `react-dom`, `react-native`.
|
||||
|
||||
The `options` object may contain:
|
||||
The `config` object may contain:
|
||||
* `host: string` (defaults to "localhost") - Websocket will connect to this host.
|
||||
* `port: number` (defaults to `8097`) - Websocket will connect to this port.
|
||||
* `websocket: Websocket` - Custom websocked to use. Overrides `host` and `port` settings if provided.
|
||||
* `resolveNativeStyle: (style: number) => ?Object` - Used by the React Native style plug-in.
|
||||
* `resolveRNStyle: (style: number) => ?Object` - Used by the React Native style plug-in.
|
||||
* `isAppActive: () => boolean` - If provided, DevTools will poll this method and wait until it returns true before connecting to React.
|
||||
|
||||
## `react-devtools-core/standalone`
|
||||
@@ -42,3 +39,15 @@ require("react-devtools-core/standalone")
|
||||
```
|
||||
|
||||
Reference the `react-devtools` package for a complete integration example.
|
||||
|
||||
## Development
|
||||
|
||||
Watch for changes made to the backend entry point and rebuild:
|
||||
```sh
|
||||
yarn start:backend
|
||||
```
|
||||
|
||||
Watch for changes made to the standalone UI entry point and rebuild:
|
||||
```sh
|
||||
yarn start:standalone
|
||||
```
|
||||
@@ -1,12 +1,13 @@
|
||||
{
|
||||
"name": "react-devtools-core",
|
||||
"version": "4.0.0-alpha.9",
|
||||
"version": "4.1.0",
|
||||
"description": "Use react-devtools outside of the browser",
|
||||
"license": "MIT",
|
||||
"main": "./dist/backend.js",
|
||||
"repository": {
|
||||
"url": "https://github.com/bvaughn/react-devtools-experimental.git",
|
||||
"type": "git"
|
||||
"type": "git",
|
||||
"url": "https://github.com/facebook/react.git",
|
||||
"directory": "packages/react-devtools-core"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
@@ -23,6 +24,7 @@
|
||||
"start:standalone": "cross-env NODE_ENV=development webpack --config webpack.standalone.js --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"es6-symbol": "^3",
|
||||
"shell-quote": "^1.6.1",
|
||||
"ws": "^7"
|
||||
},
|
||||
|
||||
9
packages/react-devtools-core/src/backend.js
vendored
9
packages/react-devtools-core/src/backend.js
vendored
@@ -1,4 +1,11 @@
|
||||
// @flow
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import Agent from 'react-devtools-shared/src/backend/agent';
|
||||
import Bridge from 'react-devtools-shared/src/bridge';
|
||||
|
||||
9
packages/react-devtools-core/src/editor.js
vendored
9
packages/react-devtools-core/src/editor.js
vendored
@@ -1,4 +1,11 @@
|
||||
// @flow
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import {existsSync} from 'fs';
|
||||
import {basename, join, isAbsolute} from 'path';
|
||||
|
||||
32
packages/react-devtools-core/src/standalone.js
vendored
32
packages/react-devtools-core/src/standalone.js
vendored
@@ -1,4 +1,11 @@
|
||||
// @flow
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import {createElement} from 'react';
|
||||
import {
|
||||
@@ -14,7 +21,8 @@ import {
|
||||
getAppendComponentStack,
|
||||
} from 'react-devtools-shared/src/utils';
|
||||
import {Server} from 'ws';
|
||||
import {existsSync, readFileSync} from 'fs';
|
||||
import {join} from 'path';
|
||||
import {readFileSync} from 'fs';
|
||||
import {installHook} from 'react-devtools-shared/src/hook';
|
||||
import DevTools from 'react-devtools-shared/src/devtools/views/DevTools';
|
||||
import {doesFilePathExist, launchEditor} from './editor';
|
||||
@@ -32,11 +40,6 @@ let nodeWaitingToConnectHTML: string = '';
|
||||
let projectRoots: Array<string> = [];
|
||||
let statusListener: StatusListener = (message: string) => {};
|
||||
|
||||
// Unlike browser extension users, people using the standalone have actively installed version 4,
|
||||
// So we probably don't need to show them a changelog notice.
|
||||
// We should give embedded users (e.g. Nuclide, Sonar) a way of showing this dialog though.
|
||||
let showWelcomeToTheNewDevToolsDialog: boolean = false;
|
||||
|
||||
function setContentDOMNode(value: HTMLElement) {
|
||||
node = value;
|
||||
|
||||
@@ -55,11 +58,6 @@ function setStatusListener(value: StatusListener) {
|
||||
return DevtoolsUI;
|
||||
}
|
||||
|
||||
function setShowWelcomeToTheNewDevToolsDialog(value: boolean) {
|
||||
showWelcomeToTheNewDevToolsDialog = value;
|
||||
return DevtoolsUI;
|
||||
}
|
||||
|
||||
let bridge: FrontendBridge | null = null;
|
||||
let store: Store | null = null;
|
||||
let root = null;
|
||||
@@ -100,7 +98,6 @@ function reload() {
|
||||
bridge: ((bridge: any): FrontendBridge),
|
||||
canViewElementSourceFunction,
|
||||
showTabBar: true,
|
||||
showWelcomeToTheNewDevToolsDialog,
|
||||
store: ((store: any): Store),
|
||||
warnIfLegacyBackendDetected: true,
|
||||
viewElementSourceFunction,
|
||||
@@ -259,14 +256,8 @@ function startServer(port?: number = 8097) {
|
||||
});
|
||||
|
||||
httpServer.on('request', (request, response) => {
|
||||
// NPM installs should read from node_modules,
|
||||
// But local dev mode needs to use a relative path.
|
||||
const basePath = existsSync('./node_modules/react-devtools-core')
|
||||
? 'node_modules/react-devtools-core'
|
||||
: '../react-devtools-core';
|
||||
|
||||
// Serve a file that immediately sets up the connection.
|
||||
const backendFile = readFileSync(`${basePath}/dist/backend.js`);
|
||||
const backendFile = readFileSync(join(__dirname, 'backend.js'));
|
||||
|
||||
// The renderer interface doesn't read saved component filters directly,
|
||||
// because they are generally stored in localStorage within the context of the extension.
|
||||
@@ -316,7 +307,6 @@ const DevtoolsUI = {
|
||||
connectToSocket,
|
||||
setContentDOMNode,
|
||||
setProjectRoots,
|
||||
setShowWelcomeToTheNewDevToolsDialog,
|
||||
setStatusListener,
|
||||
startServer,
|
||||
};
|
||||
|
||||
@@ -40,6 +40,12 @@ module.exports = {
|
||||
scheduler: resolve(builtModulesDir, 'scheduler'),
|
||||
},
|
||||
},
|
||||
node: {
|
||||
// Don't replace __dirname!
|
||||
// This would break the standalone DevTools ability to load the backend.
|
||||
// see https://github.com/facebook/react-devtools/issues/1269
|
||||
__dirname: false,
|
||||
},
|
||||
plugins: [
|
||||
new DefinePlugin({
|
||||
__DEV__: false,
|
||||
|
||||
@@ -1,17 +1,27 @@
|
||||
This is the source code for the React DevTools browser extension.
|
||||
|
||||
# Installation
|
||||
## Installation
|
||||
|
||||
The easiest way to install this extension is as a browser add-on:
|
||||
* [Chrome web store](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en)
|
||||
* [Firefox Add-ons](https://addons.mozilla.org/en-US/firefox/addon/react-devtools/)
|
||||
|
||||
# Build
|
||||
## Development
|
||||
|
||||
You can also build and install from source:
|
||||
You can also build and install this extension from source.
|
||||
|
||||
DevTools embeds local versions of several NPM packages also in this workspae. If you have not already built them, you'll need to do that before getting started by running the following command in the root directory of this repository:
|
||||
```sh
|
||||
yarn install
|
||||
|
||||
yarn build:chrome # builds at "packages/react-devtools-extensions/chrome/build"
|
||||
yarn build:firefox # builds at "packages/react-devtools-extensions/firefox/build"
|
||||
yarn build -- react,react-dom,react-is,scheduler --type=NODE
|
||||
```
|
||||
|
||||
Once the above packages have been built, you can build the extension by running:
|
||||
```sh
|
||||
cd packages/react-devtools-extensions/
|
||||
|
||||
yarn build:chrome # => packages/react-devtools-extensions/chrome/build
|
||||
yarn run test:chrome # Test Chrome extension
|
||||
|
||||
yarn build:firefox # => packages/react-devtools-extensions/firefox/build
|
||||
yarn run test:firefox # Test Firefox extension
|
||||
```
|
||||
|
||||
7
packages/react-devtools-extensions/build.js
vendored
7
packages/react-devtools-extensions/build.js
vendored
@@ -61,14 +61,13 @@ const build = async (tempPath, manifestPath) => {
|
||||
);
|
||||
|
||||
const commit = getGitCommit();
|
||||
const versionDateString = `${commit} (${new Date().toLocaleDateString()})`;
|
||||
|
||||
const dateString = new Date().toLocaleDateString();
|
||||
const manifest = JSON.parse(readFileSync(copiedManifestPath).toString());
|
||||
const versionDateString = `${manifest.version} (${dateString})`;
|
||||
if (manifest.version_name) {
|
||||
manifest.version_name = versionDateString;
|
||||
} else {
|
||||
manifest.description += `\n\nCreated from revision ${versionDateString}`;
|
||||
}
|
||||
manifest.description += `\n\nCreated from revision ${commit} on ${dateString}.`;
|
||||
|
||||
writeFileSync(copiedManifestPath, JSON.stringify(manifest, null, 2));
|
||||
|
||||
|
||||
@@ -29,9 +29,22 @@ const main = async () => {
|
||||
safeKeyPath = join(relative(cwd, process.cwd()), keyPath);
|
||||
}
|
||||
|
||||
execSync(`crx pack ./unpacked -o ReactDevTools.crx -p ${safeKeyPath}`, {
|
||||
cwd,
|
||||
});
|
||||
const crxPath = join(
|
||||
__dirname,
|
||||
'..',
|
||||
'..',
|
||||
'..',
|
||||
'node_modules',
|
||||
'.bin',
|
||||
'crx'
|
||||
);
|
||||
|
||||
execSync(
|
||||
`${crxPath} pack ./unpacked -o ReactDevTools.crx -p ${safeKeyPath}`,
|
||||
{
|
||||
cwd,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
console.log(chalk.green('\nThe Chrome extension has been built!'));
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
"manifest_version": 2,
|
||||
"name": "React Developer Tools",
|
||||
"description": "Adds React debugging tools to the Chrome Developer Tools.",
|
||||
"version": "4.0.0",
|
||||
"version_name": "4.0.0",
|
||||
"version": "4.1.0",
|
||||
"version_name": "4.1.0",
|
||||
|
||||
"minimum_chrome_version": "49",
|
||||
|
||||
@@ -40,15 +40,7 @@
|
||||
"persistent": false
|
||||
},
|
||||
|
||||
"permissions": [
|
||||
"<all_urls>",
|
||||
"background",
|
||||
"tabs",
|
||||
"webNavigation",
|
||||
"file:///*",
|
||||
"http://*/*",
|
||||
"https://*/*"
|
||||
],
|
||||
"permissions": ["file:///*", "http://*/*", "https://*/*"],
|
||||
|
||||
"content_scripts": [
|
||||
{
|
||||
|
||||
@@ -18,12 +18,11 @@
|
||||
|
||||
<h3>
|
||||
Created on <strong>%date%</strong> from
|
||||
<a href="http://github.com/bvaughn/react-devtools-experimental/commit/%commit%"><code>%commit%</code></a>
|
||||
<a href="http://github.com/facebook/react/commit/%commit%"><code>%commit%</code></a>
|
||||
</h3>
|
||||
|
||||
<p>
|
||||
This is a preview build of an <a href="https://github.com/facebook/react/tree/master/packages/react-devtools-extensions">unreleased DevTools extension</a>.
|
||||
It has no developer support.
|
||||
This is a preview build of the <a href="https://github.com/facebook/react">React DevTools extension</a>.
|
||||
</p>
|
||||
|
||||
<h2>Installation instructions</h2>
|
||||
@@ -37,10 +36,5 @@
|
||||
Please report bugs as <a href="https://github.com/facebook/react/issues/new?labels=Component:%20Developer%20Tools">GitHub issues</a>.
|
||||
Please include all of the info required to reproduce the bug (e.g. links, code, instructions).
|
||||
</p>
|
||||
|
||||
<h2>Feature requests</h2>
|
||||
<p>
|
||||
Feature requests are not being accepted at this time.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"manifest_version": 2,
|
||||
"name": "React Developer Tools",
|
||||
"description": "Adds React debugging tools to the Firefox Developer Tools.",
|
||||
"version": "4.0.0",
|
||||
"version": "4.1.0",
|
||||
|
||||
"applications": {
|
||||
"gecko": {
|
||||
@@ -44,15 +44,7 @@
|
||||
"scripts": ["build/background.js"]
|
||||
},
|
||||
|
||||
"permissions": [
|
||||
"<all_urls>",
|
||||
"activeTab",
|
||||
"tabs",
|
||||
"webNavigation",
|
||||
"file:///*",
|
||||
"http://*/*",
|
||||
"https://*/*"
|
||||
],
|
||||
"permissions": ["file:///*", "http://*/*", "https://*/*"],
|
||||
|
||||
"content_scripts": [
|
||||
{
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
// @flow
|
||||
|
||||
'use strict';
|
||||
|
||||
declare var chrome: {
|
||||
devtools: {
|
||||
network: {
|
||||
onNavigated: {
|
||||
addListener: (cb: (url: string) => void) => void,
|
||||
removeListener: (cb: () => void) => void,
|
||||
},
|
||||
},
|
||||
inspectedWindow: {
|
||||
eval: (code: string, cb?: (res: any, err: ?Object) => any) => void,
|
||||
tabId: number,
|
||||
},
|
||||
panels: {
|
||||
create: (
|
||||
title: string,
|
||||
icon: string,
|
||||
filename: string,
|
||||
cb: (panel: {
|
||||
onHidden: {
|
||||
addListener: (cb: (window: Object) => void) => void,
|
||||
},
|
||||
onShown: {
|
||||
addListener: (cb: (window: Object) => void) => void,
|
||||
},
|
||||
}) => void
|
||||
) => void,
|
||||
themeName: ?string,
|
||||
},
|
||||
},
|
||||
tabs: {
|
||||
create: (options: Object) => void,
|
||||
executeScript: (tabId: number, options: Object, fn: () => void) => void,
|
||||
onUpdated: {
|
||||
addListener: (
|
||||
fn: (tabId: number, changeInfo: Object, tab: Object) => void
|
||||
) => void,
|
||||
},
|
||||
query: (options: Object, fn: (tabArray: Array<Object>) => void) => void,
|
||||
},
|
||||
browserAction: {
|
||||
setIcon: (options: {
|
||||
tabId: number,
|
||||
path: {[key: string]: string},
|
||||
}) => void,
|
||||
setPopup: (options: {
|
||||
tabId: number,
|
||||
popup: string,
|
||||
}) => void,
|
||||
},
|
||||
runtime: {
|
||||
getURL: (path: string) => string,
|
||||
sendMessage: (config: Object) => void,
|
||||
connect: (
|
||||
config: Object
|
||||
) => {
|
||||
disconnect: () => void,
|
||||
onMessage: {
|
||||
addListener: (fn: (message: Object) => void) => void,
|
||||
},
|
||||
onDisconnect: {
|
||||
addListener: (fn: (message: Object) => void) => void,
|
||||
},
|
||||
postMessage: (data: Object) => void,
|
||||
},
|
||||
onConnect: {
|
||||
addListener: (
|
||||
fn: (port: {
|
||||
name: string,
|
||||
sender: {
|
||||
tab: {
|
||||
id: number,
|
||||
url: string,
|
||||
},
|
||||
},
|
||||
}) => void
|
||||
) => void,
|
||||
},
|
||||
onMessage: {
|
||||
addListener: (
|
||||
fn: (
|
||||
req: Object,
|
||||
sender: {
|
||||
url: string,
|
||||
tab: {
|
||||
id: number,
|
||||
},
|
||||
}
|
||||
) => void
|
||||
) => void,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -30,6 +30,7 @@
|
||||
"chrome-launch": "^1.1.4",
|
||||
"child-process-promise": "^2.2.1",
|
||||
"css-loader": "^1.0.1",
|
||||
"crx": "^5.0.0",
|
||||
"firefox-profile": "^1.0.2",
|
||||
"node-libs-browser": "0.5.3",
|
||||
"nullthrows": "^1.0.0",
|
||||
|
||||
@@ -28,5 +28,5 @@
|
||||
</p>
|
||||
<hr />
|
||||
<p>
|
||||
Open the developer tools, and the React tab will appear to the right.
|
||||
Open the developer tools, and "Components" and "Profiler" tabs will appear to the right.
|
||||
</p>
|
||||
|
||||
@@ -24,5 +24,5 @@
|
||||
</p>
|
||||
<hr />
|
||||
<p>
|
||||
Open the developer tools, and the React tab will appear to the right.
|
||||
Open the developer tools, and "Components" and "Profiler" tabs will appear to the right.
|
||||
</p>
|
||||
|
||||
@@ -17,5 +17,5 @@
|
||||
<p>
|
||||
<b>This page doesn’t appear to be using React.</b>
|
||||
<br />
|
||||
If this seems wrong, follow the <a href="https://github.com/facebook/react-devtools/blob/master/README.md#the-react-tab-doesnt-show-up">troubleshooting instructions</a>.
|
||||
If this seems wrong, follow the <a href="https://github.com/facebook/react/tree/master/packages/react-devtools#the-react-tab-doesnt-show-up">troubleshooting instructions</a>.
|
||||
</p>
|
||||
|
||||
@@ -25,5 +25,5 @@
|
||||
</p>
|
||||
<hr />
|
||||
<p>
|
||||
Open the developer tools, and the React tab will appear to the right.
|
||||
Open the developer tools, and "Components" and "Profiler" tabs will appear to the right.
|
||||
</p>
|
||||
|
||||
@@ -17,5 +17,5 @@
|
||||
<p>
|
||||
<b>This page is using the production build of React. ✅</b>
|
||||
<br />
|
||||
Open the developer tools, and the React tab will appear to the right.
|
||||
Open the developer tools, and "Components" and "Profiler" tabs will appear to the right.
|
||||
</p>
|
||||
|
||||
@@ -27,5 +27,5 @@
|
||||
</p>
|
||||
<hr />
|
||||
<p>
|
||||
Open the developer tools, and the React tab will appear to the right.
|
||||
Open the developer tools, and "Components" and "Profiler" tabs will appear to the right.
|
||||
</p>
|
||||
|
||||
27
packages/react-devtools-extensions/src/main.js
vendored
27
packages/react-devtools-extensions/src/main.js
vendored
@@ -24,6 +24,8 @@ import DevTools from 'react-devtools-shared/src/devtools/views/DevTools';
|
||||
const LOCAL_STORAGE_SUPPORTS_PROFILING_KEY =
|
||||
'React::DevTools::supportsProfiling';
|
||||
|
||||
const isChrome = getBrowserName() === 'Chrome';
|
||||
|
||||
let panelCreated = false;
|
||||
|
||||
// The renderer interface can't read saved component filters directly,
|
||||
@@ -120,9 +122,13 @@ function createPanelIfReactLoaded() {
|
||||
localStorageRemoveItem(LOCAL_STORAGE_SUPPORTS_PROFILING_KEY);
|
||||
}
|
||||
|
||||
if (store !== null) {
|
||||
profilingData = store.profilerStore.profilingData;
|
||||
}
|
||||
|
||||
store = new Store(bridge, {
|
||||
isProfiling,
|
||||
supportsReloadAndProfile: getBrowserName() === 'Chrome',
|
||||
supportsReloadAndProfile: isChrome,
|
||||
supportsProfiling,
|
||||
});
|
||||
store.profilerStore.profilingData = profilingData;
|
||||
@@ -228,7 +234,7 @@ function createPanelIfReactLoaded() {
|
||||
let needsToSyncElementSelection = false;
|
||||
|
||||
chrome.devtools.panels.create(
|
||||
'⚛ Components',
|
||||
isChrome ? '⚛ Components' : 'Components',
|
||||
'',
|
||||
'panel.html',
|
||||
extensionPanel => {
|
||||
@@ -258,7 +264,7 @@ function createPanelIfReactLoaded() {
|
||||
);
|
||||
|
||||
chrome.devtools.panels.create(
|
||||
'⚛ Profiler',
|
||||
isChrome ? '⚛ Profiler' : 'Profiler',
|
||||
'',
|
||||
'panel.html',
|
||||
extensionPanel => {
|
||||
@@ -281,21 +287,6 @@ function createPanelIfReactLoaded() {
|
||||
|
||||
chrome.devtools.network.onNavigated.removeListener(checkPageForReact);
|
||||
|
||||
// Shutdown bridge before a new page is loaded.
|
||||
chrome.webNavigation.onBeforeNavigate.addListener(
|
||||
function onBeforeNavigate(details) {
|
||||
// Ignore navigation events from other tabs (or from within frames).
|
||||
if (details.tabId !== tabId || details.frameId !== 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// `bridge.shutdown()` will remove all listeners we added, so we don't have to.
|
||||
bridge.shutdown();
|
||||
|
||||
profilingData = store.profilerStore.profilingData;
|
||||
},
|
||||
);
|
||||
|
||||
// Re-initialize DevTools panel when a new page is loaded.
|
||||
chrome.devtools.network.onNavigated.addListener(function onNavigated() {
|
||||
// Re-initialize saved filters on navigation,
|
||||
|
||||
13
packages/react-devtools-extensions/src/utils.js
vendored
13
packages/react-devtools-extensions/src/utils.js
vendored
@@ -12,10 +12,21 @@ export function createViewElementSource(bridge: Bridge, store: Store) {
|
||||
|
||||
setTimeout(() => {
|
||||
// Ask Chrome to display the location of the component function,
|
||||
// or a render method if it is a Class (ideally Class instance, not type)
|
||||
// assuming the renderer found one.
|
||||
chrome.devtools.inspectedWindow.eval(`
|
||||
if (window.$type != null) {
|
||||
inspect(window.$type);
|
||||
if (
|
||||
window.$type &&
|
||||
window.$type.prototype &&
|
||||
window.$type.prototype.isReactComponent
|
||||
) {
|
||||
// inspect Component.render, not constructor
|
||||
inspect(window.$type.prototype.render);
|
||||
} else {
|
||||
// inspect Functional Component
|
||||
inspect(window.$type);
|
||||
}
|
||||
}
|
||||
`);
|
||||
}, 100);
|
||||
|
||||
@@ -21,24 +21,32 @@ The frontend and backend can be initialized in any order, but **the backend must
|
||||
### `react-devtools-inline/backend`
|
||||
|
||||
* **`initialize(contentWindow)`** -
|
||||
Installs the global hook on the window. This hook is how React and DevTools communicate. **This method must be called before React is loaded.** (This means before any `import` or `require` statements!)
|
||||
Installs the global hook on the window. This hook is how React and DevTools communicate. **This method must be called before React is loaded.**<sup>2</sup>
|
||||
* **`activate(contentWindow)`** -
|
||||
Lets the backend know when the frontend is ready. It should not be called until after the frontend has been initialized, else the frontend might miss important tree-initialization events.
|
||||
|
||||
```js
|
||||
import { activate, initialize } from 'react-devtools-inline/backend';
|
||||
|
||||
// This should be the iframe the React application is running in.
|
||||
const iframe = document.getElementById(frameID);
|
||||
const contentWindow = iframe.contentWindow;
|
||||
|
||||
// Call this before importing React (or any other packages that might import React).
|
||||
initialize();
|
||||
initialize(contentWindow);
|
||||
|
||||
// Initialize the frontend...
|
||||
|
||||
// Call this only once the frontend has been initialized.
|
||||
activate();
|
||||
activate(contentWindow);
|
||||
```
|
||||
|
||||
<sup>2</sup> The backend must be initialized before React is loaded. (This means before any `import` or `require` statements or `<script>` tags that include React.)
|
||||
|
||||
### `react-devtools-inline/frontend`
|
||||
|
||||
* **`initialize(contentWindow)`** -
|
||||
Configures the DevTools interface to listen to the `window` the backend was injected into. This method returns a React component that can be rendered directly<sup>2</sup>.
|
||||
Configures the DevTools interface to listen to the `window` the backend was injected into. This method returns a React component that can be rendered directly<sup>3</sup>.
|
||||
|
||||
```js
|
||||
import { initialize } from 'react-devtools-inline/frontend';
|
||||
@@ -52,7 +60,7 @@ const contentWindow = iframe.contentWindow;
|
||||
const DevTools = initialize(contentWindow);
|
||||
```
|
||||
|
||||
<sup>2</sup> Because the DevTools interface makes use of several new React APIs (e.g. suspense, concurrent mode) it should be rendered using either `ReactDOM.unstable_createRoot` or `ReactDOM.unstable_createSyncRoot`. It should not be rendered with `ReactDOM.render`.
|
||||
<sup>3</sup> Because the DevTools interface makes use of several new React APIs (e.g. suspense, concurrent mode) it should be rendered using either `ReactDOM.unstable_createRoot` or `ReactDOM.unstable_createSyncRoot`. **It should not be rendered with `ReactDOM.render`.**
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -143,4 +151,11 @@ iframe.onload = () => {
|
||||
"*"
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
Watch for changes made to the source code and rebuild:
|
||||
```sh
|
||||
yarn start
|
||||
```
|
||||
@@ -1,12 +1,13 @@
|
||||
{
|
||||
"name": "react-devtools-inline",
|
||||
"version": "4.0.0-alpha.9",
|
||||
"version": "4.1.0",
|
||||
"description": "Embed react-devtools within a website",
|
||||
"license": "MIT",
|
||||
"main": "./dist/backend.js",
|
||||
"repository": {
|
||||
"url": "https://github.com/bvaughn/react-devtools-experimental.git",
|
||||
"type": "git"
|
||||
"type": "git",
|
||||
"url": "https://github.com/facebook/react.git",
|
||||
"directory": "packages/react-devtools-inline"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
@@ -19,7 +20,9 @@
|
||||
"prepublish": "yarn run build",
|
||||
"start": "cross-env NODE_ENV=development webpack --config webpack.config.js --watch"
|
||||
},
|
||||
"dependencies": {},
|
||||
"dependencies": {
|
||||
"es6-symbol": "^3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.1.6",
|
||||
"@babel/plugin-proposal-class-properties": "^7.1.0",
|
||||
|
||||
@@ -2,12 +2,17 @@
|
||||
"private": true,
|
||||
"name": "react-devtools-shared",
|
||||
"version": "0.0.0",
|
||||
"devDependencies": {
|
||||
"react-15": "npm:react@^15",
|
||||
"react-dom-15": "npm:react-dom@^15"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.0.0",
|
||||
"@reach/menu-button": "^0.1.17",
|
||||
"@reach/tooltip": "^0.2.2",
|
||||
"clipboard-js": "^0.3.6",
|
||||
"events": "^3.0.0",
|
||||
"local-storage-fallback": "^4.1.1",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"memoize-one": "^3.1.1",
|
||||
"react-virtualized-auto-sizer": "^1.0.2"
|
||||
|
||||
@@ -1,5 +1,41 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`InspectedElementContext should dehydrate complex nested values when requested: 1: Initially inspect element 1`] = `
|
||||
{
|
||||
"id": 2,
|
||||
"owners": null,
|
||||
"context": null,
|
||||
"hooks": null,
|
||||
"props": {
|
||||
"set_of_sets": {
|
||||
"0": {},
|
||||
"1": {}
|
||||
}
|
||||
},
|
||||
"state": null
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`InspectedElementContext should dehydrate complex nested values when requested: 2: Inspect props.set_of_sets.0 1`] = `
|
||||
{
|
||||
"id": 2,
|
||||
"owners": null,
|
||||
"context": null,
|
||||
"hooks": null,
|
||||
"props": {
|
||||
"set_of_sets": {
|
||||
"0": {
|
||||
"0": 1,
|
||||
"1": 2,
|
||||
"2": 3
|
||||
},
|
||||
"1": {}
|
||||
}
|
||||
},
|
||||
"state": null
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`InspectedElementContext should include updates for nested values that were previously hydrated: 1: Initially inspect element 1`] = `
|
||||
{
|
||||
"id": 2,
|
||||
@@ -91,6 +127,28 @@ exports[`InspectedElementContext should include updates for nested values that w
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`InspectedElementContext should inspect hooks for components that only use context: 1: Inspected element 2 1`] = `
|
||||
{
|
||||
"id": 2,
|
||||
"owners": null,
|
||||
"context": null,
|
||||
"hooks": [
|
||||
{
|
||||
"id": null,
|
||||
"isStateEditable": false,
|
||||
"name": "Context",
|
||||
"value": true,
|
||||
"subHooks": []
|
||||
}
|
||||
],
|
||||
"props": {
|
||||
"a": 1,
|
||||
"b": "abc"
|
||||
},
|
||||
"state": null
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`InspectedElementContext should inspect the currently selected element: 1: Inspected element 2 1`] = `
|
||||
{
|
||||
"id": 2,
|
||||
@@ -427,13 +485,38 @@ exports[`InspectedElementContext should support complex data types: 1: Inspected
|
||||
"context": null,
|
||||
"hooks": null,
|
||||
"props": {
|
||||
"html_element": {},
|
||||
"fn": {},
|
||||
"symbol": {},
|
||||
"react_element": {},
|
||||
"array_buffer": {},
|
||||
"typed_array": {},
|
||||
"date": {}
|
||||
"date": {},
|
||||
"fn": {},
|
||||
"html_element": {},
|
||||
"immutable": {
|
||||
"0": {},
|
||||
"1": {},
|
||||
"2": {}
|
||||
},
|
||||
"map": {
|
||||
"0": {},
|
||||
"1": {}
|
||||
},
|
||||
"map_of_maps": {
|
||||
"0": {},
|
||||
"1": {}
|
||||
},
|
||||
"react_element": {},
|
||||
"set": {
|
||||
"0": "abc",
|
||||
"1": 123
|
||||
},
|
||||
"set_of_sets": {
|
||||
"0": {},
|
||||
"1": {}
|
||||
},
|
||||
"symbol": {},
|
||||
"typed_array": {
|
||||
"0": 100,
|
||||
"1": -100,
|
||||
"2": 0
|
||||
}
|
||||
},
|
||||
"state": null
|
||||
}
|
||||
|
||||
@@ -1091,7 +1091,7 @@ Object {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`TreeListContext tree state should select the next and previous elements in the tree: 3: select element after (0) 1`] = `
|
||||
exports[`TreeListContext tree state should select the next and previous elements in the tree: 3: select element after (0) 1`] = `
|
||||
Object {
|
||||
"inspectedElementID": 3,
|
||||
"numElements": 4,
|
||||
@@ -1105,7 +1105,7 @@ Object {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`TreeListContext tree state should select the next and previous elements in the tree: 3: select element after (1) 1`] = `
|
||||
exports[`TreeListContext tree state should select the next and previous elements in the tree: 3: select element after (1) 1`] = `
|
||||
Object {
|
||||
"inspectedElementID": 4,
|
||||
"numElements": 4,
|
||||
@@ -1119,7 +1119,7 @@ Object {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`TreeListContext tree state should select the next and previous elements in the tree: 3: select element after (2) 1`] = `
|
||||
exports[`TreeListContext tree state should select the next and previous elements in the tree: 3: select element after (2) 1`] = `
|
||||
Object {
|
||||
"inspectedElementID": 5,
|
||||
"numElements": 4,
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
// @flow
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
describe('Bridge', () => {
|
||||
let Bridge;
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
// @flow
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
describe('console', () => {
|
||||
let React;
|
||||
@@ -13,23 +20,12 @@ describe('console', () => {
|
||||
let unpatchConsole;
|
||||
|
||||
beforeEach(() => {
|
||||
const Console = require('../backend/console');
|
||||
jest.resetModules();
|
||||
|
||||
const Console = require('react-devtools-shared/src/backend/console');
|
||||
patchConsole = Console.patch;
|
||||
unpatchConsole = Console.unpatch;
|
||||
|
||||
const inject = global.__REACT_DEVTOOLS_GLOBAL_HOOK__.inject;
|
||||
global.__REACT_DEVTOOLS_GLOBAL_HOOK__.inject = internals => {
|
||||
inject(internals);
|
||||
|
||||
Console.registerRenderer(internals);
|
||||
};
|
||||
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
|
||||
const utils = require('./utils');
|
||||
act = utils.act;
|
||||
|
||||
// Patch a fake console so we can verify with tests below.
|
||||
// Patching the real console is too complicated,
|
||||
// because Jest itself has hooks into it as does our test env setup.
|
||||
@@ -46,7 +42,22 @@ describe('console', () => {
|
||||
|
||||
Console.dangerous_setTargetConsoleForTesting(fakeConsole);
|
||||
|
||||
// Note the Console module only patches once,
|
||||
// so it's important to patch the test console before injection.
|
||||
patchConsole();
|
||||
|
||||
const inject = global.__REACT_DEVTOOLS_GLOBAL_HOOK__.inject;
|
||||
global.__REACT_DEVTOOLS_GLOBAL_HOOK__.inject = internals => {
|
||||
inject(internals);
|
||||
|
||||
Console.registerRenderer(internals);
|
||||
};
|
||||
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
|
||||
const utils = require('./utils');
|
||||
act = utils.act;
|
||||
});
|
||||
|
||||
function normalizeCodeLocInfo(str) {
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
// @flow
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import typeof ReactTestRenderer from 'react-test-renderer';
|
||||
import type {GetInspectedElementPath} from 'react-devtools-shared/src/devtools/views/Components/InspectedElementContext';
|
||||
@@ -8,6 +15,7 @@ import type Store from 'react-devtools-shared/src/devtools/store';
|
||||
describe('InspectedElementContext', () => {
|
||||
let React;
|
||||
let ReactDOM;
|
||||
let PropTypes;
|
||||
let TestRenderer: ReactTestRenderer;
|
||||
let bridge: FrontendBridge;
|
||||
let store: Store;
|
||||
@@ -33,6 +41,7 @@ describe('InspectedElementContext', () => {
|
||||
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
PropTypes = require('prop-types');
|
||||
TestUtils = require('react-dom/test-utils');
|
||||
TestRenderer = utils.requireTestRenderer();
|
||||
|
||||
@@ -107,6 +116,119 @@ describe('InspectedElementContext', () => {
|
||||
done();
|
||||
});
|
||||
|
||||
it('should have hasLegacyContext flag set to either "true" or "false" depending on which context API is used.', async done => {
|
||||
const contextData = {
|
||||
bool: true,
|
||||
};
|
||||
|
||||
// Legacy Context API.
|
||||
class LegacyContextProvider extends React.Component<any> {
|
||||
static childContextTypes = {
|
||||
bool: PropTypes.bool,
|
||||
};
|
||||
getChildContext() {
|
||||
return contextData;
|
||||
}
|
||||
render() {
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
class LegacyContextConsumer extends React.Component<any> {
|
||||
static contextTypes = {
|
||||
bool: PropTypes.bool,
|
||||
};
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Modern Context API
|
||||
const BoolContext = React.createContext(contextData.bool);
|
||||
BoolContext.displayName = 'BoolContext';
|
||||
|
||||
class ModernContextType extends React.Component<any> {
|
||||
static contextType = BoolContext;
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const ModernContext = React.createContext();
|
||||
ModernContext.displayName = 'ModernContext';
|
||||
|
||||
const container = document.createElement('div');
|
||||
await utils.actAsync(() =>
|
||||
ReactDOM.render(
|
||||
<React.Fragment>
|
||||
<LegacyContextProvider>
|
||||
<LegacyContextConsumer />
|
||||
</LegacyContextProvider>
|
||||
<BoolContext.Consumer>{value => null}</BoolContext.Consumer>
|
||||
<ModernContextType />
|
||||
<ModernContext.Provider value={contextData}>
|
||||
<ModernContext.Consumer>{value => null}</ModernContext.Consumer>
|
||||
</ModernContext.Provider>
|
||||
</React.Fragment>,
|
||||
container,
|
||||
),
|
||||
);
|
||||
|
||||
const ids = [
|
||||
{
|
||||
// <LegacyContextConsumer />
|
||||
id: ((store.getElementIDAtIndex(1): any): number),
|
||||
shouldHaveLegacyContext: true,
|
||||
},
|
||||
{
|
||||
// <BoolContext.Consumer>
|
||||
id: ((store.getElementIDAtIndex(2): any): number),
|
||||
shouldHaveLegacyContext: false,
|
||||
},
|
||||
{
|
||||
// <ModernContextType />
|
||||
id: ((store.getElementIDAtIndex(3): any): number),
|
||||
shouldHaveLegacyContext: false,
|
||||
},
|
||||
{
|
||||
// <ModernContext.Consumer>
|
||||
id: ((store.getElementIDAtIndex(5): any): number),
|
||||
shouldHaveLegacyContext: false,
|
||||
},
|
||||
];
|
||||
|
||||
function Suspender({target, shouldHaveLegacyContext}) {
|
||||
const {getInspectedElement} = React.useContext(InspectedElementContext);
|
||||
const inspectedElement = getInspectedElement(target);
|
||||
|
||||
expect(inspectedElement.context).not.toBe(null);
|
||||
expect(inspectedElement.hasLegacyContext).toBe(shouldHaveLegacyContext);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
for (let i = 0; i < ids.length; i++) {
|
||||
const {id, shouldHaveLegacyContext} = ids[i];
|
||||
|
||||
await utils.actAsync(
|
||||
() =>
|
||||
TestRenderer.create(
|
||||
<Contexts
|
||||
defaultSelectedElementID={id}
|
||||
defaultSelectedElementIndex={0}>
|
||||
<React.Suspense fallback={null}>
|
||||
<Suspender
|
||||
target={id}
|
||||
shouldHaveLegacyContext={shouldHaveLegacyContext}
|
||||
/>
|
||||
</React.Suspense>
|
||||
</Contexts>,
|
||||
),
|
||||
false,
|
||||
);
|
||||
}
|
||||
done();
|
||||
});
|
||||
|
||||
it('should poll for updates for the currently selected element', async done => {
|
||||
const Example = () => null;
|
||||
|
||||
@@ -385,23 +507,42 @@ describe('InspectedElementContext', () => {
|
||||
});
|
||||
|
||||
it('should support complex data types', async done => {
|
||||
const Immutable = require('immutable');
|
||||
|
||||
const Example = () => null;
|
||||
|
||||
const div = document.createElement('div');
|
||||
const exmapleFunction = () => {};
|
||||
const typedArray = new Uint8Array(3);
|
||||
const exampleFunction = () => {};
|
||||
const setShallow = new Set(['abc', 123]);
|
||||
const mapShallow = new Map([['name', 'Brian'], ['food', 'sushi']]);
|
||||
const setOfSets = new Set([new Set(['a', 'b', 'c']), new Set([1, 2, 3])]);
|
||||
const mapOfMaps = new Map([['first', mapShallow], ['second', mapShallow]]);
|
||||
const typedArray = Int8Array.from([100, -100, 0]);
|
||||
const immutableMap = Immutable.fromJS({
|
||||
a: [{hello: 'there'}, 'fixed', true],
|
||||
b: 123,
|
||||
c: {
|
||||
'1': 'xyz',
|
||||
xyz: 1,
|
||||
},
|
||||
});
|
||||
|
||||
const container = document.createElement('div');
|
||||
await utils.actAsync(() =>
|
||||
ReactDOM.render(
|
||||
<Example
|
||||
html_element={div}
|
||||
fn={exmapleFunction}
|
||||
symbol={Symbol('symbol')}
|
||||
react_element={<span />}
|
||||
array_buffer={typedArray.buffer}
|
||||
typed_array={typedArray}
|
||||
date={new Date()}
|
||||
fn={exampleFunction}
|
||||
html_element={div}
|
||||
immutable={immutableMap}
|
||||
map={mapShallow}
|
||||
map_of_maps={mapOfMaps}
|
||||
react_element={<span />}
|
||||
set={setShallow}
|
||||
set_of_sets={setOfSets}
|
||||
symbol={Symbol('symbol')}
|
||||
typed_array={typedArray}
|
||||
/>,
|
||||
container,
|
||||
),
|
||||
@@ -435,37 +576,77 @@ describe('InspectedElementContext', () => {
|
||||
expect(inspectedElement).toMatchSnapshot(`1: Inspected element ${id}`);
|
||||
|
||||
const {
|
||||
html_element,
|
||||
fn,
|
||||
symbol,
|
||||
react_element,
|
||||
array_buffer,
|
||||
typed_array,
|
||||
date,
|
||||
fn,
|
||||
html_element,
|
||||
immutable,
|
||||
map,
|
||||
map_of_maps,
|
||||
react_element,
|
||||
set,
|
||||
set_of_sets,
|
||||
symbol,
|
||||
typed_array,
|
||||
} = (inspectedElement: any).props;
|
||||
expect(html_element[meta.inspectable]).toBe(false);
|
||||
expect(html_element[meta.name]).toBe('DIV');
|
||||
expect(html_element[meta.type]).toBe('html_element');
|
||||
expect(fn[meta.inspectable]).toBe(false);
|
||||
expect(fn[meta.name]).toBe('exmapleFunction');
|
||||
expect(fn[meta.type]).toBe('function');
|
||||
expect(symbol[meta.inspectable]).toBe(false);
|
||||
expect(symbol[meta.name]).toBe('Symbol(symbol)');
|
||||
expect(symbol[meta.type]).toBe('symbol');
|
||||
expect(react_element[meta.inspectable]).toBe(false);
|
||||
expect(react_element[meta.name]).toBe('span');
|
||||
expect(react_element[meta.type]).toBe('react_element');
|
||||
|
||||
expect(array_buffer[meta.size]).toBe(3);
|
||||
expect(array_buffer[meta.inspectable]).toBe(false);
|
||||
expect(array_buffer[meta.name]).toBe('ArrayBuffer');
|
||||
expect(array_buffer[meta.type]).toBe('array_buffer');
|
||||
expect(typed_array[meta.size]).toBe(3);
|
||||
expect(typed_array[meta.inspectable]).toBe(false);
|
||||
expect(typed_array[meta.name]).toBe('Uint8Array');
|
||||
expect(typed_array[meta.type]).toBe('typed_array');
|
||||
|
||||
expect(date[meta.inspectable]).toBe(false);
|
||||
expect(date[meta.type]).toBe('date');
|
||||
|
||||
expect(fn[meta.inspectable]).toBe(false);
|
||||
expect(fn[meta.name]).toBe('exampleFunction');
|
||||
expect(fn[meta.type]).toBe('function');
|
||||
|
||||
expect(html_element[meta.inspectable]).toBe(false);
|
||||
expect(html_element[meta.name]).toBe('DIV');
|
||||
expect(html_element[meta.type]).toBe('html_element');
|
||||
|
||||
expect(immutable[meta.inspectable]).toBeUndefined(); // Complex type
|
||||
expect(immutable[meta.name]).toBe('Map');
|
||||
expect(immutable[meta.type]).toBe('iterator');
|
||||
|
||||
expect(map[meta.inspectable]).toBeUndefined(); // Complex type
|
||||
expect(map[meta.name]).toBe('Map');
|
||||
expect(map[meta.type]).toBe('iterator');
|
||||
expect(map[0][meta.type]).toBe('array');
|
||||
|
||||
expect(map_of_maps[meta.inspectable]).toBeUndefined(); // Complex type
|
||||
expect(map_of_maps[meta.name]).toBe('Map');
|
||||
expect(map_of_maps[meta.type]).toBe('iterator');
|
||||
expect(map_of_maps[0][meta.type]).toBe('array');
|
||||
|
||||
expect(react_element[meta.inspectable]).toBe(false);
|
||||
expect(react_element[meta.name]).toBe('span');
|
||||
expect(react_element[meta.type]).toBe('react_element');
|
||||
|
||||
expect(set[meta.inspectable]).toBeUndefined(); // Complex type
|
||||
expect(set[meta.name]).toBe('Set');
|
||||
expect(set[meta.type]).toBe('iterator');
|
||||
expect(set[0]).toBe('abc');
|
||||
expect(set[1]).toBe(123);
|
||||
|
||||
expect(set_of_sets[meta.inspectable]).toBeUndefined(); // Complex type
|
||||
expect(set_of_sets[meta.name]).toBe('Set');
|
||||
expect(set_of_sets[meta.type]).toBe('iterator');
|
||||
expect(set_of_sets['0'][meta.inspectable]).toBe(true);
|
||||
|
||||
expect(symbol[meta.inspectable]).toBe(false);
|
||||
expect(symbol[meta.name]).toBe('Symbol(symbol)');
|
||||
expect(symbol[meta.type]).toBe('symbol');
|
||||
|
||||
expect(typed_array[meta.inspectable]).toBeUndefined(); // Complex type
|
||||
expect(typed_array[meta.size]).toBe(3);
|
||||
expect(typed_array[meta.name]).toBe('Int8Array');
|
||||
expect(typed_array[meta.type]).toBe('typed_array');
|
||||
expect(typed_array[0]).toBe(100);
|
||||
expect(typed_array[1]).toBe(-100);
|
||||
expect(typed_array[2]).toBe(0);
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
@@ -655,6 +836,61 @@ describe('InspectedElementContext', () => {
|
||||
done();
|
||||
});
|
||||
|
||||
it('should dehydrate complex nested values when requested', async done => {
|
||||
const Example = () => null;
|
||||
|
||||
const container = document.createElement('div');
|
||||
await utils.actAsync(() =>
|
||||
ReactDOM.render(
|
||||
<Example
|
||||
set_of_sets={new Set([new Set([1, 2, 3]), new Set(['a', 'b', 'c'])])}
|
||||
/>,
|
||||
container,
|
||||
),
|
||||
);
|
||||
|
||||
const id = ((store.getElementIDAtIndex(0): any): number);
|
||||
|
||||
let getInspectedElementPath: GetInspectedElementPath = ((null: any): GetInspectedElementPath);
|
||||
let inspectedElement = null;
|
||||
|
||||
function Suspender({target}) {
|
||||
const context = React.useContext(InspectedElementContext);
|
||||
getInspectedElementPath = context.getInspectedElementPath;
|
||||
inspectedElement = context.getInspectedElement(target);
|
||||
return null;
|
||||
}
|
||||
|
||||
await utils.actAsync(
|
||||
() =>
|
||||
TestRenderer.create(
|
||||
<Contexts
|
||||
defaultSelectedElementID={id}
|
||||
defaultSelectedElementIndex={0}>
|
||||
<React.Suspense fallback={null}>
|
||||
<Suspender target={id} />
|
||||
</React.Suspense>
|
||||
</Contexts>,
|
||||
),
|
||||
false,
|
||||
);
|
||||
expect(getInspectedElementPath).not.toBeNull();
|
||||
expect(inspectedElement).not.toBeNull();
|
||||
expect(inspectedElement).toMatchSnapshot('1: Initially inspect element');
|
||||
|
||||
inspectedElement = null;
|
||||
TestUtils.act(() => {
|
||||
TestRenderer.act(() => {
|
||||
getInspectedElementPath(id, ['props', 'set_of_sets', 0]);
|
||||
jest.runOnlyPendingTimers();
|
||||
});
|
||||
});
|
||||
expect(inspectedElement).not.toBeNull();
|
||||
expect(inspectedElement).toMatchSnapshot('2: Inspect props.set_of_sets.0');
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
it('should include updates for nested values that were previously hydrated', async done => {
|
||||
const Example = () => null;
|
||||
|
||||
@@ -846,4 +1082,46 @@ describe('InspectedElementContext', () => {
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
it('should inspect hooks for components that only use context', async done => {
|
||||
const Context = React.createContext(true);
|
||||
const Example = () => {
|
||||
const value = React.useContext(Context);
|
||||
return value;
|
||||
};
|
||||
|
||||
const container = document.createElement('div');
|
||||
await utils.actAsync(() =>
|
||||
ReactDOM.render(<Example a={1} b="abc" />, container),
|
||||
);
|
||||
|
||||
const id = ((store.getElementIDAtIndex(0): any): number);
|
||||
|
||||
let didFinish = false;
|
||||
|
||||
function Suspender({target}) {
|
||||
const {getInspectedElement} = React.useContext(InspectedElementContext);
|
||||
const inspectedElement = getInspectedElement(id);
|
||||
expect(inspectedElement).toMatchSnapshot(`1: Inspected element ${id}`);
|
||||
didFinish = true;
|
||||
return null;
|
||||
}
|
||||
|
||||
await utils.actAsync(
|
||||
() =>
|
||||
TestRenderer.create(
|
||||
<Contexts
|
||||
defaultSelectedElementID={id}
|
||||
defaultSelectedElementIndex={0}>
|
||||
<React.Suspense fallback={null}>
|
||||
<Suspender target={id} />
|
||||
</React.Suspense>
|
||||
</Contexts>,
|
||||
),
|
||||
false,
|
||||
);
|
||||
expect(didFinish).toBe(true);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -126,13 +126,38 @@ Object {
|
||||
"context": {},
|
||||
"hooks": null,
|
||||
"props": {
|
||||
"html_element": {},
|
||||
"fn": {},
|
||||
"symbol": {},
|
||||
"react_element": {},
|
||||
"array_buffer": {},
|
||||
"typed_array": {},
|
||||
"date": {}
|
||||
"date": {},
|
||||
"fn": {},
|
||||
"html_element": {},
|
||||
"immutable": {
|
||||
"0": {},
|
||||
"1": {},
|
||||
"2": {}
|
||||
},
|
||||
"map": {
|
||||
"0": {},
|
||||
"1": {}
|
||||
},
|
||||
"map_of_maps": {
|
||||
"0": {},
|
||||
"1": {}
|
||||
},
|
||||
"react_element": {},
|
||||
"set": {
|
||||
"0": "abc",
|
||||
"1": 123
|
||||
},
|
||||
"set_of_sets": {
|
||||
"0": {},
|
||||
"1": {}
|
||||
},
|
||||
"symbol": {},
|
||||
"typed_array": {
|
||||
"0": 100,
|
||||
"1": -100,
|
||||
"2": 0
|
||||
}
|
||||
},
|
||||
"state": null
|
||||
},
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
// @flow
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {InspectedElementPayload} from 'react-devtools-shared/src/backend/types';
|
||||
import type {DehydratedData} from 'react-devtools-shared/src/devtools/views/Components/types';
|
||||
@@ -23,7 +30,11 @@ describe('InspectedElementContext', () => {
|
||||
dehydratedData: DehydratedData | null,
|
||||
): Object | null {
|
||||
if (dehydratedData !== null) {
|
||||
return hydrate(dehydratedData.data, dehydratedData.cleaned);
|
||||
return hydrate(
|
||||
dehydratedData.data,
|
||||
dehydratedData.cleaned,
|
||||
dehydratedData.unserializable,
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
@@ -132,22 +143,41 @@ describe('InspectedElementContext', () => {
|
||||
});
|
||||
|
||||
it('should support complex data types', async done => {
|
||||
const Immutable = require('immutable');
|
||||
|
||||
const Example = () => null;
|
||||
|
||||
const div = document.createElement('div');
|
||||
const exmapleFunction = () => {};
|
||||
const typedArray = new Uint8Array(3);
|
||||
const exampleFunction = () => {};
|
||||
const setShallow = new Set(['abc', 123]);
|
||||
const mapShallow = new Map([['name', 'Brian'], ['food', 'sushi']]);
|
||||
const setOfSets = new Set([new Set(['a', 'b', 'c']), new Set([1, 2, 3])]);
|
||||
const mapOfMaps = new Map([['first', mapShallow], ['second', mapShallow]]);
|
||||
const typedArray = Int8Array.from([100, -100, 0]);
|
||||
const immutableMap = Immutable.fromJS({
|
||||
a: [{hello: 'there'}, 'fixed', true],
|
||||
b: 123,
|
||||
c: {
|
||||
'1': 'xyz',
|
||||
xyz: 1,
|
||||
},
|
||||
});
|
||||
|
||||
act(() =>
|
||||
ReactDOM.render(
|
||||
<Example
|
||||
html_element={div}
|
||||
fn={exmapleFunction}
|
||||
symbol={Symbol('symbol')}
|
||||
react_element={<span />}
|
||||
array_buffer={typedArray.buffer}
|
||||
typed_array={typedArray}
|
||||
date={new Date()}
|
||||
fn={exampleFunction}
|
||||
html_element={div}
|
||||
immutable={immutableMap}
|
||||
map={mapShallow}
|
||||
map_of_maps={mapOfMaps}
|
||||
react_element={<span />}
|
||||
set={setShallow}
|
||||
set_of_sets={setOfSets}
|
||||
symbol={Symbol('symbol')}
|
||||
typed_array={typedArray}
|
||||
/>,
|
||||
document.createElement('div'),
|
||||
),
|
||||
@@ -159,37 +189,77 @@ describe('InspectedElementContext', () => {
|
||||
expect(inspectedElement).toMatchSnapshot('1: Initial inspection');
|
||||
|
||||
const {
|
||||
html_element,
|
||||
fn,
|
||||
symbol,
|
||||
react_element,
|
||||
array_buffer,
|
||||
typed_array,
|
||||
date,
|
||||
fn,
|
||||
html_element,
|
||||
immutable,
|
||||
map,
|
||||
map_of_maps,
|
||||
react_element,
|
||||
set,
|
||||
set_of_sets,
|
||||
symbol,
|
||||
typed_array,
|
||||
} = inspectedElement.value.props;
|
||||
expect(html_element[meta.inspectable]).toBe(false);
|
||||
expect(html_element[meta.name]).toBe('DIV');
|
||||
expect(html_element[meta.type]).toBe('html_element');
|
||||
expect(fn[meta.inspectable]).toBe(false);
|
||||
expect(fn[meta.name]).toBe('exmapleFunction');
|
||||
expect(fn[meta.type]).toBe('function');
|
||||
expect(symbol[meta.inspectable]).toBe(false);
|
||||
expect(symbol[meta.name]).toBe('Symbol(symbol)');
|
||||
expect(symbol[meta.type]).toBe('symbol');
|
||||
expect(react_element[meta.inspectable]).toBe(false);
|
||||
expect(react_element[meta.name]).toBe('span');
|
||||
expect(react_element[meta.type]).toBe('react_element');
|
||||
|
||||
expect(array_buffer[meta.size]).toBe(3);
|
||||
expect(array_buffer[meta.inspectable]).toBe(false);
|
||||
expect(array_buffer[meta.name]).toBe('ArrayBuffer');
|
||||
expect(array_buffer[meta.type]).toBe('array_buffer');
|
||||
expect(typed_array[meta.size]).toBe(3);
|
||||
expect(typed_array[meta.inspectable]).toBe(false);
|
||||
expect(typed_array[meta.name]).toBe('Uint8Array');
|
||||
expect(typed_array[meta.type]).toBe('typed_array');
|
||||
|
||||
expect(date[meta.inspectable]).toBe(false);
|
||||
expect(date[meta.type]).toBe('date');
|
||||
|
||||
expect(fn[meta.inspectable]).toBe(false);
|
||||
expect(fn[meta.name]).toBe('exampleFunction');
|
||||
expect(fn[meta.type]).toBe('function');
|
||||
|
||||
expect(html_element[meta.inspectable]).toBe(false);
|
||||
expect(html_element[meta.name]).toBe('DIV');
|
||||
expect(html_element[meta.type]).toBe('html_element');
|
||||
|
||||
expect(immutable[meta.inspectable]).toBeUndefined(); // Complex type
|
||||
expect(immutable[meta.name]).toBe('Map');
|
||||
expect(immutable[meta.type]).toBe('iterator');
|
||||
|
||||
expect(map[meta.inspectable]).toBeUndefined(); // Complex type
|
||||
expect(map[meta.name]).toBe('Map');
|
||||
expect(map[meta.type]).toBe('iterator');
|
||||
expect(map[0][meta.type]).toBe('array');
|
||||
|
||||
expect(map_of_maps[meta.inspectable]).toBeUndefined(); // Complex type
|
||||
expect(map_of_maps[meta.name]).toBe('Map');
|
||||
expect(map_of_maps[meta.type]).toBe('iterator');
|
||||
expect(map_of_maps[0][meta.type]).toBe('array');
|
||||
|
||||
expect(react_element[meta.inspectable]).toBe(false);
|
||||
expect(react_element[meta.name]).toBe('span');
|
||||
expect(react_element[meta.type]).toBe('react_element');
|
||||
|
||||
expect(set[meta.inspectable]).toBeUndefined(); // Complex type
|
||||
expect(set[meta.name]).toBe('Set');
|
||||
expect(set[meta.type]).toBe('iterator');
|
||||
expect(set[0]).toBe('abc');
|
||||
expect(set[1]).toBe(123);
|
||||
|
||||
expect(set_of_sets[meta.inspectable]).toBeUndefined(); // Complex type
|
||||
expect(set_of_sets[meta.name]).toBe('Set');
|
||||
expect(set_of_sets[meta.type]).toBe('iterator');
|
||||
expect(set_of_sets['0'][meta.inspectable]).toBe(true);
|
||||
|
||||
expect(symbol[meta.inspectable]).toBe(false);
|
||||
expect(symbol[meta.name]).toBe('Symbol(symbol)');
|
||||
expect(symbol[meta.type]).toBe('symbol');
|
||||
|
||||
expect(typed_array[meta.inspectable]).toBeUndefined(); // Complex type
|
||||
expect(typed_array[meta.size]).toBe(3);
|
||||
expect(typed_array[meta.name]).toBe('Int8Array');
|
||||
expect(typed_array[meta.type]).toBe('typed_array');
|
||||
expect(typed_array[0]).toBe(100);
|
||||
expect(typed_array[1]).toBe(-100);
|
||||
expect(typed_array[2]).toBe(0);
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
// @flow
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
describe('Store (legacy)', () => {
|
||||
let React;
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
// @flow
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import typeof ReactTestRenderer from 'react-test-renderer';
|
||||
import type {Element} from 'react-devtools-shared/src/devtools/views/Components/types';
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
// @flow
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import typeof ReactTestRenderer from 'react-test-renderer';
|
||||
import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
|
||||
@@ -136,7 +143,7 @@ describe('ProfilerContext', () => {
|
||||
expect(context.didRecordCommits).toBe(false);
|
||||
expect(context.isProcessingData).toBe(false);
|
||||
expect(context.isProfiling).toBe(false);
|
||||
expect(context.profilingData).not.toBe(null);
|
||||
expect(context.profilingData).toBe(null);
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
// @flow
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type Store from 'react-devtools-shared/src/devtools/store';
|
||||
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
// @flow
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import typeof ReactTestRenderer from 'react-test-renderer';
|
||||
import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
// @flow
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import typeof TestRendererType from 'react-test-renderer';
|
||||
import type Store from 'react-devtools-shared/src/devtools/store';
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
// @flow
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import typeof TestRendererType from 'react-test-renderer';
|
||||
import type Store from 'react-devtools-shared/src/devtools/store';
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
// @flow
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
describe('profiling utils', () => {
|
||||
let utils;
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
// @flow
|
||||
|
||||
import storage from 'local-storage-fallback';
|
||||
|
||||
// In case async/await syntax is used in a test.
|
||||
import 'regenerator-runtime/runtime';
|
||||
'use strict';
|
||||
|
||||
// DevTools stores preferences between sessions in localStorage
|
||||
if (!global.hasOwnProperty('localStorage')) {
|
||||
global.localStorage = storage;
|
||||
global.localStorage = require('local-storage-fallback').default;
|
||||
}
|
||||
|
||||
// Mimic the global we set with Webpack's DefinePlugin
|
||||
global.__DEV__ = process.env.NODE_ENV !== 'production';
|
||||
global.__TEST__ = process.env.NODE_ENV === 'test';
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
// @flow
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {
|
||||
BackendBridge,
|
||||
@@ -27,9 +34,17 @@ env.beforeEach(() => {
|
||||
const originalConsoleError = console.error;
|
||||
// $FlowFixMe
|
||||
console.error = (...args) => {
|
||||
if (args[0] === 'Warning: React DevTools encountered an error: %s') {
|
||||
const firstArg = args[0];
|
||||
if (firstArg === 'Warning: React DevTools encountered an error: %s') {
|
||||
// Rethrow errors from React.
|
||||
throw args[1];
|
||||
} else if (
|
||||
typeof firstArg === 'string' &&
|
||||
firstArg.startsWith("Warning: It looks like you're using the wrong act()")
|
||||
) {
|
||||
// DevTools intentionally wraps updates with acts from both DOM and test-renderer,
|
||||
// since test updates are expected to impact both renderers.
|
||||
return;
|
||||
}
|
||||
originalConsoleError.apply(console, args);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
// @flow
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
describe('Store', () => {
|
||||
let React;
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
// @flow
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
|
||||
import type Store from 'react-devtools-shared/src/devtools/store';
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
// @flow
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
const {printOwnersList} = require('../devtools/utils');
|
||||
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
// @flow
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
describe('StoreStress (Sync Mode)', () => {
|
||||
let React;
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
// @flow
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
describe('StoreStressConcurrent', () => {
|
||||
let React;
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
// @flow
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import typeof ReactTestRenderer from 'react-test-renderer';
|
||||
import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
|
||||
|
||||
173
packages/react-devtools-shared/src/__tests__/useEditableValue-test.js
vendored
Normal file
173
packages/react-devtools-shared/src/__tests__/useEditableValue-test.js
vendored
Normal file
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
describe('useEditableValue', () => {
|
||||
let act;
|
||||
let React;
|
||||
let ReactDOM;
|
||||
let useEditableValue;
|
||||
|
||||
beforeEach(() => {
|
||||
const utils = require('./utils');
|
||||
act = utils.act;
|
||||
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
|
||||
useEditableValue = require('../devtools/views/hooks').useEditableValue;
|
||||
});
|
||||
|
||||
it('should override editable state when external props are updated', () => {
|
||||
let state;
|
||||
|
||||
function Example({value}) {
|
||||
const tuple = useEditableValue(value);
|
||||
state = tuple[0];
|
||||
return null;
|
||||
}
|
||||
|
||||
const container = document.createElement('div');
|
||||
ReactDOM.render(<Example value={1} />, container);
|
||||
expect(state.editableValue).toEqual('1');
|
||||
expect(state.externalValue).toEqual(1);
|
||||
expect(state.parsedValue).toEqual(1);
|
||||
expect(state.hasPendingChanges).toBe(false);
|
||||
expect(state.isValid).toBe(true);
|
||||
|
||||
// If there are NO pending changes,
|
||||
// an update to the external prop value should override the local/pending value.
|
||||
ReactDOM.render(<Example value={2} />, container);
|
||||
expect(state.editableValue).toEqual('2');
|
||||
expect(state.externalValue).toEqual(2);
|
||||
expect(state.parsedValue).toEqual(2);
|
||||
expect(state.hasPendingChanges).toBe(false);
|
||||
expect(state.isValid).toBe(true);
|
||||
});
|
||||
|
||||
it('should not override editable state when external props are updated if there are pending changes', () => {
|
||||
let dispatch, state;
|
||||
|
||||
function Example({value}) {
|
||||
const tuple = useEditableValue(value);
|
||||
state = tuple[0];
|
||||
dispatch = tuple[1];
|
||||
return null;
|
||||
}
|
||||
|
||||
const container = document.createElement('div');
|
||||
ReactDOM.render(<Example value={1} />, container);
|
||||
expect(state.editableValue).toEqual('1');
|
||||
expect(state.externalValue).toEqual(1);
|
||||
expect(state.parsedValue).toEqual(1);
|
||||
expect(state.hasPendingChanges).toBe(false);
|
||||
expect(state.isValid).toBe(true);
|
||||
|
||||
// Update (local) editable state.
|
||||
act(() =>
|
||||
dispatch({
|
||||
type: 'UPDATE',
|
||||
editableValue: '2',
|
||||
externalValue: 1,
|
||||
}),
|
||||
);
|
||||
expect(state.editableValue).toEqual('2');
|
||||
expect(state.externalValue).toEqual(1);
|
||||
expect(state.parsedValue).toEqual(2);
|
||||
expect(state.hasPendingChanges).toBe(true);
|
||||
expect(state.isValid).toBe(true);
|
||||
|
||||
// If there ARE pending changes,
|
||||
// an update to the external prop value should NOT override the local/pending value.
|
||||
ReactDOM.render(<Example value={3} />, container);
|
||||
expect(state.editableValue).toEqual('2');
|
||||
expect(state.externalValue).toEqual(3);
|
||||
expect(state.parsedValue).toEqual(2);
|
||||
expect(state.hasPendingChanges).toBe(true);
|
||||
expect(state.isValid).toBe(true);
|
||||
});
|
||||
|
||||
it('should parse edits to ensure valid JSON', () => {
|
||||
let dispatch, state;
|
||||
|
||||
function Example({value}) {
|
||||
const tuple = useEditableValue(value);
|
||||
state = tuple[0];
|
||||
dispatch = tuple[1];
|
||||
return null;
|
||||
}
|
||||
|
||||
const container = document.createElement('div');
|
||||
ReactDOM.render(<Example value={1} />, container);
|
||||
expect(state.editableValue).toEqual('1');
|
||||
expect(state.externalValue).toEqual(1);
|
||||
expect(state.parsedValue).toEqual(1);
|
||||
expect(state.hasPendingChanges).toBe(false);
|
||||
expect(state.isValid).toBe(true);
|
||||
|
||||
// Update (local) editable state.
|
||||
act(() =>
|
||||
dispatch({
|
||||
type: 'UPDATE',
|
||||
editableValue: '"a',
|
||||
externalValue: 1,
|
||||
}),
|
||||
);
|
||||
expect(state.editableValue).toEqual('"a');
|
||||
expect(state.externalValue).toEqual(1);
|
||||
expect(state.parsedValue).toEqual(1);
|
||||
expect(state.hasPendingChanges).toBe(true);
|
||||
expect(state.isValid).toBe(false);
|
||||
});
|
||||
|
||||
it('should reset to external value upon request', () => {
|
||||
let dispatch, state;
|
||||
|
||||
function Example({value}) {
|
||||
const tuple = useEditableValue(value);
|
||||
state = tuple[0];
|
||||
dispatch = tuple[1];
|
||||
return null;
|
||||
}
|
||||
|
||||
const container = document.createElement('div');
|
||||
ReactDOM.render(<Example value={1} />, container);
|
||||
expect(state.editableValue).toEqual('1');
|
||||
expect(state.externalValue).toEqual(1);
|
||||
expect(state.parsedValue).toEqual(1);
|
||||
expect(state.hasPendingChanges).toBe(false);
|
||||
expect(state.isValid).toBe(true);
|
||||
|
||||
// Update (local) editable state.
|
||||
act(() =>
|
||||
dispatch({
|
||||
type: 'UPDATE',
|
||||
editableValue: '2',
|
||||
externalValue: 1,
|
||||
}),
|
||||
);
|
||||
expect(state.editableValue).toEqual('2');
|
||||
expect(state.externalValue).toEqual(1);
|
||||
expect(state.parsedValue).toEqual(2);
|
||||
expect(state.hasPendingChanges).toBe(true);
|
||||
expect(state.isValid).toBe(true);
|
||||
|
||||
// Reset editable state
|
||||
act(() =>
|
||||
dispatch({
|
||||
type: 'RESET',
|
||||
externalValue: 1,
|
||||
}),
|
||||
);
|
||||
expect(state.editableValue).toEqual('1');
|
||||
expect(state.externalValue).toEqual(1);
|
||||
expect(state.parsedValue).toEqual(1);
|
||||
expect(state.hasPendingChanges).toBe(false);
|
||||
expect(state.isValid).toBe(true);
|
||||
});
|
||||
});
|
||||
40
packages/react-devtools-shared/src/__tests__/utils-test.js
vendored
Normal file
40
packages/react-devtools-shared/src/__tests__/utils-test.js
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import {getDisplayName} from 'react-devtools-shared/src/utils';
|
||||
|
||||
describe('utils', () => {
|
||||
describe('getDisplayName', () => {
|
||||
it('should return a function name', () => {
|
||||
function FauxComponent() {}
|
||||
expect(getDisplayName(FauxComponent)).toEqual('FauxComponent');
|
||||
});
|
||||
|
||||
it('should return a displayName name if specified', () => {
|
||||
function FauxComponent() {}
|
||||
FauxComponent.displayName = 'OverrideDisplayName';
|
||||
expect(getDisplayName(FauxComponent)).toEqual('OverrideDisplayName');
|
||||
});
|
||||
|
||||
it('should return the fallback for anonymous functions', () => {
|
||||
expect(getDisplayName(() => {}, 'Fallback')).toEqual('Fallback');
|
||||
});
|
||||
|
||||
it('should return Anonymous for anonymous functions without a fallback', () => {
|
||||
expect(getDisplayName(() => {})).toEqual('Anonymous');
|
||||
});
|
||||
|
||||
// Simulate a reported bug:
|
||||
// https://github.com/facebook/react/issues/16685
|
||||
it('should return a fallback when the name prop is not a string', () => {
|
||||
const FauxComponent = {name: {}};
|
||||
expect(getDisplayName(FauxComponent, 'Fallback')).toEqual('Fallback');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,11 @@
|
||||
// @flow
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import typeof ReactTestRenderer from 'react-test-renderer';
|
||||
|
||||
@@ -18,11 +25,13 @@ export function act(callback: Function): void {
|
||||
});
|
||||
|
||||
// Flush Bridge operations
|
||||
actDOM(() => {
|
||||
actTestRenderer(() => {
|
||||
jest.runAllTimers();
|
||||
while (global.mockGetTimersCount() > 0) {
|
||||
actDOM(() => {
|
||||
actTestRenderer(() => {
|
||||
jest.runAllTimers();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function actAsync(
|
||||
@@ -40,7 +49,8 @@ export async function actAsync(
|
||||
});
|
||||
|
||||
if (recursivelyFlush) {
|
||||
while (jest.getTimerCount() > 0) {
|
||||
// TODO (Jest v24) Replace with jest.getTimerCount() after we upgrade Jest
|
||||
while (global.mockGetTimersCount() > 0) {
|
||||
// $FlowFixMe Flow doens't know about "await act()" yet
|
||||
await actDOM(async () => {
|
||||
await actTestRenderer(async () => {
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
// @flow
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {BoxStyle} from './types';
|
||||
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
// @flow
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import Agent from 'react-devtools-shared/src/backend/agent';
|
||||
import resolveBoxStyle from './resolveBoxStyle';
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
// @flow
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
export type BoxStyle = $ReadOnly<{|
|
||||
bottom: number,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user