diff --git a/packages/runtime-core/__tests__/apiOptions.spec.ts b/packages/runtime-core/__tests__/apiOptions.spec.ts
index 2ba4bc3d..8c3fbdf2 100644
--- a/packages/runtime-core/__tests__/apiOptions.spec.ts
+++ b/packages/runtime-core/__tests__/apiOptions.spec.ts
@@ -8,7 +8,8 @@ import {
nextTick,
renderToString,
ref,
- createComponent
+ createComponent,
+ mockWarn
} from '@vue/runtime-test'
describe('api: options', () => {
@@ -505,4 +506,37 @@ describe('api: options', () => {
await nextTick()
expect(serializeInner(root)).toBe(`
1,1,3
`)
})
+
+ describe('warnings', () => {
+ mockWarn()
+
+ test('Expected a function as watch handler', () => {
+ const Comp = {
+ watch: {
+ foo: 'notExistingMethod'
+ },
+ render() {}
+ }
+
+ const root = nodeOps.createElement('div')
+ render(h(Comp), root)
+
+ expect(
+ 'Invalid watch handler specified by key "notExistingMethod"'
+ ).toHaveBeenWarned()
+ })
+
+ test('Invalid watch option', () => {
+ const Comp = {
+ watch: { foo: true },
+ render() {}
+ }
+
+ const root = nodeOps.createElement('div')
+ // @ts-ignore
+ render(h(Comp), root)
+
+ expect('Invalid watch option: "foo"').toHaveBeenWarned()
+ })
+ })
})
diff --git a/packages/runtime-core/__tests__/componentProxy.spec.ts b/packages/runtime-core/__tests__/componentProxy.spec.ts
new file mode 100644
index 00000000..b102889a
--- /dev/null
+++ b/packages/runtime-core/__tests__/componentProxy.spec.ts
@@ -0,0 +1,40 @@
+import { createApp, nodeOps, mockWarn } from '@vue/runtime-test'
+
+const createTestInstance = (props?: any) => {
+ const component = {
+ render() {}
+ }
+ const root = nodeOps.createElement('div')
+ return createApp().mount(component, root, props)
+}
+
+describe('component proxy', () => {
+ describe('warnings', () => {
+ mockWarn()
+
+ test('Attempting to mutate public property', () => {
+ const app = createTestInstance()
+
+ try {
+ app.$props = { foo: 'bar' }
+ } catch {
+ expect(
+ 'Attempting to mutate public property "$props". ' +
+ 'Properties starting with $ are reserved and readonly.'
+ ).toHaveBeenWarned()
+ }
+ })
+
+ test('Attempting to mutate prop', () => {
+ const app = createTestInstance({ foo: 'foo' })
+
+ try {
+ app.foo = 'bar'
+ } catch {
+ expect(
+ 'Attempting to mutate prop "foo". Props are readonly.'
+ ).toHaveBeenWarned()
+ }
+ })
+ })
+})
diff --git a/packages/runtime-core/src/apiOptions.ts b/packages/runtime-core/src/apiOptions.ts
index fd3f2ff8..fe91d300 100644
--- a/packages/runtime-core/src/apiOptions.ts
+++ b/packages/runtime-core/src/apiOptions.ts
@@ -267,7 +267,7 @@ export function applyOptions(
if (isFunction(handler)) {
watch(getter, handler as WatchHandler)
} else if (__DEV__) {
- // TODO warn invalid watch handler path
+ warn(`Invalid watch handler specified by key "${raw}"`, handler)
}
} else if (isFunction(raw)) {
watch(getter, raw.bind(ctx))
@@ -275,7 +275,7 @@ export function applyOptions(
// TODO 2.x compat
watch(getter, raw.handler.bind(ctx), raw)
} else if (__DEV__) {
- // TODO warn invalid watch options
+ warn(`Invalid watch option: "${key}"`)
}
}
}
diff --git a/packages/runtime-core/src/componentProxy.ts b/packages/runtime-core/src/componentProxy.ts
index e02496ed..b175a838 100644
--- a/packages/runtime-core/src/componentProxy.ts
+++ b/packages/runtime-core/src/componentProxy.ts
@@ -4,6 +4,7 @@ import { instanceWatch } from './apiWatch'
import { EMPTY_OBJ, hasOwn, globalsWhitelist } from '@vue/shared'
import { ExtractComputedReturns } from './apiOptions'
import { UnwrapRef } from '@vue/reactivity'
+import { warn } from './warning'
// public properties exposed on the proxy, which is used as the render context
// in templates (as `this` in the render option)
@@ -91,10 +92,16 @@ export const PublicInstanceProxyHandlers = {
} else if (hasOwn(renderContext, key)) {
renderContext[key] = value
} else if (key[0] === '$' && key.slice(1) in target) {
- // TODO warn attempt of mutating public property
+ __DEV__ &&
+ warn(
+ `Attempting to mutate public property "${key}". ` +
+ `Properties starting with $ are reserved and readonly.`,
+ target
+ )
return false
} else if (key in target.props) {
- // TODO warn attempt of mutating prop
+ __DEV__ &&
+ warn(`Attempting to mutate prop "${key}". Props are readonly.`, target)
return false
} else {
target.user[key] = value