-
Notifications
You must be signed in to change notification settings - Fork 432
test: add tests for data-binding & lifecycles #1592
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: ospp-2025/source-to-dsl
Are you sure you want to change the base?
test: add tests for data-binding & lifecycles #1592
Conversation
WalkthroughAdds comprehensive test suites and mock schemas for data binding and lifecycle generation in the Vue generator. Updates expected app artifacts: new LifeCyclePage, router entry, component/version bumps, and refactors to views to use nested reactive state with v-model. Extends generator mock data with new pages, state, methods, and lifecycle definitions. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant Page as LifeCyclePage.vue
participant Vue as Vue Lifecycle
participant Doc as Document Visibility
participant Timer as setInterval(30s)
Vue->>Page: onBeforeMount()
Vue->>Page: onMounted()
Page->>Page: refreshData()
Page->>Page: startAutoRefresh()
Page->>Timer: start interval
Doc-->>Page: visibilitychange (hidden)
Page->>Page: stopAutoRefresh()
Page->>Timer: clear interval
Doc-->>Page: visibilitychange (visible)
Page->>Page: startAutoRefresh()
Page->>Timer: restart interval
User->>Page: Click "Refresh Data"
Page->>Page: refreshData()
Vue->>Page: onUpdated() [state changed]
Vue->>Page: onDeactivated()/onActivated() [keep-alive]
Vue->>Page: onUnmounted() / onBeforeUnmount()
Page->>Timer: clear interval
sequenceDiagram
autonumber
participant Tests as Vitest Suites
participant Gen as generateApp()
participant Schemas as Mock Schemas
participant Files as Generated .vue
Tests->>Schemas: load schema (basic/advanced/edge/complex/lifecycle)
Tests->>Gen: generateApp(schema)
Gen-->>Files: emit .vue files
Tests->>Files: read *.vue
Tests-->>Tests: assert v-models, state structure, hooks, imports
Tests->>Files: write to result/* (debug)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 10
🧹 Nitpick comments (28)
packages/vue-generator/test/testcases/generator/expected/appdemo01/src/views/testCanvasRowCol.vue (1)
49-54
: Event handler correctly mutates nested reactive state; optional ergonomic tweak.Current pattern with
wrap(function handleElementClick...)
andthis.state...
matches existing generator conventions—keep it. Optionally, if we later favor closure style overthis
, we could switch to an arrow fn using the capturedstate
.-const handleElementClick = wrap(function handleElementClick(elementId) { - this.state.uiState.selectedElement = elementId -}) +const handleElementClick = wrap(function handleElementClick(elementId) { + // keep "this" style to align with current wrap semantics + this.state.uiState.selectedElement = elementId +})packages/vue-generator/test/testcases/generator/expected/appdemo01/src/views/createVm.vue (1)
58-58
: Solid v-model migration to centralized nested state; add keys for v-for and minor UX polish.
- The two-way bindings mapped into
state.formData
,state.inputValues
, andstate.selectValues
look consistent and testable.- In the “数据盘” repeater, add a stable
:key
to avoid Vue warnings and improve patch efficiency. You can also iterate with(disk, idx)
for clarity.- <div v-for="() in state.dataDisk" style="margin-top: 12px; display: flex"> + <div v-for="(disk, idx) in state.dataDisk" :key="idx" style="margin-top: 12px; display: flex">
- Import aliases
ButtonGroup as TinyButtonGroup, Button as TinyButton
align with the 3.24.0 API and the rest of this PR’s expected outputs—good.Also applies to: 92-92, 105-105, 116-116, 124-126, 217-217, 226-226, 267-267, 273-277, 299-299, 305-309, 312-312, 351-355, 399-401, 416-433
packages/vue-generator/test/testcases/lifecycle/mockData.js (2)
16-18
: Unify component version declarations with fixtures (optional).Schemas still declare
'^3.10.0'
while the expected fixture pins3.24.0
. Aligning these to'^3.24.0'
keeps version intent coherent across tests and reduces cognitive overhead.- version: '^3.10.0', + version: '^3.24.0',Apply to all
componentsMap
entries in this file.Also applies to: 89-91, 177-186
166-260
: Great coverage: mixed lifecycle + data-binding schema. Add a test to exercise it.
mixedLifecycleSchema
is a useful scenario (child prop-level lifecycle + binding). It isn’t currently exercised by tests; consider adding/adjusting a spec to validate its transformation (no prop-boundonMounted=
, presence of script-levelvue.onMounted
, and emitted method calls likefocusInput()
).I can provide a test diff (see lifecycle.test.js comment) to wire this in.
packages/vue-generator/test/testcases/generator/expected/appdemo01/src/views/LifeCyclePage.vue (2)
89-93
: Fix onUpdated log to reference existing state
this.state.loginCount
doesn’t exist; logs will showundefined
. Prefer logging relevant metrics.Apply this diff:
-vue.onUpdated( - wrap(function onUpdated() { - console.log('页面已更新', this.state.loginCount) - }) -) +vue.onUpdated( + wrap(function onUpdated() { + console.log('页面已更新', { + activeUsers: this.state.activeUsers, + systemLoad: this.state.systemLoad + }) + }) +)
18-21
: Drop macro imports and unused destructure for leaner SFC
- In <script setup>,
defineProps/defineEmits
are compile-time macros; importing them is unnecessary and can confuse tooling.t
is destructured but not used.Apply this diff:
import { Button as TinyButton, Card as TinyCard } from '@opentiny/vue' import * as vue from 'vue' -import { defineProps, defineEmits } from 'vue' import { I18nInjectionKey } from 'vue-i18n' @@ -const { t, lowcodeWrap, stores } = vue.inject(I18nInjectionKey).lowcode() +const { lowcodeWrap, stores } = vue.inject(I18nInjectionKey).lowcode()Also applies to: 26-26
packages/vue-generator/test/testcases/generator/mockData.js (3)
1087-1091
: Consistent conversion to JSExpression + model for v-model sourcesAll updated bindings now point to the new nested paths (state.formData/, state.inputValues/, state.selectValues/*). This should produce correct
v-model
generation.Small follow-up:
- Ensure default values match component expectations (numbers vs strings) to avoid implicit coercion when two-way binding. If Tiny components expect string values, the current string defaults are fine.
Also applies to: 1140-1144, 1182-1186, 1220-1224, 1259-1264, 1470-1473, 1492-1496, 1576-1579, 1600-1603, 1661-1664, 1685-1688, 1706-1709, 1822-1825
1252-1252
: UI nit: width increased to 120pxThe label width bump is fine; consider checking adjacent labels for alignment to avoid misaligned baselines in tight layouts.
2539-2545
: Verify component map versions for @opentiny/vueMost entries are pinned to 3.24.0, while TinyCard uses ^3.10.0. That resolves to 3.24.0 and won’t conflict, but aligning versions can prevent duplicate installs in other contexts.
If you want to harmonize:
- version: '^3.10.0', + version: '3.24.0',Also applies to: 2611-2617
packages/vue-generator/test/testcases/data-binding/edge-case.test.js (2)
71-79
: Gate debug artifact writes behind an env switchAvoid polluting the repo during CI and local runs by making writes opt-in.
Apply this diff:
- const outputDir = path.resolve(__dirname, './result/edge-case') - fs.mkdirSync(outputDir, { recursive: true }) - - for (const { fileName, fileContent } of genResult) { - if (fileName.endsWith('.vue')) { - fs.writeFileSync(path.join(outputDir, fileName), fileContent) - } - } + if (process.env.WRITE_GEN_FIXTURES === '1') { + const outputDir = path.resolve(__dirname, './result/edge-case') + fs.mkdirSync(outputDir, { recursive: true }) + for (const { fileName, fileContent } of genResult) { + if (fileName.endsWith('.vue')) { + fs.writeFileSync(path.join(outputDir, fileName), fileContent) + } + } + }
1-6
: Make __dirname robust for ESM runnersVitest often runs tests as ESM; in that case
__dirname
may be undefined. Provide a fallback viaimport.meta.url
.If your repo uses
"type": "module"
, apply:import { expect, test, describe } from 'vitest' import path from 'path' import fs from 'fs' +import { fileURLToPath } from 'url' @@ - const outputDir = path.resolve(__dirname, './result/edge-case') + const __filename = typeof __filename !== 'undefined' ? __filename : fileURLToPath(import.meta.url) + const __dirnameSafe = typeof __dirname !== 'undefined' ? __dirname : path.dirname(__filename) + const outputDir = path.resolve(__dirnameSafe, './result/edge-case')Alternatively, confirm you’re running in CJS; if so, you can skip this change.
Also applies to: 71-73
packages/vue-generator/test/testcases/data-binding/basic.test.js (2)
60-69
: Tighten the v-model regex to avoid matching modifiers/argumentsCurrent
/v-model[^=]*="..."/
may matchv-model:foo
orv-model.trim
. If you intend only default bindings, restrict it.Apply this diff:
- const vModelMatches = content.match(/v-model[^=]*="[^"]+"/g) || [] + const vModelMatches = content.match(/(?<!:)v-model="[^"]+"/g) || []
40-49
: Gate debug artifact writes behind an env switch (consistency with other tests)Prevents stray files in CI/local runs; keeps behavior consistent with edge-case test suggestion.
Apply this diff:
- const outputDir = path.resolve(__dirname, './result/basic') - fs.mkdirSync(outputDir, { recursive: true }) - - for (const { fileName, fileContent } of genResult) { - if (fileName.endsWith('.vue')) { - fs.writeFileSync(path.join(outputDir, fileName), fileContent) - } - } + if (process.env.WRITE_GEN_FIXTURES === '1') { + const outputDir = path.resolve(__dirname, './result/basic') + fs.mkdirSync(outputDir, { recursive: true }) + for (const { fileName, fileContent } of genResult) { + if (fileName.endsWith('.vue')) { + fs.writeFileSync(path.join(outputDir, fileName), fileContent) + } + } + }packages/vue-generator/test/testcases/data-binding/mockData.js (3)
282-286
: Avoid empty JSExpression for modelValue in edge-caseAn empty JSExpression can lead to invalid
v-model
likev-model=""
if the transformer doesn’t guard for it. Make the intent explicit: static empty value without data-binding.- modelValue: { - type: 'JSExpression', - value: '' // 空字段 - } + // 静态空值,无数据绑定 + modelValue: ''Alternatively, if you want to keep JSExpression form while ensuring it won't become v-model:
- modelValue: { - type: 'JSExpression', - value: '' - } + modelValue: { + type: 'JSExpression', + value: "''", + model: false + }
, tests assert
361-372
: Confirm tag expectation: schema usesThe schema’s root is
componentName: 'form'
, yetform.test.js
asserts<div class="complex-form">
. Unless the generator normalizesform
todiv
, this will fail.Prefer adjusting the test to accept either
<form>
or<div>
with the same class. See suggested test change inform.test.js
comment.If you intended a
<div>
, switchcomponentName
to'div'
here to match the test.
11-26
: Deduplicate componentsMap declarationsThe component entries (package, version, destructuring) are repeated across schemas. Centralizing them reduces drift when versions change.
Consider exporting shared factories, e.g.,
makeTinyInput()
,makeTinySelect()
,makeTinyCheckbox()
,makeTinySwitch()
,makeTinyRadio()
- or a
const base = { package: '@opentiny/vue', version: '^3.10.0', destructuring: true }
Then spread and override
componentName
/exportName
per schema.Also applies to: 97-126, 336-358
packages/vue-generator/test/testcases/data-binding/advanced.test.js (4)
52-54
: Harden file lookup and guard against undefinedUse endsWith to avoid path prefix mismatches and assert existence before dereferencing.
- const vueFile = genResult.find((file) => file.fileName === 'AdvancedFormPage.vue') - const content = vueFile.fileContent + const vueFile = genResult.find((file) => file.fileName?.endsWith('AdvancedFormPage.vue')) + expect(vueFile).toBeDefined() + const content = vueFile.fileContent
74-76
: Repeat the guard for subsequent testsAvoid potential
Cannot read properties of undefined
if generation shape changes.- const vueFile = genResult.find((file) => file.fileName === 'AdvancedFormPage.vue') - const content = vueFile.fileContent + const vueFile = genResult.find((file) => file.fileName?.endsWith('AdvancedFormPage.vue')) + expect(vueFile).toBeDefined() + const content = vueFile.fileContent
92-94
: Repeat the guard for the radio group testSame robustness improvement as above.
- const vueFile = genResult.find((file) => file.fileName === 'AdvancedFormPage.vue') - const content = vueFile.fileContent + const vueFile = genResult.find((file) => file.fileName?.endsWith('AdvancedFormPage.vue')) + expect(vueFile).toBeDefined() + const content = vueFile.fileContent
47-67
: Optional: generate once per suite to speed up testsYou regenerate for each test. Caching
genResult
inbeforeAll
reduces runtime and IO. Keep a test that ensures idempotency if needed.I can provide a consolidated pattern if you’d like.
packages/vue-generator/test/testcases/data-binding/form.test.js (8)
50-52
: Harden file lookup and guard against undefined (test 2)Consistent with advanced tests: avoid brittle equality and assert existence.
- const vueFile = genResult.find((file) => file.fileName === 'ComplexFormPage.vue') + const vueFile = genResult.find((file) => file.fileName?.endsWith('ComplexFormPage.vue')) + expect(vueFile).toBeDefined() const content = vueFile.fileContent
68-70
: Harden file lookup and guard against undefined (test 3)- const vueFile = genResult.find((file) => file.fileName === 'ComplexFormPage.vue') + const vueFile = genResult.find((file) => file.fileName?.endsWith('ComplexFormPage.vue')) + expect(vueFile).toBeDefined() const content = vueFile.fileContent
85-87
: Harden file lookup and guard against undefined (test 4)- const vueFile = genResult.find((file) => file.fileName === 'ComplexFormPage.vue') + const vueFile = genResult.find((file) => file.fileName?.endsWith('ComplexFormPage.vue')) + expect(vueFile).toBeDefined() const content = vueFile.fileContent
102-104
: Harden file lookup and guard against undefined (test 5)- const vueFile = genResult.find((file) => file.fileName === 'ComplexFormPage.vue') + const vueFile = genResult.find((file) => file.fileName?.endsWith('ComplexFormPage.vue')) + expect(vueFile).toBeDefined() const content = vueFile.fileContent
129-131
: Harden file lookup and guard against undefined (test 6)- const vueFile = genResult.find((file) => file.fileName === 'ComplexFormPage.vue') + const vueFile = genResult.find((file) => file.fileName?.endsWith('ComplexFormPage.vue')) + expect(vueFile).toBeDefined() const content = vueFile.fileContent
133-135
: Relax import assertion to tolerate formattingUse a regex for spacing/line-break variations in the import list.
- expect(content).toContain( - "import { Input as TinyInput, Select as TinySelect, Checkbox as TinyCheckbox } from '@opentiny/vue'" - ) + const importRegex = + /import\s*{\s*Input\s+as\s+TinyInput\s*,\s*Select\s+as\s+TinySelect\s*,\s*Checkbox\s+as\s+TinyCheckbox\s*}\s*from\s*['"]@opentiny\/vue['"]/ + expect(content).toMatch(importRegex)
113-121
: Optional: gate artifact export behind an env flagWriting files during tests can slow CI and create noisy artifacts locally. Make it opt-in.
- // 写入测试结果 - const outputDir = path.resolve(__dirname, './result/complex-form') - fs.mkdirSync(outputDir, { recursive: true }) - - for (const { fileName, fileContent } of genResult) { - if (fileName.endsWith('.vue')) { - fs.writeFileSync(path.join(outputDir, fileName), fileContent) - } - } + // 写入测试结果(可选:设置 EXPORT_GEN=1 启用) + if (process.env.EXPORT_GEN) { + const outputDir = path.resolve(__dirname, './result/complex-form') + fs.mkdirSync(outputDir, { recursive: true }) + for (const { fileName, fileContent } of genResult) { + if (fileName.endsWith('.vue')) { + fs.writeFileSync(path.join(outputDir, fileName), fileContent) + } + } + }
7-12
: Generate once per suite to reduce overheadYou can move
generateApp()
+generate(schema)
tobeforeAll
and sharegenResult
across tests; current repeated generation is unnecessary here.I can provide a refactor sketch if you want to proceed.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (13)
packages/vue-generator/test/testcases/data-binding/advanced.test.js
(1 hunks)packages/vue-generator/test/testcases/data-binding/basic.test.js
(1 hunks)packages/vue-generator/test/testcases/data-binding/edge-case.test.js
(1 hunks)packages/vue-generator/test/testcases/data-binding/form.test.js
(1 hunks)packages/vue-generator/test/testcases/data-binding/mockData.js
(1 hunks)packages/vue-generator/test/testcases/generator/expected/appdemo01/package.json
(1 hunks)packages/vue-generator/test/testcases/generator/expected/appdemo01/src/router/index.js
(1 hunks)packages/vue-generator/test/testcases/generator/expected/appdemo01/src/views/LifeCyclePage.vue
(1 hunks)packages/vue-generator/test/testcases/generator/expected/appdemo01/src/views/createVm.vue
(10 hunks)packages/vue-generator/test/testcases/generator/expected/appdemo01/src/views/testCanvasRowCol.vue
(1 hunks)packages/vue-generator/test/testcases/generator/mockData.js
(23 hunks)packages/vue-generator/test/testcases/lifecycle/lifecycle.test.js
(1 hunks)packages/vue-generator/test/testcases/lifecycle/mockData.js
(1 hunks)
🧰 Additional context used
🧠 Learnings (6)
📚 Learning: 2024-09-30T07:51:10.036Z
Learnt from: chilingling
PR: opentiny/tiny-engine#837
File: packages/vue-generator/src/plugins/genDependenciesPlugin.js:66-66
Timestamp: 2024-09-30T07:51:10.036Z
Learning: In the `tiny-engine` project, `opentiny/tiny-engine-dsl-vue` refers to the current package itself, and importing types from it may cause circular dependencies.
Applied to files:
packages/vue-generator/test/testcases/generator/expected/appdemo01/package.json
packages/vue-generator/test/testcases/generator/mockData.js
📚 Learning: 2025-01-14T06:49:00.797Z
Learnt from: gene9831
PR: opentiny/tiny-engine#1011
File: packages/configurator/src/router-select-configurator/RouterSelectConfigurator.vue:63-73
Timestamp: 2025-01-14T06:49:00.797Z
Learning: In the tiny-engine project, the SvgIcon component is globally registered and available throughout Vue components without requiring explicit imports.
Applied to files:
packages/vue-generator/test/testcases/generator/expected/appdemo01/package.json
packages/vue-generator/test/testcases/generator/expected/appdemo01/src/views/createVm.vue
📚 Learning: 2025-01-14T11:09:32.013Z
Learnt from: rhlin
PR: opentiny/tiny-engine#1011
File: packages/vue-generator/src/plugins/genRouterPlugin.js:122-125
Timestamp: 2025-01-14T11:09:32.013Z
Learning: The router configuration in Vue Generator should maintain the current structure without a catch-all route for 404 handling or modification to the root path prefix, as these are not required at this time.
Applied to files:
packages/vue-generator/test/testcases/generator/expected/appdemo01/src/router/index.js
📚 Learning: 2025-02-17T12:11:22.718Z
Learnt from: gene9831
PR: opentiny/tiny-engine#1117
File: packages/canvas/container/src/components/CanvasViewerSwitcher.vue:96-117
Timestamp: 2025-02-17T12:11:22.718Z
Learning: In CanvasViewerSwitcher.vue, `state.usedHoverState.element` is guaranteed to have a value when `handleClick` is called, making additional error handling unnecessary.
Applied to files:
packages/vue-generator/test/testcases/generator/expected/appdemo01/src/views/testCanvasRowCol.vue
📚 Learning: 2025-03-20T07:20:12.221Z
Learnt from: gene9831
PR: opentiny/tiny-engine#1233
File: packages/canvas/container/src/components/CanvasDivider.vue:184-185
Timestamp: 2025-03-20T07:20:12.221Z
Learning: In CanvasDivider.vue, even though state.verLeft and state.horizontalTop already include 'px' suffix, the CSS properties in state.dividerStyle still need to append 'px' again according to gene9831, suggesting that these state variables might be processed differently than expected when used in style binding.
Applied to files:
packages/vue-generator/test/testcases/generator/expected/appdemo01/src/views/testCanvasRowCol.vue
📚 Learning: 2025-01-14T06:55:59.692Z
Learnt from: gene9831
PR: opentiny/tiny-engine#1011
File: packages/configurator/src/router-select-configurator/RouterSelectConfigurator.vue:95-98
Timestamp: 2025-01-14T06:55:59.692Z
Learning: The tiny-select component from opentiny/vue library ensures selected options are valid internally, requiring no additional validation in the change handler.
Applied to files:
packages/vue-generator/test/testcases/generator/expected/appdemo01/src/views/createVm.vue
packages/vue-generator/test/testcases/generator/mockData.js
🧬 Code graph analysis (5)
packages/vue-generator/test/testcases/data-binding/advanced.test.js (2)
packages/vue-generator/src/generator/generateApp.js (1)
generateApp
(23-82)packages/vue-generator/test/testcases/data-binding/mockData.js (2)
advancedDataBindingSchema
(92-247)advancedDataBindingSchema
(92-247)
packages/vue-generator/test/testcases/data-binding/basic.test.js (2)
packages/vue-generator/src/generator/generateApp.js (1)
generateApp
(23-82)packages/vue-generator/test/testcases/data-binding/mockData.js (2)
basicDataBindingSchema
(6-89)basicDataBindingSchema
(6-89)
packages/vue-generator/test/testcases/lifecycle/lifecycle.test.js (2)
packages/vue-generator/src/generator/generateApp.js (1)
generateApp
(23-82)packages/vue-generator/test/testcases/lifecycle/mockData.js (4)
lifecycleTestSchema
(6-76)lifecycleTestSchema
(6-76)complexLifecycleSchema
(79-164)complexLifecycleSchema
(79-164)
packages/vue-generator/test/testcases/data-binding/form.test.js (2)
packages/vue-generator/src/generator/generateApp.js (1)
generateApp
(23-82)packages/vue-generator/test/testcases/data-binding/mockData.js (2)
complexFormDataBindingSchema
(331-451)complexFormDataBindingSchema
(331-451)
packages/vue-generator/test/testcases/data-binding/edge-case.test.js (2)
packages/vue-generator/src/generator/generateApp.js (1)
generateApp
(23-82)packages/vue-generator/test/testcases/data-binding/mockData.js (2)
edgeCaseDataBindingSchema
(250-328)edgeCaseDataBindingSchema
(250-328)
🔇 Additional comments (5)
packages/vue-generator/test/testcases/generator/expected/appdemo01/src/views/testCanvasRowCol.vue (1)
43-46
: Nested reactive state shape is clear and future-proof.The
canvasConfig
anduiState
split makes downstream bindings straightforward. InitializingselectedElement
asnull
is appropriate for tri-state logic (unset vs. selected).packages/vue-generator/test/testcases/lifecycle/lifecycle.test.js (1)
63-65
: This expectation assumes anerror
identifier is present; ensure schema fix or relax assertion.Once
mockData.js
is fixed to declare theerror
parameter, these assertions are fine. If not, consider loosening to only assert the hook registration and handler names.- expect(content).toContain('handleError(error)') - expect(content).toContain('reportError(error)') + expect(content).toMatch(/handleError\s*\(/) + expect(content).toMatch(/reportError\s*\(/)packages/vue-generator/test/testcases/generator/expected/appdemo01/src/router/index.js (1)
31-36
: Route wiring and component existence verified—ApprovedThe
LifeCyclePage.vue
component is present inpackages/vue-generator/test/testcases/generator/expected/appdemo01/src/views
, and its use of<script setup>
is valid in Vue 3 (noexport default
required). The router entry inpackages/vue-generator/test/testcases/generator/expected/appdemo01/src/router/index.js
lines 31–36 correctly references this component by name, path, and import.Files to confirm:
- packages/vue-generator/test/testcases/generator/expected/appdemo01/src/router/index.js (lines 31–36)
- packages/vue-generator/test/testcases/generator/expected/appdemo01/src/views/LifeCyclePage.vue
Route wiring for lifecycle page looks correct
- Name/path/component align with the new view and keep the existing router shape (no catch-all, no root prefix changes), consistent with project learnings.
packages/vue-generator/test/testcases/generator/mockData.js (1)
911-941
: Good shift to centralized, nested reactive state for bindingsUsing formData/inputValues/selectValues clarifies ownership of fields and simplifies v-model wiring in generated pages.
packages/vue-generator/test/testcases/data-binding/advanced.test.js (1)
48-60
: Alias'@'
Mapping Verified
Theresolve.alias
block inpackages/vue-generator/vite.config.js
already maps'@'
to./src
(lines 24–28), ensuring imports like@/generator/generateApp
resolve correctly in Vitest. No further changes are needed.
// 验证HTML结构和样式类 | ||
expect(content).toContain('<div class="complex-form">') | ||
expect(content).toContain('class="user-section"') | ||
expect(content).toContain('class="preferences-section"') | ||
|
||
// 验证嵌套div结构 | ||
expect(content).toMatch(/<div[^>]*class="user-section"[^>]*>[\s\S]*<tiny-input[\s\S]*<\/div>/) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Make tag assertion resilient to form/div difference
Schema uses componentName: 'form'
; assertion currently hard-codes <div>
. Accept either <form>
or <div>
with the same class.
- expect(content).toContain('<div class="complex-form">')
+ expect(content).toMatch(/<(div|form)[^>]*class="complex-form"/)
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
// 验证HTML结构和样式类 | |
expect(content).toContain('<div class="complex-form">') | |
expect(content).toContain('class="user-section"') | |
expect(content).toContain('class="preferences-section"') | |
// 验证嵌套div结构 | |
expect(content).toMatch(/<div[^>]*class="user-section"[^>]*>[\s\S]*<tiny-input[\s\S]*<\/div>/) | |
}) | |
// 验证HTML结构和样式类 | |
expect(content).toMatch(/<(div|form)[^>]*class="complex-form"/) | |
expect(content).toContain('class="user-section"') | |
expect(content).toContain('class="preferences-section"') |
🤖 Prompt for AI Agents
packages/vue-generator/test/testcases/data-binding/form.test.js around lines 71
to 78: the test currently hard-codes <div> tag assertions but schema may render
a <form>; update assertions to accept either <div> or <form>. Replace the
literal '<div class="complex-form">' assertion with a match that accepts either
tag (e.g. use a regex like /<(?:div|form)[^>]*class="complex-form"/), keep the
class existence checks as-is, and change the nested structure assertion to use a
regex that allows <div> or <form> for the user-section tag (e.g.
/<(?:div|form)[^>]*class="user-section"[^>]*>[\s\S]*<tiny-input[\s\S]*<\/(?:div|form)>/).
Ensure tests still assert class names but use toMatch with those regexes so both
tag variants pass.
componentName: 'TinyRadio', | ||
props: { | ||
value: 'male', | ||
checked: { | ||
type: 'JSExpression', | ||
model: true, | ||
value: 'state.formData.gender' | ||
} | ||
} | ||
}, | ||
{ | ||
componentName: 'TinyRadio', | ||
props: { | ||
value: 'female', | ||
checked: { | ||
type: 'JSExpression', | ||
model: true, | ||
value: 'state.formData.gender' | ||
} | ||
} | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Use modelValue instead of checked for TinyRadio v-model
For TinyRadio, binding checked
to a shared string field is semantically off. Radios should bind the selected value via v-model
/modelValue
on each radio, with value
indicating the option. This avoids boolean coercion and aligns with Vue and component semantics.
Apply this diff to both radio entries:
{
componentName: 'TinyRadio',
props: {
value: 'male',
- checked: {
+ modelValue: {
type: 'JSExpression',
model: true,
value: 'state.formData.gender'
}
}
},
{
componentName: 'TinyRadio',
props: {
value: 'female',
- checked: {
+ modelValue: {
type: 'JSExpression',
model: true,
value: 'state.formData.gender'
}
}
},
🤖 Prompt for AI Agents
In packages/vue-generator/test/testcases/data-binding/mockData.js around lines
163 to 183, the TinyRadio entries use a `checked` JSExpression bound to the
shared string field; replace `checked` with `modelValue` on each radio so
v-model semantics are used: keep the existing `value` keys, and change the
`checked: { type: 'JSExpression', model: true, value: 'state.formData.gender' }`
objects for both radio entries to `modelValue: { type: 'JSExpression', model:
true, value: 'state.formData.gender' }` so each radio binds the selected value
correctly.
componentName: 'TinyCheckbox', | ||
props: { | ||
label: '接收邮件通知', | ||
checked: { | ||
type: 'JSExpression', | ||
model: true, | ||
value: 'state.user.preferences.emailNotifications' | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Use modelValue instead of checked for TinyCheckbox v-model
Library components typically expose modelValue
for v-model
. Using checked
here risks producing a non-standard binding shape.
props: {
label: '接收邮件通知',
- checked: {
+ modelValue: {
type: 'JSExpression',
model: true,
value: 'state.user.preferences.emailNotifications'
}
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
componentName: 'TinyCheckbox', | |
props: { | |
label: '接收邮件通知', | |
checked: { | |
type: 'JSExpression', | |
model: true, | |
value: 'state.user.preferences.emailNotifications' | |
} | |
} | |
} | |
componentName: 'TinyCheckbox', | |
props: { | |
label: '接收邮件通知', | |
modelValue: { | |
type: 'JSExpression', | |
model: true, | |
value: 'state.user.preferences.emailNotifications' | |
} | |
} | |
} |
🤖 Prompt for AI Agents
packages/vue-generator/test/testcases/data-binding/mockData.js around lines 420
to 429: the TinyCheckbox prop uses a non-standard "checked" key for v-model;
change the prop key from "checked" to "modelValue" while preserving type:
'JSExpression', model: true and the value
'state.user.preferences.emailNotifications' so the generated binding uses
modelValue for v-model with the same expression.
"@opentiny/vue": "3.24.0", | ||
"@opentiny/vue-icon": "3.24.0", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Version bump to @opentiny/vue 3.24.0 and @opentiny/vue-icon 3.24.0 looks good; please align schema component versions for consistency.
- Good move pinning exact versions in an expected fixture to keep snapshots deterministic.
- For coherence across the test suite and to avoid confusion when reading generated imports, consider updating lifecycle/data-binding mock schemas that still declare
'^3.10.0'
to'^3.24.0'
(see lifecycle/mockData.js in this PR). This does not affect runtime here but reduces drift between fixture deps and schema-declared component versions.
Run this quick scan to spot older declared versions in test schemas:
🏁 Script executed:
#!/bin/bash
rg -nP "@opentiny/vue[\"']?\\s*[:,]\\s*[\"']\\^?3\\.10\\.0[\"']|@opentiny/vue-icon[\"']?\\s*[:,]\\s*[\"']\\^?3\\.10\\.0[\"']" packages/vue-generator/test -C2
Length of output: 906
Align schema mock versions to 3.24.0
I scanned the packages/vue-generator/test
suite for any remaining ^3.10.0
declarations and found the following outdated fixture:
- packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/package.json
Lines 14–15: - "@opentiny/vue": "^3.10.0",
- "@opentiny/vue-icon": "^3.10.0",
- "@opentiny/vue": "3.24.0",
- "@opentiny/vue-icon": "3.24.0",
No other instances of `^3.10.0` were detected in the test schemas. Updating these ensures all expected fixtures remain in lockstep with the new `3.24.0` pinning for deterministic snapshots.
<!-- suggestion_start -->
<details>
<summary>📝 Committable suggestion</summary>
> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
```suggestion
"@opentiny/vue": "3.24.0",
"@opentiny/vue-icon": "3.24.0",
🤖 Prompt for AI Agents
In
packages/vue-generator/test/testcases/generator/expected/appdemo01/package.json
around lines 14-15, the fixture lists @opentiny/vue and @opentiny/vue-icon with
the outdated ^3.10.0 pinning; update both version strings to 3.24.0 (remove the
caret if present) so the test fixture matches the new 3.24.0 deterministic
snapshots and run the tests to validate no other fixtures need changes.
<tiny-button type="success" @click="refreshData">刷新数据</tiny-button></tiny-card | ||
> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix malformed closing tag for TinyCard
The closing tag is split across lines, which is fragile and may break template parsing in stricter tooling.
Apply this diff:
- <tiny-button type="success" @click="refreshData">刷新数据</tiny-button></tiny-card
- >
+ <tiny-button type="success" @click="refreshData">刷新数据</tiny-button>
+ </tiny-card>
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
<tiny-button type="success" @click="refreshData">刷新数据</tiny-button></tiny-card | |
> | |
<tiny-button type="success" @click="refreshData">刷新数据</tiny-button> | |
</tiny-card> |
🤖 Prompt for AI Agents
In
packages/vue-generator/test/testcases/generator/expected/appdemo01/src/views/LifeCyclePage.vue
around lines 12 to 13, the TinyCard closing tag is split across lines
("</tiny-card\n >"), which can break template parsing; replace the malformed
split closing tag with a single proper closing tag ("</tiny-card>") so the
element is closed on one line and the template remains valid.
const startAutoRefresh = wrap(function startAutoRefresh() { | ||
if (this.autoRefresh) { | ||
this.refreshInterval = setInterval(() => this.refreshData(), 30000) | ||
} | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Use state-qualified flag and guard against duplicate intervals; restart on activation
- startAutoRefresh checks
this.autoRefresh
but the flag lives understate
; this prevents auto-refresh from ever starting. - When the page is wrapped with KeepAlive, calling startAutoRefresh in both onMounted and onActivated can spawn multiple intervals unless guarded.
- onActivated should restart auto-refresh after deactivation.
Apply this diff:
-const startAutoRefresh = wrap(function startAutoRefresh() {
- if (this.autoRefresh) {
- this.refreshInterval = setInterval(() => this.refreshData(), 30000)
- }
-})
+const startAutoRefresh = wrap(function startAutoRefresh() {
+ if (!this.state.autoRefresh) return
+ if (this.refreshInterval) return
+ this.refreshInterval = setInterval(() => this.refreshData(), 30000)
+})
vue.onActivated(
wrap(function onActivated() {
console.log('页面激活')
refreshData()
+ startAutoRefresh()
})
)
Also applies to: 77-82
🤖 Prompt for AI Agents
In
packages/vue-generator/test/testcases/generator/expected/appdemo01/src/views/LifeCyclePage.vue
around lines 41-45 (also apply same changes to lines 77-82): startAutoRefresh
currently checks this.autoRefresh but the flag lives under this.state, and it
can create duplicate intervals when called from both onMounted and onActivated;
update the function to read this.state.autoRefresh, guard against creating
another interval if this.refreshInterval is already set, and ensure onActivated
restarts the interval by calling startAutoRefresh after clearing/stopping any
existing interval on deactivation. Ensure refreshInterval is a single source
(clearInterval when stopping), and only setInterval when state.autoRefresh is
true and no existing interval exists.
{ | ||
componentName: 'div', | ||
fileName: 'LifeCyclePage', | ||
meta: { | ||
id: 'lifecycle-page', | ||
isPage: true, | ||
parentId: '0', | ||
router: '/lifecycle' | ||
}, | ||
props: { | ||
class: 'lifecycle-container' | ||
}, | ||
children: [ | ||
{ | ||
componentName: 'TinyCard', | ||
props: { | ||
title: '仪表板' | ||
}, | ||
children: [ | ||
{ | ||
componentName: 'div', | ||
props: { | ||
class: 'metrics-grid' | ||
}, | ||
children: [ | ||
{ | ||
componentName: 'div', | ||
props: { | ||
class: 'metric-item' | ||
}, | ||
children: [ | ||
{ | ||
componentName: 'Text', | ||
props: { | ||
text: { | ||
type: 'JSExpression', | ||
value: '"活跃用户: " + this.state.activeUsers' | ||
} | ||
} | ||
} | ||
] | ||
}, | ||
{ | ||
componentName: 'div', | ||
props: { | ||
class: 'metric-item' | ||
}, | ||
children: [ | ||
{ | ||
componentName: 'Text', | ||
props: { | ||
text: { | ||
type: 'JSExpression', | ||
value: '"系统负载: " + this.state.systemLoad + "%"' | ||
} | ||
} | ||
} | ||
] | ||
} | ||
] | ||
}, | ||
{ | ||
componentName: 'TinyButton', | ||
props: { | ||
type: 'success', | ||
onClick: { | ||
type: 'JSExpression', | ||
value: 'this.refreshData' | ||
} | ||
}, | ||
children: ['刷新数据'] | ||
} | ||
] | ||
} | ||
], | ||
state: { | ||
activeUsers: 0, | ||
systemLoad: 0, | ||
lastUpdateTime: null, | ||
autoRefresh: true | ||
}, | ||
methods: { | ||
refreshData: { | ||
type: 'JSFunction', | ||
value: 'function refreshData() { this.fetchMetrics(); this.state.lastUpdateTime = new Date(); }' | ||
}, | ||
fetchMetrics: { | ||
type: 'JSFunction', | ||
value: | ||
'function fetchMetrics() { this.state.activeUsers = Math.floor(Math.random() * 1000); this.state.systemLoad = Math.floor(Math.random() * 100); }' | ||
}, | ||
startAutoRefresh: { | ||
type: 'JSFunction', | ||
value: | ||
'function startAutoRefresh() { if (this.autoRefresh) { this.refreshInterval = setInterval(() => this.refreshData(), 30000); } }' | ||
}, | ||
stopAutoRefresh: { | ||
type: 'JSFunction', | ||
value: | ||
'function stopAutoRefresh() { if (this.refreshInterval) { clearInterval(this.refreshInterval); this.refreshInterval = null; } }' | ||
}, | ||
handleVisibilityChange: { | ||
type: 'JSFunction', | ||
value: | ||
'function handleVisibilityChange() { if (document.hidden) { this.stopAutoRefresh(); } else { this.startAutoRefresh(); } }' | ||
} | ||
}, | ||
lifeCycles: { | ||
onMounted: { | ||
type: 'JSFunction', | ||
value: | ||
'function onMounted() { console.log("onMounted."); refreshData(); startAutoRefresh(); document.addEventListener("visibilitychange", handleVisibilityChange) }' | ||
}, | ||
onUnmounted: { | ||
type: 'JSFunction', | ||
value: | ||
'function onUnmounted() { console.log("onUnmounted."); stopAutoRefresh(); document.removeEventListener("visibilitychange", handleVisibilityChange) }' | ||
}, | ||
onActivated: { | ||
type: 'JSFunction', | ||
value: 'function onActivated() { console.log("页面激活"); refreshData() }' | ||
}, | ||
onDeactivated: { | ||
type: 'JSFunction', | ||
value: 'function onDeactivated() { console.log("页面停用"); stopAutoRefresh() }' | ||
}, | ||
onUpdated: { | ||
type: 'JSFunction', | ||
value: 'function onUpdated() { console.log("页面已更新", this.state.loginCount) }' | ||
}, | ||
onBeforeMount: { | ||
type: 'JSFunction', | ||
value: 'function onBeforeMount() { console.log("onBeforeMount.") }' | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Lifecycle page schema mirrors the SFC issues: fix autoRefresh access, duplicate intervals, and missing restart; correct onUpdated log
Align the schema functions with the SFC fixes so generated output and tests stay in sync.
Apply this diff:
startAutoRefresh: {
type: 'JSFunction',
- value:
- 'function startAutoRefresh() { if (this.autoRefresh) { this.refreshInterval = setInterval(() => this.refreshData(), 30000); } }'
+ value:
+ 'function startAutoRefresh() { if (!this.state.autoRefresh) { return; } if (this.refreshInterval) { return; } this.refreshInterval = setInterval(() => this.refreshData(), 30000); }'
},
@@
onActivated: {
type: 'JSFunction',
- value: 'function onActivated() { console.log("页面激活"); refreshData() }'
+ value: 'function onActivated() { console.log("页面激活"); refreshData(); startAutoRefresh() }'
},
@@
onUpdated: {
type: 'JSFunction',
- value: 'function onUpdated() { console.log("页面已更新", this.state.loginCount) }'
+ value: 'function onUpdated() { console.log("页面已更新", { activeUsers: this.state.activeUsers, systemLoad: this.state.systemLoad }) }'
},
Run to locate any remaining this.autoRefresh
references that should be this.state.autoRefresh
:
🏁 Script executed:
#!/bin/bash
rg -nP "this\.autoRefresh" packages/vue-generator/test -C2
Length of output: 1269
Align schema and expected output with updated SFC lifecycle logic
The test‐schema in mockData.js
and the expected Vue file still reference this.autoRefresh
and omit duplicate‐interval guards, restarting on activation, and a correct update log. Update both so that:
- startAutoRefresh guards on
!this.state.autoRefresh
and prevents multiple intervals - onActivated restarts the auto‐refresh
- onUpdated logs the actual metrics (
activeUsers
,systemLoad
)
Files to update:
• packages/vue-generator/test/testcases/generator/mockData.js
• packages/vue-generator/test/testcases/generator/expected/appdemo01/src/views/LifeCyclePage.vue
Apply these diffs:
--- mockData.js
+++ mockData.js
@@ methods: {
startAutoRefresh: {
type: 'JSFunction',
- value:
- 'function startAutoRefresh() { if (this.autoRefresh) { this.refreshInterval = setInterval(() => this.refreshData(), 30000); } }'
+ value:
+ 'function startAutoRefresh() { if (!this.state.autoRefresh) { return; } if (this.refreshInterval) { return; } this.refreshInterval = setInterval(() => this.refreshData(), 30000); }'
},
@@ lifeCycles: {
onActivated: {
type: 'JSFunction',
- value: 'function onActivated() { console.log("页面激活"); refreshData() }'
+ value: 'function onActivated() { console.log("页面激活"); this.refreshData(); this.startAutoRefresh() }'
},
@@
onUpdated: {
type: 'JSFunction',
- value: 'function onUpdated() { console.log("页面已更新", this.state.loginCount) }'
+ value: 'function onUpdated() { console.log("页面已更新", { activeUsers: this.state.activeUsers, systemLoad: this.state.systemLoad }) }'
},
--- expected/appdemo01/src/views/LifeCyclePage.vue
+++ expected/appdemo01/src/views/LifeCyclePage.vue
@@ const startAutoRefresh = wrap(function startAutoRefresh() {
- if (this.autoRefresh) {
- this.refreshInterval = setInterval(() => this.refreshData(), 30000)
- }
+ if (!this.state.autoRefresh) {
+ return
+ }
+ if (this.refreshInterval) {
+ return
+ }
+ this.refreshInterval = setInterval(() => this.refreshData(), 30000)
@@
- onActivated() {
- console.log("页面激活")
- this.refreshData()
- },
+ onActivated() {
+ console.log("页面激活")
+ this.refreshData()
+ this.startAutoRefresh()
+ },
@@
- onUpdated() {
- console.log("页面已更新", this.state.loginCount)
- },
+ onUpdated() {
+ console.log("页面已更新", { activeUsers: this.state.activeUsers, systemLoad: this.state.systemLoad })
+ },
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
{ | |
componentName: 'div', | |
fileName: 'LifeCyclePage', | |
meta: { | |
id: 'lifecycle-page', | |
isPage: true, | |
parentId: '0', | |
router: '/lifecycle' | |
}, | |
props: { | |
class: 'lifecycle-container' | |
}, | |
children: [ | |
{ | |
componentName: 'TinyCard', | |
props: { | |
title: '仪表板' | |
}, | |
children: [ | |
{ | |
componentName: 'div', | |
props: { | |
class: 'metrics-grid' | |
}, | |
children: [ | |
{ | |
componentName: 'div', | |
props: { | |
class: 'metric-item' | |
}, | |
children: [ | |
{ | |
componentName: 'Text', | |
props: { | |
text: { | |
type: 'JSExpression', | |
value: '"活跃用户: " + this.state.activeUsers' | |
} | |
} | |
} | |
] | |
}, | |
{ | |
componentName: 'div', | |
props: { | |
class: 'metric-item' | |
}, | |
children: [ | |
{ | |
componentName: 'Text', | |
props: { | |
text: { | |
type: 'JSExpression', | |
value: '"系统负载: " + this.state.systemLoad + "%"' | |
} | |
} | |
} | |
] | |
} | |
] | |
}, | |
{ | |
componentName: 'TinyButton', | |
props: { | |
type: 'success', | |
onClick: { | |
type: 'JSExpression', | |
value: 'this.refreshData' | |
} | |
}, | |
children: ['刷新数据'] | |
} | |
] | |
} | |
], | |
state: { | |
activeUsers: 0, | |
systemLoad: 0, | |
lastUpdateTime: null, | |
autoRefresh: true | |
}, | |
methods: { | |
refreshData: { | |
type: 'JSFunction', | |
value: 'function refreshData() { this.fetchMetrics(); this.state.lastUpdateTime = new Date(); }' | |
}, | |
fetchMetrics: { | |
type: 'JSFunction', | |
value: | |
'function fetchMetrics() { this.state.activeUsers = Math.floor(Math.random() * 1000); this.state.systemLoad = Math.floor(Math.random() * 100); }' | |
}, | |
startAutoRefresh: { | |
type: 'JSFunction', | |
value: | |
'function startAutoRefresh() { if (this.autoRefresh) { this.refreshInterval = setInterval(() => this.refreshData(), 30000); } }' | |
}, | |
stopAutoRefresh: { | |
type: 'JSFunction', | |
value: | |
'function stopAutoRefresh() { if (this.refreshInterval) { clearInterval(this.refreshInterval); this.refreshInterval = null; } }' | |
}, | |
handleVisibilityChange: { | |
type: 'JSFunction', | |
value: | |
'function handleVisibilityChange() { if (document.hidden) { this.stopAutoRefresh(); } else { this.startAutoRefresh(); } }' | |
} | |
}, | |
lifeCycles: { | |
onMounted: { | |
type: 'JSFunction', | |
value: | |
'function onMounted() { console.log("onMounted."); refreshData(); startAutoRefresh(); document.addEventListener("visibilitychange", handleVisibilityChange) }' | |
}, | |
onUnmounted: { | |
type: 'JSFunction', | |
value: | |
'function onUnmounted() { console.log("onUnmounted."); stopAutoRefresh(); document.removeEventListener("visibilitychange", handleVisibilityChange) }' | |
}, | |
onActivated: { | |
type: 'JSFunction', | |
value: 'function onActivated() { console.log("页面激活"); refreshData() }' | |
}, | |
onDeactivated: { | |
type: 'JSFunction', | |
value: 'function onDeactivated() { console.log("页面停用"); stopAutoRefresh() }' | |
}, | |
onUpdated: { | |
type: 'JSFunction', | |
value: 'function onUpdated() { console.log("页面已更新", this.state.loginCount) }' | |
}, | |
onBeforeMount: { | |
type: 'JSFunction', | |
value: 'function onBeforeMount() { console.log("onBeforeMount.") }' | |
} | |
} | |
methods: { | |
// … other methods … | |
startAutoRefresh: { | |
type: 'JSFunction', | |
value: | |
'function startAutoRefresh() { if (!this.state.autoRefresh) { return; } if (this.refreshInterval) { return; } this.refreshInterval = setInterval(() => this.refreshData(), 30000); }' | |
}, | |
// … other methods … | |
}, | |
lifeCycles: { | |
// … other lifecycle hooks … | |
onActivated: { | |
type: 'JSFunction', | |
value: 'function onActivated() { console.log("页面激活"); this.refreshData(); this.startAutoRefresh() }' | |
}, | |
onUpdated: { | |
type: 'JSFunction', | |
value: 'function onUpdated() { console.log("页面已更新", { activeUsers: this.state.activeUsers, systemLoad: this.state.systemLoad }) }' | |
}, | |
// … other lifecycle hooks … | |
} |
expect(content).toContain("console.log('Component mounted')") | ||
expect(content).toContain('vue.onUnmounted(') | ||
expect(content).toContain("console.log('Component unmounted')") | ||
expect(content).toContain('vue.onUpdated(') | ||
expect(content).toContain("console.log('Component updated')") | ||
expect(content).toContain('vue.onBeforeMount(') | ||
expect(content).toContain("console.log('Before mount')") | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assertions are brittle on quote style; prefer regex to accept single/double quotes.
Formatting may switch between '
and "
depending on prettier/eslint config. Use regex to avoid false negatives.
- expect(content).toContain("console.log('Component mounted')")
+ expect(content).toMatch(/console\.log\(['"]Component mounted['"]\)/)
- expect(content).toContain("console.log('Component unmounted')")
+ expect(content).toMatch(/console\.log\(['"]Component unmounted['"]\)/)
- expect(content).toContain("console.log('Component updated')")
+ expect(content).toMatch(/console\.log\(['"]Component updated['"]\)/)
- expect(content).toContain("console.log('Before mount')")
+ expect(content).toMatch(/console\.log\(['"]Before mount['"]\)/)
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
expect(content).toContain("console.log('Component mounted')") | |
expect(content).toContain('vue.onUnmounted(') | |
expect(content).toContain("console.log('Component unmounted')") | |
expect(content).toContain('vue.onUpdated(') | |
expect(content).toContain("console.log('Component updated')") | |
expect(content).toContain('vue.onBeforeMount(') | |
expect(content).toContain("console.log('Before mount')") | |
expect(content).toMatch(/console\.log\(['"]Component mounted['"]\)/) | |
expect(content).toContain('vue.onUnmounted(') | |
expect(content).toMatch(/console\.log\(['"]Component unmounted['"]\)/) | |
expect(content).toContain('vue.onUpdated(') | |
expect(content).toMatch(/console\.log\(['"]Component updated['"]\)/) | |
expect(content).toContain('vue.onBeforeMount(') | |
expect(content).toMatch(/console\.log\(['"]Before mount['"]\)/) |
🤖 Prompt for AI Agents
In packages/vue-generator/test/testcases/lifecycle/lifecycle.test.js around
lines 24 to 31, the assertions compare exact string quotes which makes the test
brittle; replace the direct toContain checks with regex-based assertions that
accept either single or double quotes (e.g., use RegExp objects or
expect(content).toMatch(/pattern/)) for each lifecycle log and hook check so
tests pass regardless of quote style; update each expect to use a regex that
matches both ' and " variants while keeping the same text being matched.
test('should handle lifecycle in component props', async () => { | ||
const instance = generateApp() | ||
const res = await instance.generate(lifecycleTestSchema) | ||
const { genResult, errors } = res | ||
|
||
expect(errors).toHaveLength(0) | ||
|
||
const vueFile = genResult.find((file) => file.fileName === 'LifecycleTestPage.vue') | ||
expect(vueFile).toBeDefined() | ||
|
||
const content = vueFile.fileContent | ||
|
||
// 验证生命周期钩子不会作为属性绑定到组件上 | ||
expect(content).not.toContain('onMounted=') | ||
expect(content).not.toContain(':onMounted=') | ||
|
||
// 而是应该在script中 | ||
expect(content).toContain('vue.onMounted(') | ||
expect(content).toContain("console.log('Component mounted')") | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Exercise mixedLifecycleSchema to validate prop-level lifecycle hooks aren’t emitted as attributes.
Currently this test reuses lifecycleTestSchema
, which doesn’t define lifecycle hooks on child component props. Switching to mixedLifecycleSchema
strengthens coverage and intention.
-import { lifecycleTestSchema, complexLifecycleSchema } from './mockData'
+import { lifecycleTestSchema, complexLifecycleSchema, mixedLifecycleSchema } from './mockData'
@@
- test('should handle lifecycle in component props', async () => {
+ test('should handle lifecycle in component props', async () => {
const instance = generateApp()
- const res = await instance.generate(lifecycleTestSchema)
+ const res = await instance.generate(mixedLifecycleSchema)
const { genResult, errors } = res
@@
- const vueFile = genResult.find((file) => file.fileName === 'LifecycleTestPage.vue')
+ const vueFile = genResult.find((file) => file.fileName === 'MixedLifecyclePage.vue')
@@
- // 验证生命周期钩子不会作为属性绑定到组件上
+ // 验证生命周期钩子不会作为属性绑定到组件上
expect(content).not.toContain('onMounted=')
expect(content).not.toContain(':onMounted=')
- // 而是应该在script中
- expect(content).toContain('vue.onMounted(')
- expect(content).toContain("console.log('Component mounted')")
+ // 而是应该在script中(并触发子组件对应方法)
+ expect(content).toContain('vue.onMounted(')
+ expect(content).toMatch(/focusInput\s*\(\)/)
+ // 同时不应出现 onBeforeMount 作为属性
+ expect(content).not.toContain('onBeforeMount=')
+ expect(content).not.toContain(':onBeforeMount=')
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
test('should handle lifecycle in component props', async () => { | |
const instance = generateApp() | |
const res = await instance.generate(lifecycleTestSchema) | |
const { genResult, errors } = res | |
expect(errors).toHaveLength(0) | |
const vueFile = genResult.find((file) => file.fileName === 'LifecycleTestPage.vue') | |
expect(vueFile).toBeDefined() | |
const content = vueFile.fileContent | |
// 验证生命周期钩子不会作为属性绑定到组件上 | |
expect(content).not.toContain('onMounted=') | |
expect(content).not.toContain(':onMounted=') | |
// 而是应该在script中 | |
expect(content).toContain('vue.onMounted(') | |
expect(content).toContain("console.log('Component mounted')") | |
}) | |
++ packages/vue-generator/test/testcases/lifecycle/lifecycle.test.js | |
@@ | |
import { lifecycleTestSchema, complexLifecycleSchema, mixedLifecycleSchema } from './mockData' | |
@@ | |
test('should handle lifecycle in component props', async () => { | |
const instance = generateApp() | |
const res = await instance.generate(mixedLifecycleSchema) | |
const { genResult, errors } = res | |
@@ | |
const vueFile = genResult.find((file) => file.fileName === 'MixedLifecyclePage.vue') | |
@@ | |
const content = vueFile.fileContent | |
// 验证生命周期钩子不会作为属性绑定到组件上 | |
expect(content).not.toContain('onMounted=') | |
expect(content).not.toContain(':onMounted=') | |
- // 而是应该在script中 | |
- expect(content).toContain('vue.onMounted(') | |
// 而是应该在script中(并触发子组件对应方法) | |
expect(content).toContain('vue.onMounted(') | |
expect(content).toMatch(/focusInput\s*\(\)/) | |
// 同时不应出现 onBeforeMount 作为属性 | |
expect(content).not.toContain('onBeforeMount=') | |
expect(content).not.toContain(':onBeforeMount=') | |
}) |
🤖 Prompt for AI Agents
packages/vue-generator/test/testcases/lifecycle/lifecycle.test.js around lines
80 to 99: the test currently calls instance.generate(lifecycleTestSchema) but
should exercise mixedLifecycleSchema to validate prop-level lifecycle hooks
aren’t emitted as attributes; change the generate call to use
mixedLifecycleSchema (and update any variable names if needed), and if
mixedLifecycleSchema isn’t imported in this test file, add the import at the top
so the test uses the intended schema.
onErrorCaptured: { | ||
type: 'JSFunction', | ||
value: 'function onErrorCaptured() { handleError(error); reportError(error) }' | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: onErrorCaptured uses undeclared variable “error”. Pass the parameter explicitly.
onErrorCaptured
receives the error as its first parameter; the current function references error
without declaring it, which would throw at runtime in generated code.
Apply this patch:
- onErrorCaptured: {
- type: 'JSFunction',
- value: 'function onErrorCaptured() { handleError(error); reportError(error) }'
- }
+ onErrorCaptured: {
+ type: 'JSFunction',
+ value: 'function onErrorCaptured(error) { handleError(error); reportError(error) }'
+ }
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
onErrorCaptured: { | |
type: 'JSFunction', | |
value: 'function onErrorCaptured() { handleError(error); reportError(error) }' | |
} | |
onErrorCaptured: { | |
type: 'JSFunction', | |
value: 'function onErrorCaptured(error) { handleError(error); reportError(error) }' | |
} |
🤖 Prompt for AI Agents
In packages/vue-generator/test/testcases/lifecycle/mockData.js around lines 153
to 156, the onErrorCaptured function references an undeclared variable "error";
change the function signature to accept the error parameter (e.g. function
onErrorCaptured(error) { ... }) and use that parameter in the body
(handleError(error); reportError(error)) so the generated code receives the
error value instead of referencing an undefined variable.
English | 简体中文
PR
PR Checklist
Please check if your PR fulfills the following requirements:
PR Type
What kind of change does this PR introduce?
Background and solution
The generator lacked targeted tests and mock data for:
This gap made it hard to detect regressions when adjusting the generator logic around bindings or hook handling.
What is the current behavior?
What is the new behavior?
Does this PR introduce a breaking change?
Summary by CodeRabbit
New Features
Improvements
Routing
Dependency Updates