material-svelte / vite-web-test-runner-plugin

MIT License
39 stars 13 forks source link

web-test-runner integration with vite 3.0 #22

Open diervo opened 2 years ago

diervo commented 2 years ago

Hi, I found this repo through a link on vitest and on google.

I was looking for the latest vite support (currently 3.x), but quickly realized the code was for the old version. After hitting myself against the wall for some time, I develop an integration that works with the latest version.

In the spirit of OSS, rather than creating another npm package, I would love to help the maintainer in this repo with the package update if there is the willingness to do so.

For the record, if anyone wants to do the integration in the meantime, here is the code:

// vite-web-test-runner-plugin.js
import { createServer } from 'vite';
import { join, relative } from 'node:path';
import koaConnect from 'koa-connect';
import minimatch from 'minimatch';

/** @typedef { import('@web/test-runner').TestRunnerPlugin } RunnerPlugin */

/**
 * @return { RunnerPlugin }
*/
export default function viteWebTestRunnerPlugin({ testMatch, testRoot = process.cwd(), viteConfigFile } = {}) {
    let viteServer;
    let viteRoot;
    let relativeRoot;

    return {
        name: 'vite-wtr-plugin',
        async serverStart({ app, fileWatcher }) {
            viteServer = await createServer({
                clearScreen: true,
                server: { middlewareMode: true, hmr: false },
                appType: 'custom',
                configFile: viteConfigFile || join(testRoot, 'vite.config.ts')
            });

            viteRoot = viteServer.config.root;

            // This path represents the diff beween the test root and the vite root
            // viteRoot should always be a relative path from the root (ex. testRoot: /root/test vs viteRoot: /root/test/src/module)
            relativeRoot = `/${relative(testRoot, viteRoot)}`;

            // Allow vite to take over most requests
            app.use(koaConnect(viteServer.middlewares));

            // Vite is taking over the handling of URLs, hence we need to forward the watching to the runner
            viteServer.watcher.on('change', (...args) => fileWatcher.emit('change', ...args));

        },
        async transformImport({ source }) {
            const [absSource, ] = source.split('?'); // Remove the queryString otherwise vite will fail resolving
            const relativeSource = absSource.at(0) === '/' ? absSource.substring(1) : absSource;
            for (const match of testMatch) {
                if (minimatch(relativeSource, match)) {
                    const newPath = absSource.replace(relativeRoot, '');
                    return newPath;
                }
            }
        },
        async serverStop() {
            return viteServer.close();
        },
    }
}

Example usage:

// web-test-runner-config.js
import { jasmineTestRunnerConfig } from 'web-test-runner-jasmine';
import viteWebTestRunnerPlugin from './scripts/testing/vite-web-test-runner-plugin.mjs';

const testMatch = ['src/**/*.test.ts'];

export default {
    ...jasmineTestRunnerConfig(),
    files: testMatch,
    nodeResolve: true,
    plugins: [
        viteWebTestRunnerPlugin({ testMatch })
    ],
};

Also, if you want to use commands in WTR you have to tell vite to ignore that endpoint

//vite.config.ts
const webTestRunnerIgnore = (): Plugin => {
    return {
        name: 'web-dev-server',
        resolveId: (id): { external: boolean; id: string } | void => {
            // Vite should not transform the WebSocket route to allow WTR commands to function correctly
            if (id === '/__web-dev-server__web-socket.js') {
                return { external: true, id };
            }
        },
    };
};

export default defineConfig({
  plugins: [webTestRunnerIgnore()]
});

My 2cents: If you are using vite and a web-component framework like lit, using web-test-runner is a superior testing approach rather than using any synthetic DOM implementation like JSDOM or HappyDOM.

crisward commented 2 years ago

Just as a counterpoint to this, just copying this plugin as an inline plugin has always worked for me. "vite-web-test-runner-plugin" is probably not enough code to really warrent another dependency and you can the manage any peer dependencies within your own project. For context my web-test-runner.config.js is below. I just update to vite 3 and it worked without any other modifications, YMMV. spec-reporter.js included below here btw - https://github.com/modernweb-dev/web/issues/229#issuecomment-732005741

if(!process.stdin.isTTY) process.stdin.isTTY = true
const vite = require("vite");
const {defaultReporter,chromeLauncher} = require('@web/test-runner');
const specReporter = require('./spec-reporter.js');
module.exports = {
  coverage: true,
  groups:[
    {
      name:"Admin",
      files: 'admin/**/*.test.js'
    },{
      name:"Editor",
      files: 'editor/**/*.test.js'
    }
  ],
  coverageConfig: {
    include: [
      'editor/src/**/*.{svelte,js}',
      'admin/src/**/*.{svelte,js}'
    ],
    report:true,
    reportDir:"editor/coverage",
  },
  plugins: [

    {name: "vite-plugin",
      async serverStart({ app }) {
        server = await vite.createServer({clearScreen: false, mode:"test",server:{port:8181}});// use diff port so it doesn't conflict with dev server
        await server.listen();
        const port = server.config.server.port;
        app.use((ctx, next) =>{
          ctx.redirect(`http://localhost:${port}${ctx.originalUrl}`) 
        })
      },
      async serverStop() { server.close() },
    },
  ],
  browsers: [
    chromeLauncher({
      concurrency: 1,
      launchOptions: {
        headless: true,
        defaultViewport:{width:1400,height:900},
        chromeFlags: [
          '--disable-background-timer-throttling',
          '--disable-renderer-backgrounding',
          '--disable-backgrounding-occluded-windows',
          '--disable-renderer-backgrounding',
          '--enable-automation'
        ],
      },
    }),
  ],
  reporters: [
    defaultReporter({ reportTestResults: true, reportTestProgress: true }),
    specReporter(),
  ],
  testRunnerHtml: testFramework => `
    <html>
      <head>
        <script>
          // Note: globals expected by @testing-library/svelte
          global = window;
          process = { env: {} };
        </script>
        <script type="module" src="${testFramework}"></script>
      </head>
    </html>
  `,
};
remcovaes commented 1 year ago

Hey @diervo, I was a need of a working plugin, so I made one. I also made sure that @web/test-runner-commands work properly. See: @remcovaes/web-test-runner-vite-plugin

crisward commented 1 year ago

@remcovaes out of interest, does your plugin work with coverage? My solution above worked apart from coverage (which stopped working recently, not sure why 🤷 )

remcovaes commented 1 year ago

@crisward it does not :( I would have to look into why, but it is probably because the @web/dev-server is not in control of the build, so it can not know which files are touched in the test. Did it work for this plugin?

remcovaes commented 1 year ago

@crisward, forgot to mention, I made sure that the web test commands work. This makes it easier to test keyboard interactions.

remcovaes commented 1 year ago

@crisward I fixed the code coverage in a new release: https://github.com/remcovaes/web-test-runner-vite-plugin/releases/tag/v1.1.1