Giter Site home page Giter Site logo

vue-router-mock's Introduction

vue-router-mock test npm package codecov thanks

Easily mock routing interactions in your Vue 3 apps

Installation

pnpm i -D vue-router-mock
# or
yarn add -D vue-router-mock
# or
npm install -D vue-router-mock

Requirements

This library

  • @vue/test-utils >= 2.4.0
  • vue 3 and vue router 4

Goal

The goal of Vue Router Mock is to enable users to unit and integration test navigation scenarios. This means tests that are isolated enough to not be end to end tests (e.g. using Cypress) or are edge cases (e.g. network failures). Because of this, some scenarios are more interesting as end to end tests, using the real vue router.

Introduction

Vue Router Mock exposes a few functions to be used individually and they are all documented through TS. But most of the time you want to globally inject the router in a setupFilesAfterEnv file. Create a tests/router-mock-setup.js file at the root of your project (it can be named differently):

import {
  VueRouterMock,
  createRouterMock,
  injectRouterMock,
} from 'vue-router-mock'
import { config } from '@vue/test-utils'

// create one router per test file
const router = createRouterMock()
beforeEach(() => {
  router.reset() // reset the router state
  injectRouterMock(router)
})

// Add properties to the wrapper
config.plugins.VueWrapper.install(VueRouterMock)

Note: you might need to write this file in CommonJS for Jest. In Vite, you can write it in Typescript

Then add this line to your jest.config.js:

  setupFilesAfterEnv: ['<rootDir>/tests/router-mock-setup.js'],

or to your vitest.config.ts:

import { defineConfig } from 'vitest/config'

export default defineConfig({
  test: {
    environment: 'happy-dom', // <- or jsdom, needed to mount Vue Components
    setupFiles: ['./tests/setup-router-mock.ts'],
  },
})

This will inject a router in all your tests. If for specific tests, you need to inject a different version of the router, you can do so:

import { createRouterMock, injectRouterMock } from 'vue-router-mock'

describe('SearchUsers', () => {
  // create one mock instance, pass options
  const router = createRouterMock({
    // ...
  })
  beforeEach(() => {
    // inject it globally to ensure `useRoute()`, `$route`, etc work
    // properly and give you access to test specific functions
    injectRouterMock(router)
  })

  it('should paginate', async () => {
    const wrapper = mount(SearchUsers)

    expect(wrapper.router).toBe(router)

    // go to the next page
    // this will internally trigger `router.push({ query: { page: 2 }})`
    wrapper.find('button.next-page').click()

    expect(wrapper.router.push).toHaveBeenCalledWith(
      expect.objectContaining({ query: { page: 2 } })
    )
    expect(wrapper.router.push).toHaveBeenCalledTimes(1)

    // if we had a navigation guard fetching the search results,
    // waiting for it to be done will allow us to wait until it's done.
    // Note you need to mock the fetch and to activate navigation
    // guards as explained below
    await router.getPendingNavigation()
    // wait for the component to render again if we want to check
    await wrapper.vm.nextTick()

    expect(wrapper.find('#user-results .user').text()).toMatchSnapshot()
  })
})

If you need to create a specific version of the router for one single test (or a nested suite of them), you should call the same functions:

it('should paginate', async () => {
  const router = createRouterMock()
  injectRouterMock(router)
  const wrapper = mount(SearchUsers)
})

Guide

Accessing the Router Mock instance

You can access the instance of the router mock in multiple ways:

  • Access wrapper.router:

    it('tests something', async () => {
      const wrapper = mount(MyComponent)
      await wrapper.router.push('/new-location')
    })
  • Access it through wrapper.vm:

    it('tests something', async () => {
      const wrapper = mount(MyComponent)
      await wrapper.vm.$router.push('/new-location')
      expect(wrapper.vm.$route.name).toBe('NewLocation')
    })
  • Call getRouter() inside of a test:

    it('tests something', async () => {
      // can be called before creating the wrapper
      const router = getRouter()
      const wrapper = mount(MyComponent)
      await router.push('/new-location')
    })

Setting parameters

setParams allows you to change route params without triggering a navigation:

it('should display the user details', async () => {
  const wrapper = mount(UserDetails)
  getRouter().setParams({ userId: 12 })

  // test...
})

It can be awaited if you need to wait for Vue to render again:

it('should display the user details', async () => {
  const wrapper = mount(UserDetails)
  await getRouter().setParams({ userId: 12 })

  // test...
})

setQuery and setHash are very similar. They can be used to set the route query or hash without triggering a navigation, and can be awaited too.

Setting the initial location

By default the router mock starts on START_LOCATION. In some scenarios this might need to be adjusted by pushing a new location and awaiting it before testing:

it('should paginate', async () => {
  await router.push('/users?q=haruno')
  const wrapper = mount(SearchUsers)

  // test...
})

You can also set the initial location for all your tests by passing an initialLocation:

const router = createRouterMock({
  initialLocation: '/users?q=jolyne',
})

initialLocation accepts anything that can be passed to router.push().

Simulating navigation failures

You can simulate the failure of the next navigation

Simulating a navigation guard

By default, all navigation guards are ignored so that you can simulate the return of the next guard by using setNextGuardReturn() without depending on existing ones:

// simulate a navigation guard that returns false
router.setNextGuardReturn(false)
// simulate a redirection
router.setNextGuardReturn('/login')

If you want to still run existing navigation guards inside component, you can active them when creating your router mock:

const router = createRouterMock({
  // run `onBeforeRouteLeave()`, `onBeforeRouteUpdate()`, `beforeRouteEnter()`, `beforeRouteUpdate()`, and `beforeRouteLeave()`
  runInComponentGuards: true,
  // run `beforeEnter` of added routes. Note that you must manually add these routes with `router.addRoutes()`
  runPerRouteGuards: true,
})

Stubs

By default, both <router-link> and <router-view> are stubbed but you can override them locally. This is specially useful when you have nested <router-view> and you rely on them for a test:

const wrapper = mount(MyComponent, {
  global: {
    stubs: { RouterView: MyNestedComponent },
  },
})

You need to manually specify the component that is supposed to be displayed because the mock won't be able to know the level of nesting.

NOTE: this might change to become automatic if the necessary routes are provided.

Testing libraries

Vue Router Mock automatically detects if you are using Sinon.js, Jest, or Vitest and use their spying methods. You can of course configure Vue Router Mock to use any spying library you want.

For example, if you use Vitest with globals: false, then you need to manually configure the spy option and pass vi.fn() to it:

const router = createRouterMock({
  spy: {
    create: fn => vi.fn(fn),
    reset: spy => spy.mockClear(),
  },
})

Caveats

Nested Routes

By default, the router mock comes with one single catch all route. You can add routes calling the router.addRoute() function but if you add nested routes and you are relying on running navigation guards, you must manually set the depth of the route you are displaying. This is because the router has no way to know which level of nesting you are trying to display. e.g. Imagine the following routes:

const routes = [
  {
    path: '/users',
    // we are not testing this one so it doesn't matter
    component: UserView,
    children: [
      // UserDetail must be the same component we are unit testing
      { path: ':id', component: UserDetail },
    ],
  },
]
// 0 would be if we were testing UserView at /users
router.depth.value = 1
const wrapper = mount(UserDetail)

Remember, this is not necessary if you are not adding routes or if they are not nested.

Related

License

MIT

This project was created using the Vue Library template by posva

vue-router-mock's People

Contributors

benshelton avatar cexbrayat avatar dependabot[bot] avatar jessicasachs avatar kalvenschraut avatar michaelprather avatar posva avatar renovate-bot avatar renovate[bot] 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

vue-router-mock's Issues

"Symbol(router)" not found (regression with vue-router v4.1)

Reproduction

Tests using vue-router-mock fail when upgrading vue-router from v4.0.16 to v4.1

Hello.vue

<script setup lang="ts">
import { useRouter } from 'vue-router';

const router = useRouter();
function navigate() {
  router.push('/');
}
</script>

<template>
  <button @click="navigate()">Home</button>
</template>

basic.test.ts

import { mount } from '@vue/test-utils';
import { createRouterMock, injectRouterMock } from 'vue-router-mock';
import Hello from '../components/Hello.vue';

test('mount component', async () => {
  expect(Hello).toBeTruthy();

  const router = createRouterMock({
    spy: {
      create: (fn) => vi.fn(fn),
      reset: (spy) => spy.restore(),
    },
  });
  injectRouterMock(router);
  const wrapper = mount(Hello);

  await wrapper.get('button').trigger('click');

  expect(router.push).toHaveBeenCalledWith('/');
});

Steps to reproduce the behavior

Repro using this test and component

https://stackblitz.com/edit/vitest-dev-vitest-ttnyxb

The test succeeds.
Bump the router to v4.1.0 in the package.json and run npm install && npm run test again

Expected behavior

The test should still succeed

Actual behavior

The test fails with:

stderr | test/basic.test.ts > mount component
[Vue warn]: injection "Symbol(router)" not found. 
  at <Hello ref="VTU_COMPONENT" > 
  at <VTUROOT>

In-Component guard ignored when initializing mock with routes

Hey,
I am testing routing on a component (composition API) which uses an onBeforeRouteUpdate navigation guard.
When I initialize the router mock like this:

const router = createRouterMock({
    runInComponentGuards: true,
});

and run a test like this:

it('checks beforeRouteUpdate', async () => {
        const router = getRouter();
        await router.push('/test1/1');
        const wrapper = createWrapper();
        await wrapper.router.push('/test2/2');
        ...
    });

I can see a breakpoint in my onBeforeRouteUpdate being hit as expected.

But if I initialize the mock with routes:

const router = createRouterMock({
    runInComponentGuards: true,
    routes: [
        { name: 'test1', path: '/test1/:id', component: MockComponent},
        { name: 'test2', path: '/test2/:id', component: MockComponent},
    ],
});

the breakpoint in the guard never hits.
I need to test behavior of the guard depending on the :id param, and from my understanding the param will not be recognized if the routes array is empty. When I check router.currentRoute in the second example, I can see that the second route is correctly loaded with the expected :id param.

Am I doing something wrong? Or is there another way to test the in-component guard behavior with vue 3 / composition API?

Thank you in advance.

Versions:
vitest: 0.33.0
vue: 3.3.4
vue-router-mock: 1.0.0

`initialLocation` doesn't support what router.push() supports

Reproduction

The documentation says:

initialLocation accepts anything that can be passed to router.push().

However, this results in an error:

const router = createRouterMock({
    initialLocation: {
      name: 'Reports',
      params: {
        domain: 'example.com',
        code: 'code'
      }
    }
  })

...but this does not:

await router.push({
      name: 'Reports',
      params: {
        domain: 'example.com',
        code: 'code'
      }
    })

The error with the first one is:

Serialized Error: {
  "location": {
    "name": "Reports",
    "params": {
      "code": "code",
      "domain": "example.com",
    },
  },
}

(To be fair, the router.push() code also doesn't work when I try to use:

const route = useRoute()
const aliasDomain = String(route.params.domain)

...which I'm still trying to figure out?

TypeError: Cannot read property 'value' of undefined

Adding vue-router-mock to this existing repository breaks, but an isolated repository works fine. I can't understand why these repositories are different. I've been diffing the package-locks and can't find a difference.

Here's the stack trace:
Screen Shot 2021-02-08 at 10 48 53 PM

Reproduction

Here are the two repos (working + broken)

  1. Working example repo
  2. Broken example repo

Can't understand what's different between the two jest configurations, dependencies, etc to cause this failure. I've been copy and pasting the differences -- the jest config + setup + package deps are the same :/

Steps to reproduce the behavior

  1. Clone the repository
  2. npm install
  3. npm run test:unit

Expected behavior

Doesn't crash :-(

router.push could be a simple mock functions by default?

What problem is this solving

For a component doing a navigation with router.push({ name: 'home' }); on an action, our associated unit test usually does a simple assertion like the following:

const mockRouter = createRouter({ history: createWebHistory(), routes: [] });
jest.spyOn(mockRouter, 'push').mockResolvedValue();
const wrapper = mount(App, {   global: { plugins: [mockRouter] } });
// do the action
expect(mockRouter.push).toHaveBeenCalledWith({ name: 'home' });

Using vue-router-mock, we can simplify the test a bit by using createRouterMock:

// do the action
expect(getRouter().push).toHaveBeenCalledWith({ name: 'home' });

but the mock router created tries to really resolve the navigation and throws:

Error: Uncaught [Error: No match for {"name":"home","params":{}}]

Proposed solution

Would it be possible to have a simpler router mock that just have mock functions by default?
Most use cases would probably not need a real navigation to be resolved and triggered.
Or maybe that could be an option?

Describe alternatives you've considered

It's of course possible to spy on getRouter().push but I feel that this should be the default.

index.mjs causes jest to throw (regression in v1.0.4)

Reproduction

A CLI app using Jest and createRouterMock in a unit test will throw when upgrading to v1.0.4 with:

/Users/cedric/Code/ninjasquad/vue-ebook/book-tests/node_modules/vue-router-mock/dist/index.mjs:1
    ({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){import { createRouter, createMemoryHistory, START_LOCATION, routerKey, routeLocationKey, routerViewLocationKey, matchedRouteKey, RouterView, RouterLink } from 'vue-router';
                                                                                      ^^^^^^

    SyntaxError: Cannot use import statement outside a module

      1 | import { flushPromises, mount } from '@vue/test-utils';
    > 2 | import { createRouterMock, injectRouterMock } from 'vue-router-mock';

This is because the main file switched to the mjs format, which is great for Vite but not ideal for Jest.

Expected behavior

Jest tests should still work

`npm install vue-router-mock@2` does not work

Reproduction

npm install vue-router-mock@2 --save-dev
npm ERR! code ETARGET
npm ERR! notarget No matching version found for vue-router-mock@2.
npm ERR! notarget In most cases you or one of your dependencies are requesting
npm ERR! notarget a package version that doesn't exist.

npm ERR! A complete log of this run can be found in:
npm ERR!     /Users/user/.npm/_logs/2021-04-20T23_46_41_778Z-debug.log

Steps to reproduce the behavior

  1. Try to npm install

Expected behavior

The package to be available

Actual behavior

Package isn't published

TypeError: Cannot read properties of undefined (reading 'push')

Reproduction

I have used the library to mock the vue router behaviour in my test and followed the steps in readme file, in general everything starts working pretty well, I managed to check the wrapper.router object functions, but when I run the test I got this bug that says Cannot read properties of undefined (reading 'push') in this line of my code
router.push('/dashboard').

Steps to reproduce the behavior

  1. run the test

Additional information

image

Handle other spies by default

  • Handle other global spies by default (apart from jest) like sinon which is used in peeky or cypress spies
  • Find a way to set the TS type. Is it possible to have this automatic? If not, allowing the user to extend an interface seems like a good middle point

add functions to easily set params

What problem is this solving

If a component accesses the route params, it is fairly common in a unit test to set the params of the current route to some dummy values:

getRouter().currentRoute.value.params = {
  userId: '12'
};

Proposed solution

Maybe there is an easier way to do this?
If not, what do you think about introducing a method like setCurrentRouteParams({ userId: '12' })?

I'm willing to open a PR if you agree and settle for a (better?) name/API.

Warnings when used with @vue/test-utils v2.0.0-rc.4.

Reproduction

After upgrading to @vue/[email protected], tests pass but with warnings about RouterView and RouterLink components having already been registered.

Steps to reproduce the behavior

Dependencies:

  • "vue-router-mock": "0.0.2"
  • "@vue/test-utils": "2.0.0-rc.4"

Config (in Jest setup file):

import { config } from '@vue/test-utils';
import {
  createRouterMock,
  injectRouterMock,
  VueRouterMock,
} from 'vue-router-mock';

const router = createRouterMock();
config.plugins.VueWrapper.install(VueRouterMock);

beforeEach(() => {
  injectRouterMock(router);
});

Expected behavior

Tests pass with no warnings.

Actual behavior

Tests pass, with warnings:

    console.warn
      [Vue warn]: Component "RouterView" has already been registered in target app.

      45 |
      46 |   it('passes', async () => {
    > 47 |     const { findByText } = render(MyComponent, {
         |                            ^
      48 |       props: { id: 'test' },

      at warn (node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:40:17)
      at Object.component (node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:3050:21)
      at mount (node_modules/@vue/test-utils/dist/vue-test-utils.cjs.js:2326:21)
      at render (node_modules/@testing-library/vue/dist/render.js:43:38)
      at Object.<anonymous> (tests/js/components/MyComponent.test.js:47:28)
    console.warn
      [Vue warn]: Component "RouterLink" has already been registered in target app.

      45 |
      46 |   it('passes', async () => {
    > 47 |     const { findByText } = render(MyComponent, {
         |                            ^
      48 |       props: { id: 'test' },

      at warn (node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:40:17)
      at Object.component (node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:3050:21)
      at mount (node_modules/@vue/test-utils/dist/vue-test-utils.cjs.js:2326:21)
      at render (node_modules/@testing-library/vue/dist/render.js:43:38)
      at Object.<anonymous> (tests/js/components/MyComponent.test.js:47:28)

setParams triggers Vue warning: 'inject() can only be used inside setup() or functional components' when used after render

Steps to reproduce the behavior

When testing components with this package as its mocked router and using setParams() after mounting (or in my case, rendering via Testing Library instead of Vue Test Utils) causes Vue to print the following warning: inject() can only be used inside setup() or functional components

The mocked router is set up as follows:

import {
  createRouterMock,
  injectRouterMock,
} from 'vue-router-mock'

const router = createRouterMock()

In the beforeEach() of the test suite:

beforeEach(async () => {
    injectRouterMock(router)
})

This code causes the warning:

it('Navigates to page if token was valid and user is fetched', async () => {
    await render(MyComponent, {
      global: {
        plugins: [
          router,
          createTestingPinia(),
        ],
      },
    })
    await router.setParams({ token: '123' }) // It's called after the render()
    await nextTick()
    expect(router.push).toHaveBeenCalledWith(
      expect.objectContaining({ name: 'pageName' }),
    )
  })

Moving the setParams() before the render removes the warning.
In other words, this code does not prompt the warning:

it('Navigates to page if token was valid and user is fetched', async () => {
    await router.setParams({ token: '123' }) // It's called before the render()
    // Render code here again
    await nextTick()
    expect(router.push).toHaveBeenCalledWith(
      expect.objectContaining({ name: 'pageName' }),
    )
  })

Expected behavior

I'm not sure why the warning shows up, but it's not always possible to set every router param early on in the test.

Setup file and wrapper.router not working and Router.push not changing url!

Hello!

Im having a few issues with this library I was hoping i could get some help with. I'm using Vue3 with Quasar running on Vite with Vitest

First off my setup file is set up as below

import {
  VueRouterMock,
  createRouterMock,
  injectRouterMock,
} from 'vue-router-mock';
const { config } = require('@vue/test-utils');
import { vi, beforeEach } from 'vitest';

// create one router per test file
const router = createRouterMock({
  spy: {
    create: (fn) => vi.fn(fn),
    reset: (spy) => spy.mockReset(),
  },
});

beforeEach(() => {
  injectRouterMock(router);
});

// Add properties to the wrapper
config.plugins.VueWrapper.install(VueRouterMock);

However when i call wrapper.router.push('/') then I get TypeError: Cannot read properties of undefined (reading 'push')

Secondly, I have a use case where I want to read query data from the URL in the case of someone copying and pasting a link and my component then handles translating the url into the correct variables to display the component properly.

If i set initialLocation

const router = createRouterMock({
  initialLocation: '/substances?search=1&regulations=Latest&page=1&limit=10',
  spy: {
    create: (fn) => vi.fn(fn),
    reset: (spy) => spy.mockReset(),
  },
})

Then this works great.

However if i do something like:

    await router.push('/substances?search=1&regulations=Latest&regulations=Previous&page=1&limit=10')
    console.log(router.currentRoute);
    const wrapper = mount(RegulationsSelector, { global: {
        plugins: [createTestingPinia({createSpy: vi.fn()})],
      }});

The console.log shows the old route still and my component incorrectly renders with regulations=Latest instead of [Latest, Previous] as i would expect.

image

Just to be clear, doing the same thing in initialLocation gives me the correct output:

image

Really not sure what I'm doing wrong in either of these cases, any help would be massively appreciated.

Dependency Dashboard

This issue provides visibility into Renovate updates and their statuses. Learn more

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.


  • Check this box to trigger a request for Renovate to run again on this repository

TypeError: createRouterMock is not a function

Reproduction

I've installed the "vue-router-mock" package and I created "jest.setup.js" file as in the documentation and i got the error after running a test.

Jest throws the following error.

TypeError: createRouterMock is not a function.

Steps to reproduce the behavior

Dev Dependencies:

  • "vue-router-mock": "0.0.3"
  • "@jest/types": "^27.0.2"
  • "@vue/test-utils": "2.0.0-rc.15"
  • "vue-jest": "^5.0.0-0"
  • "typescript": "~4.1.5"

Expected behavior

Tests pass with no error.

Actual behavior

TypeError: createRouterMock is not a function
  3 |
  4 | // create one router per test file
> 5 | const router = createRouterMock();
    |                ^
  6 | beforeEach(() => {
  7 |   injectRouterMock(router);
  8 | });

  at Object.<anonymous> (tests/unit/jest.setup.js:5:16)
      at Array.forEach (<anonymous>)

Additional information

I've tried another "@vue/test-utils" versions(2.0.0-0, 2.0.0-rc.4) but i got same error.

Route props functionality in router.mock

What problem is this solving

When using the router mock as it currently is, we cannot test the prop changing functionality (described here in the vue docs).

More specifically to my situation: I have two routes that load the same component with a different prop, and the component itself contains links to both routes. I want to test some functionality that happens when the prop changes. I'd like to keep it as close to real use as possible, and in a real use situation the flow of events would go something like User clicks link -> vue-router pushes the given route -> vue-router updates the component's prop -> a watcher is triggered. I could easily use VTU's setProps function, but that would skip the first two steps, which isn't ideal.

Proposed solution

When I use the mocked router's push function, I would like for it to update the current component's props if there is a props option given in the route.

In order to not break existing code this could be added as an option similar to runInComponentGuards, which would be disabled by default.

Describe alternatives you've considered

As I stated above it's easy enough to use setProps, but that adds another step away from real use.

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.