]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
test: add all tests for `v-on` (#52)
authorRizumu Ayaka <rizumu@ayaka.moe>
Tue, 12 Dec 2023 07:58:07 +0000 (15:58 +0800)
committerGitHub <noreply@github.com>
Tue, 12 Dec 2023 07:58:07 +0000 (15:58 +0800)
packages/compiler-vapor/__tests__/transforms/__snapshots__/vOn.spec.ts.snap
packages/compiler-vapor/__tests__/transforms/vHtml.spec.ts
packages/compiler-vapor/__tests__/transforms/vOn.spec.ts
packages/compiler-vapor/src/transforms/vOn.ts

index df47f1598b379254850968ca417f384e045a4ba1..b1c8449cc9eefce866d37dc693fc14cfec789a66 100644 (file)
@@ -1,5 +1,31 @@
 // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
 
+exports[`v-on > case conversion for kebab-case events 1`] = `
+"import { template as _template, children as _children, on as _on } from 'vue/vapor';
+
+export function render(_ctx) {
+  const t0 = _template("<div></div>")
+  const n0 = t0()
+  const { 0: [n1],} = _children(n0)
+  _on(n1, "fooBar", (...args) => (_ctx.onMount && _ctx.onMount(...args)))
+  return n0
+}"
+`;
+
+exports[`v-on > dynamic arg 1`] = `
+"import { template as _template, children as _children, effect as _effect, on as _on } from 'vue/vapor';
+
+export function render(_ctx) {
+  const t0 = _template("<div></div>")
+  const n0 = t0()
+  const { 0: [n1],} = _children(n0)
+  _effect(() => {
+    _on(n1, _ctx.event, (...args) => (_ctx.handler && _ctx.handler(...args)))
+  })
+  return n0
+}"
+`;
+
 exports[`v-on > event modifier 1`] = `
 "import { template as _template, children as _children, on as _on, withModifiers as _withModifiers, withKeys as _withKeys } from 'vue/vapor';
 
@@ -33,6 +59,131 @@ export function render(_ctx) {
 }"
 `;
 
+exports[`v-on > should not wrap keys guard if no key modifier is present 1`] = `
+"import { template as _template, children as _children, on as _on, withModifiers as _withModifiers } from 'vue/vapor';
+
+export function render(_ctx) {
+  const t0 = _template("<div></div>")
+  const n0 = t0()
+  const { 0: [n1],} = _children(n0)
+  _on(n1, "keyup", _withModifiers((...args) => (_ctx.test && _ctx.test(...args)), ["exact"]))
+  return n0
+}"
+`;
+
+exports[`v-on > should support multiple events and modifiers options w/ prefixIdentifiers: true 1`] = `
+"import { template as _template, children as _children, on as _on, withModifiers as _withModifiers, withKeys as _withKeys } from 'vue/vapor';
+
+export function render(_ctx) {
+  const t0 = _template("<div></div>")
+  const n0 = t0()
+  const { 0: [n1],} = _children(n0)
+  _on(n1, "click", _withModifiers((...args) => (_ctx.test && _ctx.test(...args)), ["stop"]))
+  _on(n1, "keyup", _withKeys((...args) => (_ctx.test && _ctx.test(...args)), ["enter"]))
+  return n0
+}"
+`;
+
+exports[`v-on > should support multiple modifiers and event options w/ prefixIdentifiers: true 1`] = `
+"import { template as _template, children as _children, on as _on, withModifiers as _withModifiers } from 'vue/vapor';
+
+export function render(_ctx) {
+  const t0 = _template("<div></div>")
+  const n0 = t0()
+  const { 0: [n1],} = _children(n0)
+  _on(n1, "click", _withModifiers((...args) => (_ctx.test && _ctx.test(...args)), ["stop", "prevent"]), { capture: true, once: true })
+  return n0
+}"
+`;
+
+exports[`v-on > should transform click.middle 1`] = `
+"import { template as _template, children as _children, on as _on, withModifiers as _withModifiers } from 'vue/vapor';
+
+export function render(_ctx) {
+  const t0 = _template("<div></div>")
+  const n0 = t0()
+  const { 0: [n1],} = _children(n0)
+  _on(n1, "mouseup", _withModifiers((...args) => (_ctx.test && _ctx.test(...args)), ["middle"]))
+  return n0
+}"
+`;
+
+exports[`v-on > should transform click.middle 2`] = `
+"import { template as _template, children as _children, effect as _effect, on as _on, withModifiers as _withModifiers } from 'vue/vapor';
+
+export function render(_ctx) {
+  const t0 = _template("<div></div>")
+  const n0 = t0()
+  const { 0: [n1],} = _children(n0)
+  _effect(() => {
+    _on(n1, (_ctx.event) === "click" ? "mouseup" : (_ctx.event), _withModifiers((...args) => (_ctx.test && _ctx.test(...args)), ["middle"]))
+  })
+  return n0
+}"
+`;
+
+exports[`v-on > should transform click.right 1`] = `
+"import { template as _template, children as _children, on as _on, withModifiers as _withModifiers } from 'vue/vapor';
+
+export function render(_ctx) {
+  const t0 = _template("<div></div>")
+  const n0 = t0()
+  const { 0: [n1],} = _children(n0)
+  _on(n1, "contextmenu", _withModifiers((...args) => (_ctx.test && _ctx.test(...args)), ["right"]))
+  return n0
+}"
+`;
+
+exports[`v-on > should transform click.right 2`] = `
+"import { template as _template, children as _children, effect as _effect, on as _on, withKeys as _withKeys, withModifiers as _withModifiers } from 'vue/vapor';
+
+export function render(_ctx) {
+  const t0 = _template("<div></div>")
+  const n0 = t0()
+  const { 0: [n1],} = _children(n0)
+  _effect(() => {
+    _on(n1, (_ctx.event) === "click" ? "contextmenu" : (_ctx.event), _withKeys(_withModifiers((...args) => (_ctx.test && _ctx.test(...args)), ["right"]), ["right"]))
+  })
+  return n0
+}"
+`;
+
+exports[`v-on > should wrap as function if expression is inline statement 1`] = `
+"import { template as _template, children as _children, on as _on } from 'vue/vapor';
+
+export function render(_ctx) {
+  const t0 = _template("<div></div>")
+  const n0 = t0()
+  const { 0: [n1],} = _children(n0)
+  _on(n1, "click", (...args) => (_ctx.i++ && _ctx.i++(...args)))
+  return n0
+}"
+`;
+
+exports[`v-on > should wrap keys guard for keyboard events or dynamic events 1`] = `
+"import { template as _template, children as _children, on as _on, withKeys as _withKeys, withModifiers as _withModifiers } from 'vue/vapor';
+
+export function render(_ctx) {
+  const t0 = _template("<div></div>")
+  const n0 = t0()
+  const { 0: [n1],} = _children(n0)
+  _on(n1, "keydown", _withKeys(_withModifiers((...args) => (_ctx.test && _ctx.test(...args)), ["stop", "ctrl"]), ["a"]), { capture: true })
+  return n0
+}"
+`;
+
+exports[`v-on > should wrap keys guard for static key event w/ left/right modifiers 1`] = `
+"import { template as _template, children as _children, on as _on, withKeys as _withKeys } from 'vue/vapor';
+
+export function render(_ctx) {
+  const t0 = _template("<div></div>")
+  const n0 = t0()
+  const { 0: [n1],} = _children(n0)
+  _on(n1, "keyup", _withKeys((...args) => (_ctx.test && _ctx.test(...args)), ["left"]))
+  return n0
+}"
+`;
+
 exports[`v-on > simple expression 1`] = `
 "import { template as _template, children as _children, on as _on } from 'vue/vapor';
 
index 0e38f9e452b2bcb268e2722f71c53fa2d4fcba31..c9c8d8132ac13a1926937be11e93e017e20af617 100644 (file)
@@ -80,6 +80,9 @@ describe('v-html', () => {
     expect(ir.vaporHelpers).contains('setHtml')
     expect(ir.helpers.size).toBe(0)
 
+    // children should have been removed
+    expect(ir.template).toMatchObject([{ template: '<div></div>' }])
+
     expect(ir.operation).toEqual([])
     expect(ir.effect).toMatchObject([
       {
@@ -109,6 +112,8 @@ describe('v-html', () => {
     ])
 
     expect(code).matchSnapshot()
+    // children should have been removed
+    expect(code).contains('template("<div></div>")')
   })
 
   test('should raise error if has no expression', () => {
index 353396ba1d0311a13315e4cb2a2ce735dddf4f99..c731f30d0a24970ec4b6936174b5d01a77747393 100644 (file)
@@ -1,45 +1,72 @@
-import { type RootNode, BindingTypes, ErrorCodes } from '@vue/compiler-dom'
-import { type CompilerOptions, compile as _compile } from '../../src'
+import { BindingTypes, ErrorCodes, parse, NodeTypes } from '@vue/compiler-dom'
+import {
+  type CompilerOptions,
+  compile as _compile,
+  RootIRNode,
+  transform,
+  generate,
+  IRNodeTypes,
+} from '../../src'
 
-function compile(template: string | RootNode, options: CompilerOptions = {}) {
-  let { code } = _compile(template, {
-    ...options,
-    mode: 'module',
+import { transformVOn } from '../../src/transforms/vOn'
+import { transformElement } from '../../src/transforms/transformElement'
+
+function compileWithVOn(
+  template: string,
+  options: CompilerOptions = {},
+): {
+  ir: RootIRNode
+  code: string
+} {
+  const ast = parse(template, { prefixIdentifiers: true, ...options })
+  const ir = transform(ast, {
+    nodeTransforms: [transformElement],
+    directiveTransforms: {
+      on: transformVOn,
+    },
     prefixIdentifiers: true,
+    ...options,
   })
-  return code
+  const { code } = generate(ir, { prefixIdentifiers: true, ...options })
+  return { ir, code }
 }
 
 describe('v-on', () => {
   test('simple expression', () => {
-    const code = compile(`<div @click="handleClick"></div>`, {
+    const { code, ir } = compileWithVOn(`<div @click="handleClick"></div>`, {
       bindingMetadata: {
         handleClick: BindingTypes.SETUP_CONST,
       },
     })
-    expect(code).matchSnapshot()
-  })
 
-  test('should error if no expression AND no modifier', () => {
-    const onError = vi.fn()
-    compile(`<div v-on:click />`, { onError })
-    expect(onError.mock.calls[0][0]).toMatchObject({
-      code: ErrorCodes.X_V_ON_NO_EXPRESSION,
-      loc: {
-        start: {
-          line: 1,
-          column: 6,
+    expect(ir.vaporHelpers).contains('on')
+    expect(ir.helpers.size).toBe(0)
+    expect(ir.effect).toEqual([])
+
+    expect(ir.operation).toMatchObject([
+      {
+        type: IRNodeTypes.SET_EVENT,
+        element: 1,
+        key: {
+          type: NodeTypes.SIMPLE_EXPRESSION,
+          content: 'click',
+          isStatic: true,
         },
-        end: {
-          line: 1,
-          column: 16,
+        value: {
+          type: NodeTypes.SIMPLE_EXPRESSION,
+          content: 'handleClick',
+          isStatic: false,
         },
+        modifiers: { keys: [], nonKeys: [], options: [] },
+        keyOverride: undefined,
       },
-    })
+    ])
+
+    expect(code).matchSnapshot()
   })
 
   test('event modifier', () => {
-    const code = compile(
+    const { code } = compileWithVOn(
       `<a @click.stop="handleEvent"></a>
         <form @submit.prevent="handleEvent"></form>
         <a @click.stop.prevent="handleEvent"></a>
@@ -70,4 +97,364 @@ describe('v-on', () => {
     )
     expect(code).matchSnapshot()
   })
+
+  test('dynamic arg', () => {
+    const { code, ir } = compileWithVOn(`<div v-on:[event]="handler"/>`)
+
+    expect(ir.vaporHelpers).contains('on')
+    expect(ir.vaporHelpers).contains('effect')
+    expect(ir.helpers.size).toBe(0)
+    expect(ir.operation).toEqual([])
+
+    expect(ir.effect[0].operations[0]).toMatchObject({
+      type: IRNodeTypes.SET_EVENT,
+      element: 1,
+      key: {
+        type: NodeTypes.SIMPLE_EXPRESSION,
+        content: 'event',
+        isStatic: false,
+      },
+      value: {
+        type: NodeTypes.SIMPLE_EXPRESSION,
+        content: 'handler',
+        isStatic: false,
+      },
+    })
+
+    expect(code).matchSnapshot()
+  })
+
+  test.todo('dynamic arg with prefixing')
+  test.todo('dynamic arg with complex exp prefixing')
+  test.todo('should wrap as function if expression is inline statement')
+  test.todo('should handle multiple inline statement')
+  test.todo('should handle multi-line statement')
+  test.todo('inline statement w/ prefixIdentifiers: true')
+  test.todo('multiple inline statements w/ prefixIdentifiers: true')
+  test.todo(
+    'should NOT wrap as function if expression is already function expression',
+  )
+  test.todo(
+    'should NOT wrap as function if expression is already function expression (with Typescript)',
+  )
+  test.todo(
+    'should NOT wrap as function if expression is already function expression (with newlines)',
+  )
+  test.todo(
+    'should NOT wrap as function if expression is already function expression (with newlines + function keyword)',
+  )
+  test.todo(
+    'should NOT wrap as function if expression is complex member expression',
+  )
+  test.todo('complex member expression w/ prefixIdentifiers: true')
+  test.todo('function expression w/ prefixIdentifiers: true')
+
+  test('should error if no expression AND no modifier', () => {
+    const onError = vi.fn()
+    compileWithVOn(`<div v-on:click />`, { onError })
+    expect(onError.mock.calls[0][0]).toMatchObject({
+      code: ErrorCodes.X_V_ON_NO_EXPRESSION,
+      loc: {
+        start: {
+          line: 1,
+          column: 6,
+        },
+        end: {
+          line: 1,
+          column: 16,
+        },
+      },
+    })
+  })
+
+  test('should NOT error if no expression but has modifier', () => {
+    const onError = vi.fn()
+    compileWithVOn(`<div v-on:click.prevent />`, { onError })
+    expect(onError).not.toHaveBeenCalled()
+  })
+
+  test('case conversion for kebab-case events', () => {
+    const { ir, code } = compileWithVOn(`<div v-on:foo-bar="onMount"/>`)
+
+    expect(ir.vaporHelpers).contains('on')
+    expect(ir.helpers.size).toBe(0)
+    expect(ir.effect).toEqual([])
+
+    expect(ir.operation).toMatchObject([
+      {
+        type: IRNodeTypes.SET_EVENT,
+        element: 1,
+        key: {
+          type: NodeTypes.SIMPLE_EXPRESSION,
+          content: 'fooBar',
+          isStatic: true,
+        },
+        value: {
+          type: NodeTypes.SIMPLE_EXPRESSION,
+          content: 'onMount',
+          isStatic: false,
+        },
+      },
+    ])
+
+    expect(code).matchSnapshot()
+    expect(code).contains('fooBar')
+  })
+
+  test('error for vnode hooks', () => {
+    const onError = vi.fn()
+    compileWithVOn(`<div v-on:vnode-mounted="onMount"/>`, { onError })
+    expect(onError.mock.calls[0][0]).toMatchObject({
+      code: ErrorCodes.X_VNODE_HOOKS,
+      loc: {
+        start: {
+          line: 1,
+          column: 11,
+        },
+        end: {
+          line: 1,
+          column: 24,
+        },
+      },
+    })
+  })
+
+  test.todo('vue: prefixed events')
+
+  test('should support multiple modifiers and event options w/ prefixIdentifiers: true', () => {
+    const { code, ir } = compileWithVOn(
+      `<div @click.stop.prevent.capture.once="test"/>`,
+      {
+        prefixIdentifiers: true,
+      },
+    )
+
+    expect(ir.vaporHelpers).contains('on')
+    expect(ir.vaporHelpers).contains('withModifiers')
+
+    expect(ir.operation).toMatchObject([
+      {
+        type: IRNodeTypes.SET_EVENT,
+        value: {
+          type: NodeTypes.SIMPLE_EXPRESSION,
+          content: 'test',
+          isStatic: false,
+        },
+        modifiers: {
+          keys: [],
+          nonKeys: ['stop', 'prevent'],
+          options: ['capture', 'once'],
+        },
+        keyOverride: undefined,
+      },
+    ])
+
+    expect(code).matchSnapshot()
+    expect(code).contains('_withModifiers')
+    expect(code).contains('["stop", "prevent"]')
+    expect(code).contains('{ capture: true, once: true }')
+  })
+
+  test('should support multiple events and modifiers options w/ prefixIdentifiers: true', () => {
+    const { code, ir } = compileWithVOn(
+      `<div @click.stop="test" @keyup.enter="test" />`,
+      {
+        prefixIdentifiers: true,
+      },
+    )
+
+    expect(ir.operation).toMatchObject([
+      {
+        type: IRNodeTypes.SET_EVENT,
+        key: {
+          type: NodeTypes.SIMPLE_EXPRESSION,
+          content: 'click',
+          isStatic: true,
+        },
+        value: {
+          type: NodeTypes.SIMPLE_EXPRESSION,
+          content: 'test',
+          isStatic: false,
+        },
+        modifiers: {
+          keys: [],
+          nonKeys: ['stop'],
+          options: [],
+        },
+      },
+      {
+        type: IRNodeTypes.SET_EVENT,
+        key: {
+          type: NodeTypes.SIMPLE_EXPRESSION,
+          content: 'keyup',
+          isStatic: true,
+        },
+        value: {
+          type: NodeTypes.SIMPLE_EXPRESSION,
+          content: 'test',
+          isStatic: false,
+        },
+        modifiers: {
+          keys: ['enter'],
+          nonKeys: [],
+          options: [],
+        },
+      },
+    ])
+
+    expect(code).matchSnapshot()
+    expect(code).contains(
+      '_on(n1, "click", _withModifiers((...args) => (_ctx.test && _ctx.test(...args)), ["stop"]))',
+    )
+    expect(code).contains(
+      '_on(n1, "keyup", _withKeys((...args) => (_ctx.test && _ctx.test(...args)), ["enter"]))',
+    )
+  })
+
+  test('should wrap keys guard for keyboard events or dynamic events', () => {
+    const { code, ir } = compileWithVOn(
+      `<div @keydown.stop.capture.ctrl.a="test"/>`,
+      {
+        prefixIdentifiers: true,
+      },
+    )
+
+    expect(ir.operation).toMatchObject([
+      {
+        type: IRNodeTypes.SET_EVENT,
+        element: 1,
+        key: {
+          type: NodeTypes.SIMPLE_EXPRESSION,
+          content: 'keydown',
+          isStatic: true,
+        },
+        value: {
+          type: NodeTypes.SIMPLE_EXPRESSION,
+          content: 'test',
+          isStatic: false,
+        },
+        modifiers: {
+          keys: ['a'],
+          nonKeys: ['stop', 'ctrl'],
+          options: ['capture'],
+        },
+      },
+    ])
+
+    expect(code).matchSnapshot()
+  })
+
+  test('should not wrap keys guard if no key modifier is present', () => {
+    const { code, ir } = compileWithVOn(`<div @keyup.exact="test"/>`, {
+      prefixIdentifiers: true,
+    })
+    expect(ir.operation).toMatchObject([
+      {
+        type: IRNodeTypes.SET_EVENT,
+        modifiers: { nonKeys: ['exact'] },
+      },
+    ])
+
+    expect(code).matchSnapshot()
+  })
+
+  test('should wrap keys guard for static key event w/ left/right modifiers', () => {
+    const { code, ir } = compileWithVOn(`<div @keyup.left="test"/>`, {
+      prefixIdentifiers: true,
+    })
+
+    expect(ir.operation).toMatchObject([
+      {
+        type: IRNodeTypes.SET_EVENT,
+        modifiers: { keys: ['left'] },
+      },
+    ])
+
+    expect(code).matchSnapshot()
+  })
+
+  test.todo('should wrap both for dynamic key event w/ left/right modifiers')
+
+  test('should transform click.right', () => {
+    const { code, ir } = compileWithVOn(`<div @click.right="test"/>`)
+    expect(ir.operation).toMatchObject([
+      {
+        type: IRNodeTypes.SET_EVENT,
+        key: {
+          type: NodeTypes.SIMPLE_EXPRESSION,
+          content: 'contextmenu',
+          isStatic: true,
+        },
+        modifiers: { nonKeys: ['right'] },
+        keyOverride: undefined,
+      },
+    ])
+
+    expect(code).matchSnapshot()
+    expect(code).contains('"contextmenu"')
+
+    // dynamic
+    const { code: code2, ir: ir2 } = compileWithVOn(
+      `<div @[event].right="test"/>`,
+    )
+    expect(ir2.effect[0].operations).toMatchObject([
+      {
+        type: IRNodeTypes.SET_EVENT,
+        key: {
+          type: NodeTypes.SIMPLE_EXPRESSION,
+          content: 'event',
+          isStatic: false,
+        },
+        modifiers: { nonKeys: ['right'] },
+        keyOverride: ['click', 'contextmenu'],
+      },
+    ])
+
+    expect(code2).matchSnapshot()
+    expect(code2).contains(
+      '(_ctx.event) === "click" ? "contextmenu" : (_ctx.event)',
+    )
+  })
+
+  test('should transform click.middle', () => {
+    const { code, ir } = compileWithVOn(`<div @click.middle="test"/>`)
+    expect(ir.operation).toMatchObject([
+      {
+        type: IRNodeTypes.SET_EVENT,
+        key: {
+          type: NodeTypes.SIMPLE_EXPRESSION,
+          content: 'mouseup',
+          isStatic: true,
+        },
+        modifiers: { nonKeys: ['middle'] },
+        keyOverride: undefined,
+      },
+    ])
+
+    expect(code).matchSnapshot()
+    expect(code).contains('"mouseup"')
+
+    // dynamic
+    const { code: code2, ir: ir2 } = compileWithVOn(
+      `<div @[event].middle="test"/>`,
+    )
+
+    expect(ir2.effect[0].operations).toMatchObject([
+      {
+        type: IRNodeTypes.SET_EVENT,
+        key: {
+          type: NodeTypes.SIMPLE_EXPRESSION,
+          content: 'event',
+          isStatic: false,
+        },
+        modifiers: { nonKeys: ['middle'] },
+        keyOverride: ['click', 'mouseup'],
+      },
+    ])
+
+    expect(code2).matchSnapshot()
+    expect(code2).contains(
+      '(_ctx.event) === "click" ? "mouseup" : (_ctx.event)',
+    )
+  })
 })
index de5fc38a93912e9b275e093d2ddf88801eeb9342..363a49788e6ceba3e0caff407842463104e260ff 100644 (file)
@@ -1,7 +1,12 @@
-import { createCompilerError, ErrorCodes } from '@vue/compiler-core'
+import {
+  createCompilerError,
+  ElementTypes,
+  ErrorCodes,
+} from '@vue/compiler-core'
 import type { DirectiveTransform } from '../transform'
-import { IRNodeTypes, KeyOverride } from '../ir'
+import { IRNodeTypes, KeyOverride, SetEventIRNode } from '../ir'
 import { resolveModifiers } from '@vue/compiler-dom'
+import { camelize } from '@vue/shared'
 
 export const transformVOn: DirectiveTransform = (dir, node, context) => {
   let { arg, exp, loc, modifiers } = dir
@@ -16,6 +21,23 @@ export const transformVOn: DirectiveTransform = (dir, node, context) => {
     return
   }
 
+  if (arg.isStatic) {
+    let rawName = arg.content
+    if (__DEV__ && rawName.startsWith('vnode')) {
+      context.options.onError(
+        createCompilerError(ErrorCodes.X_VNODE_HOOKS, arg.loc),
+      )
+    }
+
+    if (
+      node.tagType !== ElementTypes.ELEMENT ||
+      rawName.startsWith('vnode') ||
+      !/[A-Z]/.test(rawName)
+    ) {
+      arg.content = camelize(arg.content)
+    }
+  }
+
   const { keyModifiers, nonKeyModifiers, eventOptionModifiers } =
     resolveModifiers(
       arg.isStatic ? `on${arg.content}` : arg,
@@ -48,7 +70,7 @@ export const transformVOn: DirectiveTransform = (dir, node, context) => {
     }
   }
 
-  context.registerOperation({
+  const operation: SetEventIRNode = {
     type: IRNodeTypes.SET_EVENT,
     loc,
     element: context.reference(),
@@ -60,5 +82,11 @@ export const transformVOn: DirectiveTransform = (dir, node, context) => {
       options: eventOptionModifiers,
     },
     keyOverride,
-  })
+  }
+
+  if (arg.isStatic) {
+    context.registerOperation(operation)
+  } else {
+    context.registerEffect([arg], [operation])
+  }
 }