From 599273b17937533383186d63bc02992ff9af837a Mon Sep 17 00:00:00 2001 From: Kelly Mears Date: Thu, 27 Jun 2024 00:44:50 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=A7=AA=20test(none):=20improve=20@roots/b?= =?UTF-8?q?ud-react=20coverage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sources/@roots/bud-react/package.json | 3 +- sources/@roots/bud-react/src/index.ts | 2 +- .../bud-react/src/react-refresh/index.ts | 46 ++--- .../@roots/bud-react/src/swc-refresh/index.ts | 10 +- .../bud-react/src/typescript-refresh/index.ts | 10 +- .../test/babel-refresh/extension.test.ts | 43 ++++- .../@roots/bud-react/test/extension.test.ts | 104 ++++++++++- .../test/extension/extension.test.ts | 4 +- .../test/react-refresh/extension.test.ts | 161 +++++++++++++++++- .../test/swc-refresh/extension.test.ts | 46 ++++- .../test/typescript-refresh/extension.test.ts | 47 ++++- sources/@roots/bud-react/vitest.config.ts | 11 ++ yarn.lock | 1 + 13 files changed, 417 insertions(+), 71 deletions(-) create mode 100644 sources/@roots/bud-react/vitest.config.ts diff --git a/sources/@roots/bud-react/package.json b/sources/@roots/bud-react/package.json index 898a6e1004..ca1aa86331 100644 --- a/sources/@roots/bud-react/package.json +++ b/sources/@roots/bud-react/package.json @@ -91,7 +91,8 @@ "@types/babel__core": "7.20.5", "@types/node": "20.12.8", "@types/react": "18.3.1", - "@types/react-dom": "18.3.0" + "@types/react-dom": "18.3.0", + "vitest": "1.6.0" }, "dependencies": { "@babel/preset-react": "7.24.1", diff --git a/sources/@roots/bud-react/src/index.ts b/sources/@roots/bud-react/src/index.ts index 301476dfd8..9122d598d1 100644 --- a/sources/@roots/bud-react/src/index.ts +++ b/sources/@roots/bud-react/src/index.ts @@ -37,4 +37,4 @@ declare module '@roots/bud-framework' { } } -export default BudReact +export {default} from '@roots/bud-react/extension' diff --git a/sources/@roots/bud-react/src/react-refresh/index.ts b/sources/@roots/bud-react/src/react-refresh/index.ts index 66c2f69f8b..bd5f3c26cb 100644 --- a/sources/@roots/bud-react/src/react-refresh/index.ts +++ b/sources/@roots/bud-react/src/react-refresh/index.ts @@ -84,16 +84,15 @@ export default class BudReactRefresh extends Extension< */ @bind public override async configAfter(bud: Bud) { - if (!this.isEnabled()) return - if (bud.context.mode !== `development`) return - if (bud.context.hot === false) return + if (!bud.isDevelopment) return + if (!(`hot` in bud.server.enabledMiddleware)) return if (!this.compilerExtension) { - const signifier = bud.swc + const signifier = bud.swc?.enabled ? `@roots/bud-react/swc-refresh` - : bud.typescript && bud.typescript.babel === false + : bud.typescript?.enabled && bud.typescript.babel === false ? `@roots/bud-react/typescript-refresh` - : bud.babel || bud.typescript?.babel === true + : bud.babel?.enabled || bud.typescript?.babel === true ? `@roots/bud-react/babel-refresh` : false @@ -117,10 +116,10 @@ export default class BudReactRefresh extends Extension< */ @bind public override async make( - bud: Bud, + _bud: Bud, options: Options, ): Promise { - return new RefreshPlugin(omit(this.options, [`compilerExtension`])) + return new RefreshPlugin(omit(options, [`compilerExtension`])) } /** @@ -154,30 +153,17 @@ export default class BudReactRefresh extends Extension< */ @bind public configure(userOptions?: false | Options): this { - this.app.hooks.action( - `config.after`, - this.makeReactRefreshCallback(userOptions), - ) - - return this - } - - /** - * Callback handling {@link RefreshPlugin} configuration - */ - @bind - protected makeReactRefreshCallback( - userOptions?: false | Options, - ): (bud: Bud) => Promise { - return async () => { - if (!this.app.isDevelopment) return this + if (userOptions === false || !this.app.isDevelopment) { + this.enable(false) + return + } - if (!isUndefined(userOptions) && !isBoolean(userOptions)) - this.setOptions(userOptions) + this.enable(true) - userOptions === false ? this.enable(false) : this.enable() + this.app.hooks.action(`config.after`, async () => { + this.setOptions(userOptions) + }) - return this - } + return this } } diff --git a/sources/@roots/bud-react/src/swc-refresh/index.ts b/sources/@roots/bud-react/src/swc-refresh/index.ts index 3cfb670dc8..4b47b5a6ae 100644 --- a/sources/@roots/bud-react/src/swc-refresh/index.ts +++ b/sources/@roots/bud-react/src/swc-refresh/index.ts @@ -14,10 +14,10 @@ import { @development export default class BudSWCRefresh extends Extension { /** - * {@link Extension.buildBefore} + * {@link Extension.register} */ @bind - public override async buildBefore(bud: Bud) { + public override async register(bud: Bud) { await this.registerTransform(bud) } @@ -26,12 +26,6 @@ export default class BudSWCRefresh extends Extension { */ public async registerTransform({isDevelopment, swc}: Bud) { this.logger.log(`Registering swc react-refresh transformer`) - if (!swc) { - this.logger.warn( - `SWC not found. Skipping registration of ${this.constructor.name}.`, - ) - return this - } swc.setTransform((transform = {}) => ({ ...(transform ?? {}), diff --git a/sources/@roots/bud-react/src/typescript-refresh/index.ts b/sources/@roots/bud-react/src/typescript-refresh/index.ts index 3e933785c7..7b9b12e649 100644 --- a/sources/@roots/bud-react/src/typescript-refresh/index.ts +++ b/sources/@roots/bud-react/src/typescript-refresh/index.ts @@ -14,10 +14,10 @@ import { @development export default class BudTypeScriptRefresh extends Extension { /** - * {@link Extension.buildBefore} + * {@link Extension.register} */ @bind - public override async buildBefore(bud: Bud) { + public override async register(bud: Bud) { this.registerTransform(bud) } @@ -27,12 +27,6 @@ export default class BudTypeScriptRefresh extends Extension { @bind public async registerTransform(bud: Bud) { this.logger.log(`Registering react-refresh-typescript transformer`) - if (!bud.typescript) { - this.logger.warn( - `Typescript not found. Skipping registration of ${this.constructor.name}.`, - ) - return this - } const transform = await this.import( `react-refresh-typescript`, diff --git a/sources/@roots/bud-react/test/babel-refresh/extension.test.ts b/sources/@roots/bud-react/test/babel-refresh/extension.test.ts index 5e9b09fdd2..5031541191 100644 --- a/sources/@roots/bud-react/test/babel-refresh/extension.test.ts +++ b/sources/@roots/bud-react/test/babel-refresh/extension.test.ts @@ -1,8 +1,43 @@ -import Extension from '@roots/bud-react/babel-refresh' -import {describe, expect, it} from 'vitest' +import {beforeEach, describe, expect, it, vi} from 'vitest' + +import Extension from '../../src/babel-refresh' describe(`@roots/bud-react/babel-refresh`, () => { - it(`should be constructable`, () => { - expect(Extension).toBeInstanceOf(Function) + let bud: any + let extension: Extension + beforeEach(async () => { + bud = { + babel: { + setPlugin: vi.fn(), + }, + module: { + resolve: vi.fn(async (...args) => `/test/path/`), + }, + } + extension = new Extension(bud) + }) + describe(`register()`, async () => { + it(`should call logger.log`, async () => { + const spy = vi.spyOn(extension.logger, `log`) + await extension.register(bud) + + expect(spy).toHaveBeenCalledWith( + `Registering react-refresh-babel transformer`, + ) + }) + + it(`should interface with bud.babel`, async () => { + await extension.register(bud) + + expect(bud.babel.setPlugin).toHaveBeenCalledWith( + `react-refresh/babel`, + expect.arrayContaining([ + `/test/path/`, + { + skipEnvCheck: true, + }, + ]), + ) + }) }) }) diff --git a/sources/@roots/bud-react/test/extension.test.ts b/sources/@roots/bud-react/test/extension.test.ts index 8697d19d49..44fa0de80a 100644 --- a/sources/@roots/bud-react/test/extension.test.ts +++ b/sources/@roots/bud-react/test/extension.test.ts @@ -1,8 +1,102 @@ -import Extension from '@roots/bud-react/extension' -import {describe, expect, it} from 'vitest' +import type {Bud} from '@roots/bud-framework' -describe(`@roots/bud-react`, () => { - it(`should be constructable`, () => { - expect(Extension).toBeInstanceOf(Function) +import {beforeEach, describe, expect, it, vi} from 'vitest' + +import DefaultExport from '../src' +import Extension from '../src/extension' + +describe(`@roots/bud-react`, async () => { + let bud: any + let extension: Extension + + beforeEach(async () => { + bud = { + extensions: { + add: vi.fn(), + get: vi.fn(), + }, + module: { + resolve: vi.fn(async (...args) => `/test/path/`), + }, + provide: vi.fn(), + } as unknown as Bud + + extension = new Extension(bud) + }) + + it(`should re-export @roots/bud-react/extension`, async () => { + const module = await import(`../src`) + expect(module.default).toBe(DefaultExport) + }) + + describe(`boot()`, async () => { + it(`should be a function`, () => { + expect(extension.boot).toBeInstanceOf(Function) + }) + + it(`should resolve react`, async () => { + const spy = vi.spyOn(extension, `resolve`) + await extension.boot(bud) + + expect(spy).toHaveBeenCalledWith( + `react`, + expect.stringMatching(/@roots\/bud-react\/src\/extension\/index/), + ) + }) + + it(`should call bud.provide`, async () => { + await extension.boot(bud) + expect(bud.provide).toHaveBeenCalledWith( + `/test/path/`, + expect.arrayContaining([`React`]), + ) + }) + + it(`should interface with swc if available`, async () => { + bud.swc = { + jsc: {}, + setJsc: vi.fn(), + setTransform: vi.fn(), + } + + await extension.boot(bud) + + expect(bud.swc.setJsc).toHaveBeenCalledWith( + expect.objectContaining({ + transform: { + react: { + runtime: `automatic`, + }, + }, + }), + ) + + expect(bud.swc.setTransform).toHaveBeenCalledWith( + expect.any(Function), + ) + }) + + it(`should interface with babel if available`, async () => { + bud.babel = { + setPreset: vi.fn(), + } + + await extension.boot(bud) + + expect(bud.babel.setPreset).toHaveBeenCalledWith( + `@babel/preset-react`, + `/test/path/`, + ) + }) + }) + + describe(`get refresh()`, async () => { + it(`should call bud.extensions.get when referenced`, () => { + extension.refresh + + expect(bud.extensions.get).toHaveBeenCalledWith( + `@roots/bud-react/react-refresh`, + ) + }) }) }) diff --git a/sources/@roots/bud-react/test/extension/extension.test.ts b/sources/@roots/bud-react/test/extension/extension.test.ts index b7e8c107a4..e0628d1bf9 100644 --- a/sources/@roots/bud-react/test/extension/extension.test.ts +++ b/sources/@roots/bud-react/test/extension/extension.test.ts @@ -1,7 +1,7 @@ -import '@roots/bud-react' -import Extension from '@roots/bud-react/extension' import {describe, expect, it} from 'vitest' +import Extension from '../../src/extension' + describe(`@roots/bud-react`, () => { it(`should be constructable`, () => { expect(Extension).toBeInstanceOf(Function) diff --git a/sources/@roots/bud-react/test/react-refresh/extension.test.ts b/sources/@roots/bud-react/test/react-refresh/extension.test.ts index f3754c6781..6a5b422694 100644 --- a/sources/@roots/bud-react/test/react-refresh/extension.test.ts +++ b/sources/@roots/bud-react/test/react-refresh/extension.test.ts @@ -1,8 +1,161 @@ -import Extension from '@roots/bud-react/react-refresh' -import {describe, expect, it} from 'vitest' +import ReactRefreshPlugin from '@pmmmwh/react-refresh-webpack-plugin' +import {beforeEach, describe, expect, it, vi} from 'vitest' + +import Extension from '../../src/react-refresh' describe(`@roots/bud-react/react-refresh`, () => { - it(`should be constructable`, () => { - expect(Extension).toBeInstanceOf(Function) + let bud: any + let extension: Extension + + beforeEach(async () => { + bud = { + context: { + manifest: { + type: `module`, + }, + }, + extensions: { + add: vi.fn(async () => ({})), + get: vi.fn(label => ({ + label, + })), + }, + hooks: { + action: vi.fn(), + }, + isDevelopment: true, + module: { + resolve: vi.fn(async (...args) => `/test/path/`), + }, + server: { + enabledMiddleware: { + hot: {}, + }, + }, + swc: { + enabled: true, + }, + } + + extension = new Extension(bud) + }) + + describe(`compilerExtension`, () => { + it(`should be undefined`, () => { + expect(extension.compilerExtension).toBeUndefined() + }) + }) + + describe(`configAfter()`, async () => { + it(`should add @roots/bud-react/swc-refresh`, async () => { + await extension.configAfter(bud) + expect(bud.extensions.add).toHaveBeenCalledWith( + `@roots/bud-react/swc-refresh`, + ) + }) + + it(`should return early if bud.isProduction`, async () => { + bud.isDevelopment = false + await extension.configAfter(bud) + expect(bud.extensions.add).not.toHaveBeenCalled() + }) + + it(`should return early if hot middleware is not enabled`, async () => { + bud.server.enabledMiddleware = {} + await extension.configAfter(bud) + expect(bud.extensions.add).not.toHaveBeenCalled() + }) + + it(`should set compilerExtension`, async () => { + await extension.configAfter(bud) + expect(extension.compilerExtension).toEqual({ + label: `@roots/bud-react/swc-refresh`, + }) + }) + + it(`should set typescript-refresh if babel is disabled and typescript is enabled`, async () => { + bud.swc.enabled = false + bud.typescript = {babel: false, enabled: true} + await extension.configAfter(bud) + + expect(bud.extensions.add).toHaveBeenCalledWith( + `@roots/bud-react/typescript-refresh`, + ) + }) + + it(`should set babel-refresh if typescript is disabled and babel is enabled`, async () => { + bud.swc.enabled = false + bud.typescript = {babel: true, enabled: true} + await extension.configAfter(bud) + + expect(bud.extensions.add).toHaveBeenCalledWith( + `@roots/bud-react/babel-refresh`, + ) + }) + + it(`should set babel-refresh if babel is enabled`, async () => { + bud.swc = {enabled: false} + bud.babel = {enabled: true} + await extension.configAfter(bud) + + expect(bud.extensions.add).toHaveBeenCalledWith( + `@roots/bud-react/babel-refresh`, + ) + }) + + it(`should not add an extension if compilerExtension is set`, async () => { + extension.setCompilerExtension({ + label: `custom-compiler-extension`, + } as any) + await extension.configAfter(bud) + + expect(bud.extensions.add).not.toHaveBeenCalled() + }) + + it(`should not add an extension if no requirements are met`, async () => { + bud.swc.enabled = false + await extension.configAfter(bud) + + expect(bud.extensions.add).not.toHaveBeenCalled() + }) + }) + + describe(`make()`, async () => { + it(`should return a new instance of RefreshPlugin`, async () => { + const plugin = await extension.make(bud, { + compilerExtension: {} as any, + }) + + expect(plugin).toBeInstanceOf(ReactRefreshPlugin) + }) + }) + + describe(`configure()`, async () => { + it(`should disable plugin when called with false`, async () => { + const spy = vi.spyOn(extension, `enable`) + extension.configure(false) + expect(spy).toHaveBeenCalledWith(false) + }) + + it(`should call bud.hooks.action`, async () => { + const spy = vi.spyOn(extension, `enable`) + const obj = {compilerExtension: {} as any} + extension.configure(obj) + expect(spy).toHaveBeenCalledWith(true) + expect(bud.hooks.action).toHaveBeenCalledWith( + `config.after`, + expect.any(Function), + ) + }) + + it(`should set options when called with obj`, async () => { + const setOptionsSpy = vi.spyOn(extension, `setOptions`) + bud.hooks.action = vi.fn(async (name, cb) => { + return await cb(bud) + }) + const options = {foo: `bar`} as any + extension.configure(options) + expect(setOptionsSpy).toHaveBeenCalledWith(options) + }) }) }) diff --git a/sources/@roots/bud-react/test/swc-refresh/extension.test.ts b/sources/@roots/bud-react/test/swc-refresh/extension.test.ts index 2b189b64da..dac303b23c 100644 --- a/sources/@roots/bud-react/test/swc-refresh/extension.test.ts +++ b/sources/@roots/bud-react/test/swc-refresh/extension.test.ts @@ -1,8 +1,46 @@ -import Extension from '@roots/bud-react/swc-refresh' -import {describe, expect, it} from 'vitest' +import {beforeEach, describe, expect, it, vi} from 'vitest' + +import Extension from '../../src/swc-refresh' describe(`@roots/bud-react/swc-refresh`, () => { - it(`should be constructable`, () => { - expect(Extension).toBeInstanceOf(Function) + let bud: any + let extension: Extension + beforeEach(async () => { + bud = { + module: { + resolve: vi.fn(async (...args) => `/test/path/`), + }, + swc: { + setTransform: vi.fn(), + }, + } + extension = new Extension(bud) + }) + + describe(`register()`, async () => { + it(`should call registerTransform`, async () => { + const spy = vi.spyOn(extension, `registerTransform`) + await extension.register(bud) + expect(spy).toHaveBeenCalledWith(bud) + }) + }) + + describe(`setTransform()`, async () => { + it(`should call logger.log`, async () => { + const spy = vi.spyOn(extension.logger, `log`) + await extension.registerTransform(bud) + + expect(spy).toHaveBeenCalledWith( + `Registering swc react-refresh transformer`, + ) + }) + + it(`should call bud.swc.setTransform`, async () => { + await extension.registerTransform(bud) + + expect(bud.swc.setTransform).toHaveBeenCalledWith( + expect.any(Function), + ) + }) }) }) diff --git a/sources/@roots/bud-react/test/typescript-refresh/extension.test.ts b/sources/@roots/bud-react/test/typescript-refresh/extension.test.ts index 24453d1c08..5d4d6c1def 100644 --- a/sources/@roots/bud-react/test/typescript-refresh/extension.test.ts +++ b/sources/@roots/bud-react/test/typescript-refresh/extension.test.ts @@ -1,8 +1,47 @@ -import Extension from '@roots/bud-react/typescript-refresh' -import {describe, expect, it} from 'vitest' +import {beforeEach, describe, expect, it, vi} from 'vitest' + +import Extension from '../../src/typescript-refresh' describe(`@roots/bud-react/typescript-refresh`, () => { - it(`should be constructable`, () => { - expect(Extension).toBeInstanceOf(Function) + let bud: any + let extension: Extension + beforeEach(async () => { + bud = { + module: { + import: vi.fn(async () => ({})), + resolve: vi.fn(async (...args) => `/test/path/`), + }, + typescript: { + setGetCustomTransformers: vi.fn(), + }, + } + extension = new Extension(bud) + }) + + describe(`register()`, async () => { + it(`should call registerTransform`, async () => { + const spy = vi.spyOn(extension, `registerTransform`) + await extension.register(bud) + expect(spy).toHaveBeenCalledWith(bud) + }) + }) + + describe(`setTransform()`, async () => { + it(`should call logger.log`, async () => { + const spy = vi.spyOn(extension.logger, `log`) + await extension.registerTransform(bud) + + expect(spy).toHaveBeenCalledWith( + `Registering react-refresh-typescript transformer`, + ) + }) + + it(`should call bud.typescript.setGetCustomTransformers`, async () => { + await extension.registerTransform(bud) + + expect(bud.typescript.setGetCustomTransformers).toHaveBeenCalledWith( + expect.any(Function), + ) + }) }) }) diff --git a/sources/@roots/bud-react/vitest.config.ts b/sources/@roots/bud-react/vitest.config.ts new file mode 100644 index 0000000000..02ac07d792 --- /dev/null +++ b/sources/@roots/bud-react/vitest.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + coverage: { + include: [`src/**/*.ts`], + provider: `v8`, + }, + include: [`test/**/*.test.ts`, `test/**/*.test.tsx`], + }, +}) diff --git a/yarn.lock b/yarn.lock index 5ef4fd3c2a..e5a045e20e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11831,6 +11831,7 @@ __metadata: react-dom: "npm:18.3.1" react-refresh: "npm:0.14.2" tslib: "npm:2.6.2" + vitest: "npm:1.6.0" webpack: "npm:5.91.0" languageName: unknown linkType: soft