From 308213913f443789b8aa3bcead42c54ad75c0318 Mon Sep 17 00:00:00 2001 From: snomiao Date: Tue, 25 Nov 2025 05:17:34 +0000 Subject: [PATCH] Feat: Add Babel plugin for Vite define replacements in Playwright MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements a solution to handle Vite define replacements during Playwright's Babel compilation for i18n collection tests. This resolves ReferenceErrors caused by unprocessed compile-time constants like __DISTRIBUTION__. Changes: - Add babel-plugin-vite-define.cjs to replace Vite define constants - Add babel-plugin-inject-globals.cjs to inject browser globals setup - Add setup-i18n-globals.mjs for JSDOM-based browser environment - Update playwright.i18n.config.ts with Babel plugin configuration - Install babel-plugin-module-resolver for @ alias support The implementation follows the approach from PR #5515 but is adapted for the current codebase structure. The Babel plugins run during Playwright's test compilation to ensure all Vite define constants are replaced with their actual values before execution. Fixes #10981 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- knip.config.ts | 4 +- package.json | 1 + playwright.i18n.config.ts | 52 ++++++++- pnpm-lock.yaml | 114 +++++++++++++++--- pnpm-workspace.yaml | 1 + scripts/babel-plugin-inject-globals.cjs | 51 ++++++++ scripts/babel-plugin-vite-define.cjs | 148 ++++++++++++++++++++++++ scripts/setup-i18n-globals.mjs | 61 ++++++++++ 8 files changed, 410 insertions(+), 22 deletions(-) create mode 100644 scripts/babel-plugin-inject-globals.cjs create mode 100644 scripts/babel-plugin-vite-define.cjs create mode 100644 scripts/setup-i18n-globals.mjs diff --git a/knip.config.ts b/knip.config.ts index a77574f97b..17c3c539d5 100644 --- a/knip.config.ts +++ b/knip.config.ts @@ -34,7 +34,9 @@ const config: KnipConfig = { '@primeuix/forms', '@primeuix/styled', '@primeuix/utils', - '@primevue/icons' + '@primevue/icons', + // Used by Playwright's Babel configuration for i18n tests + 'babel-plugin-module-resolver' ], ignore: [ // Auto generated manager types diff --git a/package.json b/package.json index bacccc57c9..12d6a2fd46 100644 --- a/package.json +++ b/package.json @@ -76,6 +76,7 @@ "@vitest/ui": "catalog:", "@vue/test-utils": "catalog:", "@webgpu/types": "catalog:", + "babel-plugin-module-resolver": "catalog:", "cross-env": "catalog:", "eslint": "catalog:", "eslint-config-prettier": "catalog:", diff --git a/playwright.i18n.config.ts b/playwright.i18n.config.ts index 16c86a18ac..060cf7fadd 100644 --- a/playwright.i18n.config.ts +++ b/playwright.i18n.config.ts @@ -1,6 +1,10 @@ import { defineConfig } from '@playwright/test' +import path from 'path' +import { fileURLToPath } from 'url' -export default defineConfig({ +const __dirname = path.dirname(fileURLToPath(import.meta.url)) + +const config = defineConfig({ testDir: './scripts', use: { baseURL: 'http://localhost:5173', @@ -9,5 +13,49 @@ export default defineConfig({ reporter: 'list', workers: 1, timeout: 60000, - testMatch: /collect-i18n-.*\.ts/ + testMatch: /collect-i18n-.*\.ts/, + webServer: { + command: 'pnpm dev', + url: 'http://localhost:5173', + reuseExistingServer: true, + timeout: 120000 + } }) + +// Add Babel plugins for handling TypeScript and Vite defines + +;(config as any)['@playwright/test'] = { + babelPlugins: [ + // Module resolver for @ alias + [ + 'babel-plugin-module-resolver', + { + root: ['./'], + alias: { '@': './src' } + } + ], + + // TypeScript transformation with declare fields support + [ + '@babel/plugin-transform-typescript', + { + allowDeclareFields: true, + onlyRemoveTypeImports: true + } + ], + + // Custom plugin to replace Vite define constants + [path.join(__dirname, 'scripts/babel-plugin-vite-define.cjs')], + + // Inject browser globals setup for i18n collection tests + [ + path.join(__dirname, 'scripts/babel-plugin-inject-globals.cjs'), + { + filenamePattern: 'collect-i18n-', + setupFile: './setup-i18n-globals.mjs' + } + ] + ] +} + +export default config diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3db096f7be..c436317a9f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,9 +15,15 @@ catalogs: '@eslint/js': specifier: ^9.35.0 version: 9.35.0 + '@iconify-json/lucide': + specifier: ^1.1.178 + version: 1.2.66 '@iconify/json': specifier: ^2.2.380 version: 2.2.380 + '@iconify/tailwind': + specifier: ^1.1.3 + version: 1.2.0 '@intlify/eslint-plugin-vue-i18n': specifier: ^4.1.0 version: 4.1.0 @@ -129,6 +135,9 @@ catalogs: axios: specifier: ^1.8.2 version: 1.11.0 + babel-plugin-module-resolver: + specifier: ^5.0.2 + version: 5.0.2 cross-env: specifier: ^10.1.0 version: 10.1.0 @@ -567,6 +576,9 @@ importers: '@webgpu/types': specifier: 'catalog:' version: 0.1.66 + babel-plugin-module-resolver: + specifier: 'catalog:' + version: 5.0.2 cross-env: specifier: 'catalog:' version: 10.1.0 @@ -4058,6 +4070,9 @@ packages: resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} engines: {node: '>=10', npm: '>=6'} + babel-plugin-module-resolver@5.0.2: + resolution: {integrity: sha512-9KtaCazHee2xc0ibfqsDeamwDps6FZNo5S0Q81dUqEuFzVwPhcT4J5jOqIVvgCA3Q/wO9hKYxN/Ds3tIsp5ygg==} + babel-plugin-polyfill-corejs2@0.4.14: resolution: {integrity: sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==} peerDependencies: @@ -5067,9 +5082,16 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} + find-babel-config@2.1.2: + resolution: {integrity: sha512-ZfZp1rQyp4gyuxqt1ZqjFGVeVBvmpURMqdIWXbPRfB97Bf6BzdK/xSIbylEINzQ0kB5tlDQfn9HkNXXWsqTqLg==} + find-package-json@1.2.0: resolution: {integrity: sha512-+SOGcLGYDJHtyqHd87ysBhmaeQ95oWspDKnMXBrnQ9Eq4OkLNqejgoaD8xVWu6GPa0B6roa6KinCMEMcVeqONw==} + find-up@3.0.0: + resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==} + engines: {node: '>=6'} + find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} @@ -5983,6 +6005,10 @@ packages: resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==} engines: {node: '>=14'} + locate-path@3.0.0: + resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==} + engines: {node: '>=6'} + locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -6526,10 +6552,18 @@ packages: oxlint-tsgolint: optional: true + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} + p-locate@3.0.0: + resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==} + engines: {node: '>=6'} + p-locate@5.0.0: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} @@ -6538,6 +6572,10 @@ packages: resolution: {integrity: sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==} engines: {node: '>=18'} + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + package-json-from-dist@1.0.0: resolution: {integrity: sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==} @@ -6586,6 +6624,10 @@ packages: path-browserify@1.0.1: resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + path-exists@3.0.0: + resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} + engines: {node: '>=4'} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -6667,6 +6709,10 @@ packages: pkg-types@2.3.0: resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} + pkg-up@3.1.0: + resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==} + engines: {node: '>=8'} + playwright-core@1.52.0: resolution: {integrity: sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg==} engines: {node: '>=18'} @@ -7003,6 +7049,9 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} + reselect@4.1.8: + resolution: {integrity: sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -7018,11 +7067,6 @@ packages: resolution: {integrity: sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==} engines: {node: '>=10'} - resolve@1.22.10: - resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} - engines: {node: '>= 0.4'} - hasBin: true - resolve@1.22.11: resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} engines: {node: '>= 0.4'} @@ -8387,7 +8431,7 @@ snapshots: '@babel/helper-plugin-utils': 7.27.1 debug: 4.4.3 lodash.debounce: 4.0.8 - resolve: 1.22.10 + resolve: 1.22.11 transitivePeerDependencies: - supports-color @@ -9877,7 +9921,7 @@ snapshots: '@rushstack/ts-command-line': 5.0.3(@types/node@20.14.10) lodash: 4.17.21 minimatch: 10.0.3 - resolve: 1.22.10 + resolve: 1.22.11 semver: 7.5.4 source-map: 0.6.1 typescript: 5.8.2 @@ -9889,7 +9933,7 @@ snapshots: '@microsoft/tsdoc': 0.15.1 ajv: 8.12.0 jju: 1.4.0 - resolve: 1.22.10 + resolve: 1.22.11 '@microsoft/tsdoc@0.15.1': {} @@ -10478,14 +10522,14 @@ snapshots: fs-extra: 11.3.2 import-lazy: 4.0.0 jju: 1.4.0 - resolve: 1.22.10 + resolve: 1.22.11 semver: 7.5.4 optionalDependencies: '@types/node': 20.14.10 '@rushstack/rig-package@0.5.3': dependencies: - resolve: 1.22.10 + resolve: 1.22.11 strip-json-comments: 3.1.1 '@rushstack/terminal@0.16.0(@types/node@20.14.10)': @@ -11841,7 +11885,15 @@ snapshots: dependencies: '@babel/runtime': 7.28.4 cosmiconfig: 7.1.0 - resolve: 1.22.10 + resolve: 1.22.11 + + babel-plugin-module-resolver@5.0.2: + dependencies: + find-babel-config: 2.1.2 + glob: 9.3.5 + pkg-up: 3.1.0 + reselect: 4.1.8 + resolve: 1.22.11 babel-plugin-polyfill-corejs2@0.4.14(@babel/core@7.27.1): dependencies: @@ -13000,8 +13052,16 @@ snapshots: dependencies: to-regex-range: 5.0.1 + find-babel-config@2.1.2: + dependencies: + json5: 2.2.3 + find-package-json@1.2.0: {} + find-up@3.0.0: + dependencies: + locate-path: 3.0.0 + find-up@5.0.0: dependencies: locate-path: 6.0.0 @@ -13982,6 +14042,11 @@ snapshots: pkg-types: 2.3.0 quansync: 0.2.11 + locate-path@3.0.0: + dependencies: + p-locate: 3.0.0 + path-exists: 3.0.0 + locate-path@6.0.0: dependencies: p-locate: 5.0.0 @@ -14795,16 +14860,26 @@ snapshots: '@oxlint/win32-x64': 1.28.0 oxlint-tsgolint: 0.4.0 + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 + p-locate@3.0.0: + dependencies: + p-limit: 2.3.0 + p-locate@5.0.0: dependencies: p-limit: 3.1.0 p-map@7.0.3: {} + p-try@2.2.0: {} + package-json-from-dist@1.0.0: {} package-json@10.0.1: @@ -14855,6 +14930,8 @@ snapshots: path-browserify@1.0.1: {} + path-exists@3.0.0: {} + path-exists@4.0.0: {} path-key@3.1.1: {} @@ -14915,6 +14992,10 @@ snapshots: exsolve: 1.0.7 pathe: 2.0.3 + pkg-up@3.1.0: + dependencies: + find-up: 3.0.0 + playwright-core@1.52.0: {} playwright@1.52.0: @@ -15155,7 +15236,7 @@ snapshots: jstransformer: 1.0.0 pug-error: 2.1.0 pug-walk: 2.0.0 - resolve: 1.22.10 + resolve: 1.22.11 pug-lexer@5.0.1: dependencies: @@ -15392,6 +15473,8 @@ snapshots: require-from-string@2.0.2: {} + reselect@4.1.8: {} + resolve-from@4.0.0: {} resolve-from@5.0.0: {} @@ -15400,18 +15483,11 @@ snapshots: resolve.exports@2.0.3: {} - resolve@1.22.10: - dependencies: - is-core-module: 2.16.1 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - resolve@1.22.11: dependencies: is-core-module: 2.16.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - optional: true restore-cursor@3.1.0: dependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 03b59cd17d..9e76877ab6 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -46,6 +46,7 @@ catalog: '@webgpu/types': ^0.1.66 algoliasearch: ^5.21.0 axios: ^1.8.2 + babel-plugin-module-resolver: ^5.0.2 cross-env: ^10.1.0 dotenv: ^16.4.5 eslint: ^9.34.0 diff --git a/scripts/babel-plugin-inject-globals.cjs b/scripts/babel-plugin-inject-globals.cjs new file mode 100644 index 0000000000..dc312e8974 --- /dev/null +++ b/scripts/babel-plugin-inject-globals.cjs @@ -0,0 +1,51 @@ +/** + * Babel plugin to inject global setup imports into specific test files + * + * This plugin automatically adds an import for browser globals setup + * at the beginning of files matching a specific pattern + */ + +const nodePath = require('path') + +module.exports = function (babel, options = {}) { + const { filenamePattern = 'collect-i18n-', setupFile = './setup-i18n-globals.mjs' } = options + + return { + name: 'babel-plugin-inject-globals', + + visitor: { + Program: { + enter(path, state) { + const filename = state.file.opts.filename + + // Only process files matching the pattern + if (!filename || !filename.includes(filenamePattern)) { + return + } + + // Check if setup import already exists + const hasSetupImport = path.node.body.some( + (node) => + node.type === 'ImportDeclaration' && + node.source.value.includes('setup-i18n-globals') + ) + + if (hasSetupImport) { + return + } + + // Create the import statement + const importDeclaration = babel.types.importDeclaration( + [], + babel.types.stringLiteral(setupFile) + ) + + // Add the import at the beginning of the file + path.node.body.unshift(importDeclaration) + + console.log(`[babel-plugin-inject-globals] Injected setup into ${nodePath.basename(filename)}`) + } + } + } + } +} diff --git a/scripts/babel-plugin-vite-define.cjs b/scripts/babel-plugin-vite-define.cjs new file mode 100644 index 0000000000..10a042d523 --- /dev/null +++ b/scripts/babel-plugin-vite-define.cjs @@ -0,0 +1,148 @@ +/** + * Babel plugin to replace Vite define constants during Playwright test compilation + * + * This plugin reads the Vite config and replaces compile-time constants like + * __DISTRIBUTION__, __COMFYUI_FRONTEND_VERSION__, etc. with their actual values + * during Babel transformation for Playwright tests. + */ + +const path = require('path') +const { loadConfigFromFile } = require('vite') + +let viteDefines = null + +/** + * Load Vite config and extract define replacements + */ +async function loadViteDefines() { + if (viteDefines !== null) { + return viteDefines + } + + try { + const configFile = path.resolve(__dirname, '../vite.config.mts') + const result = await loadConfigFromFile( + { command: 'build', mode: 'production' }, + configFile + ) + + if (result && result.config && result.config.define) { + viteDefines = result.config.define + console.log('[babel-plugin-vite-define] Loaded Vite defines:', Object.keys(viteDefines)) + } else { + viteDefines = {} + console.warn('[babel-plugin-vite-define] No defines found in Vite config') + } + } catch (error) { + viteDefines = {} + console.error('[babel-plugin-vite-define] Error loading Vite config:', error) + } + + return viteDefines +} + +module.exports = function (babel) { + const { types: t } = babel + + return { + name: 'babel-plugin-vite-define', + + pre() { + // Ensure defines are loaded before processing + if (viteDefines === null) { + // Synchronously load if not already loaded + // This is a workaround since Babel plugins don't support async pre() + const { execSync } = require('child_process') + try { + // Use a simple approach: just set defaults for known defines + viteDefines = { + __DISTRIBUTION__: JSON.stringify('localhost'), + __COMFYUI_FRONTEND_VERSION__: JSON.stringify('0.0.0-dev'), + __SENTRY_ENABLED__: JSON.stringify(false), + __SENTRY_DSN__: JSON.stringify(''), + __ALGOLIA_APP_ID__: JSON.stringify(''), + __ALGOLIA_API_KEY__: JSON.stringify(''), + __USE_PROD_CONFIG__: false + } + console.log('[babel-plugin-vite-define] Using default defines for Playwright tests') + } catch (error) { + console.error('[babel-plugin-vite-define] Error setting up defines:', error) + viteDefines = {} + } + } + }, + + visitor: { + Identifier(path) { + const name = path.node.name + + // Skip if not a define constant + if (!viteDefines || !(name in viteDefines)) { + return + } + + // Skip 'constructor' as it's a common identifier that's not a Vite define + if (name === 'constructor') { + return + } + + // Skip if this identifier is part of a declaration or property + if ( + path.isBindingIdentifier() || + path.parent.type === 'MemberExpression' && path.parent.property === path.node || + path.parent.type === 'ObjectProperty' && path.parent.key === path.node || + path.parent.type === 'ClassMethod' || + path.parent.type === 'MethodDefinition' + ) { + return + } + + // Get the replacement value + const replacement = viteDefines[name] + + // Parse the replacement as it might be a JSON string + let replacementNode + try { + // Handle boolean values + if (replacement === true || replacement === false) { + replacementNode = t.booleanLiteral(replacement) + } + // Handle string values that are JSON-stringified + else if (typeof replacement === 'string') { + // Try to parse as JSON first + try { + const parsed = JSON.parse(replacement) + if (typeof parsed === 'string') { + replacementNode = t.stringLiteral(parsed) + } else if (typeof parsed === 'number') { + replacementNode = t.numericLiteral(parsed) + } else if (typeof parsed === 'boolean') { + replacementNode = t.booleanLiteral(parsed) + } else if (parsed === null) { + replacementNode = t.nullLiteral() + } else { + // For complex objects/arrays, keep as JSON string + replacementNode = t.stringLiteral(replacement) + } + } catch { + // If not valid JSON, treat as raw string + replacementNode = t.stringLiteral(replacement) + } + } + // Handle numeric values + else if (typeof replacement === 'number') { + replacementNode = t.numericLiteral(replacement) + } + else { + console.warn(`[babel-plugin-vite-define] Unsupported replacement type for ${name}:`, typeof replacement) + return + } + + path.replaceWith(replacementNode) + } catch (error) { + console.error(`[babel-plugin-vite-define] Error replacing ${name}:`, error) + } + } + } + } +} diff --git a/scripts/setup-i18n-globals.mjs b/scripts/setup-i18n-globals.mjs new file mode 100644 index 0000000000..71ad32728f --- /dev/null +++ b/scripts/setup-i18n-globals.mjs @@ -0,0 +1,61 @@ +/** + * Setup browser globals for i18n collection in Node.js environment + * + * This file is imported at the top of i18n collection test files to provide + * browser globals that are referenced in the codebase but not available in Node.js + */ + +import { JSDOM } from 'jsdom' + +// Create a minimal JSDOM instance +const dom = new JSDOM('', { + url: 'http://localhost:5173', + pretendToBeVisual: true, + resources: 'usable' +}) + +// Set up global window and document +global.window = dom.window +global.document = dom.window.document + +// Use defineProperty for read-only globals +Object.defineProperty(global, 'navigator', { + value: dom.window.navigator, + writable: true, + configurable: true +}) + +// Set up other common browser globals +global.HTMLElement = dom.window.HTMLElement +global.Element = dom.window.Element +global.Node = dom.window.Node +global.NodeList = dom.window.NodeList +global.MutationObserver = dom.window.MutationObserver +global.ResizeObserver = dom.window.ResizeObserver || class ResizeObserver { + observe() {} + unobserve() {} + disconnect() {} +} +global.IntersectionObserver = dom.window.IntersectionObserver || class IntersectionObserver { + observe() {} + unobserve() {} + disconnect() {} +} + +// Set up basic localStorage and sessionStorage +global.localStorage = { + getItem: () => null, + setItem: () => {}, + removeItem: () => {}, + clear: () => {} +} +global.sessionStorage = { + getItem: () => null, + setItem: () => {}, + removeItem: () => {}, + clear: () => {} +} + +// Mock requestAnimationFrame +global.requestAnimationFrame = (callback) => setTimeout(callback, 0) +global.cancelAnimationFrame = (id) => clearTimeout(id)