diff --git a/.changepacks/changepack_log_bun_latest_coverage.json b/.changepacks/changepack_log_bun_latest_coverage.json
new file mode 100644
index 000000000..aca3b06ca
--- /dev/null
+++ b/.changepacks/changepack_log_bun_latest_coverage.json
@@ -0,0 +1,7 @@
+{
+ "changes": {
+ "packages/eslint-plugin/package.json": "Patch"
+ },
+ "note": "Fix sparse trailing-null autofixes to preserve authored values, complete parenthesized syntax and earlier holes using token-based edit boundaries while removing the maximal trailing run of nulls and holes only when the last element is canonical literal null; preserve regular-expression and BigInt literals without validating regex syntax, preserve existing comment and trailing-comma handling, remove an unreachable AST guard and restore complete behavior coverage on the latest Bun without exclusions or new build errors.",
+ "date": "2026-10-22T00:00:00.000Z"
+}
diff --git a/packages/eslint-plugin/src/rules/css-utils-literal-only/__tests__/index.test.ts b/packages/eslint-plugin/src/rules/css-utils-literal-only/__tests__/index.test.ts
index fc7fe8169..d4686b857 100644
--- a/packages/eslint-plugin/src/rules/css-utils-literal-only/__tests__/index.test.ts
+++ b/packages/eslint-plugin/src/rules/css-utils-literal-only/__tests__/index.test.ts
@@ -18,6 +18,25 @@ describe.each(['css' /* 'globalCss', 'keyframes'*/])(
})
ruleTester.run('css-utils-literal-only rule', cssUtilsLiteralOnly, {
valid: [
+ ...[
+ "const f = ({'p': n}) => n; css({w: f({p: 1})});",
+ "const k = 'p'; const f = ({[k]: n}) => n; css({w: f({p: 1})});",
+ 'const L = [1]; unknown(L[0]); css({w: L[0]});',
+ 'const L = [1]; unknown(L[9]); css({w: L[0]});',
+ 'const L = {p: 1}; export const holder = [L]; css({w: L.p});',
+ 'const L = {p: 1}; module.exports.token = L; css({w: L.p});',
+ "const L = {p: 1}; module['exports'].token = L; css({w: L.p});",
+ 'const N = 4; function f() { return N } css({w: f()});',
+ 'const helper = () => 4; function f() { return helper() } css({w: f()});',
+ 'const helper = function() { return 4 }; function f() { return helper() } css({w: f()});',
+ ].map((use) => ({
+ code: `import { css } from "@devup-ui/react";\n${use}`,
+ filename: 'src/app/page.tsx',
+ })),
+ {
+ code: `import { 'create' as create } from '@stylexjs/stylex'; create({a: {width: 1}});`,
+ filename: 'src/app/page.tsx',
+ },
{
code: `import { ${code} } from "@devup-ui/react";\n${code}({w: 1})`,
filename: 'src/app/page.tsx',
@@ -145,6 +164,26 @@ describe.each(['css' /* 'globalCss', 'keyframes'*/])(
},
],
invalid: [
+ ...[
+ "const f = ({'normalize': n}) => n; css({w: f({normalize: 1})});",
+ 'const L = [{p: 1}]; unknown(L[0]); css({w: L[0].p});',
+ 'const A = [{p: 1}]; const L = [1, ...A]; unknown(L[0]); css({w: A[0].p});',
+ 'const L = {p: 1}; unknown([L]); css({w: L.p});',
+ 'const L = {p: 1}; other.exports.token = L; css({w: L.p});',
+ 'const helper = () => Date.now(); function f() { return helper() } css({w: f()});',
+ 'class Token {} function f() { return Token } css({w: f()});',
+ ].map((use) => ({
+ code: `import { css } from "@devup-ui/react";\n${use}`,
+ filename: 'src/app/page.tsx',
+ output: null,
+ errors: [{ messageId: 'cssUtilsLiteralOnly' as const }],
+ })),
+ {
+ code: `import { 'create' as create } from '@stylexjs/stylex'; let v = 1; create({a: {width: v}});`,
+ filename: 'src/app/page.tsx',
+ output: null,
+ errors: [{ messageId: 'cssUtilsLiteralOnly' }],
+ },
...[
[
`export function C() { let fade = keyframes({ from: { opacity: 0 } }); return css({ animationName: fade }); }`,
diff --git a/packages/eslint-plugin/src/rules/no-useless-tailing-nulls/__tests__/index.test.ts b/packages/eslint-plugin/src/rules/no-useless-tailing-nulls/__tests__/index.test.ts
index 8af51b383..cf90a4853 100644
--- a/packages/eslint-plugin/src/rules/no-useless-tailing-nulls/__tests__/index.test.ts
+++ b/packages/eslint-plugin/src/rules/no-useless-tailing-nulls/__tests__/index.test.ts
@@ -1,9 +1,110 @@
import { RuleTester } from '@typescript-eslint/rule-tester'
-import { describe } from 'bun:test'
+import { TSESLint } from '@typescript-eslint/utils'
+import { describe, expect, it } from 'bun:test'
+import { parser } from 'typescript-eslint'
import { noUselessTailingNulls } from '../index'
describe('no-useless-tailing-nulls rule', () => {
+ const boundaryCases = [
+ ['[0,(1),,null]', '[0,(1)]'],
+ ['[0,((1)),,null]', '[0,((1))]'],
+ ['[0,(1),null]', '[0,(1)]'],
+ ['[0,(1),,(null)]', '[0,(1)]'],
+ ['[(null)]', '[]'],
+ ['[1,null]', '[1]'],
+ ['[0,((1)),,((null))]', '[0,((1))]'],
+ ['[((null)),(null)]', '[]'],
+ [
+ '[0,( /* value */ 1 /* inner */ ), /* remove */ , (null)]',
+ '[0,( /* value */ 1 /* inner */ )]',
+ ],
+ ['[1 /* remove */, /* remove */ null /* keep */]', '[1 /* keep */]'],
+ ['[/* keep */ (null),null]', '[/* keep */ ]'],
+ ['[, /* remove */ (null)]', '[]'],
+ ['[0,(1),null,]', '[0,(1),]'],
+ ['[0,((1)),,((null)),]', '[0,((1)),]'],
+ ['[(null),]', '[,]'],
+ ['[null,]', '[,]'],
+ ['[1,null, /* keep */]', '[1, /* keep */]'],
+ ['[1,null /* keep */,]', '[1 /* keep */,]'],
+ [
+ '[0,(1), /* remove */ null /* keep */, /* tail */]',
+ '[0,(1) /* keep */, /* tail */]',
+ ],
+ ['[/* keep */ ((null)) /* tail */]', '[/* keep */ /* tail */]'],
+ ] as const
+ const literalKindCases = [
+ ['[/(/,null]', '[/(/]'],
+ ['[/a/gg,null]', '[/a/gg]'],
+ ['[/a/z,null]', '[/a/z]'],
+ ['[/a/gg,,null]', '[/a/gg]'],
+ ['[/a/g,null]', '[/a/g]'],
+ ['[/a/g,,null]', '[/a/g]'],
+ ['[(/(/),,(null)]', '[(/(/)]'],
+ ['[1n,null]', '[1n]'],
+ ['[0xFFn,,null]', '[0xFFn]'],
+ ['[1n,null,,(null),]', '[1n,]'],
+ ] as const
+ const literalKindControls = [
+ '[/(/]',
+ '[/a/gg]',
+ '[/a/z]',
+ '[1,null,/(/]',
+ '[/a/g]',
+ '[1n]',
+ '[0xFFn]',
+ '[null,1n]',
+ ] as const
+ const literalKindValidTests = literalKindControls.flatMap((value) =>
+ [false, true].map((jsx) => ({
+ code: `import {css,Box} from '@devup-ui/react';\n${jsx ? `` : `css({w:${value}})`}`,
+ filename: 'src/app/page.tsx',
+ })),
+ )
+ const boundaryTests = [...boundaryCases, ...literalKindCases].flatMap(
+ ([value, fixed]) =>
+ [false, true].map((jsx) => ({
+ code: `import {css,Box} from '@devup-ui/react';\n${jsx ? `` : `css({w:${value}})`}`,
+ output: `import {css,Box} from '@devup-ui/react';\n${jsx ? `` : `css({w:${fixed}})`}`,
+ filename: 'src/app/page.tsx',
+ errors: [{ messageId: 'uselessTailingNulls' as const }],
+ })),
+ )
+ it.each([
+ ...boundaryTests,
+ ...literalKindValidTests.map((test) => ({ ...test, output: test.code })),
+ ])(
+ 'preserves authored syntax and parses when fixing $code',
+ ({ code, output }) => {
+ const linter = new TSESLint.Linter()
+ const config = {
+ files: ['**/*.tsx'],
+ languageOptions: {
+ parser,
+ parserOptions: { ecmaFeatures: { jsx: true } },
+ },
+ plugins: { devup: { rules: { trailing: noUselessTailingNulls } } },
+ rules: { 'devup/trailing': 'error' },
+ } satisfies TSESLint.FlatConfig.Config
+
+ const result = linter.verifyAndFix(code, config, {
+ filename: 'src/app/page.tsx',
+ })
+
+ expect(result.output).toBe(output)
+ expect(
+ linter.verify(
+ result.output,
+ { ...config, rules: {} },
+ {
+ filename: 'src/app/page.tsx',
+ },
+ ),
+ ).toEqual([])
+ expect(result.messages).toEqual([])
+ },
+ )
const ruleTester = new RuleTester({
languageOptions: {
ecmaVersion: 'latest',
@@ -16,6 +117,21 @@ describe('no-useless-tailing-nulls rule', () => {
})
ruleTester.run('no-useless-tailing-nulls rule', noUselessTailingNulls, {
valid: [
+ ...literalKindValidTests,
+ // Real parser holes are absent elements, not Literal null nodes.
+ ...[
+ '[]',
+ '[,]',
+ '[1,null,,]',
+ '[null,,]',
+ '[,,]',
+ '[1,,]',
+ '[1,,2]',
+ '[1, null, 2]',
+ ].map((value) => ({
+ code: `import { css } from "@devup-ui/react";\ncss({w: ${value}})`,
+ filename: 'src/app/page.tsx',
+ })),
{
code: 'import { Box } from "@devup-ui/react";\n',
filename: 'src/app/page.tsx',
@@ -44,6 +160,28 @@ describe('no-useless-tailing-nulls rule', () => {
})),
],
invalid: [
+ ...boundaryTests,
+ ...[
+ ['[null]', '[]'],
+ ['[null,null]', '[]'],
+ ['[1,null]', '[1]'],
+ ['[1,,null]', '[1]'],
+ ['[,1,null]', '[,1]'],
+ ['[1,,2,null]', '[1,,2]'],
+ ['[1,,null,null]', '[1]'],
+ ['[,null]', '[]'],
+ ['[,,null,null]', '[]'],
+ ['[/* keep */ null,null]', '[/* keep */ ]'],
+ ['[1,null,,null]', '[1]'],
+ ['[null,1,,null]', '[null,1]'],
+ ['[value,,null]', '[value]'],
+ ['[...values,,null]', '[...values]'],
+ ].map(([value, fixed]) => ({
+ code: `import { css } from "@devup-ui/react";\ncss({w: ${value}})`,
+ output: `import { css } from "@devup-ui/react";\ncss({w: ${fixed}})`,
+ filename: 'src/app/page.tsx',
+ errors: [{ messageId: 'uselessTailingNulls' as const }],
+ })),
{
code: 'import { Box } from "@devup-ui/react";\n} m={[2, null]} />',
output:
diff --git a/packages/eslint-plugin/src/rules/no-useless-tailing-nulls/index.ts b/packages/eslint-plugin/src/rules/no-useless-tailing-nulls/index.ts
index 94ecb4584..d16f185fb 100644
--- a/packages/eslint-plugin/src/rules/no-useless-tailing-nulls/index.ts
+++ b/packages/eslint-plugin/src/rules/no-useless-tailing-nulls/index.ts
@@ -1,5 +1,6 @@
import {
AST_NODE_TYPES,
+ ASTUtils,
ESLintUtils,
type TSESTree,
} from '@typescript-eslint/utils'
@@ -13,36 +14,52 @@ const createRule = ESLintUtils.RuleCreator(
`https://github.com/dev-five-git/devup-ui/tree/main/packages/eslint-plugin/src/rules/${name}`,
)
+function isNullLiteral(
+ node: TSESTree.Node | null | undefined,
+): node is TSESTree.NullLiteral {
+ return node?.type === AST_NODE_TYPES.Literal && node.raw === 'null'
+}
+
function checkUselessTailingNulls>(
node: TSESTree.ArrayExpression,
context: T,
) {
- let nullCount = 0
- for (let i = node.elements.length - 1; i >= 0; i--) {
+ const lastElement = node.elements[node.elements.length - 1]
+ if (!isNullLiteral(lastElement)) return
+ let trailingCount = 1
+ for (let i = node.elements.length - 2; i >= 0; i--) {
const element = node.elements[i]
- if (element?.type === AST_NODE_TYPES.Literal && element.value === null) {
- nullCount++
+ if (!element || isNullLiteral(element)) {
+ trailingCount++
} else {
break
}
}
- if (nullCount === 0) return
- const previousElement = node.elements[node.elements.length - nullCount - 1]
- const firstElement = node.elements[0]
- const lastElement = node.elements[node.elements.length - 1]
- if (!lastElement || (node.elements.length === nullCount && !firstElement))
- return
- const removeStart =
- node.elements.length > nullCount && previousElement
- ? previousElement.range[1]
- : firstElement?.range[0]
- if (removeStart === undefined) return
-
+ const previousElement =
+ node.elements[node.elements.length - trailingCount - 1]
context.report({
node,
messageId: 'uselessTailingNulls',
fix(fixer) {
- return fixer.removeRange([removeStart, lastElement.range[1]])
+ const sourceCode = context.sourceCode
+ let removeStart = node.range[0] + 1
+ if (previousElement) {
+ const [separator] = sourceCode.getFirstTokensBetween(
+ previousElement,
+ lastElement,
+ { count: 1, filter: ASTUtils.isCommaToken },
+ )
+ const [previousToken] = sourceCode.getTokensBefore(separator, 1)
+ removeStart = previousToken.range[1]
+ } else if (node.elements[0]) {
+ const [, firstToken] = sourceCode.getFirstTokens(node, 2)
+ removeStart = firstToken.range[0]
+ }
+ const [beforeLastToken, lastToken] = sourceCode.getLastTokens(node, 3)
+ const removeEnd = ASTUtils.isCommaToken(lastToken)
+ ? beforeLastToken.range[1]
+ : lastToken.range[1]
+ return fixer.removeRange([removeStart, removeEnd])
},
})
}
diff --git a/packages/eslint-plugin/src/utils/__tests__/import-sources.test.ts b/packages/eslint-plugin/src/utils/__tests__/import-sources.test.ts
index ad5371e35..c39c935ac 100644
--- a/packages/eslint-plugin/src/utils/__tests__/import-sources.test.ts
+++ b/packages/eslint-plugin/src/utils/__tests__/import-sources.test.ts
@@ -41,6 +41,30 @@ function inspect(
}
describe('import sources', () => {
+ for (const use of ['css({w: 1})', 'styled(Base, {w: 1})'])
+ inspect(
+ `import { css, styled } from "@devup-ui/react";\n${use}`,
+ 'page.tsx',
+ (storage, context) => {
+ const statement = context.sourceCode.ast.body[1]
+ if (
+ statement.type !== 'ExpressionStatement' ||
+ statement.expression.type !== 'CallExpression'
+ )
+ throw new Error('Expected a parsed style call')
+ const call = statement.expression
+ expect(styleValueSite(call.callee, storage)).toBeNull()
+ const rules = call.arguments.at(-1)
+ if (!rules) throw new Error('Expected a rules argument')
+ expect(styleValueSite(rules, storage)).toEqual({
+ root: call,
+ start: call,
+ })
+ if (call.arguments.length === 2)
+ expect(styleValueSite(call.arguments[0], storage)).toBeNull()
+ },
+ )
+
inspect(
[
'import { css as a, Box as B, unknown as u } from "@devup-ui/react";',
diff --git a/packages/next-plugin/src/__tests__/coordinator-port.test.ts b/packages/next-plugin/src/__tests__/coordinator-port.test.ts
index 421ba1d17..08f51c22a 100644
--- a/packages/next-plugin/src/__tests__/coordinator-port.test.ts
+++ b/packages/next-plugin/src/__tests__/coordinator-port.test.ts
@@ -71,6 +71,12 @@ describe('coordinator port file', () => {
expect(Bun.file(file).size).toBe(0)
})
+ it('allows first-run cleanup when no coordinator port file exists', () => {
+ const file = join(tmp(), 'coordinator.port')
+
+ expect(() => removeStalePortFile(file)).not.toThrow()
+ })
+
it('names port, owner pid, state and recovery when the coordinator is unreachable', () => {
const dir = tmp()
const file = join(dir, 'coordinator.port')
diff --git a/packages/next-plugin/src/__tests__/wasm.test.ts b/packages/next-plugin/src/__tests__/wasm.test.ts
index d3cc7068c..1b0e3bae5 100644
--- a/packages/next-plugin/src/__tests__/wasm.test.ts
+++ b/packages/next-plugin/src/__tests__/wasm.test.ts
@@ -132,6 +132,20 @@ describe('WASM loading', () => {
).toEqual({ isolated: true })
})
+ it('resolves from the project manifest without an installed or workspace plugin', () => {
+ const root = mkdtempSync(join(tmpdir(), 'devup-ui-next-root-'))
+ tempRoots.push(root)
+ writeFileSync(join(root, 'package.json'), '{"name":"root-fallback"}')
+ writeFileSync(
+ join(root, 'dependency.cjs'),
+ 'module.exports = { root: true }',
+ )
+
+ expect(
+ requireFromPlugin<{ root: boolean }>('./dependency.cjs', root),
+ ).toEqual({ root: true })
+ })
+
it('loads or injects the Webpack plugin without a static dependency', () => {
expect(typeof loadWebpackPlugin().DevupUIWebpackPlugin).toBe('function')
setWebpackPluginForTesting(webpackPlugin)
diff --git a/packages/plugin-utils/src/__tests__/reservation-discovery.test.ts b/packages/plugin-utils/src/__tests__/reservation-discovery.test.ts
index 757f2b0b9..118651b87 100644
--- a/packages/plugin-utils/src/__tests__/reservation-discovery.test.ts
+++ b/packages/plugin-utils/src/__tests__/reservation-discovery.test.ts
@@ -169,6 +169,43 @@ it('overlays owned compiled MDX records without reading raw MDX or deduplicating
).toBe(false)
})
+it('uses an explicit relative tsconfig instead of the default alias target', () => {
+ using fs = fixture()
+ fs.write(
+ 'tsconfig.json',
+ JSON.stringify({
+ compilerOptions: { baseUrl: '.', paths: { '@token': ['wrong.ts'] } },
+ }),
+ )
+ fs.write(
+ 'tsconfig.build.json',
+ JSON.stringify({
+ compilerOptions: { baseUrl: '.', paths: { '@token': ['right.ts'] } },
+ }),
+ )
+ fs.write(
+ 'src/A.tsx',
+ "import {Box} from '@devup-ui/react'; import {n} from '@token'; export const A=()=> ",
+ )
+ fs.write('wrong.ts', 'export const n=1')
+ fs.write('right.ts', 'export const n=4')
+
+ const inventory = collectReservationInputs({
+ roots: ['src'],
+ cwd: fs.root,
+ package: '@devup-ui/react',
+ importAliases: {},
+ tsconfigPath: 'tsconfig.build.json',
+ toId: (path) => relative(fs.root, path).replaceAll('\\', '/'),
+ })
+
+ expect(inventory.inputs.map(({ filename }) => filename)).toEqual([
+ 'right.ts',
+ 'src/A.tsx',
+ ])
+ expect(inventory.inputs[0].code).toBe('export const n=4')
+})
+
it('rejects conflicting prepared bytes for one physical path before producing an inventory', () => {
// Given
using fs = fixture()
diff --git a/packages/plugin-utils/src/__tests__/reservation-errors.test.ts b/packages/plugin-utils/src/__tests__/reservation-errors.test.ts
index 55e65dd31..f575a1e1a 100644
--- a/packages/plugin-utils/src/__tests__/reservation-errors.test.ts
+++ b/packages/plugin-utils/src/__tests__/reservation-errors.test.ts
@@ -3,7 +3,7 @@ import { join, relative } from 'node:path'
import { expect, it } from 'bun:test'
-import { createModuleResolver } from '../import-graph'
+import { createModuleResolver, listSourceFiles } from '../import-graph'
import { collectReservationInputs } from '../reservation-discovery'
import {
isReservationReadFailure,
@@ -112,6 +112,33 @@ it('continues valid directories when an earlier source root changes from directo
])
})
+it('propagates a source root directory-read failure without an error handler', () => {
+ using fs = fixture()
+ fs.write('file-root', 'not a directory')
+
+ expect(() => listSourceFiles(join(fs.root, 'file-root'))).toThrow(
+ expect.objectContaining({ code: 'ENOTDIR' }),
+ )
+})
+
+it('rethrows the original directory-read error when its handler declines it', () => {
+ using fs = fixture()
+ fs.write('file-root', 'not a directory')
+ let received: unknown
+ let thrown: unknown
+
+ try {
+ listSourceFiles(join(fs.root, 'file-root'), (error) => {
+ received = error
+ return false
+ })
+ } catch (error) {
+ thrown = error
+ }
+ expect(thrown).toMatchObject({ code: 'ENOTDIR' })
+ expect(thrown).toBe(received)
+})
+
it('leaves the normal resolver as publisher of a reachable broken manifest error', () => {
// Given
using fs = fixture()
diff --git a/packages/react/src/stores/__tests__/theme-store.test.ts b/packages/react/src/stores/__tests__/theme-store.test.ts
index 1544aa265..c3c30d7de 100644
--- a/packages/react/src/stores/__tests__/theme-store.test.ts
+++ b/packages/react/src/stores/__tests__/theme-store.test.ts
@@ -11,6 +11,48 @@ type ThemeValue = Parameters[0]
const darkTheme = 'dark' as unknown as ThemeValue
describe('themeStore ssr', () => {
+ it('should select the inert server store when window is undefined', async () => {
+ // Given: evaluate the factory without a browser window, preserving its descriptor.
+ const originalWindowDescriptor = Object.getOwnPropertyDescriptor(
+ globalThis,
+ 'window',
+ )
+
+ try {
+ Object.defineProperty(globalThis, 'window', {
+ configurable: true,
+ value: undefined,
+ })
+
+ // When: a fresh import selects the factory in this process.
+ const { createThemeStore } = await import('../theme-store?ssr-factory')
+
+ // Then: the real server factory provides an inert subscription lifecycle.
+ expect(createThemeStore).toBe(createServerThemeStore)
+ const themeStore = createThemeStore()
+ const notifications: ThemeValue[] = []
+ expect(themeStore.get()).toBeNull()
+ const unsubscribe = themeStore.subscribe((theme) => {
+ notifications.push(theme)
+ })
+ expect(notifications).toEqual([])
+ expect(themeStore.set(darkTheme)).toBeUndefined()
+ expect(themeStore.get()).toBeNull()
+ expect(notifications).toEqual([])
+ expect(unsubscribe()).toBeUndefined()
+ expect(unsubscribe()).toBeUndefined()
+ expect(themeStore.set(darkTheme)).toBeUndefined()
+ expect(themeStore.get()).toBeNull()
+ expect(notifications).toEqual([])
+ } finally {
+ if (originalWindowDescriptor) {
+ Object.defineProperty(globalThis, 'window', originalWindowDescriptor)
+ } else {
+ Reflect.deleteProperty(globalThis, 'window')
+ }
+ }
+ })
+
it('should filter mutations by type and target', async () => {
// const { createThemeStore } = await import('../theme-store')
const themeStore = createServerThemeStore()
diff --git a/packages/webpack-plugin/src/__tests__/config-errors.test.ts b/packages/webpack-plugin/src/__tests__/config-errors.test.ts
new file mode 100644
index 000000000..266a4dbc5
--- /dev/null
+++ b/packages/webpack-plugin/src/__tests__/config-errors.test.ts
@@ -0,0 +1,40 @@
+import { existsSync, mkdirSync, mkdtempSync, rmSync } from 'node:fs'
+import { tmpdir } from 'node:os'
+import { join } from 'node:path'
+
+import * as wasm from '@devup-ui/wasm'
+import { expect, it, spyOn } from 'bun:test'
+
+import { DevupUIWebpackPlugin } from '../plugin'
+
+it('clears the theme and creates the CSS directory when configuration cannot be read', () => {
+ const root = mkdtempSync(join(tmpdir(), 'devup-webpack-config-'))
+ const config = join(root, 'devup.json')
+ const cssDir = join(root, 'dist/css')
+ mkdirSync(config)
+ wasm.registerTheme({ colors: { default: { brand: '#123456' } } })
+ expect(wasm.getDefaultTheme()).toBe('default')
+ const registered = spyOn(wasm, 'registerTheme')
+ const logged = spyOn(console, 'error').mockReturnValue(undefined)
+ try {
+ const plugin = new DevupUIWebpackPlugin({
+ devupFile: config,
+ distDir: join(root, 'dist'),
+ cssDir,
+ watch: false,
+ })
+
+ plugin.writeDataFiles()
+
+ expect(registered).toHaveBeenCalledTimes(1)
+ expect(registered).toHaveBeenCalledWith({})
+ expect(wasm.getDefaultTheme()).toBeUndefined()
+ expect(logged).toHaveBeenCalledTimes(1)
+ expect(logged).toHaveBeenCalledWith(expect.any(Error))
+ expect(existsSync(cssDir)).toBe(true)
+ } finally {
+ registered.mockRestore()
+ logged.mockRestore()
+ rmSync(root, { recursive: true, force: true })
+ }
+})
diff --git a/packages/webpack-plugin/src/__tests__/plugin.test.ts b/packages/webpack-plugin/src/__tests__/plugin.test.ts
index a032e8f6f..3507e4c2e 100644
--- a/packages/webpack-plugin/src/__tests__/plugin.test.ts
+++ b/packages/webpack-plugin/src/__tests__/plugin.test.ts
@@ -222,19 +222,25 @@ describe('devupUIWebpackPlugin', () => {
}
})
})
- it('W66 default regressions: should apply default options', () => {
- expect(new DevupUIWebpackPlugin({}).options).toEqual({
- include: [],
- package: '@devup-ui/react',
- cssDir: resolve('df', 'devup-ui'),
- devupFile: 'devup.json',
- distDir: 'df',
- watch: false,
- debug: false,
- singleCss: false,
- atomHoist: 2,
- })
- })
+ it.each([true, false])(
+ 'W66 default regressions: should apply default options (omitted: %s)',
+ (omitted) => {
+ const plugin = omitted
+ ? new DevupUIWebpackPlugin()
+ : new DevupUIWebpackPlugin({})
+ expect(plugin.options).toEqual({
+ include: [],
+ package: '@devup-ui/react',
+ cssDir: resolve('df', 'devup-ui'),
+ devupFile: 'devup.json',
+ distDir: 'df',
+ watch: false,
+ debug: false,
+ singleCss: false,
+ atomHoist: 2,
+ })
+ },
+ )
it('also reserves at native watchRun when watch persistence is disabled', async () => {
const manager = {