Giter Site home page Giter Site logo

approvaltests.ruby's Introduction

Approvals

BuildGem Version Code Climate Gemnasium

Contents

Approvals are based on the idea of the golden master.

You take a snapshot of an object, and then compare all future versions of the object to the snapshot.

Big hat tip to Llewellyn Falco who developed the approvals concept, as well as the original approvals libraries (.NET, Java, Ruby, PHP, probably others).

See ApprovalTests for videos and additional documentation about the general concept.

Also, check out Herding Code's podcast #117 in which Llewellyn Falco is interviewed about approvals.

Getting Started

New Projects

The easiest way to get started with a new project is to clone the Starter Project

Configuration

Approvals.configure do |config|
  config.approvals_path = 'output/dir/'
end

snippet source | anchor

The default location for the output files is

approvals/

Usage

Approvals.verify(your_subject, :format => :json)

This will raise an ApprovalError in the case of a failure.

The first time the approval is run, a file will be created with the contents of the subject of your approval:

the_name_of_the_approval.received.txt # or .json, .html, .xml as appropriate

Since you have not yet approved anything, the *.approved file does not exist, and the comparison will fail.

Customizing formatted output

The default writer uses the :to_s method on the subject to generate the output for the received file. For custom complex objects you will need to provide a custom writer to get helpful output, rather than the default:

#<Object:0x0000010105ea40> # or whatever the object id is

Create a custom writer class somewhere accessible to your test:

class MyCustomWriter < Approvals::Writers::TextWriter
  def format(data)
    # Custom data formatting here
  end

  def filter(data)
    # Custom data filtering here
  end
end

In your test, use a string to reference your custom class:

it "verifies a complex object" do
  Approvals.verify hello, :format => "MyCustomWriter"
end

Define and use different custom writers as needed!

CLI

The gem comes with a command-line tool that makes it easier to manage the *.received.* and *.approved.* files.

The basic usage is:

approvals verify

This goes through each approval failure in turn showing you the diff.

The option --diff or -d configures which difftool to use (for example opendiff, vimdiff, etc). The default value is diff.

The option --ask or -a, which after showing you a diff will offer to approve the received file (move it from *.received.* to *.approved.*.). The default is true. If you set this to false, then nothing happens beyond showing you the diff, and you will need to rename files manually.

Workflow Using VimDiff

I have the following mapped to <leader>v in my .vimrc file:

map <leader>v :!approvals verify -d vimdiff -a<cr>

I tend to run my tests from within vim with an on-the-fly mapping:

:map Q :wa <Bar> :!ruby path/to/test_file.rb<cr>

When I get one or more approval failures, I hit <leader>v. This gives me the vimdiff.

When I've inspected the result, I hit :qa which closes both sides of the diff.

Then I'm asked if I want to approve the received file [yN]. If there are multiple diffs, this handles each failure in turn.

RSpec

For the moment the only direct integration is with RSpec.

require 'approvals/rspec'

The default directory for output files when using RSpec is

spec/fixtures/approvals/

You can override this:

RSpec.configure do |config|
  config.approvals_path = 'some/other/path'
end

The basic format of the approval is modeled after RSpec's it:

it 'works' do
  verify do
    'this is the the thing you want to verify'
  end
end

Naming

When using RSpec, the namer is set for you, using the example's full_description.

Approvals.verify(thing, :name => 'the name of your test')

Formatting

You can pass a format for your output before it gets written to the file. At the moment, only text, xml, html, and json are supported, while text is the default.

Simply add a :format => :txt, :format => :xml, :format => :html, or :format => :json option to the example:

page = '<html><head></head><body><h1>ZOMG</h1></body></html>'
Approvals.verify page, :format => :html

data = '{\'beverage\':\'coffee\'}'
Approvals.verify data, :format => :json

In RSpec, it looks like this:

verify :format => :html do
  '<html><head></head><body><h1>ZOMG</h1></body></html>'
end

verify :format => :json do
  '{\'beverage\':\'coffee\'}'
end

If you like you could also change the default format globally with:

RSpec.configure do |config|
  config.approvals_default_format = :json # or :xml, :html
end

Exclude dynamically changed values from json

Approvals.configure do |config|
  config.excluded_json_keys = {
    :id =>/(\A|_)id$/,
    :date => /_at$/
  }
end

It will replace values with placeholders:

{id: 5, created_at: "2013-08-29 13:48:08 -0700"}

=>

{id: "<id>", created_at: "<date>"}

Approving a spec

If the contents of the received file is to your liking, you can approve the file by renaming it.

For an example who's full description is My Spec:

mv my_spec.received.txt my_spec.approved.txt

When you rerun the approval, it should now pass.

Expensive computations

The Executable class allows you to perform expensive operations only when the command to execute it changes.

For example, if you have a SQL query that is very slow, you can create an executable with the actual SQL to be performed.

The first time the spec runs, it will fail, allowing you to inspect the results. If this output looks right, approve the query. The next time the spec is run, it will compare only the actual SQL.

If someone changes the query, then the comparison will fail. Both the previously approved command and the received command will be executed so that you can inspect the difference between the results of the two.

executable = Approvals::Executable.new(subject.slow_sql) do |output|
  # do something on failure
end

Approvals.verify(executable, :options => :here)

RSpec executable

There is a convenience wrapper for RSpec that looks like so:

verify do
  executable(subject.slow_sql) do |command|
     result = ActiveRecord::Base.connection.execute(command)
     # do something to display the result
  end
end

Copyright (c) 2011 Katrina Owen, released under the MIT license

approvaltests.ruby's People

Contributors

actions-user avatar badquanta avatar bmarini avatar caleb-an avatar dependabot[bot] avatar donschado avatar geeksam avatar goltergaul avatar hassoncs avatar hotgazpacho avatar isidore avatar jeremyruppel avatar jjoos avatar jmks avatar kytrinyx avatar liamseanbrady avatar lukewinikates avatar markijbema avatar olivierlacan avatar randycoulman avatar shawnacscott avatar svarks avatar theotherzach avatar thomaskoppensteiner avatar tmock12 avatar willianvdv avatar zph avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

approvaltests.ruby's Issues

Make 'json' an explicit dependency

Currently the json dependency is installed here.

Ruby 1.x already reached the end of the support a long time ago. I think the json dependency should be explicitly listed by now.

A hash ( # ) should not be stripped from approval name

I noticed I test a lot of controller actions, so my names tend to be something like

"blogposts#show.json should stay the same"

I think we can do better handling of the # than stripping it. (this would mean a breaking change though, but I think this is still okay, as long as we add it to the changelog)

Some example metadata is wrong/missing (RSpec integration)

A bunch of the metadata on the example points to the DSL module rather than the place where the verify is being called, since specify() is being called in the DSL module. I think it should be possible to bind the call to specify to the callers context.

Some of the issues this causes are:

  • The approvals don't register :filtered => true, e.g. if you try to run just certain examples or groups.
  • The formatter for an example group points to the DSL file rather than the spec file

What is the preferred flow for accepting fixtures

This is more of a discussion than a direct issue. I noticed there is another workflow than I use, and wondered how other people are using this gem. I'll kick of by stating my method, and problems with it.

I check differences in file everyday. I have a perfect workflow for it, and I like it. This is my git commit tool (in my case gitx, but works for any other tool as well). I selected this tool especially for making it easy to see what has changed, so for me this is a perfect tool for comparing changes.

The problem is that to compare using my git tool, I need the approved file to change. To do this I have to move the file. Unfortunately, from the error output there is no straight-forward way to construct the move command (and due to the way this kind of stuff fails in my application, I regularly need to update multiple files). I'd like to be able to copy-paste something from the error output to my console, but this isn't totally possible due to trivial formatting differences between test output and mv command syntax.

To solve this I wrote a (bash) script which accepts all fixtures, and then check in my git client whether I want these changes, or whether this really indicates a bug.

The reason I started this discussion however is that I found a command inside the bin directory which provides similar functionality (though for myself I like the gui better than a commandline tool). So I was wondering, are other people using this commandline tool? Is it working good? If so, I think we should at least mention it in the readme ;) More generally, I think we should offer a bit better workflow in the readme than 'you move the file when you like it'.

Differences in jruby and mri json output

I often have the problem that there are minor differences between the output of an approval test in jruby and mri. We run our tests in both rubies so the approval test always fails for such cases in one of the rubies.

An example of this is that the following generates different approvals in jruby and mri:

verify(format: :json) { { weight: {} } }

In jruby:

weight: {

}

in mri:

weight: {
}

Is there any way to avoid this?

The problem seems to be jsons pretty_generate: https://github.com/kytrinyx/approvals/blob/master/lib/approvals/writers/json_writer.rb#L11

jruby:

JSON.pretty_generate({ weight: {} })
=> "{\n  \"weight\": {\n\n  }\n}"`

mri:

JSON.pretty_generate({ weight: {} })
=> "{\n  \"weight\": {\n  }\n}"

Using approvals to verify_sql

I've had good luck with the following pattern in my apps and I'm curious if there would be any interest in wrapping this up into a new feature for the gem as we have the verify helper. No worries in any case, but I thought I'd share the pattern here for other people to consider. I find this useful for watching the SQL that complex scopes generate. It also comes in handy when working with GraphQL to ensure that common queries generate reasonable SQL. The end result is something akin to what the bullet gem does, but a bit more specialized, and with an easily readable record that prevents accidental N+1s etc.

# spec/spec_helper.rb

require "approvals/rspec"
require "./spec/support/approvals_helper"

RSpec.configure do |config|
  config.include ApprovalsHelper
  config.approvals_default_format = :txt
end
# spec/support/approvals_helper.rb

module ApprovalsHelper
  def verify_sql(&block)
    sql = []

    subscriber = ->(_name, _start, _finish, _id, payload) do
      sql << payload[:sql].split("/*").first.gsub(/\d+/, "?")
    end

    ActiveSupport::Notifications.subscribed(subscriber, "sql.active_record", &block)

    verify do
      sql.join("\n") + "\n"
    end
  end
end
# spec/models/example_spec.rb

it "is an example spec" do
   verify_sql do
     expect(
       Thing.complex_query
     ).to eq(
       expected_things
     )
   end
 end

Mutual incompatibility with Rails 6.1.x over versions of `thor`

Bundler could not find compatible versions for gem "thor":
  In Gemfile:
    approvals (~> 0.0.24) was resolved to 0.0.24, which depends on
      thor (~> 0.18)

    factory_bot_rails was resolved to 6.2.0, which depends on
      railties (>= 5.0.0) was resolved to 6.1.3.2, which depends on
        thor (~> 1.0)

Any chance approvals can be updated to permit newer versions of thor?

Redifine match behaviour for filters?

I wonder why the choice was made to make a filter match regularly, instead of requiring it to match the whole string. Compare:

filters = {
  time: /^time$/,
  id: /(^|_)id$/,
  timestamp: /_at$/
}

versus

filters = {
  time: /time/,
  id: /(.*_)?id/,
  timestamp: /.*_at/
}

Imo the latter describe much clearer the content of the string matched, whereas the former sound more cryptical, and is easier to make mistakes with, like http://rubular.com/r/EnyxkU60Jo

Therefore I would be in favour of doing all-string matches always.

Newline on end of file

I have a small cosmetic issue. Approval files don't end on newlines. this means that if someone ever opens them in a properly configured editor, the file changes. It also means, that on github it shows a 'you-forgot-the-newline-at-the-end-of-file'. What are other peoples thoughts on this?

I would prefer a newline on the end of the approvals file

consolidate configuration behavior

Comment from @markijbema in #69

Also, do I understand correctly (been a while since I used approvals) that there are two ways to configure Approvals? That is suboptimal at least imho.

I don't actually know the answer to this. We should take a look, and if it's true we should normalize.

Differences in JRuby and MRI HTML output

Similar to issue #84 there are also differences in the HTML output between JRuby and MRI.

Example:

describe "a html response" do
  let(:test_html) do
<<-TEST
<!doctype html>
<head>
<title>Test Title</title>
<meta charset="utf-8"/>
</head>
<body>
<h1>Test Page</h1>
</body>
</html>
TEST
end

  it "a test" do
    verify do
      test_html
    end
  end
end

MRI Output
MRI: 2.5.3
Approvals: 0.0.24
Nokogiri: 1.10.1

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>Test Title</title>
    <meta charset="utf-8" />
  </head>
  <body>
<h1>Test Page</h1>
</body>
</html>

JRuby Output
JRuby: 9.2.5.0
Approvals: 0.0.24
Nokogiri: 1.10.1-java

<!DOCTYPE html >
<html>
  <head>
    <title>Test Title</title>
    <meta charset="utf-8" />

  </head>
  <body>
    <h1>Test Page</h1>
  </body>
</html>

Workaround
A workaround is to use the format: :txt options, which produces the same (unchanged) output for both Ruby versions.

<!doctype html>
<head>
<title>Test Title</title>
<meta charset="utf-8"/>
</head>
<body>
<h1>Test Page</h1>
</body>
</html>

As far as I can tell, the issue is related to this line of code (https://github.com/kytrinyx/approvals/blob/master/lib/approvals/writers/html_writer.rb#L10), where Nokogiri produces a different output for each of the two Ruby versions.

rspec extension specs don't clean up after themselves

The tests in spec/extensions/rspec_approvals_spec.rb lines 64 - 78 leave the *received.txt files even when the tests pass. They also add entries in the .approvals file.

This gets in the way when there are other failures and you're using the command line tool to step through each of the diffs one at a time.

$ cat .approvals 
spec/fixtures/approvals/verifies_a_failure.approved.txt spec/fixtures/approvals/verifies_a_failure.received.txt
spec/fixtures/approvals/verifies_a_failure_diff.approved.txt spec/fixtures/approvals/verifies_a_failure_diff.received.txt
spec/fixtures/approvals/verifies_directory/a_failure.approved.txt spec/fixtures/approvals/verifies_directory/a_failure.received.txt
spec/fixtures/approvals/verifies_directory/a_failure_diff.approved.txt spec/fixtures/approvals/verifies_directory/a_failure_diff.received.txt

Blows up on array of hashes

I have an array of hashes that looks like this:

people = [
  {"name" => "Alice", "age" => 28},
  {"name" => "Bob", "age" => 22}
]

When I say Approvals.verify(people, name: 'people', format: :json) it blows up.

AssignmentTest#test_files:
TypeError: can't convert Array into String
    /opt/rubies/ruby-1.9.3-p392/lib/ruby/1.9.1/json/common.rb:148:in `initialize'
    /opt/rubies/ruby-1.9.3-p392/lib/ruby/1.9.1/json/common.rb:148:in `new'
    /opt/rubies/ruby-1.9.3-p392/lib/ruby/1.9.1/json/common.rb:148:in `parse'
    /Users/kytrinyx/.gem/ruby/1.9.3/gems/approvals-0.0.9/lib/approvals/writers/json_writer.rb:24:in `parse_data'
    /Users/kytrinyx/.gem/ruby/1.9.3/gems/approvals-0.0.9/lib/approvals/writers/json_writer.rb:10:in `format'
    /Users/kytrinyx/.gem/ruby/1.9.3/gems/approvals-0.0.9/lib/approvals/writers/text_writer.rb:13:in `block in write'
    /Users/kytrinyx/.gem/ruby/1.9.3/gems/approvals-0.0.9/lib/approvals/writers/text_writer.rb:12:in `open'
    /Users/kytrinyx/.gem/ruby/1.9.3/gems/approvals-0.0.9/lib/approvals/writers/text_writer.rb:12:in `write'
    /Users/kytrinyx/.gem/ruby/1.9.3/gems/approvals-0.0.9/lib/approvals/approval.rb:42:in `verify'
    /Users/kytrinyx/.gem/ruby/1.9.3/gems/approvals-0.0.9/lib/approvals/dsl.rb:4:in `verify'

Add JRuby to travis build

It seems approvals works on JRuby, but outputs slightly different XML/HTML approvals. This shouldn't be to hard to fix.

Undocumented dependency/require

Perhaps this is a mistake on my part, but when I use approvals in a minitest environment and attempt to match a approval and the received file, there is a requirement on 'ERB' that is undocumented in approvals source.

Simply adding require 'erb' to my test/test_helper.rb fixed the problem. I haven't pushed the code I was working on at that moment yet, but it is on a public repo so I could show when I push later today.

Html approver mangles html with boolean attributes

the following (valid) html:

<!DOCTYPE html>
<html>
<title>Hoi</title>
<script async defer src=\"http://foo.com/bar.js\"></script>
<h1>yo</h1>

becomes this:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"><script>window.FactlinkProxiedUri = "http://www.example.org/foo?bar=baz";</script><script></script>defer
               src="http://localhost:8000/lib/dist/factlink_loader.js?o=proxy"
               onload="__internalFactlinkState('proxyLoaded')"
         &gt;</html>

RSpec 3 compatibility

Hey so the gem does not work under RSpec 3. It has to do with example being yielded into blocks instead of immediately available on the file itself.

I am looking into it but if you have any suggestions I am all ears.

The line that goes wrong is the following:

# /Users/mhenrixon/code/github/approvals/lib/approvals/extensions/rspec/dsl.rb:12:in 'verify'

BinaryWriter

What's the status of BinaryWriter? Is it still used? If I comment the whole file out, not a single test fails, and 'BinaryWriter' is mentioned nowhere else in the source.

Update readme

I was browsing around the code and am amazed at the amount of functionality which I didn't see from the readme. I think it would be really worthwhile to update the readme with the functionality at least, even if it isn't very well written/with lots of examples for now. I really think this is a great gem, and it apparantly is even better than I thought :)

Initialization issue w/ Approvals::CLI in v0.0.13

Cool idea for a library & I remember @rking being interested by this concept a year or so ago.

I ran into trouble w/ v0.0.13:

  • When trying to run the cmdline tool, it complains about:
$ bundle exec approvals                                                                                        
/Users/zph/projectX/vendor/ruby/1.9.1/gems/approvals-0.0.13/bin/approvals:7:in `<top (required)>': uninitialized constant Approvals (NameError)
        from /Users/zph/rentpath/source/rnr_ui/vendor/ruby/1.9.1/bin/approvals:23:in `load'
        from /Users/zph/rentpath/source/rnr_ui/vendor/ruby/1.9.1/bin/approvals:23:in `<main>'

So, I reverted to 0.0.12 in my Gemfile and it's working as expected.

After I fix the issue that I'm hoping to refactor, I'll see about digging into this further ๐Ÿ˜„.

Thanks for putting this lib out there!

Running approvals with parallel tests

We're using https://github.com/grosser/parallel_tests to speed up our integration tests. But this sometimes blows up, since the .approvals file is being reset by deleting it. This can happen at the same time when running parallel tests.

/home/jenkins/.rbenv/versions/2.0.0-p353/lib/ruby/gems/2.0.0/gems/approvals-0.0.9/lib/approvals/utilities/dotfile.rb:7:in `delete': No such file or directory - /home/jenkins/workspace/PhabricatorCore/.approvals (Errno::ENOENT)
    from /home/jenkins/.rbenv/versions/2.0.0-p353/lib/ruby/gems/2.0.0/gems/approvals-0.0.9/lib/approvals/utilities/dotfile.rb:7:in `reset'
    from /home/jenkins/.rbenv/versions/2.0.0-p353/lib/ruby/gems/2.0.0/gems/approvals-0.0.9/lib/approvals.rb:24:in `reset'
    from /home/jenkins/.rbenv/versions/2.0.0-p353/lib/ruby/gems/2.0.0/gems/approvals-0.0.9/lib/approvals.rb:29:in `<top (required)>'
    from /home/jenkins/.rbenv/versions/2.0.0-p353/lib/ruby/gems/2.0.0/gems/bundler-1.3.5/lib/bundler/runtime.rb:72:in `require'
    from /home/jenkins/.rbenv/versions/2.0.0-p353/lib/ruby/gems/2.0.0/gems/bundler-1.3.5/lib/bundler/runtime.rb:72:in `block (2 levels) in require'
    from /home/jenkins/.rbenv/versions/2.0.0-p353/lib/ruby/gems/2.0.0/gems/bundler-1.3.5/lib/bundler/runtime.rb:70:in `each'
    from /home/jenkins/.rbenv/versions/2.0.0-p353/lib/ruby/gems/2.0.0/gems/bundler-1.3.5/lib/bundler/runtime.rb:70:in `block in require'
    from /home/jenkins/.rbenv/versions/2.0.0-p353/lib/ruby/gems/2.0.0/gems/bundler-1.3.5/lib/bundler/runtime.rb:59:in `each'
    from /home/jenkins/.rbenv/versions/2.0.0-p353/lib/ruby/gems/2.0.0/gems/bundler-1.3.5/lib/bundler/runtime.rb:59:in `require'
    from /home/jenkins/.rbenv/versions/2.0.0-p353/lib/ruby/gems/2.0.0/gems/bundler-1.3.5/lib/bundler.rb:132:in `require'
    from /home/jenkins/workspace/PhabricatorCore/config/application.rb:11:in `<top (required)>'
    from /home/jenkins/workspace/PhabricatorCore/config/environment.rb:2:in `require'
    from /home/jenkins/workspace/PhabricatorCore/config/environment.rb:2:in `<top (required)>'
    from /home/jenkins/workspace/PhabricatorCore/spec/integration/spec_helper.rb:3:in `require'
    from /home/jenkins/workspace/PhabricatorCore/spec/integration/spec_helper.rb:3:in `<top (required)>'
    from /home/jenkins/workspace/PhabricatorCore/spec/integration/backend/acls/activities/bug_triaged_spec.rb:1:in `require'
    from /home/jenkins/workspace/PhabricatorCore/spec/integration/backend/acls/activities/bug_triaged_spec.rb:1:in `<top (required)>'
    from /home/jenkins/.rbenv/versions/2.0.0-p353/lib/ruby/gems/2.0.0/gems/rspec-core-2.13.1/lib/rspec/core/configuration.rb:819:in `load'
    from /home/jenkins/.rbenv/versions/2.0.0-p353/lib/ruby/gems/2.0.0/gems/rspec-core-2.13.1/lib/rspec/core/configuration.rb:819:in `block in load_spec_files'
    from /home/jenkins/.rbenv/versions/2.0.0-p353/lib/ruby/gems/2.0.0/gems/rspec-core-2.13.1/lib/rspec/core/configuration.rb:819:in `each'
    from /home/jenkins/.rbenv/versions/2.0.0-p353/lib/ruby/gems/2.0.0/gems/rspec-core-2.13.1/lib/rspec/core/configuration.rb:819:in `load_spec_files'
    from /home/jenkins/.rbenv/versions/2.0.0-p353/lib/ruby/gems/2.0.0/gems/rspec-core-2.13.1/lib/rspec/core/command_line.rb:22:in `run'
    from /home/jenkins/.rbenv/versions/2.0.0-p353/lib/ruby/gems/2.0.0/gems/rspec-core-2.13.1/lib/rspec/core/runner.rb:80:in `run'
    from /home/jenkins/.rbenv/versions/2.0.0-p353/lib/ruby/gems/2.0.0/gems/rspec-core-2.13.1/lib/rspec/core/runner.rb:17:in `block in autorun'

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.