Skip to content

Commit

Permalink
🧪 test(none): improve @roots/bud-react coverage (#2615)
Browse files Browse the repository at this point in the history
Way better coverage:

```
------------------------|---------|----------|---------|---------|-------------------
File                    | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
------------------------|---------|----------|---------|---------|-------------------
All files               |   97.61 |      100 |     100 |   97.61 |
 src                    |     100 |      100 |     100 |     100 |
  index.ts              |     100 |      100 |     100 |     100 |
 src/babel-refresh      |     100 |      100 |     100 |     100 |
  index.ts              |     100 |      100 |     100 |     100 |
 src/extension          |   96.55 |      100 |     100 |   96.55 |
  index.ts              |   96.55 |      100 |     100 |   96.55 | 44-45
 src/react-refresh      |     100 |      100 |     100 |     100 |
  index.ts              |     100 |      100 |     100 |     100 |
 src/swc-refresh        |   84.61 |      100 |     100 |   84.61 |
  index.ts              |   84.61 |      100 |     100 |   84.61 | 31-36
 src/typescript-refresh |    97.5 |      100 |     100 |    97.5 |
  index.ts              |    97.5 |      100 |     100 |    97.5 | 37
------------------------|---------|----------|---------|---------|-------------------
```

## Type of change

**PATCH: backwards compatible change**
  • Loading branch information
kellymears authored Jun 27, 2024
1 parent e0f496d commit 68bb006
Show file tree
Hide file tree
Showing 15 changed files with 422 additions and 75 deletions.
2 changes: 1 addition & 1 deletion sources/@repo/test-kit/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export type Options = {

const makeTestBud = async (options: Options = {}): Promise<Bud> =>
await factory({
basedir: options.basedir ?? path(`tests`, `util`, `project`),
basedir: path(`tests`, `util`, `project`),
cache: false,
dry: true,
force: true,
Expand Down
5 changes: 4 additions & 1 deletion sources/@roots/bud-framework/src/extension/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,10 @@ export class Extension<
if (this.meta[key] === true) return false
this.meta[key] = true

if ([`buildAfter`, `buildBefore`].includes(key) && !this.isEnabled())
if (
[`buildAfter`, `buildBefore`, `configAfter`].includes(key) &&
!this.isEnabled()
)
return false

this.logger.log(`Executing:`, key)
Expand Down
3 changes: 2 additions & 1 deletion sources/@roots/bud-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion sources/@roots/bud-react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,4 @@ declare module '@roots/bud-framework' {
}
}

export default BudReact
export {default} from '@roots/bud-react/extension'
48 changes: 16 additions & 32 deletions sources/@roots/bud-react/src/react-refresh/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ import {
label,
options,
} from '@roots/bud-framework/extension/decorators'
import isBoolean from '@roots/bud-support/isBoolean'
import isUndefined from '@roots/bud-support/isUndefined'
import omit from '@roots/bud-support/omit'

interface Options extends ReactRefreshPluginOptions {
Expand Down Expand Up @@ -84,16 +82,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

Expand All @@ -117,10 +114,10 @@ export default class BudReactRefresh extends Extension<
*/
@bind
public override async make(
bud: Bud,
_bud: Bud,
options: Options,
): Promise<RefreshPlugin> {
return new RefreshPlugin(omit(this.options, [`compilerExtension`]))
return new RefreshPlugin(omit(options, [`compilerExtension`]))
}

/**
Expand Down Expand Up @@ -154,30 +151,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<unknown> {
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
}
}
10 changes: 2 additions & 8 deletions sources/@roots/bud-react/src/swc-refresh/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand All @@ -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 ?? {}),
Expand Down
10 changes: 2 additions & 8 deletions sources/@roots/bud-react/src/typescript-refresh/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand All @@ -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`,
Expand Down
43 changes: 39 additions & 4 deletions sources/@roots/bud-react/test/babel-refresh/extension.test.ts
Original file line number Diff line number Diff line change
@@ -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,
},
]),
)
})
})
})
104 changes: 99 additions & 5 deletions sources/@roots/bud-react/test/extension.test.ts
Original file line number Diff line number Diff line change
@@ -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`,
)
})
})
})
4 changes: 2 additions & 2 deletions sources/@roots/bud-react/test/extension/extension.test.ts
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
Loading

0 comments on commit 68bb006

Please sign in to comment.