├── .circleci
└── config.yml
├── .eslintignore
├── .eslintrc.js
├── .github
└── CODEOWNERS
├── .gitignore
├── .npmignore
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
├── SECURITY.md
├── api
├── commands
│ ├── help.js
│ └── run.js
├── index.js
├── metro
│ ├── aliases
│ │ ├── empty.js
│ │ ├── index.js
│ │ └── react-native.js
│ ├── babel.js
│ ├── configure.js
│ ├── source.js
│ └── template.js
├── server.js
└── tmp
│ └── .gitkeep
├── app.json
├── bin
└── ekke
├── components
├── ekke.js
├── loading.js
└── renderer.js
├── development.js
├── docs
└── ekke-react-native-intro.gif
├── ekke.js
├── examples
├── example.mocha.js
└── example.tape.js
├── index.js
├── native
├── bridge.js
├── constants.js
├── evaluator.js
├── lifecycle.js
├── screen.js
├── subway.js
└── uncaught.js
├── package-lock.json
├── package.json
├── production.js
├── runners
├── index.js
├── mocha.js
└── tape.js
└── test
├── .babelrc
├── ekke
├── bridge.test.js
├── components
│ ├── ekke.test.js
│ ├── loading.test.js
│ └── renderer.test.js
├── development.test.js
├── evaluator.test.js
├── production.test.js
└── subway.test.js
├── mock
├── errorutils.js
├── index.js
└── servers.js
└── native
├── bridge.test.js
├── lifecycle.test.js
├── packagejson.test.js
├── subway.test.js
└── uncaught.test.js
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | # Javascript Node CircleCI 2.0 configuration file
2 | #
3 | # Check https://circleci.com/docs/2.0/language-javascript/ for more details
4 | #
5 | version: 2
6 | jobs:
7 | build:
8 | docker:
9 | # specify the version you desire here
10 | - image: circleci/node:10.15.3
11 |
12 | # Specify service dependencies here if necessary
13 | # CircleCI maintains a library of pre-built images
14 | # documented at https://circleci.com/docs/2.0/circleci-images/
15 | # - image: circleci/mongo:3.4.4
16 |
17 | working_directory: ~/repo
18 |
19 | steps:
20 | - checkout
21 |
22 | # Download and cache dependencies
23 | - restore_cache:
24 | keys:
25 | - v1-dependencies-{{ checksum "package.json" }}
26 | # fallback to using the latest cache if no exact match is found
27 | - v1-dependencies-
28 |
29 | - run: yarn install
30 |
31 | - save_cache:
32 | paths:
33 | - node_modules
34 | key: v1-dependencies-{{ checksum "package.json" }}
35 |
36 | # run tests!
37 | - run: yarn run test:nodejs
38 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | api/metro/template.js
2 | api/tmp/*.js
3 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | extends: ['godaddy-react'],
4 | globals: {
5 | '__DEV__': 'readonly',
6 | 'ErrorUtils': 'readonly'
7 | },
8 | rules: {
9 | 'no-process-env': 0,
10 | 'max-statements': 0,
11 | 'no-new-func': 0,
12 | 'complexity': 0,
13 | 'no-console': 0,
14 | 'no-sync': 0
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | #
2 | # Default:
3 | #
4 | # These users will be automatically assigned as reviewer for everything in the
5 | # repository unless a different match is made.
6 | #
7 | * @3rd-Eden @swaagie
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (https://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # TypeScript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
60 | # next.js build output
61 | .next
62 |
63 | # We don't want our build files to show up
64 | api/tmp/*
65 | !api/tmp/.gitkeep
66 |
67 | # The files are added using the `react-native eject` command for development
68 | ios
69 | android
70 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (https://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # TypeScript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
60 | # next.js build output
61 | .next
62 |
63 | # We don't want our build files to show up
64 | api/tmp/*
65 | !api/tmp/.gitkeep
66 |
67 | # The files are added using the `react-native eject` command for development
68 | ios
69 | android
70 |
71 | # Above this line should be a copy of our .gitignore
72 | # Additional files that need to be ignore before publish
73 | .github
74 | .circleci
75 | test*
76 | docs
77 | examples
78 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # CHANGELOG
2 |
3 | ### 1.1.0
4 |
5 | - [#20] Support Windows Paths
6 | - [#19] Use a custom path for the Metro cache, this change also enables the
7 | cache by default again. So if you want to kill the cache specifically for
8 | you tests you need to use the `--reset-cache` flag for that.
9 |
10 | ### 1.0.2
11 |
12 | - [#3] Add missing repository field in package.json
13 | - [#4] Correctly reference Metro in the README
14 |
15 | ### 1.0.1
16 |
17 | - [#1] Corrected the image path in the `README` so it correctly shows up on
18 | the npm packages page: https://www.npmjs.com/package/ekke
19 |
20 | ### 1.0.0
21 |
22 | - Initial release
23 |
24 | [#1]: https://github.com/godaddy/ekke/pull/1
25 | [#3]: https://github.com/godaddy/ekke/pull/3
26 | [#4]: https://github.com/godaddy/ekke/pull/4
27 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies within all project spaces, and it also applies when
49 | an individual is representing the project or its community in public spaces.
50 | Examples of representing a project or community include using an official
51 | project e-mail address, posting via an official social media account, or acting
52 | as an appointed representative at an online or offline event. Representation of
53 | a project may be further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at oss@godaddy.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
78 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2019 GoDaddy Operating Company, LLC.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # `EKKE`
2 |
3 | [](https://greenkeeper.io/)
4 |
5 | #### [Ekke-Ekke-Ekke-Ekke][NI] PTANG Zoo Boing! Z' nourrwringmm[...][Ekke]
6 |
7 | `ekke` a unique new test runner for React-Native. Unlike other testing
8 | frameworks, it doesn't execute your tests in Node.js, with a bunch of mocks, but
9 | instead, it orchestrates the bundling and execution of tests directly **inside
10 | your React-Native application**. `ekke` allows your tests to fully access every
11 | API to the platform has to offer.
12 |
13 | ### Why should you adopt Ekke for React-Native testing?
14 |
15 | - **Platform independent** The test runner **does not contain any native code**
16 | that means that every platform that React-Native supports now, or in the
17 | future will work out of the box.
18 | - **Code confidence** Tests run in the environment of your production code. No
19 | need to rely on imperfect mocks. Tests run on devices to guarantee API's match
20 | specifications.
21 | - **Different test runners** At its core, Ekke is nothing more than an
22 | orchestration tool. We have built-in support for different test runners.
23 | - **Rendering** It's not just unit tests. Ekke provides a rendering API that
24 | allows you to mount and render components in your test suite.
25 |
26 |
27 |
28 |
29 | Ekke in action: running a test suite, streaming results back to the CLI
30 |
31 |
32 | ## Installation
33 |
34 | The module is released in the public NPM Registry and can be installed by running:
35 |
36 | ```bash
37 | npm install --save ekke
38 | ```
39 |
40 | After installation, you can [integrate](#integration) `ekke` into your project.
41 |
42 | ## Table of Contents
43 |
44 | - [Installation](#installation)
45 | - [Integration](#integration)
46 | - [Runners](#runners)
47 | - [mocha](#mocha)
48 | - [Tape](#tape)
49 | - [API](#API)
50 | - [Component](#component)
51 | - [render](#render)
52 | - [CLI](#cli)
53 | - [run](#run)
54 | - [help](#help)
55 | - [Debugging](#debugging)
56 | - [The Ekke CLI](#the-ekke-cli)
57 | - [React Native Component](#react-native-component)
58 |
59 | ## Integration
60 |
61 | Ekke needs a host application to run. That can either be the application you are
62 | currently developing or fresh installation of `react-native init`.
63 |
64 | Not sure what to pick?
65 |
66 | - **Application developers** Using the application that you're currently
67 | developing is recommended. This allows your test suites to execute in
68 | precisely the same environment. Also, it will enable your test suites to
69 | leverage any `NativeModule` that your application might be consuming.
70 | - **Library authors** It depends on the library you're developing. If you are
71 | building a native add-on, or include `NativeModules` as dependencies, it's
72 | advised to create an example application in your project that has all native
73 | libraries linked. TODO: What is the alternative here, if there is one?
74 |
75 | Integrating is as easy as importing `ekke`, and including the [component] in
76 | your app!
77 |
78 | ```js
79 | import { Ekke } from 'ekke';
80 |
81 | function App() {
82 | return (
83 | <>
84 |
85 |
86 |
87 | Your App Here
88 |
89 | >
90 | )
91 | }
92 | ```
93 |
94 | You can now run your tests by executing the [run] command of the `ekke` CLI:
95 |
96 | ```sh
97 | # Make sure that the simulator of your choice is running.
98 | react-native run-ios # or react-native run-android
99 |
100 | ekke run glob/pattern/*.test.js more/test/files.js --using mocha
101 | ```
102 |
103 | And now watch the magic unfold on your app.
104 |
105 | > If you are worried about shipping `Ekke` in your application, the component is
106 | > using [process.env.NODE_ENV][env] to switch between [development][dev] and
107 | > [production][prod] builds. Production builds will completely remove `Ekke`
108 | > from your code. You can also conditionally include it `{ __DEV__ && }`.
109 |
110 | ## Runners
111 |
112 | At its core, Ekke is nothing more than an orchestration tool, it runs
113 | [Metro][metro] bundler with a specific configuration, executes code
114 | automatically in the React Native environment, and reports back in the CLI. To
115 | run the test, we need to know which test runner you prefer so we can bundle it
116 | with the tests. The following runners are available:
117 |
118 | - [mocha](#mocha)
119 | - [tape](#tape)
120 |
121 | ### mocha
122 |
123 | To use [Mocha](https://mochajs.org/) make sure you have the testing framework as
124 | well as an assertion framework installed in your project:
125 |
126 | ```bash
127 | npm install --save-dev mocha
128 | npm install --save-dev assume # or any other assert framework, e.g. chai
129 | ```
130 |
131 | Once all your dependencies finish installing, you can start writing your tests.
132 |
133 | ```js
134 | import { describe, it } from 'mocha';
135 | import { render } from 'ekke';
136 | import assume from 'assume';
137 |
138 | describe('The best test suite in the world', function () {
139 | it('is amazing', function () {
140 | const amazing = 'amazing';
141 |
142 | assume(amazing).is.a('string');
143 | assume(!!amazing).is.true();
144 | });
145 | });
146 | ```
147 |
148 | Provide `mocha` as value to the `--using` flag to select it as test runner.
149 |
150 | ```bash
151 | ekke run test.js --using mocha
152 | ```
153 |
154 | The following Mocha options can be customized using the follow CLI flags:
155 |
156 | - `--mocha.fgrep` Only run tests containing this string.
157 | **Defaults to `''`**.
158 | - `--mocha.grep` Only run tests matching this string.
159 | **Defaults to `''`**.
160 | - `--mocha.invert` Inverts grep and fgrep matches.
161 | **Defaults to `false`**.
162 | - `--mocha.ui` Specify user interface.
163 | **Defaults to `bdd`**.
164 | - `--mocha.reporter` Specify reporter to use.
165 | **Defaults to `spec`**.
166 | - `--mocha.slow` Specify "slow" test threshold (in milliseconds).
167 | **Defaults to `75`**.
168 | - `--mocha.timeout` Specify the test timeout threshold (in milliseconds).
169 | **Defaults to `2000`**.
170 | - `--mocha.bail` Abort ("bail") after first test failure.
171 | **Defaults to `true`**.
172 | - `--mocha.color` Force-enable color output.
173 | **Defaults to `true`**.
174 | - `--mocha.inlineDiffs` Display actual/expected differences inline within each string.
175 | **Defaults to `true`**.
176 |
177 | ```bash
178 | ekke run test.js --using mocha --mocha.reporter min --mocha.timeout 5000
179 | ```
180 |
181 | ### Tape
182 |
183 | Using [tape](https://github.com/substack/tape) as the test runner is pretty
184 | self-explanatory. You import `tape` into your test files and write your tests
185 | and assertions using provided by the framework.
186 |
187 | ```js
188 | import { render } from 'ekke';
189 | import test from 'tape';
190 |
191 | test('one', function (t) {
192 | t.plan(2);
193 | t.ok(true);
194 |
195 | setTimeout(function () {
196 | t.equal(1+3, 4);
197 | }, 100);
198 | });
199 | ```
200 |
201 | Once the tests are completed, simple tell `ekke` that you're `--using tape` to
202 | run the tests.
203 |
204 | ```bash
205 | ekke run test.js --using tape
206 | ```
207 |
208 | The will run your tests, and output the TAP (Test. Anything. Protocol) to your
209 | terminal which you can pipe to any of the [Pretty Tap
210 | Reporters](https://github.com/substack/tape#pretty-reporters) that you might
211 | have installed. For example, if you want to use `tap-spec`:
212 |
213 | ```bash
214 | ekke run test.js --using tape | tap-spec
215 | ```
216 |
217 | ## API
218 |
219 | The API exposes the following methods:
220 |
221 | - [component](#component)
222 | - [render](#render)
223 |
224 | ### Component
225 |
226 | ```js
227 | import { Ekke } from 'ekke';
228 | ```
229 |
230 | The `Ekke` component controls the orchestration, execution, and rendering of the
231 | test suite. The component can be used as a wrapper, or as a regular component
232 | and be included in your application.
233 |
234 | The component accepts the following **optional** properties:
235 |
236 | - `interval`, **String**, The component doesn't know when you are using the CLI,
237 | so it polls at a given interval, with an HTTP request, to the server that runs
238 | in the CLI to figure out if it's active and is waiting for tests to run. The
239 | lower the interval, the quicker your tests will be picked up by the component,
240 | but also the more performance it takes away from your app.
241 | **Defaults to `10 seconds`**.
242 | - `hostname`, **String**, The hostname of your machine that the CLI server is
243 | running on.
244 | **Defaults to `localhost` on iOS and `10.0.2.2` on Android**.
245 | - `port`, **Number**, The port number that the CLI server is running on.
246 | **Defaults to `1975`**.
247 | - `alive`, **Function**, Function to execute when the `Ekke` test runner is
248 | activated and is about to run the test suites.
249 |
250 | ```js
251 | //
252 | // Stand alone.
253 | //
254 |
255 |
256 | //
257 | // Or wrap your app with it, you decide what is best for your application.
258 | //
259 |
260 |
261 |
262 | ```
263 |
264 | To see an example of the implementation, take a look at our [index.js][index]
265 | file. It's what we use to test our code.
266 |
267 | ### render
268 |
269 | ```js
270 | import { render } from 'ekke';
271 | ```
272 |
273 | The render method allows you to render any React-Native component on the screen
274 | of the application.
275 |
276 | ```js
277 | import { View } from 'react-native';
278 | import { render } from 'ekke';
279 | import React from 'react';
280 |
281 | describe('test', function () {
282 | it('renders a red square', function () {
283 | const ref = React.createRef();
284 | await render();
289 |
290 | //
291 | // You can now use ref.current to access the rendered view.
292 | // Not only that, but there's big red square on your app as well.
293 | //
294 | });
295 | });
296 | ```
297 |
298 | ## CLI
299 |
300 | The `ekke` CLI is automatically installed in your `node_modules` when you
301 | install `ekke` in the project as a dependency. We use the CLI to communicate
302 | between the `` component that you included in your application and the
303 | terminal.
304 |
305 | The CLI should **not be globally installed**. Instead, you directly reference
306 | the locally installed binary from your `package.json`.
307 |
308 | ```json
309 | {
310 | "name": "your-super-awesome-package",
311 | "scripts": {
312 | "test": "ekke run test/*.js --using mocha"
313 | }
314 | }
315 | ```
316 |
317 | And run the scripts using `npm`.
318 |
319 | ```bash
320 | npm test
321 |
322 | # You can use the double dash support from npm to send additional flags:
323 | # npm test -- --watch
324 | ```
325 |
326 | Alternatively, you can use the `npx` command to execute the commands as well
327 | without the requirement of global installation of `ekke`:
328 |
329 | ```bash
330 | npx ekke
331 | ```
332 |
333 | The following CLI commands are available:
334 |
335 | - [run](#run)
336 | - [help](#help)
337 |
338 | ### run
339 |
340 | ```bash
341 | ekke run --flags
342 | ```
343 |
344 | The `run` command allows you to run your specified tests on the device that
345 | included the `` React component. When you run the command, we will
346 | execute the following:
347 |
348 | - Start up the Metro bundler on the specified `hostname` and `port`.
349 | - Attach a WebSocket server on the created Metro bundler, for communication purposes.
350 | - Find all the test files you want to include based on the supplied glob.
351 | - Wait for the poll request from the `` component.
352 | - Bundle all your tests, and the specified library using Metro Bundler.
353 | - Listen to progress events that are sent by `` component over the
354 | WebSocket connection.
355 | - Proxy all the `console.*`, `process.stdout` to your terminal.
356 | - Close the CLI again with error code `0` or `1` depending on if your tests pass.
357 |
358 | The `run` command assumes that all CLI arguments after the `run` command are the
359 | test files that need to execute inside React-Native. We allow single or multiple
360 | files, a [glob][glob] pattern, or a combination of files and globs.
361 |
362 | ```bash
363 | # Executes test/1.js, test/2.js, and then test/2.js using the mocha runner.
364 | ekke run test/1.js test/2.js test/3.js --using mocha
365 |
366 | # The same above, but done using a glob pattern to fetch all .js files
367 | # from the test directory
368 | ekke run test/*.js --using mocha
369 |
370 | # Executes test/1.js and then all the .test.js files
371 | ekke run test/1.js test/*.test.js --using tape
372 | ```
373 |
374 | You can use the following CLI flags to change the behavior of the command:
375 |
376 | - `--using {runner}` This tells Ekke which runner should be used to execute your
377 | tests.
378 | **Defaults to `mocha`** See [Runners](#runners) for all runners.
379 | - `--watch` By default, we close the process with either an exit code `0` or `1`
380 | as an indication of the test results (0 passes, 1 failure). If you do not want
381 | the process to exit, you can use the `--watch` flag to keep the CLI process
382 | alive.
383 | **Defaults to false.**
384 | - `--reset-cache` The Metro Bundler caching system is enabled by default so
385 | that tests run in a performant way and don't always rebuild. Using this
386 | flag will disable the cache.
387 | **Defaults to false.**
388 | - `--cache-location` We already made sure that the Metro Bundler cache of your
389 | test doesn't collide with your test cache, but if you like to store it
390 | somewhere else you can change it with this flag.
391 | **Defaults to `os.tempdir()/ekke-cache`.**
392 | - `--no-silent` We silence the output of the Metro bundler by default, this
393 | allows you to see the Metro bundler output again.
394 | **Defaults to false.**
395 | - `--hostname` The hostname we should attach our Metro Bundler on. The hostname
396 | should be accessible by React-Native application.
397 | **Defaults to `localhost`.**
398 | - `--port` The port number we should use for the Metro Bundler, we don't want to
399 | clash with the Metro bundler of your `react-native start` command so it should
400 | be different, but still accessible by your React-Native application.
401 | **Defaults to `1975`** (The year Monty Python and the Holy Grail got released)
402 | - `--require` Files that should be required before your test suites are required
403 | **Defaults to ¯\_(ツ)_/¯, nothing**
404 |
405 | In addition to these default flags, any flag that you prefix with the name of
406 | the runner will be considered as options and used as configuration:
407 |
408 | ```bash
409 | ekke run test.js --using mocha --mocha.timeout 3000
410 | ```
411 |
412 | See the [Runners](#runners) for their specific configuration flags.
413 |
414 | ### help
415 |
416 | Display a list of all the available command their supported CLI flags. The help
417 | message is visible when you run `ekke` without, an unknown, or the `help`
418 | command:
419 |
420 | ```
421 |
422 | ekke (v1.0.2)
423 | Ekke-Ekke-Ekke-Ekke-PTANG. Zoo-Boing. Z' nourrwringmm...
424 |
425 | COMMANDS:
426 |
427 | run Run the given glob of test files.
428 | --port Port number that Metro Bundler should use.
429 | --hostname Hostname that Metro Bundler should use.
430 | --using Name of the test runner to use.
431 | --watch Don't exit when the tests complete but keep listening.
432 | --no-silent Do not suppress the output of Metro.
433 | --require Require module (before tests are executed).
434 | --reset-cache Clear the Metro cache.
435 | --cache-location Change the Metro cache location.
436 | help Displays this help message.
437 | --no-color Disable colors in help message.
438 |
439 | EXAMPLES:
440 |
441 | $ ekke run ./test/*.test.js --using mocha
442 |
443 | ```
444 |
445 | The output contains colors by default if you wish to remove those you can use
446 | the `--no-color` flag.
447 |
448 | ## Debugging
449 |
450 | Both the `CLI` and the `react-native` code bases use [diagnostics] under the
451 | hood for logging purposes. The logs are disabled by default but can be enabled
452 | by using the `DEBUG` feature flags. They both log under the `ekke:*` namespace.
453 |
454 | #### The Ekke CLI
455 |
456 | ```js
457 | DEBUG=ekke* ekke
458 | ```
459 |
460 | #### React Native Component
461 |
462 | ```js
463 | import { AsyncStorage } from 'react-native';
464 |
465 | AsyncStorage.setItem('debug', 'ekke*', function () {
466 | //
467 | // Reload your app, and the debug logs will now be enabled.
468 | //
469 | });
470 | ```
471 |
472 | For more detailed information about [diagnostics], please see their project page.
473 |
474 | ## Development
475 |
476 | - **Fork** Fork the repository to create a copy to your own GitHub account.
477 | - **Clone** Clone the newly created GitHub repo to your local machine.
478 | - **Branch** Create a fresh new branch from the master branch.
479 | - **Install** Run `npm install` to install dependencies and devDependencies.
480 | - **Setup** Run `npm run setup` to create development specific folders.
481 | - **Hack** Make your changes. Write tests covering your changes.
482 | - **Test** Run both `npm test` and `npm test:ekke` to ensure nothing got broken.
483 | - **Push** Commit and push your changes to fork.
484 | - **Pull Request** Create a pull request from your created branch to our master.
485 | - **Review** We'll review your change, and ask for updates if need.
486 | - **Merge** Virtual high fives are exchanged when your PR lands.
487 |
488 | ## License
489 |
490 | [MIT](LICENSE)
491 |
492 | [diagnostics]: https://github.com/3rd-Eden/diagnostics
493 | [metro]: https://github.com/facebook/metro
494 | [NI]: https://en.wikipedia.org/wiki/Knights_Who_Say_%22Ni!%22
495 | [Ekke]: https://youtu.be/RZvsGdJP3ng?t=17
496 | [env]: ./index.js
497 | [dev]: ./development.js
498 | [prod]: ./production.js
499 | [component]: #component
500 | [run]: #run
501 | [index]: ./index.js
502 | [glob]: https://www.npmjs.com/package/glob
503 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Reporting Security Issues
2 |
3 | We take security very seriously at GoDaddy. We appreciate your efforts to
4 | responsibly disclose your findings, and will make every effort to acknowledge
5 | your contributions.
6 |
7 | ## Where should I report security issues?
8 |
9 | In order to give the community time to respond and upgrade, we strongly urge you
10 | report all security issues privately.
11 |
12 | To report a security issue in one of our Open Source projects email us directly
13 | at **oss@godaddy.com** and include the word "SECURITY" in the subject line.
14 |
15 | This mail is delivered to our Open Source Security team.
16 |
17 | After the initial reply to your report, the team will keep you informed of the
18 | progress being made towards a fix and announcement, and may ask for additional
19 | information or guidance.
20 |
--------------------------------------------------------------------------------
/api/commands/help.js:
--------------------------------------------------------------------------------
1 | const { paint, stripper } = require('es-paint');
2 |
3 | /**
4 | * Output the help information when users are exploring our CLI application.
5 | *
6 | * @param {Object} API Our command API.
7 | * @param {Object} flags CLI flags.
8 | * @public
9 | */
10 | module.exports = async function help(API, flags) {
11 | const painter = flags.color === false ? stripper : paint;
12 | const { name, version } = require('../../package.json');
13 |
14 | console.log(painter`
15 | ${name} (v${version}|>#00A63F)
16 | ${"Ekke-Ekke-Ekke-Ekke-PTANG. Zoo-Boing. Z' nourrwringmm..."}|>#00A63F
17 |
18 | COMMANDS:
19 |
20 | ${'run'}|>#00A63F Run the given glob of test files.
21 | ${'--port'}|>dimgray Port number that Metro Bundler should use.
22 | ${'--hostname'}|>dimgray Hostname that Metro Bundler should use.
23 | ${'--using'}|>dimgray Name of the test runner to use.
24 | ${'--watch'}|>dimgray Don't exit when the tests complete but keep listening.
25 | ${'--no-silent'}|>dimgray Do not suppress the output of Metro.
26 | ${'--require'}|>dimgray Require module (before tests are executed).
27 | ${'--reset-cache'}|>dimgray Clear the Metro cache.
28 | ${'--cache-location'}|>dimgray Change the Metro cache location.
29 | ${'help'}|>#00A63F Displays this help message.
30 | ${'--no-color'}|>dimgray Disable colors in help message.
31 |
32 | EXAMPLES:
33 |
34 | $ ${'ekke run ./test/*.test.js'}|>dimgray --using mocha
35 | `);
36 | };
37 |
--------------------------------------------------------------------------------
/api/commands/run.js:
--------------------------------------------------------------------------------
1 | const metro = require('../server');
2 |
3 | /**
4 | * Start our servers.
5 | *
6 | * @param {Object} API Our command API.
7 | * @param {Object} flags CLI flags.
8 | * @public
9 | */
10 | module.exports = async function run({ debug, ekke }, flags) {
11 | const { ws } = await metro(flags);
12 |
13 | ws.on('connection', (socket) => {
14 | const runner = flags.using;
15 | const opts = flags[runner] || {};
16 |
17 | /**
18 | * Helper function providing a consistent message interface with the
19 | * Component.
20 | *
21 | * @param {String} event Name of the event.
22 | * @param {Object|Array} payload Data to transfer.
23 | * @public
24 | */
25 | function send(event, payload) {
26 | return new Promise(function sender(resolve, reject) {
27 | socket.send(JSON.stringify({ event, payload }), function written(e) {
28 | if (e) return reject(e);
29 |
30 | resolve();
31 | });
32 | });
33 | }
34 |
35 | send('run', { ...flags, opts });
36 | socket.on('message', (message) => {
37 | let event, payload;
38 |
39 | try {
40 | ({ event, payload } = JSON.parse(message));
41 | } catch (e) {
42 | debug('failed to decode received JSON payload', message);
43 | return;
44 | }
45 |
46 | debug('received message', message);
47 | ekke.emit(event, payload, send);
48 |
49 | if (event === 'complete') {
50 | if (payload.length) {
51 | const failure = payload[0];
52 |
53 | //
54 | // Very specific failure, it seems like our Metro bundler
55 | // produced an error while bundling, lets output this to our
56 | // users for feedback.
57 | //
58 | if (failure && typeof failure === 'object') {
59 | if (failure.errors) {
60 | failure.errors.forEach((err) => {
61 | console.error(err.description);
62 | });
63 | } else {
64 | console.error(failure);
65 | }
66 | } else {
67 | console.error.apply(console, payload);
68 | }
69 |
70 | if (!flags.watch) process.exit(1);
71 | } else if (!flags.watch) process.exit(0);
72 | }
73 | });
74 | });
75 | };
76 |
--------------------------------------------------------------------------------
/api/index.js:
--------------------------------------------------------------------------------
1 | const EventEmitter = require('eventemitter3');
2 | const diagnostics = require('diagnostics');
3 | const cli = require('argh').argv;
4 | const os = require('os');
5 | const path = require('path');
6 |
7 | /**
8 | * A basic API, and CLI interface for Ekke. The only difference between
9 | * the API and CLI is that you manually need to pass in the options into
10 | * the constructor, and call our API methods.
11 | *
12 | * @constructor
13 | * @param {Object} [options] Parsed CLI arguments, or options object.
14 | * @public
15 | */
16 | class Ekke extends EventEmitter {
17 | constructor(options = cli) {
18 | super();
19 |
20 | const ekke = this;
21 |
22 | /**
23 | * Merge the options, with our defaults to create our API configuration.
24 | *
25 | * @returns {Object} The merged configuration.
26 | * @public
27 | */
28 | this.config = () => ({
29 | argv: [],
30 | ...Ekke.defaults,
31 | ...options
32 | });
33 |
34 | this.ekke = {
35 | ekke: {
36 | ekke: {
37 | /**
38 | * If you're reading this your WTF-a-minute's must be sky rocketing
39 | * right now. Yes, I actually made an `ekke` object, with multiple
40 | * `ekke` keys, just so I could:
41 | *
42 | * ekke.ekke.ekke.ekke.PTANGZooBoingZnourrwringmm();
43 | *
44 | * To start the CLI application.
45 | *
46 | * @public
47 | */
48 | async PTANGZooBoingZnourrwringmm() {
49 | const flags = ekke.config();
50 |
51 | //
52 | // Attempt to find the command we should be running. If no command or
53 | // an unknown command is given, we're just gonna assume that the user
54 | // needs help, and redirect them to our CLI help command.
55 | //
56 | // Also by using `flags.argv.shift()` we also ensure that we remove
57 | // our command name from the `argv` so the command that is called
58 | // will have a clean `flags.argv` that contains only the stuff
59 | // it needs.
60 | //
61 | let command = flags.argv.shift() || 'help';
62 | if (typeof ekke[command] !== 'function') command = 'help';
63 |
64 | await ekke[command](flags);
65 | }
66 | }
67 | }
68 | };
69 |
70 | this.initialize();
71 | }
72 |
73 | /**
74 | * Initialize our API.
75 | *
76 | * @public
77 | */
78 | initialize() {
79 | //
80 | // Introduce our commands to the prototype.
81 | //
82 | Object.entries(Ekke.commands).forEach(([method, fn]) => {
83 | this[method] = fn.bind(this, {
84 | debug: diagnostics(`ekke:${method}`),
85 | ekke: this
86 | });
87 | });
88 |
89 | //
90 | // Proxy the log events to the correct console.
91 | //
92 | this.on('log', args => console.log(...args));
93 | this.on('warn', args => console.warn(...args));
94 | this.on('info', args => console.info(...args));
95 | this.on('error', args => console.error(...args));
96 |
97 | //
98 | // Generic event handling
99 | //
100 | this.on('ping', (args, send) => send('pong', args));
101 | }
102 | }
103 |
104 | /**
105 | * The different commands that we're supporting.
106 | *
107 | * @type {Object}
108 | * @public
109 | */
110 | Ekke.commands = {
111 | run: require('./commands/run'),
112 | help: require('./commands/help')
113 | };
114 |
115 | /**
116 | * The default CLI flags that are being used.
117 | *
118 | * @type {Object}
119 | * @public
120 | */
121 | Ekke.defaults = {
122 | 'hostname': 'localhost', // Hostname we should create our server upon
123 | 'port': 1975, // The port number of the created server
124 | 'silent': true, // Silence Metro bundler
125 | 'reset-cache': false, // Turn off the Metro bundler cache
126 | 'cache-location': path.join(os.tmpdir(), 'ekke-cache') // Metro bundler cacheStores location
127 | };
128 |
129 | //
130 | // Expose our API class.
131 | //
132 | module.exports = Ekke;
133 |
--------------------------------------------------------------------------------
/api/metro/aliases/empty.js:
--------------------------------------------------------------------------------
1 | module.exports = {};
2 |
--------------------------------------------------------------------------------
/api/metro/aliases/index.js:
--------------------------------------------------------------------------------
1 | const libs = require('node-libs-react-native');
2 | const path = require('path');
3 |
4 | //
5 | // Provide an empty.
6 | //
7 | const empty = path.join(__dirname, 'empty.js');
8 | module.exports = {
9 | ...libs,
10 |
11 | //
12 | // So the `node-libs-react-native` does a pretty poor job as being a polyfill
13 | // for Node.js modules as it doesn't provide good defaults and randomly
14 | // decides that some of these modules should be gone. In a future revision
15 | // we should extract this logic to a seperate module, so we don't have
16 | // to depend on `process` and `stream-browserify` our self and have
17 | // apply additional fixes to get this working.
18 | //
19 | 'fs': empty,
20 | 'child_process': empty,
21 | 'dgram': empty,
22 | 'cluster': empty,
23 | 'dns': empty,
24 | 'module': empty,
25 | 'net': empty,
26 | 'readline': empty,
27 | 'repl': empty,
28 | 'tls': empty,
29 | 'vm': empty,
30 | 'stream': require.resolve('stream-browserify'),
31 |
32 | //
33 | // We need to force mocha to the node version instead of the browser
34 | // version in order for it to compile.
35 | //
36 | 'mocha': require.resolve('mocha/lib/mocha'),
37 |
38 | //
39 | // Prevent duplicate React-Native execution, the polyfill will re-use the
40 | // existing React-Native import that is bundled with the application. This
41 | // will ensure that all NativeModules are correctly registered.
42 | //
43 | 'react-native': path.join(__dirname, 'react-native.js'),
44 |
45 | //
46 | // Needed for our own testing, we just want to require `ekke`, which
47 | // will point to the root of our repo.
48 | //
49 | 'ekke': path.join(__dirname, '..', '..', '..', 'ekke.js')
50 | };
51 |
--------------------------------------------------------------------------------
/api/metro/aliases/react-native.js:
--------------------------------------------------------------------------------
1 | const bridge = global['@ Ekke Ekke Ekke Ekke @'];
2 | const ReactNative = bridge.ReactNative;
3 |
4 | /**
5 | * Create a copy of the requireNativeComponent function as we're going
6 | * to override it when we export again.
7 | *
8 | * @type {Function}
9 | * @public
10 | */
11 | const requireNativeComponent = ReactNative.requireNativeComponent;
12 |
13 | /**
14 | * Native components register their components using the `requireNativeComponent`
15 | * method. Unfortunately you cannot register the same component twice, and
16 | * that is what will happen when load in the JavaScript of previously bundled
17 | * native modules. So we're going to override the method, silence these
18 | * invariant errors.
19 | *
20 | * @param {String} name Unique name of the component to register.
21 | * @returns {Component} Native Component
22 | * @public
23 | */
24 | function interceptNativeComponent(name) {
25 | try {
26 | return requireNativeComponent(name);
27 | } catch (e) {
28 | return name;
29 | }
30 | }
31 |
32 | //
33 | // All the exports of React-Native are defined using getter so they can
34 | // lazy load the components that an application requires, and insert
35 | // deprecation warnings. This also means we can't just simply override the
36 | // exports, but need to use `defineProperty` to introduce them.
37 | //
38 | Object.defineProperty(ReactNative, 'requireNativeComponent', {
39 | get: () => interceptNativeComponent
40 | });
41 |
42 | /**
43 | * Re-Expose the React-Native that we stored in our Ekke global.
44 | *
45 | * @type {Object}
46 | * @public
47 | */
48 | module.exports = ReactNative;
49 |
--------------------------------------------------------------------------------
/api/metro/babel.js:
--------------------------------------------------------------------------------
1 | const { getCacheKey } = require('metro-react-native-babel-transformer');
2 | const { transformSync } = require('@babel/core');
3 | const aliases = require('./aliases');
4 |
5 | /**
6 | * Generates all the required presets for babel transformation.
7 | *
8 | * @param {Array} existing Any presets would have existed before.
9 | * @param {Object} options Options provided to the transform.
10 | * @returns {Array} All combined presets.
11 | * @public
12 | */
13 | function presets(existing = [], options) {
14 | const { experimentalImportSupport, ...presetOptions } = options;
15 |
16 | return [
17 | [require('metro-react-native-babel-preset'), {
18 | ...presetOptions,
19 |
20 | disableImportExportTransform: experimentalImportSupport,
21 | enableBabelRuntime: options.enableBabelRuntime
22 | }],
23 |
24 | ...existing
25 | ];
26 | }
27 |
28 | /**
29 | * The various of plugins that we need to make things happen.
30 | *
31 | * @param {Array} existing Any presets would have existed before.
32 | * @param {Object} options Options provided to the transform.
33 | * @returns {Array} All combined presets.
34 | * @public
35 | */
36 | function plugins(existing = [], options) {
37 | const optional = [];
38 |
39 | if (options.inlineRequires) {
40 | optional.push(require('babel-preset-fbjs/plugins/inline-requires'));
41 | }
42 |
43 | return [
44 | [require('babel-plugin-rewrite-require'), {
45 | throwForNonStringLiteral: true,
46 | aliases
47 | }],
48 |
49 | ...optional,
50 | ...existing
51 | ];
52 | }
53 |
54 | /**
55 | * A custom babel transformer, this gives us finegrain control over the
56 | * transformation process, and more power than some of the metro options
57 | * that are provided.
58 | *
59 | * @param {Object} transformOptions Our transformer options.
60 | * @returns {Object} Our transformed AST.
61 | * @public
62 | */
63 | function transform(transformOptions) {
64 | const { filename, options, src } = transformOptions;
65 |
66 | //
67 | // Development builds are controlled by the `options` that are specified
68 | // by the metro bundler, not using env variables that a user might has set
69 | // so we need to ensure that we nuke their babel-env and force it to the
70 | // right execution env.
71 | //
72 | const old = process.env.BABEL_ENV;
73 | process.env.BABEL_ENV = options.dev ? 'development' : old || 'production';
74 |
75 | try {
76 | const result = transformSync(src, {
77 | caller: {
78 | name: 'metro',
79 | platform: options.platform
80 | },
81 | ast: true,
82 | babelrc: !!options.enableBabelRCLookup,
83 | code: false,
84 | highlightCode: true,
85 | filename: filename,
86 | presets: presets([], options),
87 | plugins: plugins(transformOptions.plugins, options),
88 | sourceType: 'unambiguous'
89 | });
90 |
91 | //
92 | // When a file is ignored, it doesn't return a result, so we need to
93 | // optionally return the transformed AST.
94 | //
95 | return result
96 | ? { ast: result.ast }
97 | : { ast: null };
98 | } finally {
99 | process.env.BABEL_ENV = old;
100 | }
101 | }
102 |
103 | //
104 | // Expose the transform method.
105 | //
106 | module.exports = {
107 | getCacheKey,
108 | transform,
109 | presets,
110 | plugins
111 | };
112 |
--------------------------------------------------------------------------------
/api/metro/configure.js:
--------------------------------------------------------------------------------
1 | const { mergeConfig, loadConfig } = require('metro-config');
2 | const { FileStore } = require('metro-cache');
3 | const resolve = require('metro-resolver').resolve;
4 | const diagnostics = require('diagnostics');
5 | const source = require('./source');
6 | const path = require('path');
7 |
8 | //
9 | // Debug logger.
10 | //
11 | const debug = diagnostics('ekke:configure');
12 |
13 | /**
14 | * Generate the contents of the metro.config that should be used to build
15 | * the tests.
16 | *
17 | * @param {Object} flags The configuration flags of the API/CLI.
18 | * @returns {Promise