Pull requests are welcome! Here are some specific contributions we're looking for:
-
Let us know how Quixote works for you. Download the code, try it out, and let me know how it worked for you. Create an issue if anything didn't work smoothly or if you had trouble understanding something.
-
Add descriptors to support for more CSS properties. Quixote can test any CSS element using the
QElement.getRawStyle()
method, but theQElement.assert()
method is more sophisticated. (It works consistently across browsers, allows relative comparisons, and provides better failure messages.)QElement.assert()
uses descriptors and we'd eventually like to have descriptors for every CSS property. To contribute new descriptors, see the Architecture section below. -
Create a logo. I'm imagining Don Quixote jousting with a CSS windmill, but feel free to let your imagination run wild.
-
Start the website. I've set up quixote-css.com for documentation, but so far, everything's just in markdown files. The website source code is in docs. The Object Playground site and stylesheet are a good starting point.
-
Tell your friends and colleagues. Even if you can't contribute anything, spreading the word is a big help. Let people know about Quixote and why you like it.
If you're planning on making a pull request, here's a helpful checklist:
-
Create an issue so we know what you're working on and other people who are interested can coordinate with you.
-
Fork the Quixote repository on GitHub.
-
Set up your computer by following the "Working with the Code" instructions below.
-
Create a branch for your changes. Start from the
master
branch. It has the latest known-good code.$ git checkout master $ git checkout -b <your_branch>
-
Make your changes, committing as desired. When you're done, make sure the tests pass.
-
Push your changes to GitHub:
git push
. -
Create a pull request and add a comment referencing the issue it addresses.
Thank you!
To work with the code on your own computer:
- Install Node.js.
- Clone the GitHub repository:
git clone https://github.com/jamesshore/quixote.git
- All commands must run from the root of the source tree:
cd quixote
. - Run the tests as described below. They should pass. If not, open an issue or contact me @jamesshore on Twitter.
- Run
./jake.sh karma
to start the Karma server. - Start the browsers you want to test and point each one at
http://localhost:9876
. - Run
./jake.sh loose=true
to build and test, or./watch.js loose=true
to automatically rebuild when you make a change. You can use./jake.sh quick loose=true
(or./watch.js quick loose=true
) to only build files that have changed.
If you get a timeout error in __reset.js
, particularly on Safari or Chrome, it's probably due to the window being hidden or a different tab being shown. Safari and Chrome deprioritizes tabs that aren't visible, which causes the tests to timeout. To fix the issue, make sure some portion of the Karma page is visible.
At this time, the build has only been tested on Mac OS X. It should also work on Unix without any trouble. It's theoretically capable of working on Windows, but needs some script work before that's easy and convenient.
You can pass the following options to ./jake.sh
and ./watch.js
:
-
-T
displays available build targets. -
loose=true
prevents the build from failing if you don't test every browser. -
itonly=true
disables Mocha's "grep" option, allowing the.only
option to work in the tests. -
capture=Firefox,Safari,etc
automatically launches, uses, and quits the requested browsers. You can use this instead of running./jake.sh karma
and manually starting the browsers yourself. It's most useful for automated build runners such as Travis CI. Note that you may need to install the appropriate launchers; e.g.,npm install karma-safari-launcher
.
You won't need to run these scripts, but in case you're curious:
./integrate.sh
validates the dev branch and merges it into the known-good master branch../release.sh
publishes to npm, github, and our documentation site.
All the Quixote source and test code is in src
. Test code starts with an underscore. The src
directory uses the following structure:
src
contains our top-level API.src/descriptors
contains descriptors: objects that describe how a CSS value can be calculated and displayed. (See the Architecture section below for more details.)src/values
contains values: objects that represent a calculated result.
Other top-level directories contain infrastructure and support.
build
contains build scripts and configuration.docs
contains API docs and the placeholder website (online at quixote-css.com).dist
contains the compiled library.spikes
contains one-off experiments.src
contains the source code and tests. Test code starts with an underscore.node_modules
contains third-party libraries needed for the build scripts.vendor
contains third-party libraries needed for Quixote itself.
There's a tutorial for creating new descriptors in the descriptors directory and another tutorial for creating value objects in the values directory.
master
is the known-good integration branch. This branch should always build and pass its tests.dev
contains my work in progress.
Previous commits on the integration branch have "INTEGRATE" in their commit comment.
This discussion assumes you're familiar with the Quixote API.
Internally, Quixote's architecture revolves around two core concepts: "Descriptors" and "Values." Descriptor objects represent some aspect of the page, such as "the top edge of element '#foo'", and value objects represent tangible values, such as "60px".
Descriptors and values are implemented with a classical inheritance hierarchy. Descriptor classes ultimately inherit from the Descriptor
superclass and value classes inherit from the Value
superclass. Descriptor classes are located in the src/descriptors
folder and value classes are located in src/values
.
Descriptors are turned into values with the value()
method implemented by every descriptor class. (This method is for internal use only.) When you make an assertion in Quixote, value()
is called on both descriptors, then equals()
is called on the two values to check if they're the same. (This algorithm is implemented by diff()
in descriptor.js
.)
Descriptor and Value objects are always instantiated via factory methods, never via constructors.
Descriptor classes correspond to some as yet uncalculated aspect of the page. Each descriptor class is focused on one concept, such as ElementEdge
, which represents the edges of HTML elements.
Descriptor classes often inherit from a superclass that provides a useful public API. For example, ElementEdge
inherits from PositionDescriptor
, which provides the plus()
and minus()
methods.
Descriptor classes have three main features:
-
They have factory methods that are used to expose the descriptor in the public API. For example, the public
QElement.top
descriptor is instantiated inQElement.js
using this line of code:this.top = ElementEdge.top(this);
. (You can see the implementation ofElementEdge.top
inelement_edge.js
.) -
They calculate values via a
value()
method. All CSS logic happens in this method. For example,ElementEdge.value()
calculates the position of an edge by addingQElement.getRawPosition()
andQFrame.getRawScrollPosition()
. -
They provide human-readable descriptions via the
toString()
method. For example,ElementEdge.toString()
returns strings such as "top edge of 'element'".
Descriptor classes are usually less than 100 lines long. That's because there's one for each kind of CSS calculation. Find them in the src/descriptors
folder.
Value classes correspond to concrete units. Each value class represents one unit, such as Position
, which represents an X or Y coordinate on the web page, or Size
, which represents width or height.
Value classes are sometimes composed from a more fundamental value. For example, both Position
and Size
use a Pixels
object under the covers.
Value classes have five main features:
-
They have factory methods that descriptor classes use in their
value()
method. -
They perform calculations if needed by descriptors. For example,
Position
providesplus()
,minus()
, andmidpoint()
calculation methods. -
They check for compatibility via a
compatibility()
method. If two value objects that are incompatible are used together, Quixote will fail fast and throw a useful exception. For example, if you try to add a Position object to a Size, Quixote will throw an error with the message, "Size isn't compatible with Position." -
They provide a human-readable comparison via a
diff()
method. This method checks if two value objects are equal. If they aren't, it returns a human-readable explanation. For example,Position.diff()
returns strings such as "10px higher". (There's also a traditionalequals()
method that's implemented in theValue
superclass.) -
They provide a human-readable description via the
toString()
method. For example,Position.toString()
returns strings such as "60px".
Value classes are usually less than 100 lines long. The logic required isn't very complex. Find them in the src/values
folder.
For use by the project maintainer.
- Start from clean (integrated) dev branch.
- Merge pull request into dev branch using
git pull
line listed under the "command line instructions" link at the bottom of the pull request conversation thread. - Revise and make additional commits as needed.
- Credit contributor at bottom of README.md
- Run
./integrate.sh
to integrate dev branch into master branch. - Close pull request. Include a comment saying which version will include the result.
For use by the project maintainer.
- Remove any temporary branches (list with
git branch
, delete withgit branch -d <name>
) - Update readme and API documentation as needed.
- Update changelog and roadmap.
- Run
./integrate.sh
to integrate dev branch into master branch. - Run
./release [major | minor | patch]
to release. The release script releases to npm and github. - Publicize.