/**
 * @jest-environment node
 */

import { renderToString } from '../src/renderToString'
import {
  createApp,
  h,
  withDirectives,
  vShow,
  vModelText,
  vModelRadio,
  vModelCheckbox,
  vModelDynamic,
  resolveDirective
} from 'vue'
import { ssrGetDirectiveProps, ssrRenderAttrs } from '../src'

describe('ssr: directives', () => {
  describe('template v-show', () => {
    test('basic', async () => {
      expect(
        await renderToString(
          createApp({
            template: `<div v-show="true"/>`
          })
        )
      ).toBe(`<div style=""></div>`)

      expect(
        await renderToString(
          createApp({
            template: `<div v-show="false"/>`
          })
        )
      ).toBe(`<div style="display:none;"></div>`)
    })

    test('with static style', async () => {
      expect(
        await renderToString(
          createApp({
            template: `<div style="color:red" v-show="false"/>`
          })
        )
      ).toBe(`<div style="color:red;display:none;"></div>`)
    })

    test('with dynamic style', async () => {
      expect(
        await renderToString(
          createApp({
            data: () => ({ style: { color: 'red' } }),
            template: `<div :style="style" v-show="false"/>`
          })
        )
      ).toBe(`<div style="color:red;display:none;"></div>`)
    })

    test('with static + dynamic style', async () => {
      expect(
        await renderToString(
          createApp({
            data: () => ({ style: { color: 'red' } }),
            template: `<div :style="style" style="font-size:12;" v-show="false"/>`
          })
        )
      ).toBe(`<div style="color:red;font-size:12;display:none;"></div>`)
    })
  })

  describe('template v-model', () => {
    test('text', async () => {
      expect(
        await renderToString(
          createApp({
            data: () => ({ text: 'hello' }),
            template: `<input v-model="text">`
          })
        )
      ).toBe(`<input value="hello">`)
    })

    test('radio', async () => {
      expect(
        await renderToString(
          createApp({
            data: () => ({ selected: 'foo' }),
            template: `<input type="radio" value="foo" v-model="selected">`
          })
        )
      ).toBe(`<input type="radio" value="foo" checked>`)

      expect(
        await renderToString(
          createApp({
            data: () => ({ selected: 'foo' }),
            template: `<input type="radio" value="bar" v-model="selected">`
          })
        )
      ).toBe(`<input type="radio" value="bar">`)

      // non-string values
      expect(
        await renderToString(
          createApp({
            data: () => ({ selected: 'foo' }),
            template: `<input type="radio" :value="{}" v-model="selected">`
          })
        )
      ).toBe(`<input type="radio">`)
    })

    test('checkbox', async () => {
      expect(
        await renderToString(
          createApp({
            data: () => ({ checked: true }),
            template: `<input type="checkbox" v-model="checked">`
          })
        )
      ).toBe(`<input type="checkbox" checked>`)

      expect(
        await renderToString(
          createApp({
            data: () => ({ checked: false }),
            template: `<input type="checkbox" v-model="checked">`
          })
        )
      ).toBe(`<input type="checkbox">`)

      expect(
        await renderToString(
          createApp({
            data: () => ({ checked: ['foo'] }),
            template: `<input type="checkbox" value="foo" v-model="checked">`
          })
        )
      ).toBe(`<input type="checkbox" value="foo" checked>`)

      expect(
        await renderToString(
          createApp({
            data: () => ({ checked: [] }),
            template: `<input type="checkbox" value="foo" v-model="checked">`
          })
        )
      ).toBe(`<input type="checkbox" value="foo">`)
    })

    test('textarea', async () => {
      expect(
        await renderToString(
          createApp({
            data: () => ({ foo: 'hello' }),
            template: `<textarea v-model="foo"/>`
          })
        )
      ).toBe(`<textarea>hello</textarea>`)
    })

    test('dynamic type', async () => {
      expect(
        await renderToString(
          createApp({
            data: () => ({ type: 'text', model: 'hello' }),
            template: `<input :type="type" v-model="model">`
          })
        )
      ).toBe(`<input type="text" value="hello">`)

      expect(
        await renderToString(
          createApp({
            data: () => ({ type: 'checkbox', model: true }),
            template: `<input :type="type" v-model="model">`
          })
        )
      ).toBe(`<input type="checkbox" checked>`)

      expect(
        await renderToString(
          createApp({
            data: () => ({ type: 'checkbox', model: false }),
            template: `<input :type="type" v-model="model">`
          })
        )
      ).toBe(`<input type="checkbox">`)

      expect(
        await renderToString(
          createApp({
            data: () => ({ type: 'checkbox', model: ['hello'] }),
            template: `<input :type="type" value="hello" v-model="model">`
          })
        )
      ).toBe(`<input type="checkbox" value="hello" checked>`)

      expect(
        await renderToString(
          createApp({
            data: () => ({ type: 'checkbox', model: [] }),
            template: `<input :type="type" value="hello" v-model="model">`
          })
        )
      ).toBe(`<input type="checkbox" value="hello">`)

      expect(
        await renderToString(
          createApp({
            data: () => ({ type: 'radio', model: 'hello' }),
            template: `<input :type="type" value="hello" v-model="model">`
          })
        )
      ).toBe(`<input type="radio" value="hello" checked>`)

      expect(
        await renderToString(
          createApp({
            data: () => ({ type: 'radio', model: 'hello' }),
            template: `<input :type="type" value="bar" v-model="model">`
          })
        )
      ).toBe(`<input type="radio" value="bar">`)
    })

    test('with v-bind', async () => {
      expect(
        await renderToString(
          createApp({
            data: () => ({
              obj: { type: 'radio', value: 'hello' },
              model: 'hello'
            }),
            template: `<input v-bind="obj" v-model="model">`
          })
        )
      ).toBe(`<input type="radio" value="hello" checked>`)
    })
  })

  describe('vnode v-show', () => {
    test('basic', async () => {
      expect(
        await renderToString(
          createApp({
            render() {
              return withDirectives(h('div'), [[vShow, true]])
            }
          })
        )
      ).toBe(`<div></div>`)

      expect(
        await renderToString(
          createApp({
            render() {
              return withDirectives(h('div'), [[vShow, false]])
            }
          })
        )
      ).toBe(`<div style="display:none;"></div>`)
    })

    test('with merge', async () => {
      expect(
        await renderToString(
          createApp({
            render() {
              return withDirectives(
                h('div', {
                  style: {
                    color: 'red'
                  }
                }),
                [[vShow, false]]
              )
            }
          })
        )
      ).toBe(`<div style="color:red;display:none;"></div>`)
    })
  })

  describe('vnode v-model', () => {
    test('text', async () => {
      expect(
        await renderToString(
          createApp({
            render() {
              return withDirectives(h('input'), [[vModelText, 'hello']])
            }
          })
        )
      ).toBe(`<input value="hello">`)
    })

    test('radio', async () => {
      expect(
        await renderToString(
          createApp({
            render() {
              return withDirectives(
                h('input', { type: 'radio', value: 'hello' }),
                [[vModelRadio, 'hello']]
              )
            }
          })
        )
      ).toBe(`<input type="radio" value="hello" checked>`)

      expect(
        await renderToString(
          createApp({
            render() {
              return withDirectives(
                h('input', { type: 'radio', value: 'hello' }),
                [[vModelRadio, 'foo']]
              )
            }
          })
        )
      ).toBe(`<input type="radio" value="hello">`)
    })

    test('checkbox', async () => {
      expect(
        await renderToString(
          createApp({
            render() {
              return withDirectives(h('input', { type: 'checkbox' }), [
                [vModelCheckbox, true]
              ])
            }
          })
        )
      ).toBe(`<input type="checkbox" checked>`)

      expect(
        await renderToString(
          createApp({
            render() {
              return withDirectives(h('input', { type: 'checkbox' }), [
                [vModelCheckbox, false]
              ])
            }
          })
        )
      ).toBe(`<input type="checkbox">`)

      expect(
        await renderToString(
          createApp({
            render() {
              return withDirectives(
                h('input', { type: 'checkbox', value: 'foo' }),
                [[vModelCheckbox, ['foo']]]
              )
            }
          })
        )
      ).toBe(`<input type="checkbox" value="foo" checked>`)

      expect(
        await renderToString(
          createApp({
            render() {
              return withDirectives(
                h('input', { type: 'checkbox', value: 'foo' }),
                [[vModelCheckbox, []]]
              )
            }
          })
        )
      ).toBe(`<input type="checkbox" value="foo">`)
    })
  })

  describe('vnode v-model dynamic', () => {
    test('text', async () => {
      expect(
        await renderToString(
          createApp({
            render() {
              return withDirectives(h('input'), [[vModelDynamic, 'hello']])
            }
          })
        )
      ).toBe(`<input value="hello">`)
    })

    test('radio', async () => {
      expect(
        await renderToString(
          createApp({
            render() {
              return withDirectives(
                h('input', { type: 'radio', value: 'hello' }),
                [[vModelDynamic, 'hello']]
              )
            }
          })
        )
      ).toBe(`<input type="radio" value="hello" checked>`)

      expect(
        await renderToString(
          createApp({
            render() {
              return withDirectives(
                h('input', { type: 'radio', value: 'hello' }),
                [[vModelDynamic, 'foo']]
              )
            }
          })
        )
      ).toBe(`<input type="radio" value="hello">`)
    })

    test('checkbox', async () => {
      expect(
        await renderToString(
          createApp({
            render() {
              return withDirectives(h('input', { type: 'checkbox' }), [
                [vModelDynamic, true]
              ])
            }
          })
        )
      ).toBe(`<input type="checkbox" checked>`)

      expect(
        await renderToString(
          createApp({
            render() {
              return withDirectives(h('input', { type: 'checkbox' }), [
                [vModelDynamic, false]
              ])
            }
          })
        )
      ).toBe(`<input type="checkbox">`)

      expect(
        await renderToString(
          createApp({
            render() {
              return withDirectives(
                h('input', { type: 'checkbox', value: 'foo' }),
                [[vModelDynamic, ['foo']]]
              )
            }
          })
        )
      ).toBe(`<input type="checkbox" value="foo" checked>`)

      expect(
        await renderToString(
          createApp({
            render() {
              return withDirectives(
                h('input', { type: 'checkbox', value: 'foo' }),
                [[vModelDynamic, []]]
              )
            }
          })
        )
      ).toBe(`<input type="checkbox" value="foo">`)
    })
  })

  test('custom directive w/ getSSRProps (vdom)', async () => {
    expect(
      await renderToString(
        createApp({
          render() {
            return withDirectives(h('div'), [
              [
                {
                  getSSRProps({ value }) {
                    return { id: value }
                  }
                },
                'foo'
              ]
            ])
          }
        })
      )
    ).toBe(`<div id="foo"></div>`)
  })

  test('custom directive w/ getSSRProps (optimized)', async () => {
    expect(
      await renderToString(
        createApp({
          data() {
            return {
              x: 'foo'
            }
          },
          directives: {
            xxx: {
              getSSRProps({ value, arg, modifiers }) {
                return { id: [value, arg, modifiers.ok].join('-') }
              }
            }
          },
          ssrRender(_ctx, _push, _parent, _attrs) {
            const _directive_xxx = resolveDirective('xxx')!
            _push(
              `<div${ssrRenderAttrs(
                ssrGetDirectiveProps(_ctx, _directive_xxx, _ctx.x, 'arg', {
                  ok: true
                })
              )}></div>`
            )
          }
        })
      )
    ).toBe(`<div id="foo-arg-true"></div>`)
  })
})
