Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mechanism for executing JavaScript unit tests #1165

Open
simonw opened this issue Dec 30, 2020 · 9 comments
Open

Mechanism for executing JavaScript unit tests #1165

simonw opened this issue Dec 30, 2020 · 9 comments

Comments

@simonw
Copy link
Owner

simonw commented Dec 30, 2020

I'm going to need to add JavaScript unit tests for this new plugin system.

Originally posted by @simonw in #983 (comment)

@simonw
Copy link
Owner Author

simonw commented Dec 30, 2020

https://jestjs.io/ looks worth trying here.

@simonw
Copy link
Owner Author

simonw commented Dec 30, 2020

https://www.valentinog.com/blog/jest/ was useful.

I created a static/__tests__ folder and added this file as plugins.spec.js:

const datasette = require("../plugins.js");

describe("Datasette Plugins", () => {
  test("it should have datasette.plugins", () => {
    expect(!!datasette.plugins).toEqual(true);
  });
  test("registering a plugin should work", () => {
    datasette.plugins.register("numbers", (a, b) => a + b, ["a", "b"]);
    var result = datasette.plugins.call("numbers", { a: 1, b: 2 });
    expect(result).toEqual([3]);
    datasette.plugins.register("numbers", (a, b) => a * b, ["a", "b"]);
    var result2 = datasette.plugins.call("numbers", { a: 1, b: 2 });
    expect(result2).toEqual([3, 2]);
  });
});

In static/plugins.js I put this:

var datasette = datasette || {};
datasette.plugins = (() => {
    var registry = {};
    return {
        register: (hook, fn, parameters) => {
            if (!registry[hook]) {
                registry[hook] = [];
            }
            registry[hook].push([fn, parameters]);
        },
        call: (hook, args) => {
            args = args || {};
            var results = [];
            (registry[hook] || []).forEach(([fn, parameters]) => {
                /* Call with the correct arguments */
                var result = fn.apply(fn, parameters.map(parameter => args[parameter]));
                if (result !== undefined) {
                    results.push(result);
                }
            });
            return results;
        }
    };
})();

module.exports = datasette;

Note the module.exports line at the end.

Then inside static/ I ran the following command:

% npx jest -c '{}'
 PASS  __tests__/plugins.spec.js
  Datasette Plugins
    ✓ it should have datasette.plugins (3 ms)
    ✓ registering a plugin should work (1 ms)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        1.163 s
Ran all test suites.

The -c {} was necessary because I didn't have a Jest configuration or a package.json.

@simonw
Copy link
Owner Author

simonw commented Dec 30, 2020

@simonw
Copy link
Owner Author

simonw commented Dec 30, 2020

I don't know if Jest on the command-line is the right tool for this. It works for the plugins.js script but I'm increasingly going to want to start adding tests for browser JavaScript features - like the https://github.com/simonw/datasette/blob/0.53/datasette/static/table.js script - which will need to run in a browser.

So maybe I should just find a browser testing solution and figure out how to run that under CI in GitHub Actions. Maybe https://www.cypress.io/ ?

@simonw
Copy link
Owner Author

simonw commented Dec 30, 2020

Jest works with Puppeteer: https://jestjs.io/docs/en/puppeteer

@simonw
Copy link
Owner Author

simonw commented Dec 31, 2020

I got Cypress working! I added the datasette.plugins code to the table template and ran a test called plugins.spec.js using the following:

context('datasette.plugins API', () => {
    beforeEach(() => {
      cy.visit('/fixtures/compound_three_primary_keys')
    });
    it('should exist', () => {
        let datasette;
        cy.window().then(win => {
            datasette = win.datasette;
        }).then(() => {
            expect(datasette).to.exist;
            expect(datasette.plugins).to.exist;
        });
    });
    it('should register and execute plugins', () => {
        let datasette;
        cy.window().then(win => {
            datasette = win.datasette;
        }).then(() => {
            expect(datasette.plugins.call('numbers')).to.deep.equal([]);
            // Register a plugin
            datasette.plugins.register("numbers", (a, b) => a + b, ['a', 'b']);
            var result = datasette.plugins.call("numbers", {a: 1, b: 2});
            expect(result).to.deep.equal([3]);
            // Second plugin
            datasette.plugins.register("numbers", (a, b) => a * b, ['a', 'b']);
            var result2 = datasette.plugins.call("numbers", {a: 1, b: 2});
            expect(result2).to.deep.equal([3, 2]);
        });
    });
});

@simonw
Copy link
Owner Author

simonw commented Dec 31, 2020

Important to absorb the slightly bizarre assertion syntax from Chai - docs here https://www.chaijs.com/api/bdd/

@simonw
Copy link
Owner Author

simonw commented Dec 31, 2020

@dracos
Copy link

dracos commented Dec 31, 2020

Sorry to go on about it, but it's my only example ;) And thought it might be of interest/use. Here is FixMyStreet's Cypress workflow https://github.com/mysociety/fixmystreet/blob/master/.github/workflows/cypress.yml with the master script that sets up server etc at https://github.com/mysociety/fixmystreet/blob/master/bin/browser-tests (that has features such as working inside/outside Vagrant, and can do JS code coverage) and then the tests are at https://github.com/mysociety/fixmystreet/tree/master/.cypress/cypress/integration

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants