Did you run into issues? Great! Tell me here and I'll see how I can help you.
Do you want to contribute code? First, I'd like to thank you in advance for your contribution. I'll take any help I can get.
Here's an overview of the high level contributing process.
Obligatory disclaimer: By contributing to this project, you agree to make available any and all code submitted under the ISC License and/or other relevant licenses.
- Install Node and npm. You can find installers and instructions here, or if you use Linux, look here instead.
- Note that if you're using Windows, you need to ensure during setup that it also installs both
node
andnpm
onto your PATH.
- Note that if you're using Windows, you need to ensure during setup that it also installs both
- Install Git. You'll need this to clone the source.
- Note that if you're using Windows, you need to ensure during setup that it also installs
git
onto your PATH.
- Note that if you're using Windows, you need to ensure during setup that it also installs
- Fork this repo.
- Run
git clone https://github.com/<your-github-username>/thallium.git
to get the source and link it to your GitHub account. - Run
cd thallium
to enter that directory, so the rest of the commands work. - Run
git remote add upstream https://github.com/isiahmeadows/thallium.git
to set the upstream URL. - Create a new branch with
git checkout -b <some-branch>
. - Hack away, and fix whatever you need to. Don't forget to add tests for anything that was fixed.
If you ever need to sync your fork/branch with upstream, do this:
- Run
git pull upstream master
. - If it prompts you to make a merge commit, just save the file as-is.
- If it prompts you to resolve a merge conflict, that means you need to manually edit some of the files so Git knows what actually changed.
Once you're ready to submit your changes, do this:
- If you're fixing something, make sure your fix is tested. Otherwise, you're not quite ready to submit them.
- Make sure your tests pass locally. This will make my life and your life much easier.
- If you don't know why something is failing, feel free to submit it anyways, so I can help you out.
- Run
git commit
. This will bring up an editor so you can describe your changes. - Run
git push -u origin <some-branch>
to sync your new branch with the upstream repository. The branch name must match the one you initially created. - On GitHub, select the branch you just sync'd, and open a pull request. I'll take a look at it and work with you to ensure it works as well as possible.
Here's some information and utilities to help you out in hacking on this project.
I have a make script to assist in most of the common tasks.
node make
- Lint and test everything.node make lint
- Lint everything (with ESLint and CoffeeLint).node make test
- Run all the tests once in the system version of Node and in Chrome.node make watch
- Do the above, but also run them all on each file change.node make test:chrome
- Run all the tests in Chrome only.node make test:node
- Run all the tests in Node only.node make watch:chrome
- Run all the tests in Chrome only on each file change.node make watch:node
- Run all the tests in Node only on each file change.
I personally frequently use node make watch:node
when working on this, so I have relatively quick feedback on how ready my work is.
This is tested in Travis CI on Ubuntu against the following runtimes:
- Node on current LTS and later on Windows, Linux (Ubuntu), and OS X
- PhantomJS 2 on Windows, Linux (Ubuntu), and OS X
- Chrome Stable on Linux (Ubuntu)
- Firefox Stable, ESR, and Beta on Linux (Ubuntu)
For similar reasons, this is written in pure ES5 due to compatibility concerns. Some features still need polyfilled for older browsers, and the way this is written doesn't really need many ES6 features. See the tips and tricks later on for some workarounds I've created for this.
The code within the documentation and examples generally use anything stage 4 or later, including all the ES6 things like modules and arrow functions, and other very new, recently added features like async functions.
Note that I don't actually test the documentation's code, but please ensure it otherwise matches the code style elsewhere, and that it is actually correct.
-
bin
- The executables live here. Note that the binaries should also be directly linked to from thepackage.json
. -
r
- The home of all reporters. Nothing goes here except for reporter modules part of the public API. -
lib
- The core of this project. Many public API modules are just thin wrappers for something in here, including the main export. -
lib/api
- The core API. Both the primaryt
andreflect
APIs are defined here. -
lib/core
- The core test state and execution logic. Handle with care, since it's probably the most heavily used. Bugs in this can and often will affect seemingly unrelated tests. Also, the report types are defined here. -
lib/cli
- This contains 90% of the logic for the CLI. Dependency injection is heavily used so I don't have to create dozens of file system fixtures and useproxyquire
extensively. -
lib/reporter
- This contains common logic for the reporters. -
lib/replaced
- This contains anything replaced going from Node to Browserify. -
docs
- The documentation for this project, 100% Markdown. -
docs/examples
- This contains several examples of various things. -
migrate
- This contains all the code shimming most of the old behavior where applicable. -
test
- This contains all the tests. Mocha is currently used as the test runner, and the assertions are fully self-hosted. Using Thallium to test Thallium is awesome! -
fixtures
- This contains the fixtures for the various tests.- Some of the
test
files are mirrored in CoffeeScript withinfixtures/large-coffee
to help aid in more real-world usage. These are very explicitly and clearly labeled on the top, so it should be very hard to miss if you're looking at those files.
- Some of the
-
scripts
- This contains various development scripts. It's generally uninteresting unless you like looking at shell scripts. -
test-util
- This contains various test-related utilities, including the mocks.
-
This is linted with ESLint, and uses my
isiahmeadows/commonjs
preset for the main code base andisiahmeadows/es6
for the examples. -
CoffeeLint is used to lint the few CoffeeScript files littered around, mostly there for testing and examples.
-
When requiring a file, don't include the extension or
/index
, except for explicitly./index
and../index
(which avoids an ambiguity with Node, and./.
is not very obvious). It also helps keep therequire
calls a little cleaner. -
Classes are used, but mostly as C-like structs. Inheritance is minimized. They are usually used for ADTs and grouping state, and functions are preferred for callbacks and one-off things that don't involve delaying execution.
-
File names are lower cased, and namespaces are capitalized like constructors, except for ones imported from Node builtins and ones treated as values.
-
exports.foo = bar
is preferred overmodule.exports.foo = bar
, but default exports likemodule.exports = foo
are okay, as long as that's the only thing exported. -
Named exports are also preferred to static members on default exports. For example:
// Good exports.Test = Test function Test(name, index) { this.name = name this.index = index } exports.timeout = function (test) { while (!test.timeout && test.root !== test) { test = test.parent } return test.timeout || 2000 // ms - default timeout } // Bad module.exports = Test function Test(name, index) { this.name = name this.index = index } Test.timeout = function (test) { while (!test.timeout && test.root !== test) { test = test.parent } return test.timeout || 2000 // ms - default timeout }
-
All non-deterministic tests/groups of tests are suffixed with
(FLAKE)
. This includes part of one of the end-to-end fixtures. This helps me know at a glance whether rerunning it is an option, since they might fail even when working otherwise as intended (e.g. a timer taking 20 milliseconds longer than expected, or areaddir
returning files in a different order than usual).
-
I use ES6 promises extensively, because it makes the code so much easier to handle.
-
There is a class-ish
methods
swiss army knifehelper here which is used throughout. This is one of the main reasons why I don't really need ES6 beyond promises - it even handles inheritance and non-enumerability of methods. It's used to define the API, simplify the internal DSL for the core reporters, and decouple script loading in the CLI. The report types are a good example on how this can be used, since it covers most ways you can use this. Don't overuse it, though, mainly because ESLint doesn't catch undefined properties, and object oriented code itself often drives up the boilerplate unnecessarily. -
Lazy iteration of a list can be done by taking a callback and calling it when you're ready with a value:
/** * Serializes `argv` into a list of tokens. */ function serialize(argv, call) { var boolean = true for (var i = 0; i < argv.length; i++) { var entry = argv[i] if (entry === "--") { // Delegate to another function by passing the `call` parameter. serializeRest(boolean, argv, i + 1, call) break } if (!boolean || entry[0] !== "-") { // Yield a value. call({type: "value", value: entry, boolean: boolean}) boolean = true continue } // etc. } }
-
If you need an equivalent of
for ... of
to iterate things likeMap
orSet
:var iter = coll.values() for (var next = iter.next(); !next.done; next = iter.next()) { var value = next.value // do things... }
-
If you're on Linux and have
nvm
installed, there's a littlescripts/test.sh
script in the root you can run, which will test everything Travis will on your local machine, installing versions that don't exist if necessary. Note that it doesn't update existing installations for you, so if things need updated, that's on you to address. It's not quite that magical, and I don't suspect you'd want it overwriting your stuff, either. -
For the tests, feel free to use the framework's own plugin and reporter system to your advantage to simplify your testing. They are very well tested, and if any of the assertions or plugin/reporter APIs break, you'll know it immediately. For example, I used
t.reporter
withassert.match
to test the reporter output throughout the tests.