-
-
+exports[`Toggle should Toggle snapshot 12`] = `
+"
+
+
"
`;
diff --git a/packages/components/src/components/Toggle/__tests__/index.browser.test.tsx b/packages/components/src/components/Toggle/__tests__/index.browser.test.tsx
index b35828f8..c0f58e34 100644
--- a/packages/components/src/components/Toggle/__tests__/index.browser.test.tsx
+++ b/packages/components/src/components/Toggle/__tests__/index.browser.test.tsx
@@ -1,101 +1,94 @@
-import { act, render } from '@testing-library/react'
-import userEvent from '@testing-library/user-event'
-
-import { Toggle } from '../index'
-
-vi.mock('react', async (originImport: any) => {
- const origin = await originImport()
- return {
- ...origin,
- cache: vi.fn((arg) => arg),
- }
-})
-describe('Toggle', () => {
- it('should Toggle snapshot', () => {
- expect(render(
).container).toMatchSnapshot()
- expect(render(
).container).toMatchSnapshot()
- expect(render(
).container).toMatchSnapshot()
- expect(render(
).container).toMatchSnapshot()
- expect(render(
).container).toMatchSnapshot()
- expect(
- render(
).container,
- ).toMatchSnapshot()
- expect(
- render(
).container,
- ).toMatchSnapshot()
- expect(
- render(
).container,
- ).toMatchSnapshot()
- expect(
- render(
-
,
- ).container,
- ).toMatchSnapshot()
- expect(
- render(
-
,
- ).container,
- ).toMatchSnapshot()
- expect(
- render(
-
,
- ).container,
- ).toMatchSnapshot()
- expect(
- render(
-
,
- ).container,
- ).toMatchSnapshot()
- })
-
- it('should change value when use onChange prop', async () => {
- const onChange = vi.fn()
- const { container } = render(
-
,
- )
- const toggleButton = container.querySelector('.test')
- const input = container.querySelector('input')
- toggleButton &&
- (await act(async () => {
- await userEvent.click(toggleButton)
- }))
- expect(input).toHaveAttribute('value', 'true')
- })
-})
+import { describe, expect, it, mock } from 'bun:test'
+import { act, render, userEvent } from 'bun-test-env-dom'
+
+import { Toggle } from '../index'
+
+describe('Toggle', () => {
+ it('should Toggle snapshot', () => {
+ expect(render(
).container).toMatchSnapshot()
+ expect(render(
).container).toMatchSnapshot()
+ expect(render(
).container).toMatchSnapshot()
+ expect(render(
).container).toMatchSnapshot()
+ expect(render(
).container).toMatchSnapshot()
+ expect(
+ render(
).container,
+ ).toMatchSnapshot()
+ expect(
+ render(
).container,
+ ).toMatchSnapshot()
+ expect(
+ render(
).container,
+ ).toMatchSnapshot()
+ expect(
+ render(
+
,
+ ).container,
+ ).toMatchSnapshot()
+ expect(
+ render(
+
,
+ ).container,
+ ).toMatchSnapshot()
+ expect(
+ render(
+
,
+ ).container,
+ ).toMatchSnapshot()
+ expect(
+ render(
+
,
+ ).container,
+ ).toMatchSnapshot()
+ })
+
+ it('should change value when use onChange prop', async () => {
+ const onChange = mock()
+ const { container } = render(
+
,
+ )
+ const toggleButton = container.querySelector('.test')
+ const input = container.querySelector('input')
+ toggleButton &&
+ (await act(async () => {
+ await userEvent.click(toggleButton)
+ }))
+ expect(input).toHaveAttribute('value', 'true')
+ })
+})
diff --git a/packages/components/tsconfig.json b/packages/components/tsconfig.json
index 3779d817..bedb14e1 100644
--- a/packages/components/tsconfig.json
+++ b/packages/components/tsconfig.json
@@ -1,32 +1,30 @@
-{
- "compilerOptions": {
- "types": [
- "vite/client",
- "vitest/importMeta",
- "vitest/globals",
- "@testing-library/jest-dom"
- ],
- "strict": true,
- "target": "ESNext",
- "declaration": true,
- "declarationMap": true,
- "removeComments": true,
- "sourceMap": true,
- "useDefineForClassFields": true,
- "allowJs": false,
- "skipLibCheck": true,
- "noFallthroughCasesInSwitch": true,
- "esModuleInterop": true,
- "allowSyntheticDefaultImports": true,
- "forceConsistentCasingInFileNames": true,
- "strictFunctionTypes": true,
- "module": "ESNext",
- "moduleResolution": "Bundler",
- "resolveJsonModule": true,
- "isolatedModules": true,
- "noEmit": true,
- "baseUrl": ".",
- "jsx": "react-jsx"
- },
- "include": ["src/**/*", ".storybook/**/*", "./setupTests.ts"]
-}
+{
+ "compilerOptions": {
+ "types": ["bun"],
+ "strict": true,
+ "target": "ESNext",
+ "rootDir": "src",
+ "declaration": true,
+ "emitDeclarationOnly": true,
+ "outDir": "dist",
+ "declarationMap": true,
+ "removeComments": true,
+ "sourceMap": true,
+ "useDefineForClassFields": true,
+ "allowJs": false,
+ "skipLibCheck": true,
+ "noFallthroughCasesInSwitch": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "forceConsistentCasingInFileNames": true,
+ "strictFunctionTypes": true,
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "baseUrl": ".",
+ "jsx": "react-jsx"
+ },
+ "include": ["src"],
+ "exclude": ["node_modules", "dist", "src/**/__tests__"]
+}
diff --git a/packages/components/vite.config.ts b/packages/components/vite.config.ts
index f64b13e8..c496cdf5 100644
--- a/packages/components/vite.config.ts
+++ b/packages/components/vite.config.ts
@@ -1,17 +1,8 @@
import preserveDirectives from 'rollup-plugin-preserve-directives'
+import { defineConfig } from 'vite'
import dts from 'vite-plugin-dts'
-import { defineConfig } from 'vitest/config'
export default defineConfig({
- test: {
- globals: true,
- coverage: {
- provider: 'v8',
- thresholds: {
- '100': true,
- },
- },
- },
plugins: [
dts({
entryRoot: 'src',
diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json
index 3d3fc326..e3d0cae0 100644
--- a/packages/eslint-plugin/package.json
+++ b/packages/eslint-plugin/package.json
@@ -21,18 +21,16 @@
"main": "dist/index.cjs",
"type": "module",
"scripts": {
- "test": "vitest run --coverage",
- "test:s": "vitest run -u",
- "lint": "eslint",
- "build": "tsc && vite build"
+ "build": "tsc && bun build --target node src/index.ts --production --outfile dist/index.cjs --format cjs --packages external && bun build --target node src/index.ts --production --outfile dist/index.mjs --format esm --packages external"
},
"publishConfig": {
"access": "public"
},
"exports": {
".": {
- "import": "./dist/index.js",
- "require": "./dist/index.cjs"
+ "import": "./dist/index.mjs",
+ "require": "./dist/index.cjs",
+ "types": "./dist/index.d.ts"
}
},
"types": "./dist/index.d.ts",
@@ -40,17 +38,15 @@
"dist"
],
"dependencies": {
- "typescript-eslint": "^8.50",
- "@typescript-eslint/utils": "^8.50"
+ "typescript-eslint": "^8.51",
+ "@typescript-eslint/utils": "^8.51"
},
"peerDependencies": {
"typescript-eslint": "*",
"@typescript-eslint/utils": "*"
},
"devDependencies": {
- "@typescript-eslint/rule-tester": "^8.50",
- "typescript": "^5.9",
- "vite": "^7.3",
- "vite-plugin-dts": "^4.5"
+ "@typescript-eslint/rule-tester": "^8.51",
+ "typescript": "^5.9"
}
}
diff --git a/packages/eslint-plugin/src/__tests__/index.test.ts b/packages/eslint-plugin/src/__tests__/index.test.ts
index d5aa2587..d0cd2eb6 100644
--- a/packages/eslint-plugin/src/__tests__/index.test.ts
+++ b/packages/eslint-plugin/src/__tests__/index.test.ts
@@ -1,16 +1,19 @@
-import * as index from '../index'
-describe('export index', () => {
- it('export', () => {
- expect({ ...index }).toEqual({
- rules: expect.any(Object),
- configs: {
- recommended: expect.any(Object),
- },
- default: {
- configs: {
- recommended: expect.any(Object),
- },
- },
- })
- })
-})
+import { describe, expect, it } from 'bun:test'
+
+import * as index from '../index'
+
+describe('export index', () => {
+ it('export', () => {
+ expect({ ...index }).toEqual({
+ rules: expect.any(Object),
+ configs: {
+ recommended: expect.any(Object),
+ },
+ default: {
+ configs: {
+ recommended: expect.any(Object),
+ },
+ },
+ })
+ })
+})
diff --git a/packages/eslint-plugin/src/configs/__tests__/__snapshots__/recommended.test.ts.snap b/packages/eslint-plugin/src/configs/__tests__/__snapshots__/recommended.test.ts.snap
index de9a2106..c4b45e74 100644
--- a/packages/eslint-plugin/src/configs/__tests__/__snapshots__/recommended.test.ts.snap
+++ b/packages/eslint-plugin/src/configs/__tests__/__snapshots__/recommended.test.ts.snap
@@ -20,6 +20,7 @@ exports[`recommended > export recommended config 1`] = `
"schema": [],
"type": "problem",
},
+ "name": "css-utils-literal-only",
},
"no-duplicate-value": {
"create": [Function],
@@ -36,6 +37,7 @@ exports[`recommended > export recommended config 1`] = `
"schema": [],
"type": "problem",
},
+ "name": "no-duplicate-value",
},
"no-useless-responsive": {
"create": [Function],
@@ -52,6 +54,7 @@ exports[`recommended > export recommended config 1`] = `
"schema": [],
"type": "problem",
},
+ "name": "no-useless-responsive",
},
"no-useless-tailing-nulls": {
"create": [Function],
@@ -68,6 +71,7 @@ exports[`recommended > export recommended config 1`] = `
"schema": [],
"type": "problem",
},
+ "name": "no-useless-tailing-nulls",
},
"style-order-range": {
"create": [Function],
@@ -84,6 +88,111 @@ exports[`recommended > export recommended config 1`] = `
"schema": [],
"type": "problem",
},
+ "name": "style-order-range",
+ },
+ },
+ },
+ },
+ "rules": {
+ "@devup-ui/css-utils-literal-only": "error",
+ "@devup-ui/no-duplicate-value": "error",
+ "@devup-ui/no-useless-responsive": "error",
+ "@devup-ui/no-useless-tailing-nulls": "error",
+ "@devup-ui/style-order-range": "error",
+ },
+ },
+]
+`;
+
+exports[`recommended export recommended config 1`] = `
+[
+ {
+ "plugins": {
+ "@devup-ui": {
+ "rules": {
+ "css-utils-literal-only": {
+ "create": [Function: create],
+ "defaultOptions": [],
+ "meta": {
+ "docs": {
+ "description": "CSS utils should only be used with literal values.",
+ "url": "https://github.com/dev-five-git/devup-ui/tree/main/packages/eslint-plugin/src/rules/css-utils-literal-only",
+ },
+ "messages": {
+ "cssUtilsLiteralOnly": "CSS utils should only be used with literal values.",
+ },
+ "schema": [],
+ "type": "problem",
+ },
+ "name": "css-utils-literal-only",
+ },
+ "no-duplicate-value": {
+ "create": [Function: create],
+ "defaultOptions": [],
+ "meta": {
+ "docs": {
+ "description": "No duplicate value.",
+ "url": "https://github.com/dev-five-git/devup-ui/tree/main/packages/eslint-plugin/src/rules/no-duplicate-value",
+ },
+ "fixable": "code",
+ "messages": {
+ "duplicateValue": "Duplicate value found: {{value}}.",
+ },
+ "schema": [],
+ "type": "problem",
+ },
+ "name": "no-duplicate-value",
+ },
+ "no-useless-responsive": {
+ "create": [Function: create],
+ "defaultOptions": [],
+ "meta": {
+ "docs": {
+ "description": "No useless responsive.",
+ "url": "https://github.com/dev-five-git/devup-ui/tree/main/packages/eslint-plugin/src/rules/no-useless-responsive",
+ },
+ "fixable": "code",
+ "messages": {
+ "uselessResponsive": "Responsive are useless. Remove them.",
+ },
+ "schema": [],
+ "type": "problem",
+ },
+ "name": "no-useless-responsive",
+ },
+ "no-useless-tailing-nulls": {
+ "create": [Function: create],
+ "defaultOptions": [],
+ "meta": {
+ "docs": {
+ "description": "No useless tailing nulls.",
+ "url": "https://github.com/dev-five-git/devup-ui/tree/main/packages/eslint-plugin/src/rules/no-useless-tailing-nulls",
+ },
+ "fixable": "code",
+ "messages": {
+ "uselessTailingNulls": "Trailing nulls are useless. Remove them.",
+ },
+ "schema": [],
+ "type": "problem",
+ },
+ "name": "no-useless-tailing-nulls",
+ },
+ "style-order-range": {
+ "create": [Function: create],
+ "defaultOptions": [],
+ "meta": {
+ "docs": {
+ "description": "Ensures styleOrder prop is within valid range (0 < value < 255).",
+ "url": "https://github.com/dev-five-git/devup-ui/tree/main/packages/eslint-plugin/src/rules/style-order-range",
+ },
+ "messages": {
+ "styleOrderRange": "styleOrder prop must be a number greater than 0 and less than 255.",
+ "wrongType": "styleOrder prop must be a number or a string representing a number.",
+ },
+ "schema": [],
+ "type": "problem",
+ },
+ "name": "style-order-range",
},
},
},
diff --git a/packages/eslint-plugin/src/configs/__tests__/recommended.test.ts b/packages/eslint-plugin/src/configs/__tests__/recommended.test.ts
index 38a93c49..92abd73b 100644
--- a/packages/eslint-plugin/src/configs/__tests__/recommended.test.ts
+++ b/packages/eslint-plugin/src/configs/__tests__/recommended.test.ts
@@ -1,6 +1,9 @@
-import recommended from '../recommended'
-describe('recommended', () => {
- it('export recommended config', () => {
- expect(recommended).toMatchSnapshot()
- })
-})
+import { describe, expect, it } from 'bun:test'
+
+import recommended from '../recommended'
+
+describe('recommended', () => {
+ it('export recommended config', () => {
+ expect(recommended).toMatchSnapshot()
+ })
+})
diff --git a/packages/eslint-plugin/src/rules/__tests__/index.test.ts b/packages/eslint-plugin/src/rules/__tests__/index.test.ts
index 6c6900b3..16aa9626 100644
--- a/packages/eslint-plugin/src/rules/__tests__/index.test.ts
+++ b/packages/eslint-plugin/src/rules/__tests__/index.test.ts
@@ -1,12 +1,15 @@
-import * as index from '../index'
-describe('export index', () => {
- it('export', () => {
- expect({ ...index }).toEqual({
- noUselessTailingNulls: expect.any(Object),
- cssUtilsLiteralOnly: expect.any(Object),
- noDuplicateValue: expect.any(Object),
- noUselessResponsive: expect.any(Object),
- styleOrderRange: expect.any(Object),
- })
- })
-})
+import { describe, expect, it } from 'bun:test'
+
+import * as index from '../index'
+
+describe('export index', () => {
+ it('export', () => {
+ expect({ ...index }).toEqual({
+ noUselessTailingNulls: expect.any(Object),
+ cssUtilsLiteralOnly: expect.any(Object),
+ noDuplicateValue: expect.any(Object),
+ noUselessResponsive: expect.any(Object),
+ styleOrderRange: expect.any(Object),
+ })
+ })
+})
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 7c0f46d2..05deee72 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
@@ -1,123 +1,124 @@
-import { RuleTester } from '@typescript-eslint/rule-tester'
-
-import { cssUtilsLiteralOnly } from '../index'
-
-describe.each(['css' /* 'globalCss', 'keyframes'*/])(
- 'css-utils-literal-only rule',
- (code) => {
- const ruleTester = new RuleTester({
- languageOptions: {
- ecmaVersion: 'latest',
- parserOptions: {
- ecmaFeatures: {
- jsx: true,
- },
- },
- },
- })
- ruleTester.run('css-utils-literal-only rule', cssUtilsLiteralOnly, {
- valid: [
- {
- code: `import { ${code} } from "@devup-ui/react";\n${code}({w: 1})`,
- filename: 'src/app/page.tsx',
- },
- {
- code: `import { ${code} } from "@devup-ui/react";\n${code}({w: "1"})`,
- filename: 'src/app/page.tsx',
- },
- {
- code: `import { ${code} } from "other-package";\n${code}({w: [1][0]})`,
- filename: 'src/app/page.tsx',
- },
- {
- code: `import { ${code} } from "@devup-ui/react";\n${code}({w: [1]})`,
- filename: 'src/app/page.tsx',
- },
- {
- code: `import { ${code} } from "@devup-ui/react";\n${code}({w: ["1"]})`,
- filename: 'src/app/page.tsx',
- },
- {
- code: `import { ${code} as B } from "@devup-ui/react";\nB({w: ["1"]})`,
- filename: 'src/app/page.tsx',
- },
- {
- code: `import { ${code} as B } from "@devup-ui/react";\nB({_hover: {w: ["1"]}})`,
- filename: 'src/app/page.tsx',
- },
- {
- code: `import { ${code} as B } from "@devup-ui/react";\nB({ w: { a: 1, b: 2 }[v]})`,
- filename: 'src/app/page.tsx',
- },
- {
- code: `import { ${code} as B } from "@devup-ui/react";\nB({ w: v ? 1 : null})`,
- filename: 'src/app/page.tsx',
- },
- {
- code: `import { ${code} as B } from "@devup-ui/react";\nB({ w: v ? 1 : undefined})`,
- filename: 'src/app/page.tsx',
- },
- {
- code: `import { ${code} as B } from "@devup-ui/react";\nB({ w: v || 1 ? 1 : null})`,
- filename: 'src/app/page.tsx',
- },
- ],
- invalid: [
- {
- code: `import { ${code} } from "@devup-ui/react";\n${code}({w: v})`,
- filename: 'src/app/layout.tsx',
- errors: [
- {
- messageId: 'cssUtilsLiteralOnly',
- },
- ],
- },
- {
- code: `import { ${code} } from "@devup-ui/react";\n${code}({w: [v]})`,
- filename: 'src/app/layout.tsx',
- errors: [
- {
- messageId: 'cssUtilsLiteralOnly',
- },
- ],
- },
- {
- code: `import { ${code} } from "@devup-ui/react";\n${code}({w: [1, null, v]})`,
- filename: 'src/app/layout.tsx',
- errors: [
- {
- messageId: 'cssUtilsLiteralOnly',
- },
- ],
- },
- {
- code: `import { ${code} as B } from "@devup-ui/react";\nB({w: [1, null, v]})`,
- filename: 'src/app/layout.tsx',
- errors: [
- {
- messageId: 'cssUtilsLiteralOnly',
- },
- ],
- },
- {
- code: `import { ${code} as B } from "@devup-ui/react";\nB({w: v ? 1 : v})`,
- filename: 'src/app/layout.tsx',
- errors: [
- {
- messageId: 'cssUtilsLiteralOnly',
- },
- ],
- },
- {
- code: `import { ${code} as B } from "@devup-ui/react";\nB({w: v || 1 ? 1 : v})`,
- filename: 'src/app/layout.tsx',
- errors: [
- {
- messageId: 'cssUtilsLiteralOnly',
- },
- ],
- },
- ],
- })
- },
-)
+import { RuleTester } from '@typescript-eslint/rule-tester'
+import { describe } from 'bun:test'
+
+import { cssUtilsLiteralOnly } from '../index'
+
+describe.each(['css' /* 'globalCss', 'keyframes'*/])(
+ 'css-utils-literal-only rule',
+ (code) => {
+ const ruleTester = new RuleTester({
+ languageOptions: {
+ ecmaVersion: 'latest',
+ parserOptions: {
+ ecmaFeatures: {
+ jsx: true,
+ },
+ },
+ },
+ })
+ ruleTester.run('css-utils-literal-only rule', cssUtilsLiteralOnly, {
+ valid: [
+ {
+ code: `import { ${code} } from "@devup-ui/react";\n${code}({w: 1})`,
+ filename: 'src/app/page.tsx',
+ },
+ {
+ code: `import { ${code} } from "@devup-ui/react";\n${code}({w: "1"})`,
+ filename: 'src/app/page.tsx',
+ },
+ {
+ code: `import { ${code} } from "other-package";\n${code}({w: [1][0]})`,
+ filename: 'src/app/page.tsx',
+ },
+ {
+ code: `import { ${code} } from "@devup-ui/react";\n${code}({w: [1]})`,
+ filename: 'src/app/page.tsx',
+ },
+ {
+ code: `import { ${code} } from "@devup-ui/react";\n${code}({w: ["1"]})`,
+ filename: 'src/app/page.tsx',
+ },
+ {
+ code: `import { ${code} as B } from "@devup-ui/react";\nB({w: ["1"]})`,
+ filename: 'src/app/page.tsx',
+ },
+ {
+ code: `import { ${code} as B } from "@devup-ui/react";\nB({_hover: {w: ["1"]}})`,
+ filename: 'src/app/page.tsx',
+ },
+ {
+ code: `import { ${code} as B } from "@devup-ui/react";\nB({ w: { a: 1, b: 2 }[v]})`,
+ filename: 'src/app/page.tsx',
+ },
+ {
+ code: `import { ${code} as B } from "@devup-ui/react";\nB({ w: v ? 1 : null})`,
+ filename: 'src/app/page.tsx',
+ },
+ {
+ code: `import { ${code} as B } from "@devup-ui/react";\nB({ w: v ? 1 : undefined})`,
+ filename: 'src/app/page.tsx',
+ },
+ {
+ code: `import { ${code} as B } from "@devup-ui/react";\nB({ w: v || 1 ? 1 : null})`,
+ filename: 'src/app/page.tsx',
+ },
+ ],
+ invalid: [
+ {
+ code: `import { ${code} } from "@devup-ui/react";\n${code}({w: v})`,
+ filename: 'src/app/layout.tsx',
+ errors: [
+ {
+ messageId: 'cssUtilsLiteralOnly',
+ },
+ ],
+ },
+ {
+ code: `import { ${code} } from "@devup-ui/react";\n${code}({w: [v]})`,
+ filename: 'src/app/layout.tsx',
+ errors: [
+ {
+ messageId: 'cssUtilsLiteralOnly',
+ },
+ ],
+ },
+ {
+ code: `import { ${code} } from "@devup-ui/react";\n${code}({w: [1, null, v]})`,
+ filename: 'src/app/layout.tsx',
+ errors: [
+ {
+ messageId: 'cssUtilsLiteralOnly',
+ },
+ ],
+ },
+ {
+ code: `import { ${code} as B } from "@devup-ui/react";\nB({w: [1, null, v]})`,
+ filename: 'src/app/layout.tsx',
+ errors: [
+ {
+ messageId: 'cssUtilsLiteralOnly',
+ },
+ ],
+ },
+ {
+ code: `import { ${code} as B } from "@devup-ui/react";\nB({w: v ? 1 : v})`,
+ filename: 'src/app/layout.tsx',
+ errors: [
+ {
+ messageId: 'cssUtilsLiteralOnly',
+ },
+ ],
+ },
+ {
+ code: `import { ${code} as B } from "@devup-ui/react";\nB({w: v || 1 ? 1 : v})`,
+ filename: 'src/app/layout.tsx',
+ errors: [
+ {
+ messageId: 'cssUtilsLiteralOnly',
+ },
+ ],
+ },
+ ],
+ })
+ },
+)
diff --git a/packages/eslint-plugin/src/rules/no-duplicate-value/__tests__/index.test.ts b/packages/eslint-plugin/src/rules/no-duplicate-value/__tests__/index.test.ts
index 41cbe200..2c529a94 100644
--- a/packages/eslint-plugin/src/rules/no-duplicate-value/__tests__/index.test.ts
+++ b/packages/eslint-plugin/src/rules/no-duplicate-value/__tests__/index.test.ts
@@ -1,103 +1,104 @@
-import { RuleTester } from '@typescript-eslint/rule-tester'
-
-import { noDuplicateValue } from '../index'
-
-describe('no-duplicate-value rule', () => {
- const ruleTester = new RuleTester({
- languageOptions: {
- ecmaVersion: 'latest',
- parserOptions: {
- ecmaFeatures: {
- jsx: true,
- },
- },
- },
- })
- ruleTester.run('no-duplicate-value rule', noDuplicateValue, {
- valid: [
- {
- code: 'import { Box } from "@devup-ui/react";\n
',
- filename: 'src/app/page.tsx',
- },
- {
- code: 'import { Box } from "@devup-ui/react";\n
',
- filename: 'src/app/page.tsx',
- },
- {
- code: 'import { Box } from "other-package";\n
',
- filename: 'src/app/page.tsx',
- },
- {
- code: 'import { Box } from "@devup-ui/react";\n
',
- filename: 'src/app/page.tsx',
- },
- {
- code: 'import { Box } from "@devup-ui/react";\n
',
- filename: 'src/app/page.tsx',
- },
- {
- code: 'import { css } from "other-package";\ncss()',
- filename: 'src/app/page.tsx',
- },
- {
- code: 'import { Box } from "@devup-ui/react";\n
',
- filename: 'src/app/page.tsx',
- },
- ],
- invalid: [
- {
- code: 'import { Box } from "@devup-ui/react";\n
',
- output:
- 'import { Box } from "@devup-ui/react";\n
',
- filename: 'src/app/layout.tsx',
- errors: [
- {
- messageId: 'duplicateValue',
- },
- {
- messageId: 'duplicateValue',
- },
- ],
- },
- {
- code: 'import { Box } from "@devup-ui/react";\n
',
- output:
- 'import { Box } from "@devup-ui/react";\n
',
- filename: 'src/app/page.tsx',
- errors: [
- {
- messageId: 'duplicateValue',
- },
- ],
- },
- {
- code: 'import { css } from "@devup-ui/react";\ncss({w: [1, 2, 2, 2, 3]})',
- output:
- 'import { css } from "@devup-ui/react";\ncss({w: [1, 2, null, null, 3]})',
- filename: 'src/app/page.tsx',
- errors: [
- {
- messageId: 'duplicateValue',
- },
- {
- messageId: 'duplicateValue',
- },
- ],
- },
- {
- code: 'import { css } from "@devup-ui/react";\ncss({w: [1, `2`, 2, "2", 3]})',
- output:
- 'import { css } from "@devup-ui/react";\ncss({w: [1, `2`, null, null, 3]})',
- filename: 'src/app/page.tsx',
- errors: [
- {
- messageId: 'duplicateValue',
- },
- {
- messageId: 'duplicateValue',
- },
- ],
- },
- ],
- })
-})
+import { RuleTester } from '@typescript-eslint/rule-tester'
+import { describe } from 'bun:test'
+
+import { noDuplicateValue } from '../index'
+
+describe('no-duplicate-value rule', () => {
+ const ruleTester = new RuleTester({
+ languageOptions: {
+ ecmaVersion: 'latest',
+ parserOptions: {
+ ecmaFeatures: {
+ jsx: true,
+ },
+ },
+ },
+ })
+ ruleTester.run('no-duplicate-value rule', noDuplicateValue, {
+ valid: [
+ {
+ code: 'import { Box } from "@devup-ui/react";\n
',
+ filename: 'src/app/page.tsx',
+ },
+ {
+ code: 'import { Box } from "@devup-ui/react";\n
',
+ filename: 'src/app/page.tsx',
+ },
+ {
+ code: 'import { Box } from "other-package";\n
',
+ filename: 'src/app/page.tsx',
+ },
+ {
+ code: 'import { Box } from "@devup-ui/react";\n
',
+ filename: 'src/app/page.tsx',
+ },
+ {
+ code: 'import { Box } from "@devup-ui/react";\n
',
+ filename: 'src/app/page.tsx',
+ },
+ {
+ code: 'import { css } from "other-package";\ncss()',
+ filename: 'src/app/page.tsx',
+ },
+ {
+ code: 'import { Box } from "@devup-ui/react";\n
',
+ filename: 'src/app/page.tsx',
+ },
+ ],
+ invalid: [
+ {
+ code: 'import { Box } from "@devup-ui/react";\n
',
+ output:
+ 'import { Box } from "@devup-ui/react";\n
',
+ filename: 'src/app/layout.tsx',
+ errors: [
+ {
+ messageId: 'duplicateValue',
+ },
+ {
+ messageId: 'duplicateValue',
+ },
+ ],
+ },
+ {
+ code: 'import { Box } from "@devup-ui/react";\n
',
+ output:
+ 'import { Box } from "@devup-ui/react";\n
',
+ filename: 'src/app/page.tsx',
+ errors: [
+ {
+ messageId: 'duplicateValue',
+ },
+ ],
+ },
+ {
+ code: 'import { css } from "@devup-ui/react";\ncss({w: [1, 2, 2, 2, 3]})',
+ output:
+ 'import { css } from "@devup-ui/react";\ncss({w: [1, 2, null, null, 3]})',
+ filename: 'src/app/page.tsx',
+ errors: [
+ {
+ messageId: 'duplicateValue',
+ },
+ {
+ messageId: 'duplicateValue',
+ },
+ ],
+ },
+ {
+ code: 'import { css } from "@devup-ui/react";\ncss({w: [1, `2`, 2, "2", 3]})',
+ output:
+ 'import { css } from "@devup-ui/react";\ncss({w: [1, `2`, null, null, 3]})',
+ filename: 'src/app/page.tsx',
+ errors: [
+ {
+ messageId: 'duplicateValue',
+ },
+ {
+ messageId: 'duplicateValue',
+ },
+ ],
+ },
+ ],
+ })
+})
diff --git a/packages/eslint-plugin/src/rules/no-useless-responsive/__tests__/index.test.ts b/packages/eslint-plugin/src/rules/no-useless-responsive/__tests__/index.test.ts
index ef41bf8a..ca558911 100644
--- a/packages/eslint-plugin/src/rules/no-useless-responsive/__tests__/index.test.ts
+++ b/packages/eslint-plugin/src/rules/no-useless-responsive/__tests__/index.test.ts
@@ -1,179 +1,180 @@
-import { RuleTester } from '@typescript-eslint/rule-tester'
-
-import { noUselessResponsive } from '../index'
-
-describe('no-useless-responsive rule', () => {
- const ruleTester = new RuleTester({
- languageOptions: {
- ecmaVersion: 'latest',
- parserOptions: {
- ecmaFeatures: {
- jsx: true,
- },
- },
- },
- })
- ruleTester.run('no-useless-responsive rule', noUselessResponsive, {
- valid: [
- {
- code: 'import { Box } from "@devup-ui/react";\n
',
- filename: 'src/app/page.tsx',
- },
- {
- code: 'import { Box } from "@devup-ui/react";\n
',
- filename: 'src/app/page.tsx',
- },
- {
- code: 'import { Box } from "@devup-ui/react";\n
',
- filename: 'src/app/page.tsx',
- },
- {
- code: 'import { "Box" as B } from "@devup-ui/react";\n
',
- filename: 'src/app/page.tsx',
- },
- {
- code: 'import B from "@devup-ui/react";\n
',
- filename: 'src/app/page.tsx',
- },
- {
- code: 'import * as B from "@devup-ui/react";\n
',
- filename: 'src/app/page.tsx',
- },
- {
- code: 'import { Box } from "@devup-ui/react";\n
',
- filename: 'src/app/page.tsx',
- },
- {
- code: 'import { Box } from "@devup-ui/react";\n
',
- filename: 'src/app/page.tsx',
- },
- {
- code: 'import { Box } from "@devup-ui/react";\n
',
- filename: 'src/app/page.tsx',
- },
- {
- code: 'import { Box } from "@devup-ui/react";\n
{console.log([1])}} />',
- filename: 'src/app/page.tsx',
- },
- {
- code: 'import { Box } from "@devup-ui/react";\n',
- filename: 'src/app/page.tsx',
- },
- {
- // normal case
- code: '',
- filename: 'src/app/page.tsx',
- },
- {
- code: 'import { css } from "@devup-ui/react";\ncss({w: 1})',
- filename: 'src/app/page.tsx',
- },
- {
- code: 'import { css } from "@devup-ui/react";\ncss({w: "1"})',
- filename: 'src/app/page.tsx',
- },
- {
- code: 'import { css } from "other-package";\ncss({w: [1][0]})',
- filename: 'src/app/page.tsx',
- },
- {
- code: 'import { css } from "other-package";\ncss()',
- filename: 'src/app/page.tsx',
- },
- {
- code: 'import { globalCss } from "@devup-ui/react";\nglobalCss({ imports: ["@devup-ui/react/css/global.css"] })',
- filename: 'src/app/page.tsx',
- },
- {
- code: 'import { globalCss } from "@devup-ui/react";\nglobalCss({ imports: [{"url": "@devup-ui/react/css/global.css"}] })',
- filename: 'src/app/page.tsx',
- },
- ],
- invalid: [
- {
- code: 'import { Box } from "@devup-ui/react";\n',
- output: 'import { Box } from "@devup-ui/react";\n',
- filename: 'src/app/layout.tsx',
- errors: [
- {
- messageId: 'uselessResponsive',
- },
- ],
- },
- {
- code: 'import { Box } from "@devup-ui/react";\n',
- output: 'import { Box } from "@devup-ui/react";\n',
- filename: 'src/app/layout.tsx',
- errors: [
- {
- messageId: 'uselessResponsive',
- },
- ],
- },
- {
- code: 'import A from "@devup-ui/react";\n',
- output: 'import A from "@devup-ui/react";\n',
- filename: 'src/app/layout.tsx',
- errors: [
- {
- messageId: 'uselessResponsive',
- },
- ],
- },
- {
- code: 'import { css } from "@devup-ui/react";\ncss({w: [1]})',
- output: 'import { css } from "@devup-ui/react";\ncss({w: 1})',
- filename: 'src/app/layout.tsx',
- errors: [
- {
- messageId: 'uselessResponsive',
- },
- ],
- },
- {
- code: 'import { css as c } from "@devup-ui/react";\nc({w: [1]})',
- output: 'import { css as c } from "@devup-ui/react";\nc({w: 1})',
- filename: 'src/app/layout.tsx',
- errors: [
- {
- messageId: 'uselessResponsive',
- },
- ],
- },
- {
- code: 'import c from "@devup-ui/react";\nc.css({w: [1]})',
- output: 'import c from "@devup-ui/react";\nc.css({w: 1})',
- filename: 'src/app/layout.tsx',
- errors: [
- {
- messageId: 'uselessResponsive',
- },
- ],
- },
- {
- code: 'import * as c from "@devup-ui/react";\nc.css({w: [1]})',
- output: 'import * as c from "@devup-ui/react";\nc.css({w: 1})',
- filename: 'src/app/layout.tsx',
- errors: [
- {
- messageId: 'uselessResponsive',
- },
- ],
- },
- {
- code: 'import * as c from "@devup-ui/react";\nc.css({w: [1].length === 1 ? [1] : [2]})',
- output:
- 'import * as c from "@devup-ui/react";\nc.css({w: [1].length === 1 ? 1 : 2})',
- filename: 'src/app/layout.tsx',
- errors: [
- {
- messageId: 'uselessResponsive',
- },
- {
- messageId: 'uselessResponsive',
- },
- ],
- },
- ],
- })
-})
+import { RuleTester } from '@typescript-eslint/rule-tester'
+import { describe } from 'bun:test'
+
+import { noUselessResponsive } from '../index'
+
+describe('no-useless-responsive rule', () => {
+ const ruleTester = new RuleTester({
+ languageOptions: {
+ ecmaVersion: 'latest',
+ parserOptions: {
+ ecmaFeatures: {
+ jsx: true,
+ },
+ },
+ },
+ })
+ ruleTester.run('no-useless-responsive rule', noUselessResponsive, {
+ valid: [
+ {
+ code: 'import { Box } from "@devup-ui/react";\n',
+ filename: 'src/app/page.tsx',
+ },
+ {
+ code: 'import { Box } from "@devup-ui/react";\n',
+ filename: 'src/app/page.tsx',
+ },
+ {
+ code: 'import { Box } from "@devup-ui/react";\n',
+ filename: 'src/app/page.tsx',
+ },
+ {
+ code: 'import { "Box" as B } from "@devup-ui/react";\n',
+ filename: 'src/app/page.tsx',
+ },
+ {
+ code: 'import B from "@devup-ui/react";\n',
+ filename: 'src/app/page.tsx',
+ },
+ {
+ code: 'import * as B from "@devup-ui/react";\n',
+ filename: 'src/app/page.tsx',
+ },
+ {
+ code: 'import { Box } from "@devup-ui/react";\n',
+ filename: 'src/app/page.tsx',
+ },
+ {
+ code: 'import { Box } from "@devup-ui/react";\n',
+ filename: 'src/app/page.tsx',
+ },
+ {
+ code: 'import { Box } from "@devup-ui/react";\n',
+ filename: 'src/app/page.tsx',
+ },
+ {
+ code: 'import { Box } from "@devup-ui/react";\n {console.log([1])}} />',
+ filename: 'src/app/page.tsx',
+ },
+ {
+ code: 'import { Box } from "@devup-ui/react";\n',
+ filename: 'src/app/page.tsx',
+ },
+ {
+ // normal case
+ code: '',
+ filename: 'src/app/page.tsx',
+ },
+ {
+ code: 'import { css } from "@devup-ui/react";\ncss({w: 1})',
+ filename: 'src/app/page.tsx',
+ },
+ {
+ code: 'import { css } from "@devup-ui/react";\ncss({w: "1"})',
+ filename: 'src/app/page.tsx',
+ },
+ {
+ code: 'import { css } from "other-package";\ncss({w: [1][0]})',
+ filename: 'src/app/page.tsx',
+ },
+ {
+ code: 'import { css } from "other-package";\ncss()',
+ filename: 'src/app/page.tsx',
+ },
+ {
+ code: 'import { globalCss } from "@devup-ui/react";\nglobalCss({ imports: ["@devup-ui/react/css/global.css"] })',
+ filename: 'src/app/page.tsx',
+ },
+ {
+ code: 'import { globalCss } from "@devup-ui/react";\nglobalCss({ imports: [{"url": "@devup-ui/react/css/global.css"}] })',
+ filename: 'src/app/page.tsx',
+ },
+ ],
+ invalid: [
+ {
+ code: 'import { Box } from "@devup-ui/react";\n',
+ output: 'import { Box } from "@devup-ui/react";\n',
+ filename: 'src/app/layout.tsx',
+ errors: [
+ {
+ messageId: 'uselessResponsive',
+ },
+ ],
+ },
+ {
+ code: 'import { Box } from "@devup-ui/react";\n',
+ output: 'import { Box } from "@devup-ui/react";\n',
+ filename: 'src/app/layout.tsx',
+ errors: [
+ {
+ messageId: 'uselessResponsive',
+ },
+ ],
+ },
+ {
+ code: 'import A from "@devup-ui/react";\n',
+ output: 'import A from "@devup-ui/react";\n',
+ filename: 'src/app/layout.tsx',
+ errors: [
+ {
+ messageId: 'uselessResponsive',
+ },
+ ],
+ },
+ {
+ code: 'import { css } from "@devup-ui/react";\ncss({w: [1]})',
+ output: 'import { css } from "@devup-ui/react";\ncss({w: 1})',
+ filename: 'src/app/layout.tsx',
+ errors: [
+ {
+ messageId: 'uselessResponsive',
+ },
+ ],
+ },
+ {
+ code: 'import { css as c } from "@devup-ui/react";\nc({w: [1]})',
+ output: 'import { css as c } from "@devup-ui/react";\nc({w: 1})',
+ filename: 'src/app/layout.tsx',
+ errors: [
+ {
+ messageId: 'uselessResponsive',
+ },
+ ],
+ },
+ {
+ code: 'import c from "@devup-ui/react";\nc.css({w: [1]})',
+ output: 'import c from "@devup-ui/react";\nc.css({w: 1})',
+ filename: 'src/app/layout.tsx',
+ errors: [
+ {
+ messageId: 'uselessResponsive',
+ },
+ ],
+ },
+ {
+ code: 'import * as c from "@devup-ui/react";\nc.css({w: [1]})',
+ output: 'import * as c from "@devup-ui/react";\nc.css({w: 1})',
+ filename: 'src/app/layout.tsx',
+ errors: [
+ {
+ messageId: 'uselessResponsive',
+ },
+ ],
+ },
+ {
+ code: 'import * as c from "@devup-ui/react";\nc.css({w: [1].length === 1 ? [1] : [2]})',
+ output:
+ 'import * as c from "@devup-ui/react";\nc.css({w: [1].length === 1 ? 1 : 2})',
+ filename: 'src/app/layout.tsx',
+ errors: [
+ {
+ messageId: 'uselessResponsive',
+ },
+ {
+ messageId: 'uselessResponsive',
+ },
+ ],
+ },
+ ],
+ })
+})
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 28c39b25..4a67a3c7 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,80 +1,81 @@
-import { RuleTester } from '@typescript-eslint/rule-tester'
-
-import { noUselessTailingNulls } from '../index'
-
-describe('no-useless-tailing-nulls rule', () => {
- const ruleTester = new RuleTester({
- languageOptions: {
- ecmaVersion: 'latest',
- parserOptions: {
- ecmaFeatures: {
- jsx: true,
- },
- },
- },
- })
- ruleTester.run('no-useless-tailing-nulls rule', noUselessTailingNulls, {
- valid: [
- {
- code: 'import { Box } from "@devup-ui/react";\n',
- filename: 'src/app/page.tsx',
- },
- {
- code: 'import { Box } from "@devup-ui/react";\n',
- filename: 'src/app/page.tsx',
- },
- {
- // normal case
- code: 'import { Box } from "other-package";\n',
- filename: 'src/app/page.tsx',
- },
- {
- // normal case
- code: 'css({ w: [1, 2, null] })',
- filename: 'src/app/page.tsx',
- },
- ],
- invalid: [
- {
- code: 'import { Box } from "@devup-ui/react";\n',
- output: 'import { Box } from "@devup-ui/react";\n',
- filename: 'src/app/layout.tsx',
- errors: [
- {
- messageId: 'uselessTailingNulls',
- },
- ],
- },
- {
- code: 'import { Box } from "@devup-ui/react";\n',
- output: 'import { Box } from "@devup-ui/react";\n',
- filename: 'src/app/page.tsx',
- errors: [
- {
- messageId: 'uselessTailingNulls',
- },
- ],
- },
- {
- code: 'import { css } from "@devup-ui/react";\ncss({w: [1, 2, null, null]})',
- output: 'import { css } from "@devup-ui/react";\ncss({w: [1, 2]})',
- filename: 'src/app/page.tsx',
- errors: [
- {
- messageId: 'uselessTailingNulls',
- },
- ],
- },
- {
- code: 'import { Box } from "@devup-ui/react";\n',
- output: 'import { Box } from "@devup-ui/react";\n',
- filename: 'src/app/page.tsx',
- errors: [
- {
- messageId: 'uselessTailingNulls',
- },
- ],
- },
- ],
- })
-})
+import { RuleTester } from '@typescript-eslint/rule-tester'
+import { describe } from 'bun:test'
+
+import { noUselessTailingNulls } from '../index'
+
+describe('no-useless-tailing-nulls rule', () => {
+ const ruleTester = new RuleTester({
+ languageOptions: {
+ ecmaVersion: 'latest',
+ parserOptions: {
+ ecmaFeatures: {
+ jsx: true,
+ },
+ },
+ },
+ })
+ ruleTester.run('no-useless-tailing-nulls rule', noUselessTailingNulls, {
+ valid: [
+ {
+ code: 'import { Box } from "@devup-ui/react";\n',
+ filename: 'src/app/page.tsx',
+ },
+ {
+ code: 'import { Box } from "@devup-ui/react";\n',
+ filename: 'src/app/page.tsx',
+ },
+ {
+ // normal case
+ code: 'import { Box } from "other-package";\n',
+ filename: 'src/app/page.tsx',
+ },
+ {
+ // normal case
+ code: 'css({ w: [1, 2, null] })',
+ filename: 'src/app/page.tsx',
+ },
+ ],
+ invalid: [
+ {
+ code: 'import { Box } from "@devup-ui/react";\n',
+ output: 'import { Box } from "@devup-ui/react";\n',
+ filename: 'src/app/layout.tsx',
+ errors: [
+ {
+ messageId: 'uselessTailingNulls',
+ },
+ ],
+ },
+ {
+ code: 'import { Box } from "@devup-ui/react";\n',
+ output: 'import { Box } from "@devup-ui/react";\n',
+ filename: 'src/app/page.tsx',
+ errors: [
+ {
+ messageId: 'uselessTailingNulls',
+ },
+ ],
+ },
+ {
+ code: 'import { css } from "@devup-ui/react";\ncss({w: [1, 2, null, null]})',
+ output: 'import { css } from "@devup-ui/react";\ncss({w: [1, 2]})',
+ filename: 'src/app/page.tsx',
+ errors: [
+ {
+ messageId: 'uselessTailingNulls',
+ },
+ ],
+ },
+ {
+ code: 'import { Box } from "@devup-ui/react";\n',
+ output: 'import { Box } from "@devup-ui/react";\n',
+ filename: 'src/app/page.tsx',
+ errors: [
+ {
+ messageId: 'uselessTailingNulls',
+ },
+ ],
+ },
+ ],
+ })
+})
diff --git a/packages/eslint-plugin/src/rules/style-order-range/__tests__/index.test.ts b/packages/eslint-plugin/src/rules/style-order-range/__tests__/index.test.ts
index bb56e428..7c2b1c47 100644
--- a/packages/eslint-plugin/src/rules/style-order-range/__tests__/index.test.ts
+++ b/packages/eslint-plugin/src/rules/style-order-range/__tests__/index.test.ts
@@ -1,358 +1,359 @@
-import { RuleTester } from '@typescript-eslint/rule-tester'
-
-import { styleOrderRange } from '../index'
-
-describe('style-order-range rule', () => {
- const ruleTester = new RuleTester({
- languageOptions: {
- ecmaVersion: 'latest',
- parserOptions: {
- ecmaFeatures: {
- jsx: true,
- },
- },
- },
- })
-
- ruleTester.run('style-order-range rule', styleOrderRange, {
- valid: [
- {
- code: 'import { Box } from "@devup-ui/react";\n',
- filename: 'src/app/page.tsx',
- },
- {
- code: 'import { Box } from "@devup-ui/react";\n',
- filename: 'src/app/page.tsx',
- },
- {
- code: 'import { Box } from "@devup-ui/react";\n',
- filename: 'src/app/page.tsx',
- },
- {
- code: 'import { Box } from "@devup-ui/react";\n',
- filename: 'src/app/page.tsx',
- },
- {
- code: 'import { Box } from "@devup-ui/react";\n',
- filename: 'src/app/page.tsx',
- },
- {
- code: 'import { Box } from "@devup-ui/react";\n',
- filename: 'src/app/page.tsx',
- },
- {
- code: 'import { Box } from "@devup-ui/react";\n',
- filename: 'src/app/page.tsx',
- },
- {
- code: 'import { Box } from "@devup-ui/react";\n',
- filename: 'src/app/page.tsx',
- },
- {
- code: 'import { Box } from "@devup-ui/react";\n',
- filename: 'src/app/page.tsx',
- },
- {
- code: 'import { Box } from "@devup-ui/react";\n',
- filename: 'src/app/page.tsx',
- },
- {
- code: 'import { Box } from "@devup-ui/react";\n',
- filename: 'src/app/page.tsx',
- },
- {
- code: 'import { css } from "@devup-ui/react";\ncss({styleOrder: 1})',
- filename: 'src/app/page.tsx',
- },
- {
- code: 'css({styleOrder: 1})',
- filename: 'src/app/page.tsx',
- },
- {
- code: '',
- filename: 'src/app/page.tsx',
- },
- ],
- invalid: [
- {
- code: 'import { Box } from "@devup-ui/react";\n',
- filename: 'src/app/page.tsx',
- errors: [
- {
- messageId: 'styleOrderRange',
- },
- ],
- },
- {
- code: 'import { Box } from "@devup-ui/react";\n',
- filename: 'src/app/page.tsx',
- errors: [
- {
- messageId: 'styleOrderRange',
- },
- ],
- },
- {
- code: 'import { Box } from "@devup-ui/react";\n',
- filename: 'src/app/page.tsx',
- errors: [
- {
- messageId: 'styleOrderRange',
- },
- ],
- },
- {
- code: 'import { Box } from "@devup-ui/react";\n',
- filename: 'src/app/page.tsx',
- errors: [
- {
- messageId: 'styleOrderRange',
- },
- ],
- },
- {
- code: 'import { Box } from "@devup-ui/react";\n',
- filename: 'src/app/page.tsx',
- errors: [
- {
- messageId: 'styleOrderRange',
- },
- ],
- },
- {
- code: 'import { Box } from "@devup-ui/react";\n',
- filename: 'src/app/page.tsx',
- errors: [
- {
- messageId: 'styleOrderRange',
- },
- ],
- },
- {
- code: 'import { Box } from "@devup-ui/react";\n',
- filename: 'src/app/page.tsx',
- errors: [
- {
- messageId: 'styleOrderRange',
- },
- ],
- },
- {
- code: 'import { Box } from "@devup-ui/react";\n',
- filename: 'src/app/page.tsx',
- errors: [
- {
- messageId: 'styleOrderRange',
- },
- ],
- },
- {
- code: 'import { Box } from "@devup-ui/react";\n',
- filename: 'src/app/page.tsx',
- errors: [
- {
- messageId: 'styleOrderRange',
- },
- ],
- },
- {
- code: 'import { Box } from "@devup-ui/react";\n',
- filename: 'src/app/page.tsx',
- errors: [
- {
- messageId: 'styleOrderRange',
- },
- ],
- },
- {
- code: 'import { Box } from "@devup-ui/react";\n',
- filename: 'src/app/page.tsx',
- errors: [
- {
- messageId: 'styleOrderRange',
- },
- ],
- },
- {
- code: 'import { Box } from "@devup-ui/react";\n',
- filename: 'src/app/page.tsx',
- errors: [
- {
- messageId: 'styleOrderRange',
- },
- ],
- },
- {
- code: 'import { Box } from "@devup-ui/react";\n',
- filename: 'src/app/page.tsx',
- errors: [
- {
- messageId: 'styleOrderRange',
- },
- ],
- },
- {
- code: 'import { Box } from "@devup-ui/react";\n',
- filename: 'src/app/page.tsx',
- errors: [
- {
- messageId: 'styleOrderRange',
- },
- ],
- },
- {
- code: 'import { Box } from "@devup-ui/react";\n',
- filename: 'src/app/page.tsx',
- errors: [
- {
- messageId: 'styleOrderRange',
- },
- ],
- },
- {
- code: 'import { css } from "@devup-ui/react";\ncss({styleOrder: someVariable})',
- filename: 'src/app/page.tsx',
- errors: [
- {
- messageId: 'styleOrderRange',
- },
- ],
- },
- {
- code: 'import { css } from "@devup-ui/react";\ncss({styleOrder: `someVariable`})',
- filename: 'src/app/page.tsx',
- errors: [
- {
- messageId: 'styleOrderRange',
- },
- ],
- },
- {
- code: 'import { css } from "@devup-ui/react";\ncss({styleOrder: 1000})',
- filename: 'src/app/page.tsx',
- errors: [
- {
- messageId: 'styleOrderRange',
- },
- ],
- },
- {
- code: 'import { css } from "@devup-ui/react";\ncss({styleOrder: -100})',
- filename: 'src/app/page.tsx',
- errors: [
- {
- messageId: 'styleOrderRange',
- },
- ],
- },
- {
- code: 'import { css } from "@devup-ui/react";\ncss({styleOrder: "1000"})',
- filename: 'src/app/page.tsx',
- errors: [
- {
- messageId: 'styleOrderRange',
- },
- ],
- },
- {
- code: 'import { css } from "@devup-ui/react";\ncss({styleOrder: `1000`})',
- filename: 'src/app/page.tsx',
- errors: [
- {
- messageId: 'styleOrderRange',
- },
- ],
- },
- {
- code: 'import { css } from "@devup-ui/react";\ncss({styleOrder: `${someVariable}`})',
- filename: 'src/app/page.tsx',
- errors: [
- {
- messageId: 'styleOrderRange',
- },
- ],
- },
- {
- code: 'import { css } from "@devup-ui/react";\ncss({styleOrder: +someVariable})',
- filename: 'src/app/page.tsx',
- errors: [
- {
- messageId: 'styleOrderRange',
- },
- ],
- },
- {
- code: 'import { css } from "@devup-ui/react";\ncss({styleOrder: -someVariable})',
- filename: 'src/app/page.tsx',
- errors: [
- {
- messageId: 'styleOrderRange',
- },
- ],
- },
- {
- code: 'import { css } from "@devup-ui/react";\ncss({styleOrder: typeof `100`})',
- filename: 'src/app/page.tsx',
- errors: [
- {
- messageId: 'styleOrderRange',
- },
- ],
- },
- {
- code: 'import { css } from "@devup-ui/react";\ncss({styleOrder: void `100`})',
- filename: 'src/app/page.tsx',
- errors: [
- {
- messageId: 'styleOrderRange',
- },
- ],
- },
- {
- code: 'import { css } from "@devup-ui/react";\ncss({styleOrder: delete `100`})',
- filename: 'src/app/page.tsx',
- errors: [
- {
- messageId: 'styleOrderRange',
- },
- ],
- },
- {
- code: 'import { css } from "@devup-ui/react";\ncss({styleOrder: ~ `100`})',
- filename: 'src/app/page.tsx',
- errors: [
- {
- messageId: 'styleOrderRange',
- },
- ],
- },
- {
- code: 'import { css } from "@devup-ui/react";\ncss({styleOrder: `100` + 100})',
- filename: 'src/app/page.tsx',
- errors: [
- {
- messageId: 'styleOrderRange',
- },
- ],
- },
- {
- code: 'import { css } from "@devup-ui/react";\ncss({styleOrder: ~10})',
- filename: 'src/app/page.tsx',
- errors: [
- {
- messageId: 'styleOrderRange',
- },
- ],
- },
- {
- code: 'import { Box } from "@devup-ui/react";\n />',
- filename: 'src/app/page.tsx',
- errors: [
- {
- messageId: 'wrongType',
- },
- ],
- },
- ],
- })
-})
+import { RuleTester } from '@typescript-eslint/rule-tester'
+import { describe } from 'bun:test'
+
+import { styleOrderRange } from '../index'
+
+describe('style-order-range rule', () => {
+ const ruleTester = new RuleTester({
+ languageOptions: {
+ ecmaVersion: 'latest',
+ parserOptions: {
+ ecmaFeatures: {
+ jsx: true,
+ },
+ },
+ },
+ })
+
+ ruleTester.run('style-order-range rule', styleOrderRange, {
+ valid: [
+ {
+ code: 'import { Box } from "@devup-ui/react";\n',
+ filename: 'src/app/page.tsx',
+ },
+ {
+ code: 'import { Box } from "@devup-ui/react";\n',
+ filename: 'src/app/page.tsx',
+ },
+ {
+ code: 'import { Box } from "@devup-ui/react";\n',
+ filename: 'src/app/page.tsx',
+ },
+ {
+ code: 'import { Box } from "@devup-ui/react";\n',
+ filename: 'src/app/page.tsx',
+ },
+ {
+ code: 'import { Box } from "@devup-ui/react";\n',
+ filename: 'src/app/page.tsx',
+ },
+ {
+ code: 'import { Box } from "@devup-ui/react";\n',
+ filename: 'src/app/page.tsx',
+ },
+ {
+ code: 'import { Box } from "@devup-ui/react";\n',
+ filename: 'src/app/page.tsx',
+ },
+ {
+ code: 'import { Box } from "@devup-ui/react";\n',
+ filename: 'src/app/page.tsx',
+ },
+ {
+ code: 'import { Box } from "@devup-ui/react";\n',
+ filename: 'src/app/page.tsx',
+ },
+ {
+ code: 'import { Box } from "@devup-ui/react";\n',
+ filename: 'src/app/page.tsx',
+ },
+ {
+ code: 'import { Box } from "@devup-ui/react";\n',
+ filename: 'src/app/page.tsx',
+ },
+ {
+ code: 'import { css } from "@devup-ui/react";\ncss({styleOrder: 1})',
+ filename: 'src/app/page.tsx',
+ },
+ {
+ code: 'css({styleOrder: 1})',
+ filename: 'src/app/page.tsx',
+ },
+ {
+ code: '',
+ filename: 'src/app/page.tsx',
+ },
+ ],
+ invalid: [
+ {
+ code: 'import { Box } from "@devup-ui/react";\n',
+ filename: 'src/app/page.tsx',
+ errors: [
+ {
+ messageId: 'styleOrderRange',
+ },
+ ],
+ },
+ {
+ code: 'import { Box } from "@devup-ui/react";\n',
+ filename: 'src/app/page.tsx',
+ errors: [
+ {
+ messageId: 'styleOrderRange',
+ },
+ ],
+ },
+ {
+ code: 'import { Box } from "@devup-ui/react";\n',
+ filename: 'src/app/page.tsx',
+ errors: [
+ {
+ messageId: 'styleOrderRange',
+ },
+ ],
+ },
+ {
+ code: 'import { Box } from "@devup-ui/react";\n',
+ filename: 'src/app/page.tsx',
+ errors: [
+ {
+ messageId: 'styleOrderRange',
+ },
+ ],
+ },
+ {
+ code: 'import { Box } from "@devup-ui/react";\n',
+ filename: 'src/app/page.tsx',
+ errors: [
+ {
+ messageId: 'styleOrderRange',
+ },
+ ],
+ },
+ {
+ code: 'import { Box } from "@devup-ui/react";\n',
+ filename: 'src/app/page.tsx',
+ errors: [
+ {
+ messageId: 'styleOrderRange',
+ },
+ ],
+ },
+ {
+ code: 'import { Box } from "@devup-ui/react";\n',
+ filename: 'src/app/page.tsx',
+ errors: [
+ {
+ messageId: 'styleOrderRange',
+ },
+ ],
+ },
+ {
+ code: 'import { Box } from "@devup-ui/react";\n',
+ filename: 'src/app/page.tsx',
+ errors: [
+ {
+ messageId: 'styleOrderRange',
+ },
+ ],
+ },
+ {
+ code: 'import { Box } from "@devup-ui/react";\n',
+ filename: 'src/app/page.tsx',
+ errors: [
+ {
+ messageId: 'styleOrderRange',
+ },
+ ],
+ },
+ {
+ code: 'import { Box } from "@devup-ui/react";\n',
+ filename: 'src/app/page.tsx',
+ errors: [
+ {
+ messageId: 'styleOrderRange',
+ },
+ ],
+ },
+ {
+ code: 'import { Box } from "@devup-ui/react";\n',
+ filename: 'src/app/page.tsx',
+ errors: [
+ {
+ messageId: 'styleOrderRange',
+ },
+ ],
+ },
+ {
+ code: 'import { Box } from "@devup-ui/react";\n',
+ filename: 'src/app/page.tsx',
+ errors: [
+ {
+ messageId: 'styleOrderRange',
+ },
+ ],
+ },
+ {
+ code: 'import { Box } from "@devup-ui/react";\n',
+ filename: 'src/app/page.tsx',
+ errors: [
+ {
+ messageId: 'styleOrderRange',
+ },
+ ],
+ },
+ {
+ code: 'import { Box } from "@devup-ui/react";\n',
+ filename: 'src/app/page.tsx',
+ errors: [
+ {
+ messageId: 'styleOrderRange',
+ },
+ ],
+ },
+ {
+ code: 'import { Box } from "@devup-ui/react";\n',
+ filename: 'src/app/page.tsx',
+ errors: [
+ {
+ messageId: 'styleOrderRange',
+ },
+ ],
+ },
+ {
+ code: 'import { css } from "@devup-ui/react";\ncss({styleOrder: someVariable})',
+ filename: 'src/app/page.tsx',
+ errors: [
+ {
+ messageId: 'styleOrderRange',
+ },
+ ],
+ },
+ {
+ code: 'import { css } from "@devup-ui/react";\ncss({styleOrder: `someVariable`})',
+ filename: 'src/app/page.tsx',
+ errors: [
+ {
+ messageId: 'styleOrderRange',
+ },
+ ],
+ },
+ {
+ code: 'import { css } from "@devup-ui/react";\ncss({styleOrder: 1000})',
+ filename: 'src/app/page.tsx',
+ errors: [
+ {
+ messageId: 'styleOrderRange',
+ },
+ ],
+ },
+ {
+ code: 'import { css } from "@devup-ui/react";\ncss({styleOrder: -100})',
+ filename: 'src/app/page.tsx',
+ errors: [
+ {
+ messageId: 'styleOrderRange',
+ },
+ ],
+ },
+ {
+ code: 'import { css } from "@devup-ui/react";\ncss({styleOrder: "1000"})',
+ filename: 'src/app/page.tsx',
+ errors: [
+ {
+ messageId: 'styleOrderRange',
+ },
+ ],
+ },
+ {
+ code: 'import { css } from "@devup-ui/react";\ncss({styleOrder: `1000`})',
+ filename: 'src/app/page.tsx',
+ errors: [
+ {
+ messageId: 'styleOrderRange',
+ },
+ ],
+ },
+ {
+ code: 'import { css } from "@devup-ui/react";\ncss({styleOrder: `${someVariable}`})',
+ filename: 'src/app/page.tsx',
+ errors: [
+ {
+ messageId: 'styleOrderRange',
+ },
+ ],
+ },
+ {
+ code: 'import { css } from "@devup-ui/react";\ncss({styleOrder: +someVariable})',
+ filename: 'src/app/page.tsx',
+ errors: [
+ {
+ messageId: 'styleOrderRange',
+ },
+ ],
+ },
+ {
+ code: 'import { css } from "@devup-ui/react";\ncss({styleOrder: -someVariable})',
+ filename: 'src/app/page.tsx',
+ errors: [
+ {
+ messageId: 'styleOrderRange',
+ },
+ ],
+ },
+ {
+ code: 'import { css } from "@devup-ui/react";\ncss({styleOrder: typeof `100`})',
+ filename: 'src/app/page.tsx',
+ errors: [
+ {
+ messageId: 'styleOrderRange',
+ },
+ ],
+ },
+ {
+ code: 'import { css } from "@devup-ui/react";\ncss({styleOrder: void `100`})',
+ filename: 'src/app/page.tsx',
+ errors: [
+ {
+ messageId: 'styleOrderRange',
+ },
+ ],
+ },
+ {
+ code: 'import { css } from "@devup-ui/react";\ncss({styleOrder: delete `100`})',
+ filename: 'src/app/page.tsx',
+ errors: [
+ {
+ messageId: 'styleOrderRange',
+ },
+ ],
+ },
+ {
+ code: 'import { css } from "@devup-ui/react";\ncss({styleOrder: ~ `100`})',
+ filename: 'src/app/page.tsx',
+ errors: [
+ {
+ messageId: 'styleOrderRange',
+ },
+ ],
+ },
+ {
+ code: 'import { css } from "@devup-ui/react";\ncss({styleOrder: `100` + 100})',
+ filename: 'src/app/page.tsx',
+ errors: [
+ {
+ messageId: 'styleOrderRange',
+ },
+ ],
+ },
+ {
+ code: 'import { css } from "@devup-ui/react";\ncss({styleOrder: ~10})',
+ filename: 'src/app/page.tsx',
+ errors: [
+ {
+ messageId: 'styleOrderRange',
+ },
+ ],
+ },
+ {
+ code: 'import { Box } from "@devup-ui/react";\n />',
+ filename: 'src/app/page.tsx',
+ errors: [
+ {
+ messageId: 'wrongType',
+ },
+ ],
+ },
+ ],
+ })
+})
diff --git a/packages/eslint-plugin/src/utils/__tests__/import-storage.test.ts b/packages/eslint-plugin/src/utils/__tests__/import-storage.test.ts
new file mode 100644
index 00000000..9f353af8
--- /dev/null
+++ b/packages/eslint-plugin/src/utils/__tests__/import-storage.test.ts
@@ -0,0 +1,703 @@
+import { AST_NODE_TYPES, type TSESTree } from '@typescript-eslint/utils'
+import { describe, expect, it } from 'bun:test'
+
+import { ImportStorage } from '../import-storage'
+
+describe('ImportStorage', () => {
+ describe('addImportByDeclaration', () => {
+ it('should ignore non-devup-ui imports', () => {
+ const storage = new ImportStorage()
+ const node = {
+ type: AST_NODE_TYPES.ImportDeclaration,
+ source: {
+ type: AST_NODE_TYPES.Literal,
+ value: 'other-package',
+ raw: '"other-package"',
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ },
+ specifiers: [],
+ importKind: 'value',
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ } as unknown as TSESTree.ImportDeclaration
+
+ storage.addImportByDeclaration(node)
+ // Should not throw and should not add any imports
+ })
+
+ it('should handle ImportSpecifier with Literal imported name', () => {
+ const storage = new ImportStorage()
+ const node = {
+ type: AST_NODE_TYPES.ImportDeclaration,
+ source: {
+ type: AST_NODE_TYPES.Literal,
+ value: '@devup-ui/react',
+ raw: '"@devup-ui/react"',
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ },
+ specifiers: [
+ {
+ type: AST_NODE_TYPES.ImportSpecifier,
+ local: {
+ type: AST_NODE_TYPES.Identifier,
+ name: 'B',
+ range: [0, 0],
+ loc: {
+ start: { line: 0, column: 0 },
+ end: { line: 0, column: 0 },
+ },
+ },
+ imported: {
+ type: AST_NODE_TYPES.Literal,
+ value: 'Box',
+ raw: '"Box"',
+ range: [0, 0],
+ loc: {
+ start: { line: 0, column: 0 },
+ end: { line: 0, column: 0 },
+ },
+ },
+ importKind: 'value',
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ },
+ ],
+ importKind: 'value',
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ } as unknown as TSESTree.ImportDeclaration
+
+ storage.addImportByDeclaration(node)
+ // Check that 'B' is mapped to 'Box'
+ const jsxNode = {
+ type: AST_NODE_TYPES.JSXOpeningElement,
+ name: {
+ type: AST_NODE_TYPES.JSXIdentifier,
+ name: 'B',
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ },
+ attributes: [],
+ selfClosing: true,
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ } as unknown as TSESTree.JSXOpeningElement
+ expect(storage.checkContextType(jsxNode)).toBe('COMPONENT')
+ })
+ })
+
+ describe('addImport', () => {
+ it('should add import directly', () => {
+ const storage = new ImportStorage()
+ storage.addImport('MyBox', 'Box')
+
+ const jsxNode = {
+ type: AST_NODE_TYPES.JSXOpeningElement,
+ name: {
+ type: AST_NODE_TYPES.JSXIdentifier,
+ name: 'MyBox',
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ },
+ attributes: [],
+ selfClosing: true,
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ } as unknown as TSESTree.JSXOpeningElement
+ expect(storage.checkContextType(jsxNode)).toBe('COMPONENT')
+ })
+ })
+
+ describe('checkContextType', () => {
+ it('should return undefined for unsupported node types', () => {
+ const storage = new ImportStorage()
+ const node = {
+ type: AST_NODE_TYPES.Identifier,
+ name: 'test',
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ } as unknown as TSESTree.Node
+ expect(storage.checkContextType(node)).toBeUndefined()
+ })
+
+ it('should return undefined for non-devup JSX component', () => {
+ const storage = new ImportStorage()
+ const jsxNode = {
+ type: AST_NODE_TYPES.JSXOpeningElement,
+ name: {
+ type: AST_NODE_TYPES.JSXIdentifier,
+ name: 'SomeOtherComponent',
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ },
+ attributes: [],
+ selfClosing: true,
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ } as unknown as TSESTree.JSXOpeningElement
+ expect(storage.checkContextType(jsxNode)).toBeUndefined()
+ })
+
+ it('should return undefined for non-devup call expression', () => {
+ const storage = new ImportStorage()
+ const callNode = {
+ type: AST_NODE_TYPES.CallExpression,
+ callee: {
+ type: AST_NODE_TYPES.Identifier,
+ name: 'someFunction',
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ },
+ arguments: [],
+ optional: false,
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ } as unknown as TSESTree.CallExpression
+ expect(storage.checkContextType(callNode)).toBeUndefined()
+ })
+
+ it('should return UTIL for devup call expression', () => {
+ const storage = new ImportStorage()
+ storage.addImport('css', 'css')
+
+ const callNode = {
+ type: AST_NODE_TYPES.CallExpression,
+ callee: {
+ type: AST_NODE_TYPES.Identifier,
+ name: 'css',
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ },
+ arguments: [],
+ optional: false,
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ } as unknown as TSESTree.CallExpression
+ expect(storage.checkContextType(callNode)).toBe('UTIL')
+ })
+
+ it('should return UTIL for member expression call on import object', () => {
+ const storage = new ImportStorage()
+ // Simulate: import devup from "@devup-ui/react"; devup.css()
+ const node = {
+ type: AST_NODE_TYPES.ImportDeclaration,
+ source: {
+ type: AST_NODE_TYPES.Literal,
+ value: '@devup-ui/react',
+ raw: '"@devup-ui/react"',
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ },
+ specifiers: [
+ {
+ type: AST_NODE_TYPES.ImportDefaultSpecifier,
+ local: {
+ type: AST_NODE_TYPES.Identifier,
+ name: 'devup',
+ range: [0, 0],
+ loc: {
+ start: { line: 0, column: 0 },
+ end: { line: 0, column: 0 },
+ },
+ },
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ },
+ ],
+ importKind: 'value',
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ } as unknown as TSESTree.ImportDeclaration
+ storage.addImportByDeclaration(node)
+
+ const callNode = {
+ type: AST_NODE_TYPES.CallExpression,
+ callee: {
+ type: AST_NODE_TYPES.MemberExpression,
+ object: {
+ type: AST_NODE_TYPES.Identifier,
+ name: 'devup',
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ },
+ property: {
+ type: AST_NODE_TYPES.Identifier,
+ name: 'css',
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ },
+ computed: false,
+ optional: false,
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ },
+ arguments: [],
+ optional: false,
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ } as unknown as TSESTree.CallExpression
+ expect(storage.checkContextType(callNode)).toBe('UTIL')
+ })
+
+ it('should return COMPONENT for JSX member expression on import object', () => {
+ const storage = new ImportStorage()
+ // Simulate: import * as devup from "@devup-ui/react";
+ const node = {
+ type: AST_NODE_TYPES.ImportDeclaration,
+ source: {
+ type: AST_NODE_TYPES.Literal,
+ value: '@devup-ui/react',
+ raw: '"@devup-ui/react"',
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ },
+ specifiers: [
+ {
+ type: AST_NODE_TYPES.ImportNamespaceSpecifier,
+ local: {
+ type: AST_NODE_TYPES.Identifier,
+ name: 'devup',
+ range: [0, 0],
+ loc: {
+ start: { line: 0, column: 0 },
+ end: { line: 0, column: 0 },
+ },
+ },
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ },
+ ],
+ importKind: 'value',
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ } as unknown as TSESTree.ImportDeclaration
+ storage.addImportByDeclaration(node)
+
+ const jsxNode = {
+ type: AST_NODE_TYPES.JSXOpeningElement,
+ name: {
+ type: AST_NODE_TYPES.JSXMemberExpression,
+ object: {
+ type: AST_NODE_TYPES.JSXIdentifier,
+ name: 'devup',
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ },
+ property: {
+ type: AST_NODE_TYPES.JSXIdentifier,
+ name: 'Box',
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ },
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ },
+ attributes: [],
+ selfClosing: true,
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ } as unknown as TSESTree.JSXOpeningElement
+ expect(storage.checkContextType(jsxNode)).toBe('COMPONENT')
+ })
+
+ it('should return undefined for non-devup member expression call', () => {
+ const storage = new ImportStorage()
+ // Simulate: import other from "other-package"; other.css()
+ const callNode = {
+ type: AST_NODE_TYPES.CallExpression,
+ callee: {
+ type: AST_NODE_TYPES.MemberExpression,
+ object: {
+ type: AST_NODE_TYPES.Identifier,
+ name: 'other',
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ },
+ property: {
+ type: AST_NODE_TYPES.Identifier,
+ name: 'css',
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ },
+ computed: false,
+ optional: false,
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ },
+ arguments: [],
+ optional: false,
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ } as unknown as TSESTree.CallExpression
+ expect(storage.checkContextType(callNode)).toBeUndefined()
+ })
+
+ it('should return undefined for member expression with non-identifier object', () => {
+ const storage = new ImportStorage()
+
+ const callNode = {
+ type: AST_NODE_TYPES.CallExpression,
+ callee: {
+ type: AST_NODE_TYPES.MemberExpression,
+ object: {
+ type: AST_NODE_TYPES.CallExpression,
+ callee: {
+ type: AST_NODE_TYPES.Identifier,
+ name: 'getModule',
+ range: [0, 0],
+ loc: {
+ start: { line: 0, column: 0 },
+ end: { line: 0, column: 0 },
+ },
+ },
+ arguments: [],
+ optional: false,
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ },
+ property: {
+ type: AST_NODE_TYPES.Identifier,
+ name: 'css',
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ },
+ computed: false,
+ optional: false,
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ },
+ arguments: [],
+ optional: false,
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ } as unknown as TSESTree.CallExpression
+ expect(storage.checkContextType(callNode)).toBeUndefined()
+ })
+
+ it('should return undefined for member expression with computed property', () => {
+ const storage = new ImportStorage()
+ const node = {
+ type: AST_NODE_TYPES.ImportDeclaration,
+ source: {
+ type: AST_NODE_TYPES.Literal,
+ value: '@devup-ui/react',
+ raw: '"@devup-ui/react"',
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ },
+ specifiers: [
+ {
+ type: AST_NODE_TYPES.ImportDefaultSpecifier,
+ local: {
+ type: AST_NODE_TYPES.Identifier,
+ name: 'devup',
+ range: [0, 0],
+ loc: {
+ start: { line: 0, column: 0 },
+ end: { line: 0, column: 0 },
+ },
+ },
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ },
+ ],
+ importKind: 'value',
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ } as unknown as TSESTree.ImportDeclaration
+ storage.addImportByDeclaration(node)
+
+ const callNode = {
+ type: AST_NODE_TYPES.CallExpression,
+ callee: {
+ type: AST_NODE_TYPES.MemberExpression,
+ object: {
+ type: AST_NODE_TYPES.Identifier,
+ name: 'devup',
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ },
+ property: {
+ type: AST_NODE_TYPES.Literal,
+ value: 'css',
+ raw: '"css"',
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ },
+ computed: true,
+ optional: false,
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ },
+ arguments: [],
+ optional: false,
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ } as unknown as TSESTree.CallExpression
+ expect(storage.checkContextType(callNode)).toBeUndefined()
+ })
+
+ it('should return undefined for non-devup property on member expression', () => {
+ const storage = new ImportStorage()
+ const node = {
+ type: AST_NODE_TYPES.ImportDeclaration,
+ source: {
+ type: AST_NODE_TYPES.Literal,
+ value: '@devup-ui/react',
+ raw: '"@devup-ui/react"',
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ },
+ specifiers: [
+ {
+ type: AST_NODE_TYPES.ImportDefaultSpecifier,
+ local: {
+ type: AST_NODE_TYPES.Identifier,
+ name: 'devup',
+ range: [0, 0],
+ loc: {
+ start: { line: 0, column: 0 },
+ end: { line: 0, column: 0 },
+ },
+ },
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ },
+ ],
+ importKind: 'value',
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ } as unknown as TSESTree.ImportDeclaration
+ storage.addImportByDeclaration(node)
+
+ const callNode = {
+ type: AST_NODE_TYPES.CallExpression,
+ callee: {
+ type: AST_NODE_TYPES.MemberExpression,
+ object: {
+ type: AST_NODE_TYPES.Identifier,
+ name: 'devup',
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ },
+ property: {
+ type: AST_NODE_TYPES.Identifier,
+ name: 'unknownMethod',
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ },
+ computed: false,
+ optional: false,
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ },
+ arguments: [],
+ optional: false,
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ } as unknown as TSESTree.CallExpression
+ expect(storage.checkContextType(callNode)).toBeUndefined()
+ })
+
+ it('should return undefined for JSX member expression with non-JSXIdentifier object', () => {
+ const storage = new ImportStorage()
+ const node = {
+ type: AST_NODE_TYPES.ImportDeclaration,
+ source: {
+ type: AST_NODE_TYPES.Literal,
+ value: '@devup-ui/react',
+ raw: '"@devup-ui/react"',
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ },
+ specifiers: [
+ {
+ type: AST_NODE_TYPES.ImportNamespaceSpecifier,
+ local: {
+ type: AST_NODE_TYPES.Identifier,
+ name: 'devup',
+ range: [0, 0],
+ loc: {
+ start: { line: 0, column: 0 },
+ end: { line: 0, column: 0 },
+ },
+ },
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ },
+ ],
+ importKind: 'value',
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ } as unknown as TSESTree.ImportDeclaration
+ storage.addImportByDeclaration(node)
+
+ // Nested member expression: devup.components.Box
+ const jsxNode = {
+ type: AST_NODE_TYPES.JSXOpeningElement,
+ name: {
+ type: AST_NODE_TYPES.JSXMemberExpression,
+ object: {
+ type: AST_NODE_TYPES.JSXMemberExpression,
+ object: {
+ type: AST_NODE_TYPES.JSXIdentifier,
+ name: 'devup',
+ range: [0, 0],
+ loc: {
+ start: { line: 0, column: 0 },
+ end: { line: 0, column: 0 },
+ },
+ },
+ property: {
+ type: AST_NODE_TYPES.JSXIdentifier,
+ name: 'components',
+ range: [0, 0],
+ loc: {
+ start: { line: 0, column: 0 },
+ end: { line: 0, column: 0 },
+ },
+ },
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ },
+ property: {
+ type: AST_NODE_TYPES.JSXIdentifier,
+ name: 'Box',
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ },
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ },
+ attributes: [],
+ selfClosing: true,
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ } as unknown as TSESTree.JSXOpeningElement
+ expect(storage.checkContextType(jsxNode)).toBeUndefined()
+ })
+
+ it('should return undefined for JSX member expression with unknown property', () => {
+ const storage = new ImportStorage()
+ const node = {
+ type: AST_NODE_TYPES.ImportDeclaration,
+ source: {
+ type: AST_NODE_TYPES.Literal,
+ value: '@devup-ui/react',
+ raw: '"@devup-ui/react"',
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ },
+ specifiers: [
+ {
+ type: AST_NODE_TYPES.ImportNamespaceSpecifier,
+ local: {
+ type: AST_NODE_TYPES.Identifier,
+ name: 'devup',
+ range: [0, 0],
+ loc: {
+ start: { line: 0, column: 0 },
+ end: { line: 0, column: 0 },
+ },
+ },
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ },
+ ],
+ importKind: 'value',
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ } as unknown as TSESTree.ImportDeclaration
+ storage.addImportByDeclaration(node)
+
+ const jsxNode = {
+ type: AST_NODE_TYPES.JSXOpeningElement,
+ name: {
+ type: AST_NODE_TYPES.JSXMemberExpression,
+ object: {
+ type: AST_NODE_TYPES.JSXIdentifier,
+ name: 'devup',
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ },
+ property: {
+ type: AST_NODE_TYPES.JSXIdentifier,
+ name: 'UnknownComponent',
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ },
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ },
+ attributes: [],
+ selfClosing: true,
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ } as unknown as TSESTree.JSXOpeningElement
+ expect(storage.checkContextType(jsxNode)).toBeUndefined()
+ })
+
+ it('should return undefined for JSX member expression with non-registered object', () => {
+ const storage = new ImportStorage()
+
+ const jsxNode = {
+ type: AST_NODE_TYPES.JSXOpeningElement,
+ name: {
+ type: AST_NODE_TYPES.JSXMemberExpression,
+ object: {
+ type: AST_NODE_TYPES.JSXIdentifier,
+ name: 'unknown',
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ },
+ property: {
+ type: AST_NODE_TYPES.JSXIdentifier,
+ name: 'Box',
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ },
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ },
+ attributes: [],
+ selfClosing: true,
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ } as unknown as TSESTree.JSXOpeningElement
+ expect(storage.checkContextType(jsxNode)).toBeUndefined()
+ })
+
+ it('should return undefined for JSXNamespacedName', () => {
+ const storage = new ImportStorage()
+ storage.addImport('Box', 'Box')
+
+ const jsxNode = {
+ type: AST_NODE_TYPES.JSXOpeningElement,
+ name: {
+ type: AST_NODE_TYPES.JSXNamespacedName,
+ namespace: {
+ type: AST_NODE_TYPES.JSXIdentifier,
+ name: 'ns',
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ },
+ name: {
+ type: AST_NODE_TYPES.JSXIdentifier,
+ name: 'Box',
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ },
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ },
+ attributes: [],
+ selfClosing: true,
+ range: [0, 0],
+ loc: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
+ } as unknown as TSESTree.JSXOpeningElement
+ expect(storage.checkContextType(jsxNode)).toBeUndefined()
+ })
+ })
+})
diff --git a/packages/eslint-plugin/src/utils/import-storage.ts b/packages/eslint-plugin/src/utils/import-storage.ts
index 008fb21a..d85cdfa5 100644
--- a/packages/eslint-plugin/src/utils/import-storage.ts
+++ b/packages/eslint-plugin/src/utils/import-storage.ts
@@ -1,87 +1,92 @@
-import { AST_NODE_TYPES, type TSESTree } from '@typescript-eslint/utils'
-const defaultImports = {
- css: 'css',
- globalCss: 'globalCss',
- keyframes: 'keyframes',
- Box: 'Box',
- Button: 'Button',
- Text: 'Text',
- Image: 'Image',
- Flex: 'Flex',
- Grid: 'Grid',
- Center: 'Center',
- VStack: 'VStack',
- Input: 'Input',
-}
-
-export class ImportStorage {
- private imports: Record = {}
- private importObject: Set = new Set()
-
- public addImportByDeclaration(node: TSESTree.ImportDeclaration) {
- if (node.source.value !== '@devup-ui/react') return
-
- for (const specifier of node.specifiers) {
- switch (specifier.type) {
- case AST_NODE_TYPES.ImportSpecifier:
- this.addImport(
- specifier.local.name,
- specifier.imported.type === AST_NODE_TYPES.Literal
- ? specifier.imported.value
- : specifier.imported.name,
- )
- break
- case AST_NODE_TYPES.ImportDefaultSpecifier:
- this.importObject.add(specifier.local.name)
- break
- case AST_NODE_TYPES.ImportNamespaceSpecifier:
- this.importObject.add(specifier.local.name)
- break
- }
- }
- }
-
- public addImport(key: string, value: string) {
- this.imports[key] = value
- }
-
- public checkContextType(node: TSESTree.Node) {
- switch (node.type) {
- case AST_NODE_TYPES.JSXOpeningElement: {
- if (this.checkDevupUIComponent(node.name)) {
- return 'COMPONENT'
- }
- break
- }
- case AST_NODE_TYPES.CallExpression: {
- if (this.checkDevupUIUtil(node)) {
- return 'UTIL'
- }
- break
- }
- }
- }
-
- private checkDevupUIUtil(node: TSESTree.CallExpression): boolean {
- return (
- (node.callee.type === AST_NODE_TYPES.Identifier &&
- node.callee.name in this.imports) ||
- (node.callee.type === AST_NODE_TYPES.MemberExpression &&
- node.callee.object.type === AST_NODE_TYPES.Identifier &&
- this.importObject.has(node.callee.object.name) &&
- node.callee.property.type === AST_NODE_TYPES.Identifier &&
- node.callee.property.name in defaultImports)
- )
- }
-
- private checkDevupUIComponent(node: TSESTree.JSXTagNameExpression): boolean {
- return (
- (node.type === AST_NODE_TYPES.JSXIdentifier &&
- node.name in this.imports) ||
- (node.type === AST_NODE_TYPES.JSXMemberExpression &&
- node.object.type === AST_NODE_TYPES.JSXIdentifier &&
- this.importObject.has(node.object.name) &&
- node.property.name in defaultImports)
- )
- }
-}
+import { AST_NODE_TYPES, type TSESTree } from '@typescript-eslint/utils'
+const defaultImports = {
+ css: 'css',
+ globalCss: 'globalCss',
+ keyframes: 'keyframes',
+ Box: 'Box',
+ Button: 'Button',
+ Text: 'Text',
+ Image: 'Image',
+ Flex: 'Flex',
+ Grid: 'Grid',
+ Center: 'Center',
+ VStack: 'VStack',
+ Input: 'Input',
+}
+
+export class ImportStorage {
+ private imports: Record
+ private importObject: Set
+
+ constructor() {
+ this.imports = {}
+ this.importObject = new Set()
+ }
+
+ public addImportByDeclaration(node: TSESTree.ImportDeclaration) {
+ if (node.source.value !== '@devup-ui/react') return
+
+ for (const specifier of node.specifiers) {
+ switch (specifier.type) {
+ case AST_NODE_TYPES.ImportSpecifier:
+ this.addImport(
+ specifier.local.name,
+ specifier.imported.type === AST_NODE_TYPES.Literal
+ ? specifier.imported.value
+ : specifier.imported.name,
+ )
+ break
+ case AST_NODE_TYPES.ImportDefaultSpecifier:
+ this.importObject.add(specifier.local.name)
+ break
+ case AST_NODE_TYPES.ImportNamespaceSpecifier:
+ this.importObject.add(specifier.local.name)
+ break
+ }
+ }
+ }
+
+ public addImport(key: string, value: string) {
+ this.imports[key] = value
+ }
+
+ public checkContextType(node: TSESTree.Node) {
+ switch (node.type) {
+ case AST_NODE_TYPES.JSXOpeningElement: {
+ if (this.checkDevupUIComponent(node.name)) {
+ return 'COMPONENT'
+ }
+ break
+ }
+ case AST_NODE_TYPES.CallExpression: {
+ if (this.checkDevupUIUtil(node)) {
+ return 'UTIL'
+ }
+ break
+ }
+ }
+ }
+
+ private checkDevupUIUtil(node: TSESTree.CallExpression): boolean {
+ return (
+ (node.callee.type === AST_NODE_TYPES.Identifier &&
+ node.callee.name in this.imports) ||
+ (node.callee.type === AST_NODE_TYPES.MemberExpression &&
+ node.callee.object.type === AST_NODE_TYPES.Identifier &&
+ this.importObject.has(node.callee.object.name) &&
+ node.callee.property.type === AST_NODE_TYPES.Identifier &&
+ node.callee.property.name in defaultImports)
+ )
+ }
+
+ private checkDevupUIComponent(node: TSESTree.JSXTagNameExpression): boolean {
+ return (
+ (node.type === AST_NODE_TYPES.JSXIdentifier &&
+ node.name in this.imports) ||
+ (node.type === AST_NODE_TYPES.JSXMemberExpression &&
+ node.object.type === AST_NODE_TYPES.JSXIdentifier &&
+ this.importObject.has(node.object.name) &&
+ node.property.name in defaultImports)
+ )
+ }
+}
diff --git a/packages/eslint-plugin/tsconfig.json b/packages/eslint-plugin/tsconfig.json
index 4c6e75be..a79a5660 100644
--- a/packages/eslint-plugin/tsconfig.json
+++ b/packages/eslint-plugin/tsconfig.json
@@ -1,26 +1,29 @@
-{
- "compilerOptions": {
- "types": ["vite/client", "vitest/importMeta", "vitest/globals"],
- "strict": true,
- "target": "ESNext",
- "declaration": true,
- "declarationMap": true,
- "removeComments": true,
- "sourceMap": true,
- "useDefineForClassFields": true,
- "allowJs": false,
- "skipLibCheck": true,
- "noFallthroughCasesInSwitch": true,
- "esModuleInterop": true,
- "allowSyntheticDefaultImports": true,
- "forceConsistentCasingInFileNames": true,
- "strictFunctionTypes": true,
- "module": "ESNext",
- "moduleResolution": "Bundler",
- "resolveJsonModule": true,
- "isolatedModules": true,
- "noEmit": true,
- "baseUrl": ".",
- "jsx": "react-jsx"
- }
-}
+{
+ "compilerOptions": {
+ "types": ["bun"],
+ "strict": true,
+ "target": "ESNext",
+ "declaration": true,
+ "emitDeclarationOnly": true,
+ "outDir": "dist",
+ "declarationMap": true,
+ "removeComments": true,
+ "sourceMap": true,
+ "useDefineForClassFields": true,
+ "allowJs": false,
+ "skipLibCheck": true,
+ "noFallthroughCasesInSwitch": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "forceConsistentCasingInFileNames": true,
+ "strictFunctionTypes": true,
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "baseUrl": ".",
+ "jsx": "react-jsx"
+ },
+ "include": ["src"],
+ "exclude": ["node_modules", "dist", "src/**/__tests__"]
+}
diff --git a/packages/eslint-plugin/vite.config.ts b/packages/eslint-plugin/vite.config.ts
deleted file mode 100644
index 0cf0c964..00000000
--- a/packages/eslint-plugin/vite.config.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-import dts from 'vite-plugin-dts'
-import { defineConfig } from 'vitest/config'
-
-export default defineConfig({
- test: {
- globals: true,
- coverage: {
- provider: 'v8',
- thresholds: {
- '100': true,
- },
- },
- },
- plugins: [
- dts({
- entryRoot: 'src',
- staticImport: true,
- pathsToAliases: false,
- exclude: [
- '**/__tests__/**/*',
- '**/*.test.(tsx|ts|js|jsx)',
- '**/*.test-d.(tsx|ts|js|jsx)',
- 'vite.config.ts',
- ],
- include: ['**/src/**/*.ts'],
- copyDtsFiles: true,
- compilerOptions: {
- isolatedModules: false,
- declaration: true,
- },
- }),
- ],
- build: {
- rollupOptions: {
- onwarn: (warning) => {
- if (warning.code === 'MODULE_LEVEL_DIRECTIVE') {
- return
- }
- },
- external: (source) => {
- return !(source.includes('src') || source.startsWith('.'))
- },
-
- output: {
- dir: 'dist',
- preserveModules: true,
- preserveModulesRoot: 'src',
-
- exports: 'named',
- assetFileNames({ name }) {
- return name?.replace(/^src\//g, '') ?? ''
- },
- },
- },
- lib: {
- formats: ['es', 'cjs'],
- entry: {
- index: 'src/index.ts',
- },
- },
- outDir: 'dist',
- },
-})
diff --git a/packages/next-plugin/package.json b/packages/next-plugin/package.json
index fcbf1317..a4b24db0 100644
--- a/packages/next-plugin/package.json
+++ b/packages/next-plugin/package.json
@@ -22,26 +22,29 @@
"version": "1.0.63",
"scripts": {
"lint": "eslint",
- "build": "tsc && vite build"
+ "build": "tsc && bun build --target node src/index.ts --production --outfile dist/index.cjs --format cjs --packages external && bun build --target node src/index.ts --production --outfile dist/index.mjs --format esm --packages external && bun build --target node src/loader.ts --production --outfile dist/loader.cjs --format cjs --packages external && bun build --target node src/loader.ts --production --outfile dist/loader.mjs --format esm --packages external && bun build --target node src/css-loader.ts --production --outfile dist/css-loader.cjs --format cjs --packages external && bun build --target node src/css-loader.ts --production --outfile dist/css-loader.mjs --format esm --packages external"
},
"publishConfig": {
"access": "public"
},
"sideEffects": false,
"main": "./dist/index.cjs",
- "module": "./dist/index.js",
+ "module": "./dist/index.mjs",
"exports": {
".": {
- "import": "./dist/index.js",
- "require": "./dist/index.cjs"
+ "import": "./dist/index.mjs",
+ "require": "./dist/index.cjs",
+ "types": "./dist/index.d.ts"
},
"./css-loader": {
- "import": "./dist/css-loader.js",
- "require": "./dist/css-loader.cjs"
+ "import": "./dist/css-loader.mjs",
+ "require": "./dist/css-loader.cjs",
+ "types": "./dist/css-loader.d.ts"
},
"./loader": {
- "import": "./dist/loader.js",
- "require": "./dist/loader.cjs"
+ "import": "./dist/loader.mjs",
+ "require": "./dist/loader.cjs",
+ "types": "./dist/loader.d.ts"
}
},
"files": [
@@ -55,9 +58,6 @@
"tinyglobby": "^0.2"
},
"devDependencies": {
- "vite": "^7.3",
- "vite-plugin-dts": "^4.5",
- "vitest": "^4.0",
"typescript": "^5.9",
"@types/webpack": "^5.28"
},
diff --git a/packages/next-plugin/src/__tests__/css-loader.test.ts b/packages/next-plugin/src/__tests__/css-loader.test.ts
index b27dc311..a5b2fd6e 100644
--- a/packages/next-plugin/src/__tests__/css-loader.test.ts
+++ b/packages/next-plugin/src/__tests__/css-loader.test.ts
@@ -1,66 +1,77 @@
-import { resolve } from 'node:path'
-
-import { getCss } from '@devup-ui/wasm'
-
-import devupUICssLoader from '../css-loader'
-
-vi.mock('node:path')
-vi.mock('@devup-ui/wasm', () => ({
- registerTheme: vi.fn(),
- getCss: vi.fn(),
-}))
-
-beforeEach(() => {
- vi.resetAllMocks()
-})
-
-describe('devupUICssLoader', () => {
- it('should return css on no watch', () => {
- const callback = vi.fn()
- const addContextDependency = vi.fn()
- vi.mocked(resolve).mockReturnValue('resolved')
- vi.mocked(getCss).mockReturnValue('get css')
- devupUICssLoader.bind({
- callback,
- addContextDependency,
- resourcePath: 'devup-ui.css',
- getOptions: () => ({ watch: false }),
- } as any)(Buffer.from('data'), '')
- expect(callback).toBeCalledWith(null, Buffer.from('data'), '', undefined)
- })
-
- it('should return _compiler hit css on watch', () => {
- const callback = vi.fn()
- const addContextDependency = vi.fn()
- vi.mocked(resolve).mockReturnValue('resolved')
- vi.mocked(getCss).mockReturnValue('get css')
- devupUICssLoader.bind({
- callback,
- addContextDependency,
- getOptions: () => ({ watch: true }),
- resourcePath: 'devup-ui.css',
- } as any)(Buffer.from('data'), '')
- expect(callback).toBeCalledWith(null, 'get css', '', undefined)
- expect(getCss).toBeCalledTimes(1)
- vi.mocked(getCss).mockReset()
- devupUICssLoader.bind({
- callback,
- addContextDependency,
- getOptions: () => ({ watch: true }),
- resourcePath: 'devup-ui.css',
- } as any)(Buffer.from('data'), '')
-
- expect(getCss).toBeCalledTimes(1)
-
- vi.mocked(getCss).mockReset()
-
- devupUICssLoader.bind({
- callback,
- addContextDependency,
- getOptions: () => ({ watch: true }),
- resourcePath: 'devup-ui-10.css',
- } as any)(Buffer.from(''), '')
-
- expect(getCss).toBeCalledTimes(1)
- })
-})
+import * as wasm from '@devup-ui/wasm'
+import {
+ afterAll,
+ beforeAll,
+ describe,
+ expect,
+ it,
+ mock,
+ spyOn,
+} from 'bun:test'
+
+import devupUICssLoader from '../css-loader'
+
+let getCssSpy: ReturnType
+let registerThemeSpy: ReturnType
+
+beforeAll(() => {
+ getCssSpy = spyOn(wasm, 'getCss').mockReturnValue('get css')
+ registerThemeSpy = spyOn(wasm, 'registerTheme').mockReturnValue(undefined)
+})
+
+afterAll(() => {
+ getCssSpy.mockRestore()
+ registerThemeSpy.mockRestore()
+})
+
+describe('devupUICssLoader', () => {
+ it('should return css on no watch', () => {
+ const callback = mock()
+ const addContextDependency = mock()
+ devupUICssLoader.bind({
+ callback,
+ addContextDependency,
+ resourcePath: 'devup-ui.css',
+ getOptions: () => ({ watch: false }),
+ } as any)(Buffer.from('data'), '')
+ expect(callback).toHaveBeenCalledWith(
+ null,
+ Buffer.from('data'),
+ '',
+ undefined,
+ )
+ })
+
+ it('should return _compiler hit css on watch', () => {
+ const callback = mock()
+ const addContextDependency = mock()
+ devupUICssLoader.bind({
+ callback,
+ addContextDependency,
+ getOptions: () => ({ watch: true }),
+ resourcePath: 'devup-ui.css',
+ } as any)(Buffer.from('data'), '')
+ expect(callback).toHaveBeenCalledWith(null, 'get css', '', undefined)
+ expect(getCssSpy).toHaveBeenCalledTimes(1)
+ getCssSpy.mockClear()
+ devupUICssLoader.bind({
+ callback,
+ addContextDependency,
+ getOptions: () => ({ watch: true }),
+ resourcePath: 'devup-ui.css',
+ } as any)(Buffer.from('data'), '')
+
+ expect(getCssSpy).toHaveBeenCalledTimes(1)
+
+ getCssSpy.mockClear()
+
+ devupUICssLoader.bind({
+ callback,
+ addContextDependency,
+ getOptions: () => ({ watch: true }),
+ resourcePath: 'devup-ui-10.css',
+ } as any)(Buffer.from(''), '')
+
+ expect(getCssSpy).toHaveBeenCalledTimes(1)
+ })
+})
diff --git a/packages/next-plugin/src/__tests__/find-top-package-root.test.ts b/packages/next-plugin/src/__tests__/find-top-package-root.test.ts
new file mode 100644
index 00000000..d41a9bee
--- /dev/null
+++ b/packages/next-plugin/src/__tests__/find-top-package-root.test.ts
@@ -0,0 +1,62 @@
+import * as fs from 'node:fs'
+import { join } from 'node:path'
+
+import { afterEach, beforeEach, describe, expect, it, spyOn } from 'bun:test'
+
+import { findTopPackageRoot } from '../find-top-package-root'
+
+describe('findTopPackageRoot', () => {
+ let existsSyncSpy: ReturnType
+
+ beforeEach(() => {
+ existsSyncSpy = spyOn(fs, 'existsSync') as unknown as ReturnType<
+ typeof spyOn
+ >
+ })
+
+ afterEach(() => {
+ existsSyncSpy.mockRestore()
+ })
+
+ it('should return pwd when no package.json is found', () => {
+ existsSyncSpy.mockReturnValue(false)
+ const result = findTopPackageRoot('/some/path')
+ expect(result).toBe('/some/path')
+ })
+
+ it('should return the top-most directory with package.json', () => {
+ existsSyncSpy.mockImplementation((path: string) => {
+ return (
+ path === join('/root/project', 'package.json') ||
+ path === join('/root', 'package.json')
+ )
+ })
+ const result = findTopPackageRoot('/root/project/nested')
+ expect(result).toBe('/root')
+ })
+
+ it('should return the current directory if it has package.json and is root', () => {
+ existsSyncSpy.mockImplementation((path: string) => {
+ return path === join('/', 'package.json')
+ })
+ const result = findTopPackageRoot('/')
+ expect(result).toBe('/')
+ })
+
+ it('should use process.cwd() as default pwd', () => {
+ const originalCwd = process.cwd()
+ existsSyncSpy.mockImplementation((path: string) => {
+ return path === join(originalCwd, 'package.json')
+ })
+ const result = findTopPackageRoot()
+ expect(result).toBe(originalCwd)
+ })
+
+ it('should traverse up and find the topmost package.json', () => {
+ existsSyncSpy.mockImplementation((path: string) => {
+ return path === join('/a/b/c', 'package.json')
+ })
+ const result = findTopPackageRoot('/a/b/c/d/e')
+ expect(result).toBe('/a/b/c')
+ })
+})
diff --git a/packages/next-plugin/src/__tests__/get-package-name.test.ts b/packages/next-plugin/src/__tests__/get-package-name.test.ts
new file mode 100644
index 00000000..6de9e40a
--- /dev/null
+++ b/packages/next-plugin/src/__tests__/get-package-name.test.ts
@@ -0,0 +1,43 @@
+import * as fs from 'node:fs'
+
+import { afterEach, beforeEach, describe, expect, it, spyOn } from 'bun:test'
+
+import { getPackageName } from '../get-package-name'
+
+describe('getPackageName', () => {
+ let readFileSyncSpy: ReturnType
+
+ beforeEach(() => {
+ readFileSyncSpy = spyOn(fs, 'readFileSync') as unknown as ReturnType<
+ typeof spyOn
+ >
+ })
+
+ afterEach(() => {
+ readFileSyncSpy.mockRestore()
+ })
+
+ it('should return the package name from package.json', () => {
+ readFileSyncSpy.mockReturnValue(JSON.stringify({ name: 'my-package' }))
+ const result = getPackageName('/path/to/package.json')
+ expect(result).toBe('my-package')
+ expect(readFileSyncSpy).toHaveBeenCalledWith(
+ '/path/to/package.json',
+ 'utf-8',
+ )
+ })
+
+ it('should return scoped package name', () => {
+ readFileSyncSpy.mockReturnValue(
+ JSON.stringify({ name: '@scope/my-package' }),
+ )
+ const result = getPackageName('/path/to/package.json')
+ expect(result).toBe('@scope/my-package')
+ })
+
+ it('should return undefined if name is not present', () => {
+ readFileSyncSpy.mockReturnValue(JSON.stringify({ version: '1.0.0' }))
+ const result = getPackageName('/path/to/package.json')
+ expect(result).toBeUndefined()
+ })
+})
diff --git a/packages/next-plugin/src/__tests__/has-localpackage.test.ts b/packages/next-plugin/src/__tests__/has-localpackage.test.ts
new file mode 100644
index 00000000..be7924ef
--- /dev/null
+++ b/packages/next-plugin/src/__tests__/has-localpackage.test.ts
@@ -0,0 +1,83 @@
+import * as fs from 'node:fs'
+
+import { afterEach, beforeEach, describe, expect, it, spyOn } from 'bun:test'
+
+import { hasLocalPackage } from '../has-localpackage'
+
+describe('hasLocalPackage', () => {
+ let readFileSyncSpy: ReturnType
+
+ beforeEach(() => {
+ readFileSyncSpy = spyOn(fs, 'readFileSync') as unknown as ReturnType<
+ typeof spyOn
+ >
+ })
+
+ afterEach(() => {
+ readFileSyncSpy.mockRestore()
+ })
+
+ it('should return true when dependencies contain workspace: protocol', () => {
+ readFileSyncSpy.mockReturnValue(
+ JSON.stringify({
+ dependencies: {
+ 'local-pkg': 'workspace:*',
+ 'external-pkg': '^1.0.0',
+ },
+ }),
+ )
+ expect(hasLocalPackage()).toBe(true)
+ })
+
+ it('should return false when no workspace: dependencies', () => {
+ readFileSyncSpy.mockReturnValue(
+ JSON.stringify({
+ dependencies: {
+ 'external-pkg': '^1.0.0',
+ 'another-pkg': '~2.0.0',
+ },
+ }),
+ )
+ expect(hasLocalPackage()).toBe(false)
+ })
+
+ it('should return false when dependencies is empty', () => {
+ readFileSyncSpy.mockReturnValue(
+ JSON.stringify({
+ dependencies: {},
+ }),
+ )
+ expect(hasLocalPackage()).toBe(false)
+ })
+
+ it('should return false when dependencies is undefined', () => {
+ readFileSyncSpy.mockReturnValue(
+ JSON.stringify({
+ name: 'test-package',
+ }),
+ )
+ expect(hasLocalPackage()).toBe(false)
+ })
+
+ it('should handle workspace:^ protocol', () => {
+ readFileSyncSpy.mockReturnValue(
+ JSON.stringify({
+ dependencies: {
+ 'local-pkg': 'workspace:^',
+ },
+ }),
+ )
+ expect(hasLocalPackage()).toBe(true)
+ })
+
+ it('should handle workspace:~ protocol', () => {
+ readFileSyncSpy.mockReturnValue(
+ JSON.stringify({
+ dependencies: {
+ 'local-pkg': 'workspace:~1.0.0',
+ },
+ }),
+ )
+ expect(hasLocalPackage()).toBe(true)
+ })
+})
diff --git a/packages/next-plugin/src/__tests__/index.test.ts b/packages/next-plugin/src/__tests__/index.test.ts
index 812ac547..b7dfc354 100644
--- a/packages/next-plugin/src/__tests__/index.test.ts
+++ b/packages/next-plugin/src/__tests__/index.test.ts
@@ -1,3 +1,43 @@
+import * as wasm from '@devup-ui/wasm'
+import * as webpackPluginModule from '@devup-ui/webpack-plugin'
+import {
+ afterAll,
+ beforeAll,
+ describe,
+ expect,
+ it,
+ mock,
+ spyOn,
+} from 'bun:test'
+
+let exportClassMapSpy: ReturnType
+let exportFileMapSpy: ReturnType
+let exportSheetSpy: ReturnType
+let getDefaultThemeSpy: ReturnType
+let getThemeInterfaceSpy: ReturnType
+let devupUIWebpackPluginSpy: ReturnType
+
+beforeAll(() => {
+ exportClassMapSpy = spyOn(wasm, 'exportClassMap').mockReturnValue('{}')
+ exportFileMapSpy = spyOn(wasm, 'exportFileMap').mockReturnValue('{}')
+ exportSheetSpy = spyOn(wasm, 'exportSheet').mockReturnValue('{}')
+ getDefaultThemeSpy = spyOn(wasm, 'getDefaultTheme').mockReturnValue(undefined)
+ getThemeInterfaceSpy = spyOn(wasm, 'getThemeInterface').mockReturnValue('')
+ devupUIWebpackPluginSpy = spyOn(
+ webpackPluginModule,
+ 'DevupUIWebpackPlugin',
+ ).mockImplementation(mock() as never)
+})
+
+afterAll(() => {
+ exportClassMapSpy.mockRestore()
+ exportFileMapSpy.mockRestore()
+ exportSheetSpy.mockRestore()
+ getDefaultThemeSpy.mockRestore()
+ getThemeInterfaceSpy.mockRestore()
+ devupUIWebpackPluginSpy.mockRestore()
+})
+
describe('export', () => {
it('should export DevupUI', async () => {
const index = await import('../index')
diff --git a/packages/next-plugin/src/__tests__/loader.test.ts b/packages/next-plugin/src/__tests__/loader.test.ts
index 9117f2a1..72991fa1 100644
--- a/packages/next-plugin/src/__tests__/loader.test.ts
+++ b/packages/next-plugin/src/__tests__/loader.test.ts
@@ -1,194 +1,144 @@
-import { existsSync, readFileSync } from 'node:fs'
-import { writeFile } from 'node:fs/promises'
-import { join, relative } from 'node:path'
+import * as fs from 'node:fs'
+import * as fsPromises from 'node:fs/promises'
+import { join } from 'node:path'
+import * as wasm from '@devup-ui/wasm'
import {
- codeExtract,
- exportClassMap,
- exportFileMap,
- exportSheet,
- getCss,
- importClassMap,
- importFileMap,
- importSheet,
- registerTheme,
-} from '@devup-ui/wasm'
-
-vi.mock('@devup-ui/wasm')
-vi.mock('node:fs')
-vi.mock('node:fs/promises')
-vi.mock('node:path', async (original: any) => {
- const origin = await original()
- return {
- ...origin,
- relative: vi.fn(origin.relative),
- }
-})
+ afterEach,
+ beforeEach,
+ describe,
+ expect,
+ it,
+ mock,
+ spyOn,
+} from 'bun:test'
+
+import devupUILoader from '../loader'
+
+let existsSyncSpy: ReturnType
+let readFileSyncSpy: ReturnType
+let writeFileSpy: ReturnType
+let codeExtractSpy: ReturnType
+let exportClassMapSpy: ReturnType
+let exportFileMapSpy: ReturnType
+let exportSheetSpy: ReturnType
+let getCssSpy: ReturnType
+let importClassMapSpy: ReturnType
+let importFileMapSpy: ReturnType
+let importSheetSpy: ReturnType
+let registerThemeSpy: ReturnType
+let dateNowSpy: ReturnType
beforeEach(() => {
- vi.resetAllMocks()
- vi.resetModules()
- Date.now = vi.fn().mockReturnValue(0)
+ existsSyncSpy = spyOn(fs, 'existsSync').mockReturnValue(false)
+ readFileSyncSpy = spyOn(fs, 'readFileSync').mockReturnValue('{}')
+ writeFileSpy = spyOn(fsPromises, 'writeFile').mockResolvedValue(undefined)
+ codeExtractSpy = spyOn(wasm, 'codeExtract')
+ exportClassMapSpy = spyOn(wasm, 'exportClassMap')
+ exportFileMapSpy = spyOn(wasm, 'exportFileMap')
+ exportSheetSpy = spyOn(wasm, 'exportSheet')
+ getCssSpy = spyOn(wasm, 'getCss')
+ importClassMapSpy = spyOn(wasm, 'importClassMap').mockImplementation(() => {})
+ importFileMapSpy = spyOn(wasm, 'importFileMap').mockImplementation(() => {})
+ importSheetSpy = spyOn(wasm, 'importSheet').mockImplementation(() => {})
+ registerThemeSpy = spyOn(wasm, 'registerTheme').mockImplementation(() => {})
+ dateNowSpy = spyOn(Date, 'now').mockReturnValue(0)
})
-describe('devupUILoader', () => {
- it.each(
- createTestMatrix({
- updatedBaseStyle: [true, false],
- }),
- )('should extract code with css', async (options) => {
- const { default: devupUILoader } = await import('../loader')
- const _compiler = {
- __DEVUP_CACHE: '',
- }
- const t = {
- getOptions: () => ({
- package: 'package',
- cssDir: 'cssFile',
- sheetFile: 'sheetFile',
- classMapFile: 'classMapFile',
- fileMapFile: 'fileMapFile',
- themeFile: 'themeFile',
- watch: true,
- singleCss: true,
- }),
- async: vi.fn().mockReturnValue(vi.fn()),
- resourcePath: 'index.tsx',
- addDependency: vi.fn(),
- _compiler,
- }
- vi.mocked(existsSync).mockReturnValue(false)
- vi.mocked(exportSheet).mockReturnValue('sheet')
- vi.mocked(exportClassMap).mockReturnValue('classMap')
- vi.mocked(exportFileMap).mockReturnValue('fileMap')
- vi.mocked(getCss).mockReturnValue('css')
-
- vi.mocked(codeExtract).mockReturnValue({
- code: 'code',
- css: 'css',
- free: vi.fn(),
- map: '{}',
- cssFile: 'cssFile',
- updatedBaseStyle: options.updatedBaseStyle,
- [Symbol.dispose]: vi.fn(),
- })
- devupUILoader.bind(t as any)(Buffer.from('code'), 'index.tsx')
+afterEach(() => {
+ existsSyncSpy.mockRestore()
+ readFileSyncSpy.mockRestore()
+ writeFileSpy.mockRestore()
+ codeExtractSpy.mockRestore()
+ exportClassMapSpy.mockRestore()
+ exportFileMapSpy.mockRestore()
+ exportSheetSpy.mockRestore()
+ getCssSpy.mockRestore()
+ importClassMapSpy.mockRestore()
+ importFileMapSpy.mockRestore()
+ importSheetSpy.mockRestore()
+ registerThemeSpy.mockRestore()
+ dateNowSpy.mockRestore()
+})
- expect(t.async).toHaveBeenCalled()
- expect(codeExtract).toHaveBeenCalledWith(
- 'index.tsx',
- 'code',
- 'package',
- './cssFile',
- true,
- false,
- true,
- )
- if (options.updatedBaseStyle) {
- await vi.waitFor(() => {
- expect(writeFile).toHaveBeenCalledWith(
- join('cssFile', 'devup-ui.css'),
- 'css',
- 'utf-8',
- )
- })
- } else {
- expect(writeFile).not.toHaveBeenCalledWith(
- join('cssFile', 'devup-ui.css'),
- 'css',
- 'utf-8',
- )
+const waitFor = async (fn: () => void, timeout = 1000) => {
+ const start = performance.now()
+ while (performance.now() - start < timeout) {
+ try {
+ fn()
+ return
+ } catch {
+ await new Promise((r) => setTimeout(r, 10))
}
- await vi.waitFor(() => {
- expect(t.async()).toHaveBeenCalledWith(null, 'code', {})
- expect(writeFile).toHaveBeenCalledWith(
- join('cssFile', 'cssFile'),
- '/* index.tsx 0 */',
- )
- expect(writeFile).toHaveBeenCalledWith('sheetFile', 'sheet')
- expect(writeFile).toHaveBeenCalledWith('classMapFile', 'classMap')
- expect(writeFile).toHaveBeenCalledWith('fileMapFile', 'fileMap')
- })
- })
+ }
+ fn()
+}
+
+describe('devupUILoader', () => {
+ // TEST ORDER MATTERS: First test for each mode initializes that mode
- it('should extract code without css', async () => {
- const { default: devupUILoader } = await import('../loader')
+ // 1. First test for BUILD mode (watch: false) - covers non-watch init (lines 70-74)
+ it('should use default maps in non-watch mode on init', async () => {
+ const asyncCallback = mock()
+ const defaultClassMap = { test: 'classMap' }
+ const defaultFileMap = { test: 'fileMap' }
+ const defaultSheet = { test: 'sheet' }
+ const theme = { colors: { primary: '#000' } }
const t = {
getOptions: () => ({
package: 'package',
cssDir: 'cssFile',
watch: false,
singleCss: true,
- defaultClassMap: {},
- defaultFileMap: {},
- defaultSheet: {},
+ theme,
+ defaultClassMap,
+ defaultFileMap,
+ defaultSheet,
}),
- async: vi.fn().mockReturnValue(vi.fn()),
- resourcePath: 'index.tsx',
- addDependency: vi.fn(),
+ async: mock().mockReturnValue(asyncCallback),
+ resourcePath: 'nowatch-init.tsx',
+ addDependency: mock(),
}
- vi.mocked(codeExtract).mockReturnValue({
+
+ codeExtractSpy.mockReturnValue({
code: 'code',
css: undefined,
- free: vi.fn(),
+ free: mock(),
map: undefined,
cssFile: undefined,
updatedBaseStyle: false,
- [Symbol.dispose]: vi.fn(),
+ [Symbol.dispose]: mock(),
})
- devupUILoader.bind(t as any)(Buffer.from('code'), 'index.tsx')
- expect(t.async).toHaveBeenCalled()
- expect(codeExtract).toHaveBeenCalledWith(
- 'index.tsx',
- 'code',
- 'package',
- './cssFile',
- true,
- false,
- true,
- )
- await vi.waitFor(() => {
- expect(t.async()).toHaveBeenCalledWith(null, 'code', null)
- })
- expect(writeFile).not.toHaveBeenCalledWith('cssFile', 'css', {
- encoding: 'utf-8',
- })
- })
+ devupUILoader.bind(t as any)(Buffer.from('code'), 'nowatch-init.tsx')
- it('should handle error', async () => {
- const { default: devupUILoader } = await import('../loader')
- const t = {
- getOptions: () => ({
- package: 'package',
- cssDir: 'cssFile',
- watch: false,
- singleCss: true,
- defaultClassMap: {},
- defaultFileMap: {},
- defaultSheet: {},
- }),
- async: vi.fn().mockReturnValue(vi.fn()),
- resourcePath: 'index.tsx',
- addDependency: vi.fn(),
- }
- vi.mocked(codeExtract).mockImplementation(() => {
- throw new Error('error')
+ await waitFor(() => {
+ expect(asyncCallback).toHaveBeenCalledWith(null, 'code', null)
})
- devupUILoader.bind(t as any)(Buffer.from('code'), 'index.tsx')
- expect(t.async).toHaveBeenCalled()
- await vi.waitFor(() => {
- expect(t.async()).toHaveBeenCalledWith(new Error('error'))
- })
+ // Verify non-watch init was executed
+ expect(importFileMapSpy).toHaveBeenCalledWith(defaultFileMap)
+ expect(importClassMapSpy).toHaveBeenCalledWith(defaultClassMap)
+ expect(importSheetSpy).toHaveBeenCalledWith(defaultSheet)
+ expect(registerThemeSpy).toHaveBeenCalledWith(theme)
})
- it('should load with date now on watch', async () => {
- const { default: devupUILoader } = await import('../loader')
+ // 2. First test for WATCH mode - covers watch init (lines 57-69) AND css writing (lines 96-112)
+ it('should initialize watch mode and write css files', async () => {
+ existsSyncSpy.mockReturnValue(true)
+ readFileSyncSpy.mockReturnValue(
+ '{"theme": {"colors": {"primary": "#fff"}}}',
+ )
+ exportSheetSpy.mockReturnValue('sheet')
+ exportClassMapSpy.mockReturnValue('classMap')
+ exportFileMapSpy.mockReturnValue('fileMap')
+ getCssSpy.mockReturnValue('base-css')
+
+ const asyncCallback = mock()
const t = {
getOptions: () => ({
package: 'package',
- cssDir: 'cssFile',
+ cssDir: 'cssDir',
sheetFile: 'sheetFile',
classMapFile: 'classMapFile',
fileMapFile: 'fileMapFile',
@@ -196,225 +146,54 @@ describe('devupUILoader', () => {
watch: true,
singleCss: true,
}),
- async: vi.fn().mockReturnValue(vi.fn()),
- resourcePath: 'index.tsx',
- addDependency: vi.fn(),
+ async: mock().mockReturnValue(asyncCallback),
+ resourcePath: 'watch-init.tsx',
+ addDependency: mock(),
}
- vi.mocked(existsSync).mockReturnValue(false)
- vi.mocked(exportSheet).mockReturnValue('sheet')
- vi.mocked(exportClassMap).mockReturnValue('classMap')
- vi.mocked(exportFileMap).mockReturnValue('fileMap')
- vi.mocked(codeExtract).mockReturnValue({
+
+ codeExtractSpy.mockReturnValue({
code: 'code',
css: 'css',
- free: vi.fn(),
- map: undefined,
- cssFile: 'cssFile',
- updatedBaseStyle: false,
- [Symbol.dispose]: vi.fn(),
+ free: mock(),
+ map: '{}',
+ cssFile: 'devup-ui-1.css',
+ updatedBaseStyle: true,
+ [Symbol.dispose]: mock(),
})
- devupUILoader.bind(t as any)(Buffer.from('code'), 'index.tsx')
- expect(t.async).toHaveBeenCalled()
- expect(codeExtract).toHaveBeenCalledWith(
- 'index.tsx',
- 'code',
- 'package',
- './cssFile',
- true,
- false,
- true,
- )
- await vi.waitFor(() => {
- expect(writeFile).toHaveBeenCalledWith(
- join('cssFile', 'cssFile'),
- '/* index.tsx 0 */',
- )
- })
- })
+ devupUILoader.bind(t as any)(Buffer.from('code'), 'watch-init.tsx')
- it('should load with nowatch', async () => {
- const { default: devupUILoader } = await import('../loader')
- const t = {
- getOptions: () => ({
- package: 'package',
- cssDir: './foo',
- watch: false,
- singleCss: true,
- defaultClassMap: {},
- defaultFileMap: {},
- defaultSheet: {},
- }),
- async: vi.fn().mockReturnValue(vi.fn()),
- resourcePath: './foo/index.tsx',
- addDependency: vi.fn(),
- }
- vi.mocked(codeExtract).mockReturnValue({
- code: 'code',
- css: 'css',
- free: vi.fn(),
- map: undefined,
- cssFile: 'cssFile',
- updatedBaseStyle: false,
- [Symbol.dispose]: vi.fn(),
- })
- vi.mocked(relative).mockReturnValue('./foo/index.tsx')
- devupUILoader.bind(t as any)(Buffer.from('code'), '/foo/index.tsx')
- await vi.waitFor(() => {
- expect(t.async()).toHaveBeenCalledWith(null, 'code', null)
+ await waitFor(() => {
+ expect(asyncCallback).toHaveBeenCalledWith(null, 'code', {})
})
- })
- it('should load with theme', async () => {
- const { default: devupUILoader } = await import('../loader')
- const t = {
- getOptions: () => ({
- package: 'package',
- cssDir: 'cssFile',
- watch: false,
- singleCss: true,
- theme: {
- colors: {
- primary: '#000',
- },
- },
- defaultClassMap: {},
- defaultFileMap: {},
- defaultSheet: {},
- }),
- async: vi.fn().mockReturnValue(vi.fn()),
- resourcePath: 'index.tsx',
- addDependency: vi.fn(),
- }
- vi.mocked(registerTheme).mockReturnValueOnce(undefined)
- vi.mocked(codeExtract).mockReturnValue({
- code: 'code',
- css: 'css',
- free: vi.fn(),
- map: undefined,
- cssFile: 'cssFile',
- updatedBaseStyle: false,
- [Symbol.dispose]: vi.fn(),
- })
- devupUILoader.bind(t as any)(Buffer.from('code'), 'index.tsx')
- expect(registerTheme).toHaveBeenCalledWith({
- colors: {
- primary: '#000',
- },
- })
- await vi.waitFor(() => {
- expect(t.async()).toHaveBeenCalledWith(null, 'code', null)
- })
- })
- it('should register theme on init', async () => {
- const { default: devupUILoader } = await import('../loader')
- const t = {
- getOptions: () => ({
- package: 'package',
- cssDir: 'cssFile',
- watch: false,
- singleCss: true,
- theme: {
- colors: {
- primary: '#000',
- },
- },
- defaultClassMap: {
- button: 'button',
- },
- defaultFileMap: {
- button: 'button',
- },
- defaultSheet: {
- button: 'button',
- },
- }),
- async: vi.fn().mockReturnValue(vi.fn()),
- resourcePath: 'index.tsx',
- addDependency: vi.fn(),
- }
- vi.mocked(codeExtract).mockReturnValue({
- code: 'code',
- css: 'css',
- free: vi.fn(),
- map: undefined,
- cssFile: undefined,
- updatedBaseStyle: false,
- [Symbol.dispose]: vi.fn(),
- })
- devupUILoader.bind(t as any)(Buffer.from('code'), 'index.tsx')
- expect(registerTheme).toHaveBeenCalledTimes(1)
- expect(importClassMap).toHaveBeenCalledWith({
- button: 'button',
- })
- expect(importFileMap).toHaveBeenCalledWith({
- button: 'button',
- })
- expect(importSheet).toHaveBeenCalledWith({
- button: 'button',
+ // Verify watch mode init was executed
+ expect(existsSyncSpy).toHaveBeenCalledWith('themeFile')
+ expect(registerThemeSpy).toHaveBeenCalledWith({
+ colors: { primary: '#fff' },
})
- devupUILoader.bind(t as any)(Buffer.from('code'), 'index.tsx')
+ // Verify updatedBaseStyle && watch branch (lines 96-100)
+ expect(writeFileSpy).toHaveBeenCalledWith(
+ join('cssDir', 'devup-ui.css'),
+ 'base-css',
+ 'utf-8',
+ )
- expect(registerTheme).toHaveBeenCalledTimes(1)
- expect(importClassMap).toHaveBeenCalledTimes(1)
- expect(importFileMap).toHaveBeenCalledTimes(1)
- expect(importSheet).toHaveBeenCalledTimes(1)
+ // Verify cssFile && watch branch (lines 102-112)
+ expect(writeFileSpy).toHaveBeenCalledWith(
+ join('cssDir', 'devup-ui-1.css'),
+ '/* watch-init.tsx 0 */',
+ )
+ expect(writeFileSpy).toHaveBeenCalledWith('sheetFile', 'sheet')
+ expect(writeFileSpy).toHaveBeenCalledWith('classMapFile', 'classMap')
+ expect(writeFileSpy).toHaveBeenCalledWith('fileMapFile', 'fileMap')
})
- it('should read files when they exist in watch mode', async () => {
- const { default: devupUILoader } = await import('../loader')
- const t = {
- getOptions: () => ({
- package: 'package',
- cssDir: 'cssFile',
- sheetFile: 'sheetFile',
- classMapFile: 'classMapFile',
- fileMapFile: 'fileMapFile',
- themeFile: 'themeFile',
- watch: true,
- singleCss: true,
- }),
- async: vi.fn().mockReturnValue(vi.fn()),
- resourcePath: 'index.tsx',
- addDependency: vi.fn(),
- }
- vi.mocked(existsSync).mockImplementation((path) => {
- return (
- path === 'sheetFile' ||
- path === 'classMapFile' ||
- path === 'fileMapFile' ||
- path === 'themeFile'
- )
- })
- vi.mocked(readFileSync).mockReturnValue('{}')
- vi.mocked(codeExtract).mockReturnValue({
- code: 'code',
- css: 'css',
- free: vi.fn(),
- map: undefined,
- cssFile: undefined,
- updatedBaseStyle: false,
- [Symbol.dispose]: vi.fn(),
- })
- devupUILoader.bind(t as any)(Buffer.from('code'), 'index.tsx')
-
- expect(existsSync).toHaveBeenCalledWith('sheetFile')
- expect(existsSync).toHaveBeenCalledWith('classMapFile')
- expect(existsSync).toHaveBeenCalledWith('fileMapFile')
- expect(existsSync).toHaveBeenCalledWith('themeFile')
- expect(readFileSync).toHaveBeenCalledWith('sheetFile', 'utf-8')
- expect(readFileSync).toHaveBeenCalledWith('classMapFile', 'utf-8')
- expect(readFileSync).toHaveBeenCalledWith('fileMapFile', 'utf-8')
- expect(readFileSync).toHaveBeenCalledWith('themeFile', 'utf-8')
- expect(importSheet).toHaveBeenCalledWith({})
- expect(importClassMap).toHaveBeenCalledWith({})
- expect(importFileMap).toHaveBeenCalledWith({})
- expect(registerTheme).toHaveBeenCalledWith({})
- })
+ // Remaining tests - init already done for both modes
- it('should not read files when they do not exist in watch mode', async () => {
- const { default: devupUILoader } = await import('../loader')
+ it('should extract code without css in watch mode', async () => {
+ const asyncCallback = mock()
const t = {
getOptions: () => ({
package: 'package',
@@ -426,31 +205,28 @@ describe('devupUILoader', () => {
watch: true,
singleCss: true,
}),
- async: vi.fn().mockReturnValue(vi.fn()),
+ async: mock().mockReturnValue(asyncCallback),
resourcePath: 'index.tsx',
- addDependency: vi.fn(),
+ addDependency: mock(),
}
- vi.mocked(existsSync).mockReturnValue(false)
- vi.mocked(codeExtract).mockReturnValue({
+ codeExtractSpy.mockReturnValue({
code: 'code',
- css: 'css',
- free: vi.fn(),
+ css: undefined,
+ free: mock(),
map: undefined,
cssFile: undefined,
updatedBaseStyle: false,
- [Symbol.dispose]: vi.fn(),
+ [Symbol.dispose]: mock(),
})
devupUILoader.bind(t as any)(Buffer.from('code'), 'index.tsx')
- expect(existsSync).toHaveBeenCalledWith('sheetFile')
- expect(existsSync).toHaveBeenCalledWith('classMapFile')
- expect(existsSync).toHaveBeenCalledWith('fileMapFile')
- expect(existsSync).toHaveBeenCalledWith('themeFile')
- expect(readFileSync).not.toHaveBeenCalled()
+ await waitFor(() => {
+ expect(asyncCallback).toHaveBeenCalledWith(null, 'code', null)
+ })
})
- it('should not write base style when watch is false even if updatedBaseStyle is true', async () => {
- const { default: devupUILoader } = await import('../loader')
+ it('should extract code without css in build mode', async () => {
+ const asyncCallback = mock()
const t = {
getOptions: () => ({
package: 'package',
@@ -461,64 +237,64 @@ describe('devupUILoader', () => {
defaultFileMap: {},
defaultSheet: {},
}),
- async: vi.fn().mockReturnValue(vi.fn()),
+ async: mock().mockReturnValue(asyncCallback),
resourcePath: 'index.tsx',
- addDependency: vi.fn(),
+ addDependency: mock(),
}
- vi.mocked(getCss).mockReturnValue('css')
- vi.mocked(codeExtract).mockReturnValue({
+ codeExtractSpy.mockReturnValue({
code: 'code',
- css: 'css',
- free: vi.fn(),
+ css: undefined,
+ free: mock(),
map: undefined,
cssFile: undefined,
- updatedBaseStyle: true,
- [Symbol.dispose]: vi.fn(),
+ updatedBaseStyle: false,
+ [Symbol.dispose]: mock(),
})
devupUILoader.bind(t as any)(Buffer.from('code'), 'index.tsx')
- await vi.waitFor(() => {
- expect(t.async()).toHaveBeenCalledWith(null, 'code', null)
- })
- expect(writeFile).not.toHaveBeenCalledWith(
- join('cssFile', 'devup-ui.css'),
- 'css',
- 'utf-8',
+ expect(codeExtractSpy).toHaveBeenCalledWith(
+ 'index.tsx',
+ 'code',
+ 'package',
+ './cssFile',
+ true,
+ false,
+ true,
)
+ await waitFor(() => {
+ expect(asyncCallback).toHaveBeenCalledWith(null, 'code', null)
+ })
})
- it('should handle promises in error case', async () => {
- const { default: devupUILoader } = await import('../loader')
+ it('should handle error in build mode', async () => {
+ const asyncCallback = mock()
const t = {
getOptions: () => ({
package: 'package',
cssDir: 'cssFile',
- sheetFile: 'sheetFile',
- classMapFile: 'classMapFile',
- fileMapFile: 'fileMapFile',
- themeFile: 'themeFile',
- watch: true,
+ watch: false,
singleCss: true,
+ defaultClassMap: {},
+ defaultFileMap: {},
+ defaultSheet: {},
}),
- async: vi.fn().mockReturnValue(vi.fn()),
+ async: mock().mockReturnValue(asyncCallback),
resourcePath: 'index.tsx',
- addDependency: vi.fn(),
+ addDependency: mock(),
}
- vi.mocked(existsSync).mockReturnValue(true)
- vi.mocked(readFileSync).mockReturnValue('{}')
- vi.mocked(codeExtract).mockImplementation(() => {
+ codeExtractSpy.mockImplementation(() => {
throw new Error('error')
})
devupUILoader.bind(t as any)(Buffer.from('code'), 'index.tsx')
- expect(t.async).toHaveBeenCalled()
- await vi.waitFor(() => {
- expect(t.async()).toHaveBeenCalledWith(new Error('error'))
+ await waitFor(() => {
+ expect(asyncCallback).toHaveBeenCalledWith(new Error('error'))
})
})
- it('should read themeFile and register theme when theme property exists', async () => {
- const { default: devupUILoader } = await import('../loader')
+ it('should handle error in watch mode', async () => {
+ const asyncCallback = mock()
+ const consoleErrorSpy = spyOn(console, 'error').mockImplementation(() => {})
const t = {
getOptions: () => ({
package: 'package',
@@ -530,118 +306,85 @@ describe('devupUILoader', () => {
watch: true,
singleCss: true,
}),
- async: vi.fn().mockReturnValue(vi.fn()),
- resourcePath: 'index.tsx',
- addDependency: vi.fn(),
- }
- const themeData = {
- theme: {
- colors: {
- primary: '#000',
- secondary: '#fff',
- },
- },
+ async: mock().mockReturnValue(asyncCallback),
+ resourcePath: 'error-test.tsx',
+ addDependency: mock(),
}
- vi.mocked(existsSync).mockImplementation((path) => path === 'themeFile')
- vi.mocked(readFileSync).mockReturnValue(JSON.stringify(themeData))
- vi.mocked(codeExtract).mockReturnValue({
- code: 'code',
- css: 'css',
- free: vi.fn(),
- map: undefined,
- cssFile: undefined,
- updatedBaseStyle: false,
- [Symbol.dispose]: vi.fn(),
+
+ codeExtractSpy.mockImplementation(() => {
+ throw new Error('extraction error')
})
- devupUILoader.bind(t as any)(Buffer.from('code'), 'index.tsx')
- expect(existsSync).toHaveBeenCalledWith('themeFile')
- expect(readFileSync).toHaveBeenCalledWith('themeFile', 'utf-8')
- expect(registerTheme).toHaveBeenCalledWith({
- colors: {
- primary: '#000',
- secondary: '#fff',
- },
+ devupUILoader.bind(t as any)(Buffer.from('code'), 'error-test.tsx')
+
+ await waitFor(() => {
+ expect(asyncCallback).toHaveBeenCalledWith(expect.any(Error))
})
+ consoleErrorSpy.mockRestore()
})
- it('should read themeFile and use empty object when theme property does not exist', async () => {
- const { default: devupUILoader } = await import('../loader')
+ it('should use correct relative css path', async () => {
+ const asyncCallback = mock()
const t = {
getOptions: () => ({
package: 'package',
- cssDir: 'cssFile',
- sheetFile: 'sheetFile',
- classMapFile: 'classMapFile',
- fileMapFile: 'fileMapFile',
- themeFile: 'themeFile',
- watch: true,
+ cssDir: './foo',
+ watch: false,
singleCss: true,
+ defaultClassMap: {},
+ defaultFileMap: {},
+ defaultSheet: {},
}),
- async: vi.fn().mockReturnValue(vi.fn()),
- resourcePath: 'index.tsx',
- addDependency: vi.fn(),
- }
- const themeDataWithoutTheme = {
- otherProperty: 'value',
+ async: mock().mockReturnValue(asyncCallback),
+ resourcePath: './foo/index.tsx',
+ addDependency: mock(),
}
- vi.mocked(existsSync).mockImplementation((path) => path === 'themeFile')
- vi.mocked(readFileSync).mockReturnValue(
- JSON.stringify(themeDataWithoutTheme),
- )
- vi.mocked(codeExtract).mockReturnValue({
+ codeExtractSpy.mockReturnValue({
code: 'code',
css: 'css',
- free: vi.fn(),
+ free: mock(),
map: undefined,
- cssFile: undefined,
+ cssFile: 'cssFile',
updatedBaseStyle: false,
- [Symbol.dispose]: vi.fn(),
+ [Symbol.dispose]: mock(),
+ })
+ devupUILoader.bind(t as any)(Buffer.from('code'), '/foo/index.tsx')
+ await waitFor(() => {
+ expect(asyncCallback).toHaveBeenCalledWith(null, 'code', null)
})
- devupUILoader.bind(t as any)(Buffer.from('code'), 'index.tsx')
-
- expect(existsSync).toHaveBeenCalledWith('themeFile')
- expect(readFileSync).toHaveBeenCalledWith('themeFile', 'utf-8')
- expect(registerTheme).toHaveBeenCalledWith({})
})
- it('should read themeFile and use empty object when theme property is null', async () => {
- const { default: devupUILoader } = await import('../loader')
+ it('should not write css files in build mode even with cssFile', async () => {
+ const asyncCallback = mock()
const t = {
getOptions: () => ({
package: 'package',
cssDir: 'cssFile',
- sheetFile: 'sheetFile',
- classMapFile: 'classMapFile',
- fileMapFile: 'fileMapFile',
- themeFile: 'themeFile',
- watch: true,
+ watch: false,
singleCss: true,
+ defaultClassMap: {},
+ defaultFileMap: {},
+ defaultSheet: {},
}),
- async: vi.fn().mockReturnValue(vi.fn()),
+ async: mock().mockReturnValue(asyncCallback),
resourcePath: 'index.tsx',
- addDependency: vi.fn(),
+ addDependency: mock(),
}
- const themeDataWithNullTheme = {
- theme: null,
- }
- vi.mocked(existsSync).mockImplementation((path) => path === 'themeFile')
- vi.mocked(readFileSync).mockReturnValue(
- JSON.stringify(themeDataWithNullTheme),
- )
- vi.mocked(codeExtract).mockReturnValue({
+ codeExtractSpy.mockReturnValue({
code: 'code',
css: 'css',
- free: vi.fn(),
- map: undefined,
- cssFile: undefined,
- updatedBaseStyle: false,
- [Symbol.dispose]: vi.fn(),
+ free: mock(),
+ map: '{}',
+ cssFile: 'cssFile',
+ updatedBaseStyle: true,
+ [Symbol.dispose]: mock(),
})
devupUILoader.bind(t as any)(Buffer.from('code'), 'index.tsx')
- expect(existsSync).toHaveBeenCalledWith('themeFile')
- expect(readFileSync).toHaveBeenCalledWith('themeFile', 'utf-8')
- expect(registerTheme).toHaveBeenCalledWith({})
+ await waitFor(() => {
+ expect(asyncCallback).toHaveBeenCalledWith(null, 'code', {})
+ })
+ // In build mode (watch=false), no CSS files should be written
+ expect(writeFileSpy).not.toHaveBeenCalled()
})
})
diff --git a/packages/next-plugin/src/__tests__/plugin.test.ts b/packages/next-plugin/src/__tests__/plugin.test.ts
index 86b2b3a9..363e394a 100644
--- a/packages/next-plugin/src/__tests__/plugin.test.ts
+++ b/packages/next-plugin/src/__tests__/plugin.test.ts
@@ -1,23 +1,51 @@
-import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
+import * as fs from 'node:fs'
import { join, resolve } from 'node:path'
-import { getDefaultTheme, getThemeInterface, setPrefix } from '@devup-ui/wasm'
-import { DevupUIWebpackPlugin } from '@devup-ui/webpack-plugin'
+import * as wasm from '@devup-ui/wasm'
+import * as webpackPluginModule from '@devup-ui/webpack-plugin'
+import {
+ afterEach,
+ beforeEach,
+ describe,
+ expect,
+ it,
+ mock,
+ spyOn,
+} from 'bun:test'
import { DevupUI } from '../plugin'
-import { preload } from '../preload'
+import * as preloadModule from '../preload'
-vi.mock('@devup-ui/webpack-plugin')
-vi.mock('node:fs')
-vi.mock('../preload')
-vi.mock('@devup-ui/wasm', async (original) => ({
- ...(await original()),
- registerTheme: vi.fn(),
- getThemeInterface: vi.fn(),
- getDefaultTheme: vi.fn(),
- getCss: vi.fn(() => ''),
- setPrefix: vi.fn(),
- exportSheet: vi.fn(() =>
+let existsSyncSpy: ReturnType
+let mkdirSyncSpy: ReturnType
+let readFileSyncSpy: ReturnType
+let writeFileSyncSpy: ReturnType
+let getDefaultThemeSpy: ReturnType
+let getThemeInterfaceSpy: ReturnType
+let setPrefixSpy: ReturnType
+let registerThemeSpy: ReturnType
+let getCssSpy: ReturnType
+let exportSheetSpy: ReturnType
+let exportClassMapSpy: ReturnType
+let exportFileMapSpy: ReturnType
+let devupUIWebpackPluginSpy: ReturnType
+let preloadSpy: ReturnType
+
+let originalEnv: NodeJS.ProcessEnv
+let originalFetch: typeof global.fetch
+let originalDebugPort: number
+
+beforeEach(() => {
+ existsSyncSpy = spyOn(fs, 'existsSync').mockReturnValue(false)
+ mkdirSyncSpy = spyOn(fs, 'mkdirSync').mockReturnValue('' as any)
+ readFileSyncSpy = spyOn(fs, 'readFileSync').mockReturnValue('{}')
+ writeFileSyncSpy = spyOn(fs, 'writeFileSync').mockReturnValue(undefined)
+ getDefaultThemeSpy = spyOn(wasm, 'getDefaultTheme').mockReturnValue(undefined)
+ getThemeInterfaceSpy = spyOn(wasm, 'getThemeInterface').mockReturnValue('')
+ setPrefixSpy = spyOn(wasm, 'setPrefix').mockReturnValue(undefined)
+ registerThemeSpy = spyOn(wasm, 'registerTheme').mockReturnValue(undefined)
+ getCssSpy = spyOn(wasm, 'getCss').mockReturnValue('')
+ exportSheetSpy = spyOn(wasm, 'exportSheet').mockReturnValue(
JSON.stringify({
css: {},
font_faces: {},
@@ -26,10 +54,44 @@ vi.mock('@devup-ui/wasm', async (original) => ({
keyframes: {},
properties: {},
}),
- ),
- exportClassMap: vi.fn(() => JSON.stringify({})),
- exportFileMap: vi.fn(() => JSON.stringify({})),
-}))
+ )
+ exportClassMapSpy = spyOn(wasm, 'exportClassMap').mockReturnValue(
+ JSON.stringify({}),
+ )
+ exportFileMapSpy = spyOn(wasm, 'exportFileMap').mockReturnValue(
+ JSON.stringify({}),
+ )
+ devupUIWebpackPluginSpy = spyOn(
+ webpackPluginModule,
+ 'DevupUIWebpackPlugin',
+ ).mockImplementation(mock() as never)
+ preloadSpy = spyOn(preloadModule, 'preload').mockReturnValue(undefined)
+
+ originalEnv = { ...process.env }
+ originalFetch = global.fetch
+ originalDebugPort = process.debugPort
+ global.fetch = mock(() => Promise.resolve({} as Response)) as any
+})
+
+afterEach(() => {
+ process.env = originalEnv
+ global.fetch = originalFetch
+ process.debugPort = originalDebugPort
+ existsSyncSpy.mockRestore()
+ mkdirSyncSpy.mockRestore()
+ readFileSyncSpy.mockRestore()
+ writeFileSyncSpy.mockRestore()
+ getDefaultThemeSpy.mockRestore()
+ getThemeInterfaceSpy.mockRestore()
+ setPrefixSpy.mockRestore()
+ registerThemeSpy.mockRestore()
+ getCssSpy.mockRestore()
+ exportSheetSpy.mockRestore()
+ exportClassMapSpy.mockRestore()
+ exportFileMapSpy.mockRestore()
+ devupUIWebpackPluginSpy.mockRestore()
+ preloadSpy.mockRestore()
+})
describe('DevupUINextPlugin', () => {
describe('webpack', () => {
@@ -38,7 +100,7 @@ describe('DevupUINextPlugin', () => {
ret.webpack!({ plugins: [] }, { buildId: 'tmpBuildId' } as any)
- expect(DevupUIWebpackPlugin).toHaveBeenCalledWith({
+ expect(devupUIWebpackPluginSpy).toHaveBeenCalledWith({
cssDir: resolve('.next/cache', 'devup-ui_tmpBuildId'),
})
})
@@ -48,7 +110,7 @@ describe('DevupUINextPlugin', () => {
ret.webpack!({ plugins: [] }, { buildId: 'tmpBuildId', dev: true } as any)
- expect(DevupUIWebpackPlugin).toHaveBeenCalledWith({
+ expect(devupUIWebpackPluginSpy).toHaveBeenCalledWith({
cssDir: resolve('df', 'devup-ui_tmpBuildId'),
watch: true,
})
@@ -64,14 +126,14 @@ describe('DevupUINextPlugin', () => {
ret.webpack!({ plugins: [] }, { buildId: 'tmpBuildId' } as any)
- expect(DevupUIWebpackPlugin).toHaveBeenCalledWith({
+ expect(devupUIWebpackPluginSpy).toHaveBeenCalledWith({
package: 'new-package',
cssDir: resolve('.next/cache', 'devup-ui_tmpBuildId'),
})
})
it('should apply webpack plugin with webpack obj', async () => {
- const webpack = vi.fn()
+ const webpack = mock()
const ret = DevupUI(
{
webpack,
@@ -83,7 +145,7 @@ describe('DevupUINextPlugin', () => {
ret.webpack!({ plugins: [] }, { buildId: 'tmpBuildId' } as any)
- expect(DevupUIWebpackPlugin).toHaveBeenCalledWith({
+ expect(devupUIWebpackPluginSpy).toHaveBeenCalledWith({
package: 'new-package',
cssDir: resolve('.next/cache', 'devup-ui_tmpBuildId'),
})
@@ -91,18 +153,9 @@ describe('DevupUINextPlugin', () => {
})
})
describe('turbo', () => {
- beforeEach(() => {
- // Mock fetch globally to prevent "http://localhost:undefined" errors
- global.fetch = vi.fn(() => Promise.resolve({} as Response))
- })
-
- afterEach(() => {
- vi.restoreAllMocks()
- })
-
it('should apply turbo config', async () => {
- vi.stubEnv('TURBOPACK', '1')
- vi.mocked(existsSync)
+ process.env.TURBOPACK = '1'
+ existsSyncSpy
.mockReturnValueOnce(true)
.mockReturnValueOnce(true)
.mockReturnValueOnce(true)
@@ -165,10 +218,10 @@ describe('DevupUINextPlugin', () => {
})
})
it('should apply turbo config with create df', async () => {
- vi.stubEnv('TURBOPACK', '1')
- vi.mocked(existsSync).mockReturnValue(false)
- vi.mocked(mkdirSync).mockReturnValue('')
- vi.mocked(writeFileSync).mockReturnValue()
+ process.env.TURBOPACK = '1'
+ existsSyncSpy.mockReturnValue(false)
+ mkdirSyncSpy.mockReturnValue('')
+ writeFileSyncSpy.mockReturnValue(undefined)
const ret = DevupUI({})
expect(ret).toEqual({
@@ -225,19 +278,20 @@ describe('DevupUINextPlugin', () => {
},
},
})
- expect(mkdirSync).toHaveBeenCalledWith('df', {
+ expect(mkdirSyncSpy).toHaveBeenCalledWith('df', {
recursive: true,
})
- expect(writeFileSync).toHaveBeenCalledWith(join('df', '.gitignore'), '*')
+ expect(writeFileSyncSpy).toHaveBeenCalledWith(
+ join('df', '.gitignore'),
+ '*',
+ )
})
it('should apply turbo config with exists df and devup.json', async () => {
- vi.stubEnv('TURBOPACK', '1')
- vi.mocked(existsSync).mockReturnValue(true)
- vi.mocked(readFileSync).mockReturnValue(
- JSON.stringify({ theme: 'theme' }),
- )
- vi.mocked(mkdirSync).mockReturnValue('')
- vi.mocked(writeFileSync).mockReturnValue()
+ process.env.TURBOPACK = '1'
+ existsSyncSpy.mockReturnValue(true)
+ readFileSyncSpy.mockReturnValue(JSON.stringify({ theme: 'theme' }))
+ mkdirSyncSpy.mockReturnValue('')
+ writeFileSyncSpy.mockReturnValue(undefined)
const ret = DevupUI({})
expect(ret).toEqual({
@@ -294,22 +348,25 @@ describe('DevupUINextPlugin', () => {
},
},
})
- expect(mkdirSync).toHaveBeenCalledWith('df', {
- recursive: true,
- })
- expect(writeFileSync).toHaveBeenCalledWith(join('df', '.gitignore'), '*')
+ // mkdirSync is NOT called when existsSync returns true
+ expect(mkdirSyncSpy).not.toHaveBeenCalled()
+ // gitignore is also NOT written when it exists
+ expect(writeFileSyncSpy).not.toHaveBeenCalledWith(
+ join('df', '.gitignore'),
+ '*',
+ )
})
it('should throw error if NODE_ENV is production', () => {
- vi.stubEnv('NODE_ENV', 'production')
- vi.stubEnv('TURBOPACK', '1')
- vi.mocked(preload).mockReturnValue()
+ ;(process.env as any).NODE_ENV = 'production'
+ process.env.TURBOPACK = '1'
+ preloadSpy.mockReturnValue(undefined)
const ret = DevupUI({})
expect(ret).toEqual({
turbopack: {
rules: expect.any(Object),
},
})
- expect(preload).toHaveBeenCalledWith(
+ expect(preloadSpy).toHaveBeenCalledWith(
new RegExp(
`(node_modules(?!.*(${['@devup-ui']
.join('|')
@@ -322,33 +379,29 @@ describe('DevupUINextPlugin', () => {
)
})
it('should create theme.d.ts file', async () => {
- vi.stubEnv('TURBOPACK', '1')
- vi.mocked(existsSync).mockReturnValue(true)
- vi.mocked(getThemeInterface).mockReturnValue('interface code')
- vi.mocked(readFileSync).mockReturnValue(
- JSON.stringify({ theme: 'theme' }),
- )
- vi.mocked(mkdirSync).mockReturnValue('')
- vi.mocked(writeFileSync).mockReturnValue()
+ process.env.TURBOPACK = '1'
+ existsSyncSpy.mockReturnValue(true)
+ getThemeInterfaceSpy.mockReturnValue('interface code')
+ readFileSyncSpy.mockReturnValue(JSON.stringify({ theme: 'theme' }))
+ mkdirSyncSpy.mockReturnValue('')
+ writeFileSyncSpy.mockReturnValue(undefined)
DevupUI({})
- expect(writeFileSync).toHaveBeenCalledWith(
+ expect(writeFileSyncSpy).toHaveBeenCalledWith(
join('df', 'theme.d.ts'),
'interface code',
)
- expect(mkdirSync).toHaveBeenCalledWith('df', {
- recursive: true,
- })
- expect(writeFileSync).toHaveBeenCalledWith(join('df', '.gitignore'), '*')
+ // mkdirSync is NOT called when existsSync returns true
+ expect(mkdirSyncSpy).not.toHaveBeenCalled()
})
it('should set DEVUP_UI_DEFAULT_THEME when getDefaultTheme returns a value', async () => {
- vi.stubEnv('TURBOPACK', '1')
- vi.stubEnv('DEVUP_UI_DEFAULT_THEME', '')
- vi.mocked(existsSync)
+ process.env.TURBOPACK = '1'
+ process.env.DEVUP_UI_DEFAULT_THEME = ''
+ existsSyncSpy
.mockReturnValueOnce(true)
.mockReturnValueOnce(true)
.mockReturnValueOnce(true)
.mockReturnValueOnce(false)
- vi.mocked(getDefaultTheme).mockReturnValue('dark')
+ getDefaultThemeSpy.mockReturnValue('dark')
const config: any = {}
const ret = DevupUI(config)
@@ -361,14 +414,14 @@ describe('DevupUINextPlugin', () => {
})
})
it('should not set DEVUP_UI_DEFAULT_THEME when getDefaultTheme returns undefined', async () => {
- vi.stubEnv('TURBOPACK', '1')
- vi.stubEnv('DEVUP_UI_DEFAULT_THEME', '')
- vi.mocked(existsSync)
+ process.env.TURBOPACK = '1'
+ process.env.DEVUP_UI_DEFAULT_THEME = ''
+ existsSyncSpy
.mockReturnValueOnce(true)
.mockReturnValueOnce(true)
.mockReturnValueOnce(true)
.mockReturnValueOnce(false)
- vi.mocked(getDefaultTheme).mockReturnValue(undefined)
+ getDefaultThemeSpy.mockReturnValue(undefined)
const config: any = {}
const ret = DevupUI(config)
@@ -377,14 +430,14 @@ describe('DevupUINextPlugin', () => {
expect(config.env).toBeUndefined()
})
it('should set DEVUP_UI_DEFAULT_THEME and preserve existing env vars', async () => {
- vi.stubEnv('TURBOPACK', '1')
- vi.stubEnv('DEVUP_UI_DEFAULT_THEME', '')
- vi.mocked(existsSync)
+ process.env.TURBOPACK = '1'
+ process.env.DEVUP_UI_DEFAULT_THEME = ''
+ existsSyncSpy
.mockReturnValueOnce(true)
.mockReturnValueOnce(true)
.mockReturnValueOnce(true)
.mockReturnValueOnce(false)
- vi.mocked(getDefaultTheme).mockReturnValue('light')
+ getDefaultThemeSpy.mockReturnValue('light')
const config: any = {
env: {
CUSTOM_VAR: 'value',
@@ -403,38 +456,36 @@ describe('DevupUINextPlugin', () => {
})
})
it('should call setPrefix when prefix option is provided', async () => {
- vi.stubEnv('TURBOPACK', '1')
- vi.mocked(existsSync)
+ process.env.TURBOPACK = '1'
+ existsSyncSpy
.mockReturnValueOnce(true)
.mockReturnValueOnce(true)
.mockReturnValueOnce(true)
.mockReturnValueOnce(false)
DevupUI({}, { prefix: 'my-prefix' })
- expect(setPrefix).toHaveBeenCalledWith('my-prefix')
+ expect(setPrefixSpy).toHaveBeenCalledWith('my-prefix')
})
it('should handle debugPort fetch failure in development mode', async () => {
- vi.stubEnv('TURBOPACK', '1')
- vi.stubEnv('NODE_ENV', 'development')
- vi.stubEnv('PORT', '3000')
- vi.mocked(existsSync)
+ process.env.TURBOPACK = '1'
+ ;(process.env as any).NODE_ENV = 'development'
+ process.env.PORT = '3000'
+ existsSyncSpy
.mockReturnValueOnce(true)
.mockReturnValueOnce(true)
.mockReturnValueOnce(true)
.mockReturnValueOnce(false)
- vi.mocked(writeFileSync).mockReturnValue()
+ writeFileSyncSpy.mockReturnValue(undefined)
// Mock process.exit to prevent actual exit
const originalExit = process.exit
- const exitSpy = vi.fn()
+ const exitSpy = mock()
process.exit = exitSpy as any
// Mock process.debugPort
- const originalDebugPort = process.debugPort
process.debugPort = 9229
// Mock fetch globally before calling DevupUI
- const originalFetch = global.fetch
- const fetchMock = vi.fn((url: string | URL) => {
+ const fetchMock = mock((url: string | URL) => {
const urlString = typeof url === 'string' ? url : url.toString()
if (urlString.includes('9229')) {
return Promise.reject(new Error('Connection refused'))
@@ -443,24 +494,17 @@ describe('DevupUINextPlugin', () => {
})
global.fetch = fetchMock as any
- // Use fake timers to control setTimeout
- vi.useFakeTimers()
-
try {
DevupUI({})
- // Wait for the fetch promise to reject (this triggers the catch handler)
- // The catch handler sets up a setTimeout, so we need to wait for that
- await vi.runAllTimersAsync()
+ // Wait for the fetch promise to reject and setTimeout to fire (500ms in plugin.ts + buffer)
+ await new Promise((resolve) => setTimeout(resolve, 600))
// Verify process.exit was called with code 77
expect(exitSpy).toHaveBeenCalledWith(77)
} finally {
// Restore
- vi.useRealTimers()
- global.fetch = originalFetch
process.exit = originalExit
- process.debugPort = originalDebugPort
}
})
})
diff --git a/packages/next-plugin/src/__tests__/preload.test.ts b/packages/next-plugin/src/__tests__/preload.test.ts
index 66a42008..ab06f18e 100644
--- a/packages/next-plugin/src/__tests__/preload.test.ts
+++ b/packages/next-plugin/src/__tests__/preload.test.ts
@@ -1,336 +1,209 @@
-import { realpathSync, writeFileSync } from 'node:fs'
-import { readFileSync } from 'node:fs'
-import { existsSync } from 'node:fs'
-import { join } from 'node:path'
-
-import { codeExtract, getCss } from '@devup-ui/wasm'
-import { globSync } from 'tinyglobby'
-
-import { findTopPackageRoot } from '../find-top-package-root'
-import { getPackageName } from '../get-package-name'
-import { hasLocalPackage } from '../has-localpackage'
+import * as fs from 'node:fs'
+import * as path from 'node:path'
+
+import * as wasm from '@devup-ui/wasm'
+import {
+ afterEach,
+ beforeEach,
+ describe,
+ expect,
+ it,
+ mock,
+ spyOn,
+} from 'bun:test'
+import * as tinyglobby from 'tinyglobby'
+
+import * as findTopPackageRootModule from '../find-top-package-root'
+import * as getPackageNameModule from '../get-package-name'
+import * as hasLocalPackageModule from '../has-localpackage'
import { preload } from '../preload'
-// Mock dependencies
-vi.mock('node:fs')
-vi.mock('@devup-ui/wasm')
-vi.mock('tinyglobby')
-
-// Mock globSync
-vi.mock('node:fs', () => ({
- readFileSync: vi.fn(),
- writeFileSync: vi.fn(),
- mkdirSync: vi.fn(),
- existsSync: vi.fn(),
- realpathSync: vi.fn().mockReturnValue('src/App.tsx'),
-}))
-
-vi.mock('tinyglobby', () => ({
- globSync: vi.fn(),
-}))
-
-// Mock @devup-ui/wasm
-vi.mock('@devup-ui/wasm', () => ({
- codeExtract: vi.fn(),
- registerTheme: vi.fn(),
- getCss: vi.fn(),
-}))
-
-vi.mock('../find-top-package-root', () => ({
- findTopPackageRoot: vi.fn(),
-}))
-
-vi.mock('../get-package-name', () => ({
- getPackageName: vi.fn(),
-}))
+let readFileSyncSpy: ReturnType
+let writeFileSyncSpy: ReturnType
+let realpathSyncSpy: ReturnType
+let globSyncSpy: ReturnType
+let codeExtractSpy: ReturnType
+let getCssSpy: ReturnType
+let hasLocalPackageSpy: ReturnType
+let findTopPackageRootSpy: ReturnType
+let getPackageNameSpy: ReturnType
+
+beforeEach(() => {
+ readFileSyncSpy = spyOn(fs, 'readFileSync').mockReturnValue('code')
+ writeFileSyncSpy = spyOn(fs, 'writeFileSync').mockReturnValue(undefined)
+ realpathSyncSpy = spyOn(fs, 'realpathSync').mockImplementation(
+ (p) => p as string,
+ )
+ globSyncSpy = spyOn(tinyglobby, 'globSync').mockReturnValue([])
+ codeExtractSpy = spyOn(wasm, 'codeExtract').mockReturnValue({
+ code: 'extracted',
+ css: 'css',
+ cssFile: null,
+ map: null,
+ updatedBaseStyle: false,
+ free: mock(),
+ [Symbol.dispose]: mock(),
+ } as any)
+ getCssSpy = spyOn(wasm, 'getCss').mockReturnValue('global css')
+ hasLocalPackageSpy = spyOn(
+ hasLocalPackageModule,
+ 'hasLocalPackage',
+ ).mockReturnValue(false)
+ findTopPackageRootSpy = spyOn(
+ findTopPackageRootModule,
+ 'findTopPackageRoot',
+ ).mockReturnValue('/root')
+ getPackageNameSpy = spyOn(
+ getPackageNameModule,
+ 'getPackageName',
+ ).mockReturnValue('test-pkg')
+})
-vi.mock('../has-localpackage', () => ({
- hasLocalPackage: vi.fn(),
-}))
+afterEach(() => {
+ readFileSyncSpy.mockRestore()
+ writeFileSyncSpy.mockRestore()
+ realpathSyncSpy.mockRestore()
+ globSyncSpy.mockRestore()
+ codeExtractSpy.mockRestore()
+ getCssSpy.mockRestore()
+ hasLocalPackageSpy.mockRestore()
+ findTopPackageRootSpy.mockRestore()
+ getPackageNameSpy.mockRestore()
+})
describe('preload', () => {
- beforeEach(() => {
- vi.clearAllMocks()
+ it('should process files and write devup-ui.css', () => {
+ globSyncSpy.mockReturnValue(['/project/src/index.tsx'])
- // Default mock implementations
- vi.mocked(globSync).mockReturnValue([
- 'src/App.tsx',
- 'src/components/Button.tsx',
- ])
- vi.mocked(readFileSync).mockReturnValue(
- 'const Button = () => Hello
',
- )
- vi.mocked(codeExtract).mockReturnValue({
- free: vi.fn(),
- cssFile: 'styles.css',
- css: '.button { color: red; }',
- code: '',
- map: '',
- updatedBaseStyle: false,
- [Symbol.dispose]: vi.fn(),
- })
- vi.mocked(existsSync).mockReturnValue(true)
- })
+ preload(/node_modules/, '@devup-ui/react', true, '/css', [], '/project')
- it('should find project root and collect files', () => {
- const excludeRegex = /node_modules/
- const libPackage = '@devup-ui/react'
- const singleCss = false
- const cssDir = '/output/css'
-
- preload(excludeRegex, libPackage, singleCss, cssDir, [])
-
- expect(globSync).toHaveBeenCalledWith(
- ['**/*.tsx', '**/*.ts', '**/*.js', '**/*.mjs'],
- {
- followSymbolicLinks: true,
- absolute: true,
- cwd: expect.any(String),
- },
- )
- })
-
- it('should process each collected file', () => {
- const files = ['src/App.tsx', 'src/components/Button.tsx', '.next/page.tsx']
- vi.mocked(globSync).mockReturnValue(files)
- vi.mocked(realpathSync)
- .mockReturnValueOnce('src/App.tsx')
- .mockReturnValueOnce('src/components/Button.tsx')
- .mockReturnValueOnce('.next/page.tsx')
- preload(/node_modules/, '@devup-ui/react', false, '/output/css', [])
-
- expect(codeExtract).toHaveBeenCalledTimes(2)
- expect(codeExtract).toHaveBeenCalledWith(
- expect.stringMatching(/App\.tsx$/),
- 'const Button = () => Hello
',
- '@devup-ui/react',
- '/output/css',
- false,
- false,
- true,
+ expect(globSyncSpy).toHaveBeenCalled()
+ expect(codeExtractSpy).toHaveBeenCalled()
+ expect(writeFileSyncSpy).toHaveBeenCalledWith(
+ path.join('/css', 'devup-ui.css'),
+ 'global css',
+ 'utf-8',
)
})
- it('should write CSS file when cssFile is returned', () => {
- vi.mocked(codeExtract).mockReturnValue({
- cssFile: 'styles.css',
- css: '.button { color: red; }',
- free: vi.fn(),
- code: '',
- map: '',
- updatedBaseStyle: false,
- [Symbol.dispose]: vi.fn(),
- })
+ it('should skip test files', () => {
+ globSyncSpy.mockReturnValue([
+ '/project/src/index.test.tsx',
+ '/project/src/index.spec.ts',
+ '/project/src/index.test-d.ts',
+ '/project/src/types.d.ts',
+ ])
- preload(/node_modules/, '@devup-ui/react', false, '/output/css', [])
+ preload(/node_modules/, '@devup-ui/react', true, '/css', [], '/project')
- expect(writeFileSync).toHaveBeenCalledWith(
- join('/output/css', 'styles.css'),
- '.button { color: red; }',
- 'utf-8',
- )
+ expect(codeExtractSpy).not.toHaveBeenCalled()
})
- it('should not write CSS file when cssFile is null', () => {
- vi.mocked(codeExtract).mockReturnValue({
- cssFile: undefined,
- css: '.button { color: red; }',
- free: vi.fn(),
- code: '',
- map: '',
- updatedBaseStyle: false,
- [Symbol.dispose]: vi.fn(),
- })
- vi.mocked(getCss).mockReturnValue('')
+ it('should skip out and .next directories', () => {
+ const cwd = process.cwd()
+ realpathSyncSpy.mockImplementation((p) => p as string)
+ globSyncSpy.mockReturnValue([
+ path.join(cwd, 'out/index.tsx'),
+ path.join(cwd, '.next/index.tsx'),
+ ])
- preload(/node_modules/, '@devup-ui/react', false, '/output/css', [])
+ preload(/node_modules/, '@devup-ui/react', true, '/css', [], cwd)
- expect(writeFileSync).toHaveBeenCalledWith(
- join('/output/css', 'devup-ui.css'),
- '',
- 'utf-8',
- )
+ expect(codeExtractSpy).not.toHaveBeenCalled()
})
- it('should handle empty CSS content', () => {
- vi.mocked(codeExtract).mockReturnValue({
- cssFile: 'styles.css',
- css: '',
- free: vi.fn(),
- code: '',
- map: '',
- updatedBaseStyle: false,
- [Symbol.dispose]: vi.fn(),
- })
+ it('should skip files matching exclude regex', () => {
+ globSyncSpy.mockReturnValue(['/project/node_modules/pkg/index.tsx'])
- preload(/node_modules/, '@devup-ui/react', false, '/output/css', [])
+ preload(/node_modules/, '@devup-ui/react', true, '/css', [], '/project')
- expect(writeFileSync).toHaveBeenCalledWith(
- join('/output/css', 'styles.css'),
- '',
- 'utf-8',
- )
+ expect(codeExtractSpy).not.toHaveBeenCalled()
})
- it('should handle undefined CSS content', () => {
- vi.mocked(codeExtract).mockReturnValue({
- cssFile: 'styles.css',
- css: undefined,
- free: vi.fn(),
- code: '',
- map: '',
+ it('should write css file when cssFile is returned', () => {
+ globSyncSpy.mockReturnValue(['/project/src/index.tsx'])
+ codeExtractSpy.mockReturnValue({
+ code: 'extracted',
+ css: 'component css',
+ cssFile: 'devup-ui-1.css',
+ map: null,
updatedBaseStyle: false,
- [Symbol.dispose]: vi.fn(),
- })
+ free: mock(),
+ [Symbol.dispose]: mock(),
+ } as any)
- preload(/node_modules/, '@devup-ui/react', false, '/output/css', [])
+ preload(/node_modules/, '@devup-ui/react', true, '/css', [], '/project')
- expect(writeFileSync).toHaveBeenCalledWith(
- join('/output/css', 'styles.css'),
- '',
+ expect(writeFileSyncSpy).toHaveBeenCalledWith(
+ path.join('/css', 'devup-ui-1.css'),
+ 'component css',
'utf-8',
)
})
- it('should pass correct parameters to codeExtract', () => {
- const libPackage = '@devup-ui/react'
- const singleCss = true
- const cssDir = '/custom/css/dir'
+ it('should process local packages when include has items and hasLocalPackage', () => {
+ hasLocalPackageSpy.mockReturnValue(true)
+ getPackageNameSpy.mockReturnValue('@scope/local-pkg')
- preload(/node_modules/, libPackage, singleCss, cssDir, [])
+ // First call returns package.json paths, second returns source files
+ globSyncSpy
+ .mockReturnValueOnce(['/root/packages/local-pkg/package.json'])
+ .mockReturnValueOnce(['/root/packages/local-pkg/src/index.tsx'])
+ .mockReturnValueOnce(['/project/src/app.tsx'])
- expect(codeExtract).toHaveBeenCalledWith(
- expect.stringMatching(/App\.tsx$/),
- 'const Button = () => Hello
',
- libPackage,
- cssDir,
- singleCss,
- false,
+ preload(
+ /node_modules/,
+ '@devup-ui/react',
true,
+ '/css',
+ ['@scope/local-pkg'],
+ '/project',
)
- })
-
- it('should handle multiple files with different CSS outputs', () => {
- const files = ['src/App.tsx', 'src/components/Button.tsx']
- vi.mocked(globSync).mockReturnValue(files)
-
- vi.mocked(codeExtract)
- .mockReturnValueOnce({
- cssFile: 'app.css',
- css: '.app { margin: 0; }',
- free: vi.fn(),
- code: '',
- map: '',
- updatedBaseStyle: false,
- [Symbol.dispose]: vi.fn(),
- })
- .mockReturnValueOnce({
- free: vi.fn(),
- cssFile: 'button.css',
- css: '.button { color: blue; }',
- code: '',
- map: '',
- updatedBaseStyle: false,
- [Symbol.dispose]: vi.fn(),
- })
-
- preload(/node_modules/, '@devup-ui/react', false, '/output/css', [])
-
- expect(writeFileSync).toHaveBeenCalledTimes(3)
- expect(writeFileSync).toHaveBeenCalledWith(
- join('/output/css', 'app.css'),
- '.app { margin: 0; }',
- 'utf-8',
- )
- expect(writeFileSync).toHaveBeenCalledWith(
- join('/output/css', 'button.css'),
- '.button { color: blue; }',
- 'utf-8',
- )
- })
-
- it('should recurse into local workspaces when include is provided', () => {
- const files = ['src/App.tsx']
- vi.mocked(findTopPackageRoot).mockReturnValue('/repo')
- vi.mocked(hasLocalPackage)
- .mockReturnValueOnce(true)
- .mockReturnValueOnce(false)
- vi.mocked(globSync)
- .mockReturnValueOnce([
- '/repo/packages/pkg-a/package.json',
- '/repo/packages/pkg-b/package.json',
- ])
- .mockReturnValueOnce(files)
- vi.mocked(getPackageName)
- .mockReturnValueOnce('pkg-a')
- .mockReturnValueOnce('pkg-b')
- vi.mocked(realpathSync).mockReturnValueOnce('src/App.tsx')
- preload(/node_modules/, '@devup-ui/react', false, '/output/css', ['pkg-a'])
-
- expect(findTopPackageRoot).toHaveBeenCalled()
- expect(globSync).toHaveBeenCalledWith(
- ['package.json', '!**/node_modules/**'],
- {
- followSymbolicLinks: true,
- absolute: true,
- cwd: '/repo',
- },
+ expect(findTopPackageRootSpy).toHaveBeenCalled()
+ expect(getPackageNameSpy).toHaveBeenCalledWith(
+ '/root/packages/local-pkg/package.json',
)
- expect(codeExtract).toHaveBeenCalledTimes(3)
- expect(realpathSync).toHaveBeenCalledWith('src/App.tsx')
})
- it('should skip test and build outputs based on filters', () => {
- vi.mocked(globSync).mockReturnValue([
- 'src/App.test.tsx',
- '.next/page.tsx',
- 'out/index.js',
- 'src/keep.ts',
- ])
- vi.mocked(realpathSync)
- .mockReturnValueOnce('src/App.test.tsx')
- .mockReturnValueOnce('.next/page.tsx')
- .mockReturnValueOnce('out/index.js')
- .mockReturnValueOnce('src/keep.ts')
-
- preload(/exclude/, '@devup-ui/react', false, '/output/css', [])
+ it('should not process local packages when nested is true', () => {
+ hasLocalPackageSpy.mockReturnValue(true)
+ getPackageNameSpy.mockReturnValue('@scope/local-pkg')
+ globSyncSpy
+ .mockReturnValueOnce(['/root/packages/local-pkg/package.json'])
+ .mockReturnValueOnce([])
- expect(codeExtract).toHaveBeenCalledTimes(1)
- expect(codeExtract).toHaveBeenCalledWith(
- expect.stringMatching(/keep\.ts$/),
- 'const Button = () => Hello
',
+ preload(
+ /node_modules/,
'@devup-ui/react',
- '/output/css',
- false,
- false,
true,
+ '/css',
+ ['@scope/local-pkg'],
+ '/project',
+ true, // nested = true
)
+
+ // Should return early when nested is true after processing local packages
+ expect(writeFileSyncSpy).not.toHaveBeenCalled()
})
- it('should return early when nested is true and include packages exist', () => {
- vi.mocked(findTopPackageRoot).mockReturnValue('/repo')
- vi.mocked(hasLocalPackage).mockReturnValue(true)
- // Return empty array so no recursive calls happen, but include.length > 0 check passes
- vi.mocked(globSync).mockReturnValue([])
- vi.mocked(getPackageName).mockReturnValue('pkg-a')
+ it('should handle empty css value', () => {
+ globSyncSpy.mockReturnValue(['/project/src/index.tsx'])
+ codeExtractSpy.mockReturnValue({
+ code: 'extracted',
+ css: undefined,
+ cssFile: 'devup-ui-1.css',
+ map: null,
+ updatedBaseStyle: false,
+ free: mock(),
+ [Symbol.dispose]: mock(),
+ } as any)
- // Call with nested = true (7th parameter)
- preload(
- /node_modules/,
- '@devup-ui/react',
- false,
- '/output/css',
- ['pkg-a'],
- '/some/path',
- true,
- )
+ preload(/node_modules/, '@devup-ui/react', true, '/css', [], '/project')
- // When nested is true, it should return early after processing includes
- // and not write the final devup-ui.css file
- expect(writeFileSync).not.toHaveBeenCalledWith(
- expect.stringContaining('devup-ui.css'),
- expect.any(String),
+ expect(writeFileSyncSpy).toHaveBeenCalledWith(
+ path.join('/css', 'devup-ui-1.css'),
+ '',
'utf-8',
)
})
diff --git a/packages/next-plugin/src/__tests__/utils.test.ts b/packages/next-plugin/src/__tests__/utils.test.ts
deleted file mode 100644
index ec2ca3dc..00000000
--- a/packages/next-plugin/src/__tests__/utils.test.ts
+++ /dev/null
@@ -1,83 +0,0 @@
-import type { PathLike } from 'node:fs'
-import { join } from 'node:path'
-
-import { describe, expect, it, vi } from 'vitest'
-
-import { findTopPackageRoot } from '../find-top-package-root'
-import { getPackageName } from '../get-package-name'
-import { hasLocalPackage } from '../has-localpackage'
-
-vi.mock('node:fs', () => ({
- existsSync: vi.fn(),
- readFileSync: vi.fn(),
-}))
-
-const { existsSync, readFileSync } = await import('node:fs')
-
-describe('findTopPackageRoot', () => {
- it('returns highest directory containing package.json', () => {
- const root = join('/', 'repo')
- const child = join(root, 'packages', 'pkg')
- vi.mocked(existsSync).mockImplementation((path: PathLike) => {
- if (path === join(root, 'package.json')) return true
- return false
- })
-
- const result = findTopPackageRoot(child)
-
- expect(result).toBe(root)
- })
-
- it('falls back to cwd when no package.json found', () => {
- const cwd = join('/', 'repo', 'packages', 'pkg')
- vi.mocked(existsSync).mockReturnValue(false)
-
- const result = findTopPackageRoot(cwd)
-
- expect(result).toBe(cwd)
- })
-})
-
-describe('hasLocalPackage', () => {
- it('detects workspace dependency', () => {
- vi.mocked(readFileSync).mockReturnValue(
- JSON.stringify({
- dependencies: {
- foo: 'workspace:*',
- bar: '^1.0.0',
- },
- }),
- )
-
- expect(hasLocalPackage()).toBe(true)
- })
-
- it('returns false when no workspace dependency', () => {
- vi.mocked(readFileSync).mockReturnValue(
- JSON.stringify({
- dependencies: {
- foo: '^1.0.0',
- },
- }),
- )
-
- expect(hasLocalPackage()).toBe(false)
- })
-
- it('returns false when dependencies field is missing', () => {
- vi.mocked(readFileSync).mockReturnValue('{}')
-
- expect(hasLocalPackage()).toBe(false)
- })
-})
-
-describe('getPackageName', () => {
- it('reads and returns package name', () => {
- vi.mocked(readFileSync).mockReturnValue(
- JSON.stringify({ name: '@scope/pkg' }),
- )
-
- expect(getPackageName('/path/package.json')).toBe('@scope/pkg')
- expect(readFileSync).toHaveBeenCalledWith('/path/package.json', 'utf-8')
- })
-})
diff --git a/packages/next-plugin/src/loader.ts b/packages/next-plugin/src/loader.ts
index fc26d020..59e8a124 100644
--- a/packages/next-plugin/src/loader.ts
+++ b/packages/next-plugin/src/loader.ts
@@ -1,122 +1,124 @@
-import { existsSync, readFileSync } from 'node:fs'
-import { writeFile } from 'node:fs/promises'
-import { basename, dirname, join, relative } from 'node:path'
-
-import {
- codeExtract,
- exportClassMap,
- exportFileMap,
- exportSheet,
- getCss,
- importClassMap,
- importFileMap,
- importSheet,
- registerTheme,
-} from '@devup-ui/wasm'
-import type { RawLoaderDefinitionFunction } from 'webpack'
-
-export interface DevupUILoaderOptions {
- package: string
- cssDir: string
- sheetFile: string
- classMapFile: string
- fileMapFile: string
- themeFile: string
- watch: boolean
- singleCss: boolean
- // turbo
- theme?: object
- defaultSheet: object
- defaultClassMap: object
- defaultFileMap: object
-}
-let init = false
-
-const devupUILoader: RawLoaderDefinitionFunction =
- function (source) {
- const {
- watch,
- package: libPackage,
- cssDir,
- sheetFile,
- classMapFile,
- fileMapFile,
- themeFile,
- singleCss,
- theme,
- defaultClassMap,
- defaultFileMap,
- defaultSheet,
- } = this.getOptions()
-
- const promises: Promise[] = []
- if (!init) {
- init = true
- if (watch) {
- // restart loader issue
- // loader should read files when they exist in watch mode
- if (existsSync(sheetFile))
- importSheet(JSON.parse(readFileSync(sheetFile, 'utf-8')))
- if (existsSync(classMapFile))
- importClassMap(JSON.parse(readFileSync(classMapFile, 'utf-8')))
- if (existsSync(fileMapFile))
- importFileMap(JSON.parse(readFileSync(fileMapFile, 'utf-8')))
- if (existsSync(themeFile))
- registerTheme(
- JSON.parse(readFileSync(themeFile, 'utf-8'))?.['theme'] ?? {},
- )
- } else {
- importFileMap(defaultFileMap)
- importClassMap(defaultClassMap)
- importSheet(defaultSheet)
- registerTheme(theme)
- }
- }
-
- const callback = this.async()
- try {
- const id = this.resourcePath
- let relCssDir = relative(dirname(id), cssDir).replaceAll('\\', '/')
-
- const relativePath = relative(process.cwd(), id)
-
- if (!relCssDir.startsWith('./')) relCssDir = `./${relCssDir}`
- const { code, map, cssFile, updatedBaseStyle } = codeExtract(
- relativePath,
- source.toString(),
- libPackage,
- relCssDir,
- singleCss,
- false,
- true,
- )
- const sourceMap = map ? JSON.parse(map) : null
- if (updatedBaseStyle && watch) {
- // update base style
- promises.push(
- writeFile(join(cssDir, 'devup-ui.css'), getCss(null, false), 'utf-8'),
- )
- }
- if (cssFile && watch) {
- // don't write file when build
- promises.push(
- writeFile(
- join(cssDir, basename(cssFile!)),
- `/* ${this.resourcePath} ${Date.now()} */`,
- ),
- writeFile(sheetFile, exportSheet()),
- writeFile(classMapFile, exportClassMap()),
- writeFile(fileMapFile, exportFileMap()),
- )
- }
- Promise.all(promises)
- .catch(console.error)
- .finally(() => callback(null, code, sourceMap))
- } catch (error) {
- Promise.all(promises)
- .catch(console.error)
- .finally(() => callback(error as Error))
- }
- return
- }
-export default devupUILoader
+import { existsSync, readFileSync } from 'node:fs'
+import { writeFile } from 'node:fs/promises'
+import { basename, dirname, join, relative } from 'node:path'
+
+import {
+ codeExtract,
+ exportClassMap,
+ exportFileMap,
+ exportSheet,
+ getCss,
+ importClassMap,
+ importFileMap,
+ importSheet,
+ registerTheme,
+} from '@devup-ui/wasm'
+import type { RawLoaderDefinitionFunction } from 'webpack'
+
+export interface DevupUILoaderOptions {
+ package: string
+ cssDir: string
+ sheetFile: string
+ classMapFile: string
+ fileMapFile: string
+ themeFile: string
+ watch: boolean
+ singleCss: boolean
+ // turbo
+ theme?: object
+ defaultSheet: object
+ defaultClassMap: object
+ defaultFileMap: object
+}
+// Track initialization per mode (watch vs non-watch are separate configurations)
+const initialized = { watch: false, build: false }
+
+const devupUILoader: RawLoaderDefinitionFunction =
+ function (source) {
+ const {
+ watch,
+ package: libPackage,
+ cssDir,
+ sheetFile,
+ classMapFile,
+ fileMapFile,
+ themeFile,
+ singleCss,
+ theme,
+ defaultClassMap,
+ defaultFileMap,
+ defaultSheet,
+ } = this.getOptions()
+
+ const promises: Promise[] = []
+ const mode = watch ? 'watch' : 'build'
+ if (!initialized[mode]) {
+ initialized[mode] = true
+ if (watch) {
+ // restart loader issue
+ // loader should read files when they exist in watch mode
+ if (existsSync(sheetFile))
+ importSheet(JSON.parse(readFileSync(sheetFile, 'utf-8')))
+ if (existsSync(classMapFile))
+ importClassMap(JSON.parse(readFileSync(classMapFile, 'utf-8')))
+ if (existsSync(fileMapFile))
+ importFileMap(JSON.parse(readFileSync(fileMapFile, 'utf-8')))
+ if (existsSync(themeFile))
+ registerTheme(
+ JSON.parse(readFileSync(themeFile, 'utf-8'))?.['theme'] ?? {},
+ )
+ } else {
+ importFileMap(defaultFileMap)
+ importClassMap(defaultClassMap)
+ importSheet(defaultSheet)
+ registerTheme(theme)
+ }
+ }
+
+ const callback = this.async()
+ try {
+ const id = this.resourcePath
+ let relCssDir = relative(dirname(id), cssDir).replaceAll('\\', '/')
+
+ const relativePath = relative(process.cwd(), id)
+
+ if (!relCssDir.startsWith('./')) relCssDir = `./${relCssDir}`
+ const { code, map, cssFile, updatedBaseStyle } = codeExtract(
+ relativePath,
+ source.toString(),
+ libPackage,
+ relCssDir,
+ singleCss,
+ false,
+ true,
+ )
+ const sourceMap = map ? JSON.parse(map) : null
+ if (updatedBaseStyle && watch) {
+ // update base style
+ promises.push(
+ writeFile(join(cssDir, 'devup-ui.css'), getCss(null, false), 'utf-8'),
+ )
+ }
+ if (cssFile && watch) {
+ // don't write file when build
+ promises.push(
+ writeFile(
+ join(cssDir, basename(cssFile!)),
+ `/* ${this.resourcePath} ${Date.now()} */`,
+ ),
+ writeFile(sheetFile, exportSheet()),
+ writeFile(classMapFile, exportClassMap()),
+ writeFile(fileMapFile, exportFileMap()),
+ )
+ }
+ Promise.all(promises)
+ .catch(console.error)
+ .finally(() => callback(null, code, sourceMap))
+ } catch (error) {
+ Promise.all(promises)
+ .catch(console.error)
+ .finally(() => callback(error as Error))
+ }
+ return
+ }
+export default devupUILoader
diff --git a/packages/next-plugin/tsconfig.json b/packages/next-plugin/tsconfig.json
index fa3c2eb3..a79a5660 100644
--- a/packages/next-plugin/tsconfig.json
+++ b/packages/next-plugin/tsconfig.json
@@ -1,28 +1,29 @@
-{
- "compilerOptions": {
- "types": ["vite/client", "vitest/importMeta", "vitest/globals"],
- "strict": true,
- "target": "ESNext",
- "declaration": true,
- "declarationMap": true,
- "removeComments": true,
- "sourceMap": true,
- "useDefineForClassFields": true,
- "allowJs": false,
- "skipLibCheck": true,
- "noFallthroughCasesInSwitch": true,
- "esModuleInterop": true,
- "allowSyntheticDefaultImports": true,
- "forceConsistentCasingInFileNames": true,
- "strictFunctionTypes": true,
- "module": "ESNext",
- "moduleResolution": "Bundler",
- "resolveJsonModule": true,
- "isolatedModules": true,
- "noEmit": true,
- "baseUrl": ".",
- "jsx": "react-jsx"
- },
- "include": ["src", "vite.config.ts", "../../vitest.setup.ts"],
- "exclude": ["node_modules", "dist"]
-}
+{
+ "compilerOptions": {
+ "types": ["bun"],
+ "strict": true,
+ "target": "ESNext",
+ "declaration": true,
+ "emitDeclarationOnly": true,
+ "outDir": "dist",
+ "declarationMap": true,
+ "removeComments": true,
+ "sourceMap": true,
+ "useDefineForClassFields": true,
+ "allowJs": false,
+ "skipLibCheck": true,
+ "noFallthroughCasesInSwitch": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "forceConsistentCasingInFileNames": true,
+ "strictFunctionTypes": true,
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "baseUrl": ".",
+ "jsx": "react-jsx"
+ },
+ "include": ["src"],
+ "exclude": ["node_modules", "dist", "src/**/__tests__"]
+}
diff --git a/packages/next-plugin/vite.config.ts b/packages/next-plugin/vite.config.ts
deleted file mode 100644
index b5c621c2..00000000
--- a/packages/next-plugin/vite.config.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-import dts from 'vite-plugin-dts'
-import { defineConfig } from 'vitest/config'
-
-export default defineConfig({
- test: {
- globals: true,
- coverage: {
- provider: 'v8',
- thresholds: {
- '100': true,
- },
- },
- },
- plugins: [
- dts({
- entryRoot: 'src',
- staticImport: true,
- pathsToAliases: false,
- exclude: [
- '**/__tests__/**/*',
- '**/*.test.(tsx|ts|js|jsx)',
- '**/*.test-d.(tsx|ts|js|jsx)',
- 'vite.config.ts',
- ],
- include: ['**/src/**/*.ts'],
- copyDtsFiles: true,
- compilerOptions: {
- isolatedModules: false,
- declaration: true,
- },
- }),
- ],
- build: {
- rollupOptions: {
- onwarn: (warning) => {
- if (warning.code === 'MODULE_LEVEL_DIRECTIVE') {
- return
- }
- },
- external: (source) => {
- return !(source.includes('src') || source.startsWith('.'))
- },
-
- output: {
- dir: 'dist',
- preserveModules: true,
- preserveModulesRoot: 'src',
-
- exports: 'named',
- assetFileNames({ name }) {
- return name?.replace(/^src\//g, '') ?? ''
- },
- },
- },
- lib: {
- formats: ['es', 'cjs'],
- entry: {
- index: 'src/index.ts',
- ['css-loader']: 'src/css-loader.ts',
- loader: 'src/loader.ts',
- },
- },
- outDir: 'dist',
- },
-})
diff --git a/packages/react/package.json b/packages/react/package.json
index 5ec34192..5033fb89 100644
--- a/packages/react/package.json
+++ b/packages/react/package.json
@@ -31,7 +31,8 @@
"exports": {
".": {
"import": "./dist/index.js",
- "require": "./dist/index.cjs"
+ "require": "./dist/index.cjs",
+ "types": "./dist/index.d.ts"
}
},
"files": [
diff --git a/packages/react/src/__tests__/index.test.ts b/packages/react/src/__tests__/index.test.ts
index 94a5f6b2..a9eee4e6 100644
--- a/packages/react/src/__tests__/index.test.ts
+++ b/packages/react/src/__tests__/index.test.ts
@@ -1,3 +1,5 @@
+import { describe, expect, it } from 'bun:test'
+
describe('export', () => {
it('should export components and css', async () => {
const index = await import('../index')
@@ -15,7 +17,7 @@ describe('export', () => {
css: expect.any(Function),
globalCss: expect.any(Function),
keyframes: expect.any(Function),
- styled: expect.any(Function),
+ styled: expect.any(Object),
ThemeScript: expect.any(Function),
diff --git a/packages/react/src/components/__tests__/Box.browser.test.tsx b/packages/react/src/components/__tests__/Box.browser.test.tsx
index 5b323593..3cb86baf 100644
--- a/packages/react/src/components/__tests__/Box.browser.test.tsx
+++ b/packages/react/src/components/__tests__/Box.browser.test.tsx
@@ -1,19 +1,8 @@
-// @ts-nocheck
import { Box } from '@devup-ui/react'
-import { render, waitFor } from '@testing-library/react'
+import { describe, expect, it } from 'bun:test'
describe('Box', () => {
- it('should render', async () => {
- const { container } = render()
-
- await waitFor(
- () => {
- expect(container.children[0]).toHaveClass('background-0-blue--255')
- },
- {
- timeout: 1000,
- interval: 10,
- },
- )
+ it('should render', () => {
+ expect().toHaveClass('background-0-blue--255')
})
})
diff --git a/packages/react/src/components/__tests__/ThemeScript.browser.test.tsx b/packages/react/src/components/__tests__/ThemeScript.browser.test.tsx
index e8af8891..0e67c327 100644
--- a/packages/react/src/components/__tests__/ThemeScript.browser.test.tsx
+++ b/packages/react/src/components/__tests__/ThemeScript.browser.test.tsx
@@ -1,15 +1,13 @@
-import { render } from '@testing-library/react'
-import { expect } from 'vitest'
+import { describe, expect, it } from 'bun:test'
+import { render } from 'bun-test-env-dom'
import { DevupTheme } from '../../types/theme'
import { ThemeScript } from '../ThemeScript'
describe('ThemeScript', () => {
it('should apply ThemeScript', () => {
- vi.stubEnv('DEVUP_UI_DEFAULT_THEME', undefined)
const { container } = render()
expect(container).toMatchSnapshot()
- vi.unstubAllEnvs()
})
it('should apply ThemeScript with theme', () => {
const { container } = render(
diff --git a/packages/react/src/components/__tests__/__snapshots__/ThemeScript.browser.test.tsx.snap b/packages/react/src/components/__tests__/__snapshots__/ThemeScript.browser.test.tsx.snap
index 5d2fbe35..41eb357e 100644
--- a/packages/react/src/components/__tests__/__snapshots__/ThemeScript.browser.test.tsx.snap
+++ b/packages/react/src/components/__tests__/__snapshots__/ThemeScript.browser.test.tsx.snap
@@ -1,25 +1,25 @@
-// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+// Bun Snapshot v1, https://bun.sh/docs/test/snapshots
-exports[`ThemeScript > should apply ThemeScript 1`] = `
-
+exports[`ThemeScript should apply ThemeScript 1`] = `
+"
-
+
"
`;
-exports[`ThemeScript > should apply ThemeScript with not auto 1`] = `
-
+exports[`ThemeScript should apply ThemeScript with theme 1`] = `
+"
-
+
"
`;
-exports[`ThemeScript > should apply ThemeScript with theme 1`] = `
-
+exports[`ThemeScript should apply ThemeScript with not auto 1`] = `
+"
-
+
"
`;
diff --git a/packages/react/src/components/__tests__/index.test.ts b/packages/react/src/components/__tests__/index.test.ts
index 9c0e1d66..7ea2fbac 100644
--- a/packages/react/src/components/__tests__/index.test.ts
+++ b/packages/react/src/components/__tests__/index.test.ts
@@ -1,3 +1,5 @@
+import { describe, expect, it } from 'bun:test'
+
import { Box } from '../Box'
import { Button } from '../Button'
import { Center } from '../Center'
diff --git a/packages/react/src/hooks/__tests__/use-safe-effect.browser.test.ts b/packages/react/src/hooks/__tests__/use-safe-effect.browser.test.ts
index 7fba8f2f..2c2d8847 100644
--- a/packages/react/src/hooks/__tests__/use-safe-effect.browser.test.ts
+++ b/packages/react/src/hooks/__tests__/use-safe-effect.browser.test.ts
@@ -1,3 +1,4 @@
+import { describe, expect, it } from 'bun:test'
import { useLayoutEffect } from 'react'
import { useSafeEffect } from '../use-safe-effect'
diff --git a/packages/react/src/hooks/__tests__/use-safe-effect.test.ts b/packages/react/src/hooks/__tests__/use-safe-effect.test.ts
index f566810d..857c0120 100644
--- a/packages/react/src/hooks/__tests__/use-safe-effect.test.ts
+++ b/packages/react/src/hooks/__tests__/use-safe-effect.test.ts
@@ -1,17 +1,28 @@
+import { afterAll, describe, expect, it } from 'bun:test'
import { useEffect, useLayoutEffect } from 'react'
-beforeEach(() => {
- vi.resetModules()
-})
describe('useSafeEffect', () => {
- it('return useEffect in the server', async () => {
+ const originalWindow = globalThis.window
+
+ afterAll(() => {
+ globalThis.window = originalWindow
+ // Clear module cache
+ Loader.registry.delete(require.resolve('../use-safe-effect'))
+ })
+
+ it('should return useEffect when window is undefined (server)', async () => {
+ // @ts-expect-error - intentionally setting window to undefined
+ globalThis.window = undefined
+ Loader.registry.delete(require.resolve('../use-safe-effect'))
+
const { useSafeEffect } = await import('../use-safe-effect')
- // @ts-ignore
expect(useSafeEffect).toBe(useEffect)
})
- it('return useEffect in the client', async () => {
- // @ts-ignore
+
+ it('should return useLayoutEffect when window is defined (client)', async () => {
+ // @ts-expect-error - intentionally setting window to object
globalThis.window = {}
+ Loader.registry.delete(require.resolve('../use-safe-effect'))
const { useSafeEffect } = await import('../use-safe-effect')
expect(useSafeEffect).toBe(useLayoutEffect)
diff --git a/packages/react/src/hooks/__tests__/use-theme.browser.test.ts b/packages/react/src/hooks/__tests__/use-theme.browser.test.ts
index a37479dc..ee7334d8 100644
--- a/packages/react/src/hooks/__tests__/use-theme.browser.test.ts
+++ b/packages/react/src/hooks/__tests__/use-theme.browser.test.ts
@@ -1,30 +1,44 @@
-import { renderHook, waitFor } from '@testing-library/react'
+import { afterAll, beforeAll, describe, expect, it } from 'bun:test'
+import { act, renderHook } from 'bun-test-env-dom'
-beforeEach(() => {
- vi.resetModules()
+beforeAll(() => {
+ document.documentElement.removeAttribute('data-theme')
+ // Clear module caches for fresh state
+ Loader.registry.delete(require.resolve('../use-theme'))
+ Loader.registry.delete(require.resolve('../../stores/theme-store'))
})
+afterAll(() => {
+ document.documentElement.removeAttribute('data-theme')
+})
+
+// Helper to wait for MutationObserver to process
+const waitForMutationObserver = () =>
+ new Promise((resolve) => setTimeout(resolve, 10))
+
describe('useTheme', () => {
it('should return theme', async () => {
const { useTheme } = await import('../use-theme')
const { result, unmount } = renderHook(() => useTheme())
expect(result.current).toBeNull()
- document.documentElement.setAttribute('data-theme', 'dark')
- await waitFor(() => {
- expect(result.current).toBe('dark')
+ await act(async () => {
+ document.documentElement.setAttribute('data-theme', 'dark')
+ await waitForMutationObserver()
})
+ expect(result.current as string | null).toBe('dark')
+
const { result: newResult, unmount: newUnmount } = renderHook(() =>
useTheme(),
)
- expect(newResult.current).toBe('dark')
+ expect(newResult.current as string | null).toBe('dark')
newUnmount()
unmount()
})
it('should return theme when already set', async () => {
- const { useTheme } = await import('../use-theme')
document.documentElement.setAttribute('data-theme', 'dark')
+ const { useTheme } = await import('../use-theme')
const { result, unmount } = renderHook(() => useTheme())
expect(result.current).toBe('dark')
unmount()
diff --git a/packages/react/src/stores/__tests__/theme-store-ssr.test.ts b/packages/react/src/stores/__tests__/theme-store-ssr.test.ts
new file mode 100644
index 00000000..a0e2f35f
--- /dev/null
+++ b/packages/react/src/stores/__tests__/theme-store-ssr.test.ts
@@ -0,0 +1,40 @@
+import { describe, expect, it } from 'bun:test'
+
+// This test file runs in isolation to test the SSR code path
+// We need to mock window before importing the module
+
+describe('themeStore SSR', () => {
+ it('should return SSR-safe store when window is undefined', async () => {
+ // Save original window
+ const originalWindow = globalThis.window
+
+ // Remove window to simulate SSR
+ // @ts-expect-error - Temporarily remove window for SSR test
+ delete globalThis.window
+
+ // Clear module cache to force fresh import
+ const modulePath = require.resolve('../theme-store')
+ delete require.cache[modulePath]
+
+ try {
+ // Import with window undefined
+ const { createThemeStore } = await import('../theme-store')
+ const themeStore = createThemeStore()
+
+ // Test SSR store behavior
+ expect(themeStore).toBeDefined()
+ expect(themeStore.get()).toBeNull()
+ expect(themeStore.set('dark' as any)).toBeUndefined()
+
+ const unsubscribe = themeStore.subscribe(() => {})
+ expect(typeof unsubscribe).toBe('function')
+
+ // The unsubscribe should return a no-op function
+ const innerUnsubscribe = unsubscribe()
+ expect(innerUnsubscribe).toBeUndefined()
+ } finally {
+ // Restore window
+ globalThis.window = originalWindow
+ }
+ })
+})
diff --git a/packages/react/src/stores/__tests__/theme-store.test.ts b/packages/react/src/stores/__tests__/theme-store.test.ts
index 37c63832..5c02f491 100644
--- a/packages/react/src/stores/__tests__/theme-store.test.ts
+++ b/packages/react/src/stores/__tests__/theme-store.test.ts
@@ -1,10 +1,34 @@
-beforeEach(() => {
- vi.resetModules()
+import {
+ afterAll,
+ afterEach,
+ beforeAll,
+ beforeEach,
+ describe,
+ expect,
+ it,
+ mock,
+} from 'bun:test'
+
+import { createThemeStore } from '../theme-store'
+
+beforeAll(() => {
+ document.documentElement.removeAttribute('data-theme')
+})
+
+afterAll(() => {
+ document.documentElement.removeAttribute('data-theme')
})
describe('themeStore', () => {
- it('should return themeStore object for browser', async () => {
- const { createThemeStore } = await import('../theme-store')
+ beforeEach(() => {
+ document.documentElement.removeAttribute('data-theme')
+ })
+
+ afterEach(() => {
+ document.documentElement.removeAttribute('data-theme')
+ })
+
+ it('should return themeStore object for browser', () => {
const themeStore = createThemeStore()
expect(themeStore).toBeDefined()
expect(themeStore.get).toEqual(expect.any(Function))
@@ -12,8 +36,98 @@ describe('themeStore', () => {
expect(themeStore.subscribe).toEqual(expect.any(Function))
expect(themeStore.get()).toBeNull()
expect(themeStore.set('dark' as any)).toBeUndefined()
- expect(themeStore.subscribe(() => {})()).toBeUndefined()
+ // subscribe returns an unsubscribe function, which returns boolean when called
+ const unsubscribe = themeStore.subscribe(() => {})
+ expect(typeof unsubscribe).toBe('function')
themeStore.subscribe(() => {})
expect(themeStore.set('dark' as any)).toBeUndefined()
})
+
+ it('should call subscriber when theme changes via set', () => {
+ const themeStore = createThemeStore()
+ const callback = mock()
+
+ themeStore.subscribe(callback)
+
+ // First call is from subscribe itself (reads current data-theme)
+ expect(callback).toHaveBeenCalledTimes(1)
+
+ themeStore.set('light' as any)
+ expect(callback).toHaveBeenCalledTimes(2)
+ expect(callback).toHaveBeenLastCalledWith('light')
+ expect(themeStore.get()).toBe('light' as any)
+ })
+
+ it('should unsubscribe correctly', () => {
+ const themeStore = createThemeStore()
+ const callback = mock()
+
+ const unsubscribe = themeStore.subscribe(callback)
+ expect(callback).toHaveBeenCalledTimes(1)
+
+ // Unsubscribe
+ const result = unsubscribe()
+ expect(result).toBe(true as any)
+
+ // Should not be called after unsubscribe
+ themeStore.set('dark' as any)
+ expect(callback).toHaveBeenCalledTimes(1)
+ })
+
+ it('should read initial theme from data-theme attribute', () => {
+ document.documentElement.setAttribute('data-theme', 'dark')
+
+ const themeStore = createThemeStore()
+ const callback = mock()
+
+ themeStore.subscribe(callback)
+
+ // Should be called with 'dark' from the attribute
+ expect(callback).toHaveBeenCalledWith('dark')
+ })
+
+ it('should update theme when data-theme attribute changes via MutationObserver', async () => {
+ const themeStore = createThemeStore()
+ const callback = mock()
+
+ themeStore.subscribe(callback)
+ expect(callback).toHaveBeenCalledTimes(1)
+
+ // Change the attribute - MutationObserver should trigger
+ document.documentElement.setAttribute('data-theme', 'dark')
+
+ // Wait for MutationObserver to fire (it's async)
+ await new Promise((resolve) => setTimeout(resolve, 10))
+
+ expect(callback).toHaveBeenCalledWith('dark')
+ expect(themeStore.get()).toBe('dark' as any)
+ })
+
+ it('should handle multiple subscribers', () => {
+ const themeStore = createThemeStore()
+ const callback1 = mock()
+ const callback2 = mock()
+
+ themeStore.subscribe(callback1)
+ themeStore.subscribe(callback2)
+
+ themeStore.set('light' as any)
+
+ expect(callback1).toHaveBeenLastCalledWith('light')
+ expect(callback2).toHaveBeenLastCalledWith('light')
+ })
+
+ it('should filter mutations by type and target', async () => {
+ const themeStore = createThemeStore()
+ const callback = mock()
+
+ themeStore.subscribe(callback)
+
+ // Change data-theme attribute (should trigger)
+ document.documentElement.setAttribute('data-theme', 'system')
+
+ await new Promise((resolve) => setTimeout(resolve, 10))
+
+ expect(themeStore.get()).toBe('system' as any)
+ })
})
diff --git a/packages/react/src/utils/__tests__/css.test.ts b/packages/react/src/utils/__tests__/css.test.ts
index 0ae0e86f..b7628674 100644
--- a/packages/react/src/utils/__tests__/css.test.ts
+++ b/packages/react/src/utils/__tests__/css.test.ts
@@ -1,3 +1,5 @@
+import { describe, expect, it } from 'bun:test'
+
import { css } from '../css'
describe('css', () => {
diff --git a/packages/react/src/utils/__tests__/get-theme.browser.test.ts b/packages/react/src/utils/__tests__/get-theme.browser.test.ts
index ae36f540..b8f07122 100644
--- a/packages/react/src/utils/__tests__/get-theme.browser.test.ts
+++ b/packages/react/src/utils/__tests__/get-theme.browser.test.ts
@@ -1,3 +1,5 @@
+import { describe, expect, it } from 'bun:test'
+
import { getTheme } from '../get-theme'
describe('getTheme', () => {
diff --git a/packages/react/src/utils/__tests__/global-css.test.ts b/packages/react/src/utils/__tests__/global-css.test.ts
index 97363bde..4c2d8b9b 100644
--- a/packages/react/src/utils/__tests__/global-css.test.ts
+++ b/packages/react/src/utils/__tests__/global-css.test.ts
@@ -1,3 +1,5 @@
+import { describe, expect, it } from 'bun:test'
+
import { globalCss } from '../global-css'
describe('globalCss', () => {
diff --git a/packages/react/src/utils/__tests__/init-theme.browser.test.ts b/packages/react/src/utils/__tests__/init-theme.browser.test.ts
index 6e4e4a5f..6d9ac559 100644
--- a/packages/react/src/utils/__tests__/init-theme.browser.test.ts
+++ b/packages/react/src/utils/__tests__/init-theme.browser.test.ts
@@ -1,7 +1,22 @@
+import { beforeEach } from 'node:test'
+
+import { describe, expect, it, mock, spyOn } from 'bun:test'
+import { afterAll } from 'bun:test'
+
import { initTheme } from '../init-theme'
+afterAll(() => {
+ mock.restore()
+})
+beforeEach(() => {
+ localStorage.removeItem('__DF_THEME_SELECTED__')
+})
+
describe('initTheme', () => {
it('should initialize the theme', () => {
+ spyOn(window, 'matchMedia').mockReturnValue({
+ matches: false,
+ } as MediaQueryList)
initTheme()
expect(document.documentElement.getAttribute('data-theme')).toBe('default')
})
@@ -11,7 +26,7 @@ describe('initTheme', () => {
})
it('should initialize the theme with the default theme', () => {
- vi.spyOn(window, 'matchMedia').mockReturnValue({
+ spyOn(window, 'matchMedia').mockReturnValue({
matches: true,
} as MediaQueryList)
diff --git a/packages/react/src/utils/__tests__/keyframes.test.ts b/packages/react/src/utils/__tests__/keyframes.test.ts
index 46de34dc..bfce3e1b 100644
--- a/packages/react/src/utils/__tests__/keyframes.test.ts
+++ b/packages/react/src/utils/__tests__/keyframes.test.ts
@@ -1,3 +1,5 @@
+import { describe, expect, it } from 'bun:test'
+
import { keyframes } from '../keyframes'
describe('keyframes', () => {
diff --git a/packages/react/src/utils/__tests__/set-theme.browser.test.ts b/packages/react/src/utils/__tests__/set-theme.browser.test.ts
index e5bdfcc1..827e4f0b 100644
--- a/packages/react/src/utils/__tests__/set-theme.browser.test.ts
+++ b/packages/react/src/utils/__tests__/set-theme.browser.test.ts
@@ -1,6 +1,16 @@
+import { afterAll, beforeAll, describe, expect, it } from 'bun:test'
+
import type { DevupTheme } from '../../types/theme'
import { setTheme } from '../set-theme'
+beforeAll(() => {
+ document.documentElement.removeAttribute('data-theme')
+})
+
+afterAll(() => {
+ document.documentElement.removeAttribute('data-theme')
+})
+
describe('setTheme', () => {
it('should set theme', async () => {
expect(document.documentElement.getAttribute('data-theme')).toBe(null)
diff --git a/packages/react/src/utils/__tests__/styled.test.ts b/packages/react/src/utils/__tests__/styled.test.ts
index 29c9a1c5..f3182e52 100644
--- a/packages/react/src/utils/__tests__/styled.test.ts
+++ b/packages/react/src/utils/__tests__/styled.test.ts
@@ -1,9 +1,11 @@
-import { styled } from '../styled'
-
-describe('globalCss', () => {
- it('should return className', () => {
- expect(() => styled.div`virtual-css`).toThrowError(
- 'Cannot run on the runtime',
- )
- })
-})
+import { describe, expect, it } from 'bun:test'
+
+import { styled } from '../styled'
+
+describe('globalCss', () => {
+ it('should return className', () => {
+ expect(() => styled.div`virtual-css`).toThrowError(
+ 'Cannot run on the runtime',
+ )
+ })
+})
diff --git a/packages/react/tsconfig.json b/packages/react/tsconfig.json
index 42005190..a79a5660 100644
--- a/packages/react/tsconfig.json
+++ b/packages/react/tsconfig.json
@@ -1,32 +1,29 @@
-{
- "compilerOptions": {
- "types": [
- "vite/client",
- "vitest/importMeta",
- "vitest/globals",
- "@testing-library/jest-dom",
- "node"
- ],
- "strict": true,
- "target": "ESNext",
- "declaration": true,
- "declarationMap": true,
- "removeComments": true,
- "sourceMap": true,
- "useDefineForClassFields": true,
- "allowJs": false,
- "skipLibCheck": true,
- "noFallthroughCasesInSwitch": true,
- "esModuleInterop": true,
- "allowSyntheticDefaultImports": true,
- "forceConsistentCasingInFileNames": true,
- "strictFunctionTypes": true,
- "module": "ESNext",
- "moduleResolution": "Bundler",
- "resolveJsonModule": true,
- "isolatedModules": true,
- "noEmit": true,
- "baseUrl": ".",
- "jsx": "react-jsx"
- }
-}
+{
+ "compilerOptions": {
+ "types": ["bun"],
+ "strict": true,
+ "target": "ESNext",
+ "declaration": true,
+ "emitDeclarationOnly": true,
+ "outDir": "dist",
+ "declarationMap": true,
+ "removeComments": true,
+ "sourceMap": true,
+ "useDefineForClassFields": true,
+ "allowJs": false,
+ "skipLibCheck": true,
+ "noFallthroughCasesInSwitch": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "forceConsistentCasingInFileNames": true,
+ "strictFunctionTypes": true,
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "baseUrl": ".",
+ "jsx": "react-jsx"
+ },
+ "include": ["src"],
+ "exclude": ["node_modules", "dist", "src/**/__tests__"]
+}
diff --git a/packages/react/vite.config.ts b/packages/react/vite.config.ts
index f64b13e8..c496cdf5 100644
--- a/packages/react/vite.config.ts
+++ b/packages/react/vite.config.ts
@@ -1,17 +1,8 @@
import preserveDirectives from 'rollup-plugin-preserve-directives'
+import { defineConfig } from 'vite'
import dts from 'vite-plugin-dts'
-import { defineConfig } from 'vitest/config'
export default defineConfig({
- test: {
- globals: true,
- coverage: {
- provider: 'v8',
- thresholds: {
- '100': true,
- },
- },
- },
plugins: [
dts({
entryRoot: 'src',
diff --git a/packages/reset-css/package.json b/packages/reset-css/package.json
index 23751e03..3b18447c 100644
--- a/packages/reset-css/package.json
+++ b/packages/reset-css/package.json
@@ -20,18 +20,19 @@
"type": "module",
"scripts": {
"lint": "eslint",
- "build": "tsc && vite build"
+ "build": "tsc && bun build --target node src/index.ts --production --outfile dist/index.cjs --format cjs --packages external && bun build --target node src/index.ts --production --outfile dist/index.mjs --format esm --packages external"
},
"publishConfig": {
"access": "public"
},
"sideEffects": false,
"main": "./dist/index.cjs",
- "module": "./dist/index.js",
+ "module": "./dist/index.mjs",
"exports": {
".": {
- "import": "./dist/index.js",
- "require": "./dist/index.cjs"
+ "import": "./dist/index.mjs",
+ "require": "./dist/index.cjs",
+ "types": "./dist/index.d.ts"
}
},
"files": [
@@ -42,10 +43,7 @@
"@devup-ui/react": "workspace:^"
},
"devDependencies": {
- "typescript": "^5.9",
- "vite": "^7.3",
- "vite-plugin-dts": "^4.5",
- "rollup-plugin-preserve-directives": "^0.4"
+ "typescript": "^5.9"
},
"peerDependencies": {
"@devup-ui/react": "workspace:^"
diff --git a/packages/reset-css/src/__tests__/index.test.ts b/packages/reset-css/src/__tests__/index.test.ts
index e0c9ffdc..fd077b21 100644
--- a/packages/reset-css/src/__tests__/index.test.ts
+++ b/packages/reset-css/src/__tests__/index.test.ts
@@ -1,10 +1,20 @@
-import { resetCss } from '../index'
-vi.mock('@devup-ui/react', () => ({
- globalCss: vi.fn(),
-}))
+import * as reactModule from '@devup-ui/react'
+import { afterAll, beforeAll, describe, expect, it, spyOn } from 'bun:test'
+
+let globalCssSpy: ReturnType
+
+beforeAll(() => {
+ globalCssSpy = spyOn(reactModule, 'globalCss').mockReturnValue(undefined)
+})
+
+afterAll(() => {
+ globalCssSpy.mockRestore()
+})
describe('reset-css', () => {
- it('should be defined', () => {
+ it('should be defined', async () => {
+ // Dynamic import to ensure spy is in place
+ const { resetCss } = await import('../index')
expect(resetCss).toBeInstanceOf(Function)
expect(resetCss()).toBeUndefined()
})
diff --git a/packages/reset-css/src/index.ts b/packages/reset-css/src/index.ts
index 8396b66e..5ead5137 100644
--- a/packages/reset-css/src/index.ts
+++ b/packages/reset-css/src/index.ts
@@ -1,145 +1,145 @@
-import { globalCss } from '@devup-ui/react'
-
-globalCss({
- '*,:after,:before': {
- boxSizing: 'border-box',
- backgroundRepeat: 'no-repeat',
- },
- ':after,:before': {
- textDecoration: 'inherit',
- verticalAlign: 'inherit',
- },
- ':where(:root)': {
- cursor: 'default',
- lineHeight: 1.5,
- overflowWrap: 'break-word',
- tabSize: 4,
- WebkitTapHighlightColor: 'transparent',
- textSizeAdjust: '100%',
- },
- ':where(body)': {
- m: 0,
- },
- ':where(h1)': {
- fontSize: '2em',
- m: '.67em 0',
- },
- ':where(dl,ol,ul) :where(dl,ol,ul)': {
- m: 0,
- },
- ':where(hr)': {
- color: 'inherit',
- h: 0,
- },
- ':where(nav) :where(ol,ul)': {
- listStyleType: 'none',
- p: 0,
- },
- ':where(nav li):before': {
- content: '"\\200B"',
- float: 'left',
- },
- ':where(pre)': {
- fontFamily: 'monospace,monospace',
- fontSize: '1em',
- overflow: 'auto',
- },
- ':where(abbr[title])': {
- textDecoration: 'underline dotted',
- textDecorationStyle: 'dotted',
- textDecorationLine: 'underline',
- },
- ':where(b,strong)': {
- fontWeight: 'bolder',
- },
- ':where(code,kbd,samp)': {
- fontFamily: 'monospace,monospace',
- fontSize: '1em',
- },
- ':where(small)': {
- fontSize: '80%',
- },
- ':where(audio,canvas,iframe,img,svg,video)': {
- verticalAlign: 'middle',
- },
- ':where(iframe)': {
- borderStyle: 'none',
- },
- ':where(svg:not([fill]))': {
- fill: 'currentColor',
- },
- ':where(table)': {
- borderCollapse: 'collapse',
- borderColor: 'inherit',
- textIndent: 0,
- },
- ':where(button,input,select)': {
- m: 0,
- },
- ':where(button,[type=button i],[type=reset i],[type=submit i])': {
- WebkitAppearance: 'button',
- },
- ':where(fieldset)': {
- border: '1px solid #a0a0a0',
- },
- ':where(progress)': {
- verticalAlign: 'baseline',
- },
- ':where(textarea)': {
- m: 0,
- resize: 'vertical',
- },
- ':where([type=search i])': {
- WebkitAppearance: 'textfield',
- outlineOffset: '-2px',
- },
- '::-webkit-inner-spin-button,::-webkit-outer-spin-button': {
- height: 'auto',
- },
- '::-webkit-input-placeholder': {
- color: 'inherit',
- opacity: 0.54,
- },
- '::-webkit-search-decoration': {
- WebkitAppearance: 'none',
- },
- '::-webkit-file-upload-button': {
- WebkitAppearance: 'button',
- font: 'inherit',
- },
- ':where(dialog)': {
- bgColor: 'white',
- border: 'solid',
- color: 'black',
- left: 0,
- m: 'auto',
- p: '1em',
- pos: 'absolute',
- right: 0,
- w: 'fit-content',
- h: 'fit-content',
- },
- ':where(dialog:not([open]))': {
- display: 'none',
- },
- ':where(details>summary:first-of-type)': {
- display: 'list-item',
- },
- ':where([aria-busy=true i])': {
- cursor: 'progress',
- },
- ':where([aria-controls])': {
- cursor: 'pointer',
- },
- ':where([aria-disabled=true i],[disabled])': {
- cursor: 'not-allowed',
- },
- ':where([aria-hidden=false i][hidden])': {
- display: 'initial',
- },
- ':where([aria-hidden=false i][hidden]:not(:focus))': {
- pos: 'absolute',
- },
-})
-
-export function resetCss(): void {}
+import { globalCss } from '@devup-ui/react'
+
+globalCss({
+ '*,:after,:before': {
+ boxSizing: 'border-box',
+ backgroundRepeat: 'no-repeat',
+ },
+ ':after,:before': {
+ textDecoration: 'inherit',
+ verticalAlign: 'inherit',
+ },
+ ':where(:root)': {
+ cursor: 'default',
+ lineHeight: 1.5,
+ overflowWrap: 'break-word',
+ tabSize: 4,
+ WebkitTapHighlightColor: 'transparent',
+ textSizeAdjust: '100%',
+ },
+ ':where(body)': {
+ m: 0,
+ },
+ ':where(h1)': {
+ fontSize: '2em',
+ m: '.67em 0',
+ },
+ ':where(dl,ol,ul) :where(dl,ol,ul)': {
+ m: 0,
+ },
+ ':where(hr)': {
+ color: 'inherit',
+ h: 0,
+ },
+ ':where(nav) :where(ol,ul)': {
+ listStyleType: 'none',
+ p: 0,
+ },
+ ':where(nav li):before': {
+ content: '"\\200B"',
+ float: 'left',
+ },
+ ':where(pre)': {
+ fontFamily: 'monospace,monospace',
+ fontSize: '1em',
+ overflow: 'auto',
+ },
+ ':where(abbr[title])': {
+ textDecoration: 'underline dotted',
+ textDecorationStyle: 'dotted',
+ textDecorationLine: 'underline',
+ },
+ ':where(b,strong)': {
+ fontWeight: 'bolder',
+ },
+ ':where(code,kbd,samp)': {
+ fontFamily: 'monospace,monospace',
+ fontSize: '1em',
+ },
+ ':where(small)': {
+ fontSize: '80%',
+ },
+ ':where(audio,canvas,iframe,img,svg,video)': {
+ verticalAlign: 'middle',
+ },
+ ':where(iframe)': {
+ borderStyle: 'none',
+ },
+ ':where(svg:not([fill]))': {
+ fill: 'currentColor',
+ },
+ ':where(table)': {
+ borderCollapse: 'collapse',
+ borderColor: 'inherit',
+ textIndent: 0,
+ },
+ ':where(button,input,select)': {
+ m: 0,
+ },
+ ':where(button,[type=button i],[type=reset i],[type=submit i])': {
+ WebkitAppearance: 'button',
+ },
+ ':where(fieldset)': {
+ border: '1px solid #a0a0a0',
+ },
+ ':where(progress)': {
+ verticalAlign: 'baseline',
+ },
+ ':where(textarea)': {
+ m: 0,
+ resize: 'vertical',
+ },
+ ':where([type=search i])': {
+ WebkitAppearance: 'textfield',
+ outlineOffset: '-2px',
+ },
+ '::-webkit-inner-spin-button,::-webkit-outer-spin-button': {
+ height: 'auto',
+ },
+ '::-webkit-input-placeholder': {
+ color: 'inherit',
+ opacity: 0.54,
+ },
+ '::-webkit-search-decoration': {
+ WebkitAppearance: 'none',
+ },
+ '::-webkit-file-upload-button': {
+ WebkitAppearance: 'button',
+ font: 'inherit',
+ },
+ ':where(dialog)': {
+ bgColor: 'white',
+ border: 'solid',
+ color: 'black',
+ left: 0,
+ m: 'auto',
+ p: '1em',
+ pos: 'absolute',
+ right: 0,
+ w: 'fit-content',
+ h: 'fit-content',
+ },
+ ':where(dialog:not([open]))': {
+ display: 'none',
+ },
+ ':where(details>summary:first-of-type)': {
+ display: 'list-item',
+ },
+ ':where([aria-busy=true i])': {
+ cursor: 'progress',
+ },
+ ':where([aria-controls])': {
+ cursor: 'pointer',
+ },
+ ':where([aria-disabled=true i],[disabled])': {
+ cursor: 'not-allowed',
+ },
+ ':where([aria-hidden=false i][hidden])': {
+ display: 'initial',
+ },
+ ':where([aria-hidden=false i][hidden]:not(:focus))': {
+ pos: 'absolute',
+ },
+})
+
+export function resetCss(): void {}
diff --git a/packages/reset-css/tsconfig.json b/packages/reset-css/tsconfig.json
index 4c6e75be..a79a5660 100644
--- a/packages/reset-css/tsconfig.json
+++ b/packages/reset-css/tsconfig.json
@@ -1,26 +1,29 @@
-{
- "compilerOptions": {
- "types": ["vite/client", "vitest/importMeta", "vitest/globals"],
- "strict": true,
- "target": "ESNext",
- "declaration": true,
- "declarationMap": true,
- "removeComments": true,
- "sourceMap": true,
- "useDefineForClassFields": true,
- "allowJs": false,
- "skipLibCheck": true,
- "noFallthroughCasesInSwitch": true,
- "esModuleInterop": true,
- "allowSyntheticDefaultImports": true,
- "forceConsistentCasingInFileNames": true,
- "strictFunctionTypes": true,
- "module": "ESNext",
- "moduleResolution": "Bundler",
- "resolveJsonModule": true,
- "isolatedModules": true,
- "noEmit": true,
- "baseUrl": ".",
- "jsx": "react-jsx"
- }
-}
+{
+ "compilerOptions": {
+ "types": ["bun"],
+ "strict": true,
+ "target": "ESNext",
+ "declaration": true,
+ "emitDeclarationOnly": true,
+ "outDir": "dist",
+ "declarationMap": true,
+ "removeComments": true,
+ "sourceMap": true,
+ "useDefineForClassFields": true,
+ "allowJs": false,
+ "skipLibCheck": true,
+ "noFallthroughCasesInSwitch": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "forceConsistentCasingInFileNames": true,
+ "strictFunctionTypes": true,
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "baseUrl": ".",
+ "jsx": "react-jsx"
+ },
+ "include": ["src"],
+ "exclude": ["node_modules", "dist", "src/**/__tests__"]
+}
diff --git a/packages/reset-css/vite.config.ts b/packages/reset-css/vite.config.ts
deleted file mode 100644
index f64b13e8..00000000
--- a/packages/reset-css/vite.config.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-import preserveDirectives from 'rollup-plugin-preserve-directives'
-import dts from 'vite-plugin-dts'
-import { defineConfig } from 'vitest/config'
-
-export default defineConfig({
- test: {
- globals: true,
- coverage: {
- provider: 'v8',
- thresholds: {
- '100': true,
- },
- },
- },
- plugins: [
- dts({
- entryRoot: 'src',
- staticImport: true,
- pathsToAliases: false,
- exclude: [
- '**/__tests__/**/*',
- '**/*.test.(tsx|ts|js|jsx)',
- '**/*.test-d.(tsx|ts|js|jsx)',
- 'vite.config.ts',
- ],
- include: ['**/src/**/*.ts', '**/src/**/*.tsx'],
- copyDtsFiles: true,
- compilerOptions: {
- isolatedModules: false,
- declaration: true,
- },
- }),
- ],
- build: {
- rollupOptions: {
- onwarn: (warning) => {
- if (warning.code === 'MODULE_LEVEL_DIRECTIVE') {
- return
- }
- },
- plugins: [preserveDirectives()],
- external: (source) => {
- return !(source.includes('src') || source.startsWith('.'))
- },
-
- output: {
- dir: 'dist',
- preserveModules: true,
- preserveModulesRoot: 'src',
-
- exports: 'named',
- assetFileNames({ name }) {
- return name?.replace(/^src\//g, '') ?? ''
- },
- },
- },
- lib: {
- formats: ['es', 'cjs'],
- entry: {
- index: 'src/index.ts',
- },
- },
- outDir: 'dist',
- },
-})
diff --git a/packages/rsbuild-plugin/package.json b/packages/rsbuild-plugin/package.json
index 0c6155fa..0a9dd574 100644
--- a/packages/rsbuild-plugin/package.json
+++ b/packages/rsbuild-plugin/package.json
@@ -21,29 +21,21 @@
"version": "1.0.46",
"scripts": {
"lint": "eslint",
- "build": "tsc && vite build"
+ "build": "tsc && bun build --target node src/index.ts --production --outfile dist/index.cjs --format cjs --packages external && bun build --target node src/index.ts --production --outfile dist/index.mjs --format esm --packages external"
},
"publishConfig": {
"access": "public"
},
"sideEffects": false,
"main": "./dist/index.cjs",
- "module": "./dist/index.js",
+ "module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"exports": {
".": {
- "import": "./dist/index.js",
- "require": "./dist/index.cjs"
- },
- "./loader": {
- "import": "./dist/loader.js",
- "require": "./dist/loader.cjs"
- },
- "./css-loader": {
- "import": "./dist/css-loader.js",
- "require": "./dist/css-loader.cjs"
- },
- "./dist/*": "./dist/*"
+ "import": "./dist/index.mjs",
+ "require": "./dist/index.cjs",
+ "types": "./dist/index.d.ts"
+ }
},
"files": [
"dist"
@@ -56,10 +48,7 @@
"@rsbuild/core": "*"
},
"devDependencies": {
- "@rsbuild/core": "^1.6",
- "vite": "^7.3",
- "vite-plugin-dts": "^4.5",
- "vitest": "^4.0",
+ "@rsbuild/core": "^1.7",
"typescript": "^5.9"
}
}
diff --git a/packages/rsbuild-plugin/src/__tests__/index.test.ts b/packages/rsbuild-plugin/src/__tests__/index.test.ts
index 66ebd170..9e93c9d0 100644
--- a/packages/rsbuild-plugin/src/__tests__/index.test.ts
+++ b/packages/rsbuild-plugin/src/__tests__/index.test.ts
@@ -1,3 +1,5 @@
+import { describe, expect, it } from 'bun:test'
+
describe('export', () => {
it('should export DevupUIVitePlugin', async () => {
const index = await import('../index')
diff --git a/packages/rsbuild-plugin/src/__tests__/plugin.test.ts b/packages/rsbuild-plugin/src/__tests__/plugin.test.ts
index e2823716..9f8b1b32 100644
--- a/packages/rsbuild-plugin/src/__tests__/plugin.test.ts
+++ b/packages/rsbuild-plugin/src/__tests__/plugin.test.ts
@@ -1,30 +1,61 @@
-import { existsSync, writeFileSync } from 'node:fs'
-import { mkdir, readFile, writeFile } from 'node:fs/promises'
+import * as fs from 'node:fs'
+import * as fsPromises from 'node:fs/promises'
import { join, resolve } from 'node:path'
+import * as wasm from '@devup-ui/wasm'
import {
- codeExtract,
- getDefaultTheme,
- getThemeInterface,
- registerTheme,
- setPrefix,
-} from '@devup-ui/wasm'
-import { vi } from 'vitest'
+ afterAll,
+ beforeAll,
+ describe,
+ expect,
+ it,
+ mock,
+ spyOn,
+} from 'bun:test'
import { DevupUI } from '../plugin'
-// Mock dependencies
-vi.mock('node:fs/promises')
-vi.mock('node:fs')
-vi.mock('@devup-ui/wasm')
+let existsSyncSpy: ReturnType
+let writeFileSyncSpy: ReturnType
+let mkdirSpy: ReturnType
+let readFileSpy: ReturnType
+let writeFileSpy: ReturnType
+let codeExtractSpy: ReturnType
+let getDefaultThemeSpy: ReturnType
+let getThemeInterfaceSpy: ReturnType
+let registerThemeSpy: ReturnType
+let setDebugSpy: ReturnType
+let setPrefixSpy: ReturnType
-describe('DevupUIRsbuildPlugin', () => {
- beforeEach(() => {
- vi.resetAllMocks()
- vi.mocked(mkdir).mockResolvedValue(undefined)
- vi.mocked(writeFile).mockResolvedValue(undefined)
- })
+beforeAll(() => {
+ existsSyncSpy = spyOn(fs, 'existsSync').mockReturnValue(false)
+ writeFileSyncSpy = spyOn(fs, 'writeFileSync').mockReturnValue(undefined)
+ mkdirSpy = spyOn(fsPromises, 'mkdir').mockResolvedValue(undefined)
+ readFileSpy = spyOn(fsPromises, 'readFile').mockResolvedValue('{}')
+ writeFileSpy = spyOn(fsPromises, 'writeFile').mockResolvedValue(undefined)
+ codeExtractSpy = spyOn(wasm, 'codeExtract')
+ getDefaultThemeSpy = spyOn(wasm, 'getDefaultTheme').mockReturnValue('')
+ getThemeInterfaceSpy = spyOn(wasm, 'getThemeInterface').mockReturnValue('')
+ registerThemeSpy = spyOn(wasm, 'registerTheme').mockReturnValue(undefined)
+ setDebugSpy = spyOn(wasm, 'setDebug').mockReturnValue(undefined)
+ setPrefixSpy = spyOn(wasm, 'setPrefix').mockReturnValue(undefined)
+})
+afterAll(() => {
+ existsSyncSpy.mockRestore()
+ writeFileSyncSpy.mockRestore()
+ mkdirSpy.mockRestore()
+ readFileSpy.mockRestore()
+ writeFileSpy.mockRestore()
+ codeExtractSpy.mockRestore()
+ getDefaultThemeSpy.mockRestore()
+ getThemeInterfaceSpy.mockRestore()
+ registerThemeSpy.mockRestore()
+ setDebugSpy.mockRestore()
+ setPrefixSpy.mockRestore()
+})
+
+describe('DevupUIRsbuildPlugin', () => {
it('should export DevupUIRsbuildPlugin', () => {
expect(DevupUI).toBeDefined()
})
@@ -39,8 +70,8 @@ describe('DevupUIRsbuildPlugin', () => {
expect(plugin.name).toBe('devup-ui-rsbuild-plugin')
expect(typeof plugin.setup).toBe('function')
- const transform = vi.fn()
- const modifyRsbuildConfig = vi.fn()
+ const transform = mock()
+ const modifyRsbuildConfig = mock()
await plugin.setup({
transform,
modifyRsbuildConfig,
@@ -49,17 +80,17 @@ describe('DevupUIRsbuildPlugin', () => {
})
it('should write data files', async () => {
- vi.mocked(readFile).mockResolvedValueOnce(JSON.stringify({}))
- vi.mocked(getThemeInterface).mockReturnValue('interface code')
- vi.mocked(existsSync).mockImplementation((path) => {
+ readFileSpy.mockResolvedValueOnce(JSON.stringify({}))
+ getThemeInterfaceSpy.mockReturnValue('interface code')
+ existsSyncSpy.mockImplementation((path: string) => {
if (path === 'devup.json') return true
return false
})
const plugin = DevupUI()
expect(plugin).toBeDefined()
expect(plugin.setup).toBeDefined()
- const transform = vi.fn()
- const modifyRsbuildConfig = vi.fn()
+ const transform = mock()
+ const modifyRsbuildConfig = mock()
await plugin.setup({
transform,
modifyRsbuildConfig,
@@ -67,37 +98,37 @@ describe('DevupUIRsbuildPlugin', () => {
})
it('should write data files without theme', async () => {
- vi.mocked(readFile).mockResolvedValueOnce(JSON.stringify({}))
- vi.mocked(getThemeInterface).mockReturnValue('')
- vi.mocked(existsSync).mockImplementation((path) => {
+ readFileSpy.mockResolvedValueOnce(JSON.stringify({}))
+ getThemeInterfaceSpy.mockReturnValue('')
+ existsSyncSpy.mockImplementation((path: string) => {
if (path === 'devup.json') return true
return false
})
const plugin = DevupUI()
expect(plugin).toBeDefined()
expect(plugin.setup).toBeDefined()
- const transform = vi.fn()
- const modifyRsbuildConfig = vi.fn()
+ const transform = mock()
+ const modifyRsbuildConfig = mock()
await plugin.setup({
transform,
modifyRsbuildConfig,
} as any)
- expect(writeFileSync).not.toHaveBeenCalled()
+ expect(writeFileSyncSpy).not.toHaveBeenCalled()
})
it('should error when write data files', async () => {
const originalConsoleError = console.error
- console.error = vi.fn()
- vi.mocked(readFile).mockRejectedValueOnce('error')
- vi.mocked(existsSync).mockImplementation((path) => {
+ console.error = mock()
+ readFileSpy.mockRejectedValueOnce('error')
+ existsSyncSpy.mockImplementation((path: string) => {
if (path === 'devup.json') return true
return false
})
const plugin = DevupUI()
expect(plugin).toBeDefined()
expect(plugin.setup).toBeDefined()
- const transform = vi.fn()
- const modifyRsbuildConfig = vi.fn()
+ const transform = mock()
+ const modifyRsbuildConfig = mock()
await plugin.setup({
transform,
modifyRsbuildConfig,
@@ -112,7 +143,7 @@ describe('DevupUIRsbuildPlugin', () => {
})
expect(plugin).toBeDefined()
expect(plugin.setup).toBeDefined()
- const transform = vi.fn()
+ const transform = mock()
await plugin.setup({
transform,
} as any)
@@ -138,8 +169,8 @@ describe('DevupUIRsbuildPlugin', () => {
const plugin = DevupUI()
expect(plugin).toBeDefined()
expect(plugin.setup).toBeDefined()
- const transform = vi.fn()
- const modifyRsbuildConfig = vi.fn()
+ const transform = mock()
+ const modifyRsbuildConfig = mock()
await plugin.setup({
transform,
modifyRsbuildConfig,
@@ -166,8 +197,8 @@ describe('DevupUIRsbuildPlugin', () => {
const plugin = DevupUI()
expect(plugin).toBeDefined()
expect(plugin.setup).toBeDefined()
- const transform = vi.fn()
- const modifyRsbuildConfig = vi.fn()
+ const transform = mock()
+ const modifyRsbuildConfig = mock()
await plugin.setup({
transform,
modifyRsbuildConfig,
@@ -185,8 +216,7 @@ describe('DevupUIRsbuildPlugin', () => {
code: ``,
}),
).toBe('')
-
- vi.mocked(codeExtract).mockReturnValue({
+ codeExtractSpy.mockReturnValue({
code: '',
css: '',
css_file: 'devup-ui.css',
@@ -222,10 +252,10 @@ const App = () => `,
})
expect(plugin).toBeDefined()
expect(plugin.setup).toBeDefined()
- const transform = vi.fn()
+ const transform = mock()
await plugin.setup({
transform,
- modifyRsbuildConfig: vi.fn(),
+ modifyRsbuildConfig: mock(),
} as any)
expect(transform).toHaveBeenCalled()
expect(transform).toHaveBeenCalledWith(
@@ -234,14 +264,14 @@ const App = () => `,
},
expect.any(Function),
)
- vi.mocked(codeExtract).mockReturnValue({
+ codeExtractSpy.mockReturnValue({
code: '',
css: '.devup-ui-1 { color: red; }',
cssFile: 'devup-ui.css',
map: undefined,
updatedBaseStyle: options.updatedBaseStyle,
- free: vi.fn(),
- [Symbol.dispose]: vi.fn(),
+ free: mock(),
+ [Symbol.dispose]: mock(),
})
const ret = await transform.mock.calls[1][1]({
code: `import { Box } from '@devup-ui/react'
@@ -254,13 +284,13 @@ const App = () => `,
})
if (options.updatedBaseStyle) {
- expect(writeFile).toHaveBeenCalledWith(
+ expect(writeFileSpy).toHaveBeenCalledWith(
resolve('df', 'devup-ui', 'devup-ui.css'),
expect.stringMatching(/\/\* src\/App\.tsx \d+ \*\//),
'utf-8',
)
}
- expect(writeFile).toHaveBeenCalledWith(
+ expect(writeFileSpy).toHaveBeenCalledWith(
resolve('df', 'devup-ui', 'devup-ui.css'),
expect.stringMatching(/\/\* src\/App\.tsx \d+ \*\//),
'utf-8',
@@ -289,11 +319,11 @@ const App = () => `,
singleCss: [true, false],
}),
)('should write data files', async (options) => {
- vi.mocked(writeFile).mockResolvedValueOnce(undefined)
- vi.mocked(readFile).mockResolvedValueOnce(JSON.stringify({}))
- vi.mocked(getThemeInterface).mockReturnValue('interface code')
- vi.mocked(getDefaultTheme).mockReturnValue(options.getDefaultTheme)
- vi.mocked(existsSync).mockImplementation((path) => {
+ writeFileSpy.mockResolvedValueOnce(undefined)
+ readFileSpy.mockResolvedValueOnce(JSON.stringify({}))
+ getThemeInterfaceSpy.mockReturnValue('interface code')
+ getDefaultThemeSpy.mockReturnValue(options.getDefaultTheme)
+ existsSyncSpy.mockImplementation((path: string) => {
if (path === 'devup.json') return options.existsDevupFile
if (path === 'df') return options.existsDistDir
if (path === resolve('df', 'devup-ui')) return options.existsCssDir
@@ -305,45 +335,45 @@ const App = () => `,
})
const plugin = DevupUI({ singleCss: options.singleCss })
await (plugin as any).setup({
- transform: vi.fn(),
- renderChunk: vi.fn(),
- generateBundle: vi.fn(),
- closeBundle: vi.fn(),
- resolve: vi.fn(),
- load: vi.fn(),
- modifyRsbuildConfig: vi.fn(),
- watchChange: vi.fn(),
- resolveId: vi.fn(),
+ transform: mock(),
+ renderChunk: mock(),
+ generateBundle: mock(),
+ closeBundle: mock(),
+ resolve: mock(),
+ load: mock(),
+ modifyRsbuildConfig: mock(),
+ watchChange: mock(),
+ resolveId: mock(),
} as any)
if (options.existsDevupFile) {
- expect(readFile).toHaveBeenCalledWith('devup.json', 'utf-8')
- expect(registerTheme).toHaveBeenCalledWith({})
- expect(getThemeInterface).toHaveBeenCalledWith(
+ expect(readFileSpy).toHaveBeenCalledWith('devup.json', 'utf-8')
+ expect(registerThemeSpy).toHaveBeenCalledWith({})
+ expect(getThemeInterfaceSpy).toHaveBeenCalledWith(
'@devup-ui/react',
'CustomColors',
'DevupThemeTypography',
'DevupTheme',
)
- expect(writeFile).toHaveBeenCalledWith(
+ expect(writeFileSpy).toHaveBeenCalledWith(
join('df', 'theme.d.ts'),
'interface code',
'utf-8',
)
} else {
- expect(registerTheme).toHaveBeenCalledWith({})
+ expect(registerThemeSpy).toHaveBeenCalledWith({})
}
- const modifyRsbuildConfig = vi.fn()
+ const modifyRsbuildConfig = mock()
await (plugin as any).setup({
- transform: vi.fn(),
- renderChunk: vi.fn(),
- generateBundle: vi.fn(),
- closeBundle: vi.fn(),
- resolve: vi.fn(),
+ transform: mock(),
+ renderChunk: mock(),
+ generateBundle: mock(),
+ closeBundle: mock(),
+ resolve: mock(),
modifyRsbuildConfig,
- load: vi.fn(),
- watchChange: vi.fn(),
- resolveId: vi.fn(),
+ load: mock(),
+ watchChange: mock(),
+ resolveId: mock(),
} as any)
if (options.getDefaultTheme) {
expect(modifyRsbuildConfig).toHaveBeenCalledWith(expect.any(Function))
@@ -381,9 +411,9 @@ const App = () => `,
it('should call setPrefix when prefix option is provided', async () => {
const plugin = DevupUI({ prefix: 'my-prefix' })
await plugin.setup({
- transform: vi.fn(),
- modifyRsbuildConfig: vi.fn(),
+ transform: mock(),
+ modifyRsbuildConfig: mock(),
} as any)
- expect(setPrefix).toHaveBeenCalledWith('my-prefix')
+ expect(setPrefixSpy).toHaveBeenCalledWith('my-prefix')
})
})
diff --git a/packages/rsbuild-plugin/tsconfig.json b/packages/rsbuild-plugin/tsconfig.json
index fa3c2eb3..a79a5660 100644
--- a/packages/rsbuild-plugin/tsconfig.json
+++ b/packages/rsbuild-plugin/tsconfig.json
@@ -1,28 +1,29 @@
-{
- "compilerOptions": {
- "types": ["vite/client", "vitest/importMeta", "vitest/globals"],
- "strict": true,
- "target": "ESNext",
- "declaration": true,
- "declarationMap": true,
- "removeComments": true,
- "sourceMap": true,
- "useDefineForClassFields": true,
- "allowJs": false,
- "skipLibCheck": true,
- "noFallthroughCasesInSwitch": true,
- "esModuleInterop": true,
- "allowSyntheticDefaultImports": true,
- "forceConsistentCasingInFileNames": true,
- "strictFunctionTypes": true,
- "module": "ESNext",
- "moduleResolution": "Bundler",
- "resolveJsonModule": true,
- "isolatedModules": true,
- "noEmit": true,
- "baseUrl": ".",
- "jsx": "react-jsx"
- },
- "include": ["src", "vite.config.ts", "../../vitest.setup.ts"],
- "exclude": ["node_modules", "dist"]
-}
+{
+ "compilerOptions": {
+ "types": ["bun"],
+ "strict": true,
+ "target": "ESNext",
+ "declaration": true,
+ "emitDeclarationOnly": true,
+ "outDir": "dist",
+ "declarationMap": true,
+ "removeComments": true,
+ "sourceMap": true,
+ "useDefineForClassFields": true,
+ "allowJs": false,
+ "skipLibCheck": true,
+ "noFallthroughCasesInSwitch": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "forceConsistentCasingInFileNames": true,
+ "strictFunctionTypes": true,
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "baseUrl": ".",
+ "jsx": "react-jsx"
+ },
+ "include": ["src"],
+ "exclude": ["node_modules", "dist", "src/**/__tests__"]
+}
diff --git a/packages/rsbuild-plugin/vite.config.ts b/packages/rsbuild-plugin/vite.config.ts
deleted file mode 100644
index 0cf0c964..00000000
--- a/packages/rsbuild-plugin/vite.config.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-import dts from 'vite-plugin-dts'
-import { defineConfig } from 'vitest/config'
-
-export default defineConfig({
- test: {
- globals: true,
- coverage: {
- provider: 'v8',
- thresholds: {
- '100': true,
- },
- },
- },
- plugins: [
- dts({
- entryRoot: 'src',
- staticImport: true,
- pathsToAliases: false,
- exclude: [
- '**/__tests__/**/*',
- '**/*.test.(tsx|ts|js|jsx)',
- '**/*.test-d.(tsx|ts|js|jsx)',
- 'vite.config.ts',
- ],
- include: ['**/src/**/*.ts'],
- copyDtsFiles: true,
- compilerOptions: {
- isolatedModules: false,
- declaration: true,
- },
- }),
- ],
- build: {
- rollupOptions: {
- onwarn: (warning) => {
- if (warning.code === 'MODULE_LEVEL_DIRECTIVE') {
- return
- }
- },
- external: (source) => {
- return !(source.includes('src') || source.startsWith('.'))
- },
-
- output: {
- dir: 'dist',
- preserveModules: true,
- preserveModulesRoot: 'src',
-
- exports: 'named',
- assetFileNames({ name }) {
- return name?.replace(/^src\//g, '') ?? ''
- },
- },
- },
- lib: {
- formats: ['es', 'cjs'],
- entry: {
- index: 'src/index.ts',
- },
- },
- outDir: 'dist',
- },
-})
diff --git a/packages/vite-plugin/package.json b/packages/vite-plugin/package.json
index 02b8cd8d..42e946e8 100644
--- a/packages/vite-plugin/package.json
+++ b/packages/vite-plugin/package.json
@@ -21,33 +21,34 @@
"version": "1.0.51",
"scripts": {
"lint": "eslint",
- "build": "tsc && vite build"
+ "build": "tsc && bun build --target node src/index.ts --production --outfile dist/index.cjs --format cjs --packages external && bun build --target node src/index.ts --production --outfile dist/index.mjs --format esm --packages external"
},
"publishConfig": {
"access": "public"
},
"sideEffects": false,
"main": "./dist/index.cjs",
- "module": "./dist/index.js",
+ "module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"exports": {
".": {
- "import": "./dist/index.js",
- "require": "./dist/index.cjs"
+ "import": "./dist/index.mjs",
+ "require": "./dist/index.cjs",
+ "types": "./dist/index.d.ts"
}
},
"files": [
"dist"
],
"dependencies": {
- "@devup-ui/wasm": "workspace:^"
+ "@devup-ui/wasm": "workspace:^",
+ "vite": "^7.3"
},
"devDependencies": {
- "vite-plugin-dts": "^4.5.4",
"typescript": "^5.9.3"
},
"peerDependencies": {
- "vite": "*",
- "@devup-ui/wasm": "*"
+ "@devup-ui/wasm": "*",
+ "vite": "*"
}
}
diff --git a/packages/vite-plugin/src/__tests__/index.test.ts b/packages/vite-plugin/src/__tests__/index.test.ts
index 66ebd170..9e93c9d0 100644
--- a/packages/vite-plugin/src/__tests__/index.test.ts
+++ b/packages/vite-plugin/src/__tests__/index.test.ts
@@ -1,3 +1,5 @@
+import { describe, expect, it } from 'bun:test'
+
describe('export', () => {
it('should export DevupUIVitePlugin', async () => {
const index = await import('../index')
diff --git a/packages/vite-plugin/src/__tests__/plugin.test.ts b/packages/vite-plugin/src/__tests__/plugin.test.ts
index 8e04a757..caac0919 100644
--- a/packages/vite-plugin/src/__tests__/plugin.test.ts
+++ b/packages/vite-plugin/src/__tests__/plugin.test.ts
@@ -1,40 +1,80 @@
-import { existsSync } from 'node:fs'
-import { mkdir, readFile, writeFile } from 'node:fs/promises'
-import { dirname, join, relative, resolve } from 'node:path'
-import { fileURLToPath } from 'node:url'
+import * as fs from 'node:fs'
+import * as fsPromises from 'node:fs/promises'
+import * as nodePath from 'node:path'
+import * as wasm from '@devup-ui/wasm'
import {
- codeExtract,
- getCss,
- getDefaultTheme,
- getThemeInterface,
- registerTheme,
- setDebug,
- setPrefix,
-} from '@devup-ui/wasm'
-import { describe } from 'vitest'
+ afterEach,
+ beforeEach,
+ describe,
+ expect,
+ it,
+ mock,
+ spyOn,
+} from 'bun:test'
import { DevupUI } from '../plugin'
-vi.mock('@devup-ui/wasm')
-vi.mock('node:fs')
-vi.mock('node:fs/promises')
-vi.mock('node:path', async (original: any) => {
- const origin = await original()
- return {
- ...origin,
- relative: vi.fn(origin.relative),
- }
-})
+const { join, resolve, relative: originalRelative } = nodePath
+
+let existsSyncSpy: ReturnType
+let mkdirSpy: ReturnType
+let readFileSpy: ReturnType
+let writeFileSpy: ReturnType
+let relativeSpy: ReturnType
+let codeExtractSpy: ReturnType
+let getCssSpy: ReturnType
+let getDefaultThemeSpy: ReturnType
+let getThemeInterfaceSpy: ReturnType
+let registerThemeSpy: ReturnType