text: /* eslint-env mocha */
  /* global VERSION */
  'use strict'
  
  import assert from 'assert'
  import { state, sequences } from '../tags'
  import { WebSocket, Server } from 'mock-socket'
  import { Devtools } from './'
  import { Controller, Module } from '../'
  import View from '../View'
  import Watch from '../Watch'
  import { FunctionTreeExecutionError } from 'function-tree'
  const version = VERSION
  
  Devtools.prototype.createSocket = function() {
    this.ws = new WebSocket(`ws://${this.host}`)
  }
  
  Devtools.prototype.reconnect = function() {
    this.reconnectTimeout = setTimeout(() => {
      this.init()
    }, this.reconnectInterval)
  }
  
  Devtools.prototype.destroy = function() {
    clearTimeout(this.reconnectTimeout)
  }
  
  function toJSON(value) {
    return JSON.parse(JSON.stringify(value))
  }
  
  let devtools
  
  describe('Devtools', () => {
    afterEach(() => {
      devtools && devtools.destroy()
      Watch.nextWatchId = 0
    })
    it('should throw when host is not set', () => {
      assert.throws(
        () => {
          devtools = new Devtools() // eslint-disable-line no-new
        },
        (err) => {
          if (err instanceof Error) {
            return (
              err.message === 'Devtools: You have to pass in the "host" option'
            )
          }
        }
      )
    })
    it('should init correctly and work when debugger is open when app loads', (done) => {
      const mockServer = new Server('ws://localhost:8585')
      let messages = []
      mockServer.on('connection', (server) => {
        server.on('message', (event) => {
          const message = JSON.parse(event)
          messages.push(message.type)
          switch (message.type) {
            case 'pong':
              server.send(JSON.stringify({ type: 'ping' }))
              break
            case 'ping':
              server.send(JSON.stringify({ type: 'pong' }))
              break
          }
        })
      })
      const rootModule = Module({})
      devtools = new Devtools({
        host: 'localhost:8585',
        reconnect: true,
        disableDebounce: true,
      })
      const controller = Controller(rootModule, {
        devtools,
      })
      assert.equal(controller.devtools.isConnected, false)
      setTimeout(() => {
        assert.deepEqual(messages, ['ping', 'init', 'watchMap'])
        assert.equal(controller.devtools.isConnected, true)
        assert.equal(controller.devtools.reconnectInterval, 5000)
        assert.equal(controller.devtools.doReconnect, true)
        assert.deepEqual(controller.devtools.debuggerWatchMap, {})
        assert.equal(controller.devtools.storeMutations, true)
        assert.equal(controller.devtools.preventExternalMutations, true)
        assert.equal(controller.devtools.preventPropsReplacement, false)
        assert.equal(controller.devtools.bigComponentsWarning, 10)
  
        assert.deepEqual(controller.devtools.controller, controller)
        assert.deepEqual(
          controller.devtools.originalRunTreeFunction,
          controller.run
        )
        assert.equal(controller.devtools.isResettingDebugger, false)
        assert.equal(
          controller.devtools.initialModelString,
          JSON.stringify(controller.model.get())
        )
        mockServer.stop(done)
      }, 70)
    })
    it('should warn, try to reconnect to Debugger and send bulk message after connecting', (done) => {
      let warnCount = 0
      const originWarn = console.warn
      console.warn = function(...args) {
        warnCount++
        assert.equal(
          args[0],
          'Debugger application is not running on selected port... will reconnect automatically behind the scenes'
        )
        originWarn.apply(this, args)
      }
  
      function actionA({ state }) {
        assert.ok(true)
        state.set('foo', 'foo')
      }
  
      const rootModule = Module({
        state: {
          foo: 'bar',
          bar: 'foo',
        },
        sequences: {
          test: [actionA],
        },
      })
      devtools = new Devtools({
        host: 'localhost:8585',
        reconnectInterval: 500,
        disableDebounce: true,
      })
      const controller = Controller(rootModule, {
        devtools,
      })
      const view = new View({
        dependencies: {
          foo: state`foo`,
          bar: state`bar`,
          test: sequences`test`,
        },
        controller,
        displayName: 'Test',
        onUpdate: (stateChanges, force) => {
          view.updateFromState(stateChanges, null, force)
        },
      })
      view.mount()
      view.render({}, (componentProps) => {
        assert.equal(componentProps.foo, 'bar')
        assert.equal(componentProps.bar, 'foo')
      })
      assert.equal(controller.devtools.isConnected, false)
      view.controller.getSequence('test')()
      let mockServer
      let messages = {}
      let messageTypes = []
      setTimeout(() => {
        mockServer = new Server('ws://localhost:8585')
        mockServer.on('connection', (server) => {
          server.on('message', (event) => {
            const message = JSON.parse(event)
            switch (message.type) {
              case 'pong':
                server.send(JSON.stringify({ type: 'ping' }))
                break
              case 'ping':
                server.send(JSON.stringify({ type: 'pong' }))
                break
              case 'init':
                break
              default:
                messageTypes.push(message.type)
                messages[message.type] = message
                break
            }
          })
        })
      }, 400)
      assert.equal(controller.devtools.backlog.length, 4)
      setTimeout(() => {
        assert.deepEqual(messageTypes, ['bulk', 'watchMap'])
  
        assert.equal(controller.devtools.isConnected, true)
        assert.deepEqual(toJSON(controller.devtools.debuggerWatchMap.foo), [
          { name: 'Test', executedCount: 1, type: 'View', id: 'Watch_0' },
        ])
        assert.deepEqual(toJSON(controller.devtools.debuggerWatchMap.bar), [
          { name: 'Test', executedCount: 1, type: 'View', id: 'Watch_0' },
        ])
        assert.equal(controller.devtools.debuggerWatchMap.test, undefined)
  
        let bulkMessages = messages.bulk.data.messages.map(JSON.parse)
        let bulkMessagesTypes = []
        bulkMessages = bulkMessages.reduce((acc, message) => {
          bulkMessagesTypes.push(message.type)
          if (Array.isArray(acc[message.type])) {
            acc[message.type].push(message)
          } else {
            acc[message.type] = [message]
          }
          return acc
        }, {})
        assert.deepEqual(bulkMessagesTypes, [
          'executionStart',
          'execution',
          'execution',
          'executionEnd',
        ])
        assert.equal(bulkMessages.executionStart[0].data.execution.name, 'test')
        assert.equal(bulkMessages.execution.length, 2)
        assert.ok(bulkMessages.execution[0].data.execution)
        assert.equal(bulkMessages.execution[0].source, 'c')
        assert.equal(bulkMessages.execution[0].version, version)
        assert.deepEqual(bulkMessages.execution[0].data.execution.payload, {})
  
        assert.ok(bulkMessages.execution[1].data.execution)
        assert.deepEqual(bulkMessages.execution[1].data.execution.payload, {})
        assert.equal(bulkMessages.execution[1].data.execution.data.method, 'set')
        assert.deepEqual(bulkMessages.execution[1].data.execution.data.args, [
          ['foo'],
          'foo',
        ])
        assert.equal(
          bulkMessages.execution[1].data.execution.data.type,
          'mutation'
        )
        assert.equal(bulkMessages.execution[1].data.execution.data.color, '#333')
  
        assert.ok(bulkMessages.executionEnd[0].data.execution)
  
        assert.equal(warnCount, 1)
        console.warn = originWarn
        mockServer.stop(done)
      }, 1050)
    })
    it('should set component details and watch executions', (done) => {
      const mockServer = new Server('ws://localhost:8585')
      let messages = {}
      let messageTypes = []
      mockServer.on('connection', (server) => {
        server.on('message', (event) => {
          const message = JSON.parse(event)
          switch (message.type) {
            case 'pong':
              server.send(JSON.stringify({ type: 'ping' }))
              break
            case 'ping':
              server.send(JSON.stringify({ type: 'pong' }))
              break
            case 'init':
              break
            case 'execution':
              messageTypes.push(message.type)
              if (Array.isArray(messages[message.type])) {
                messages[message.type].push(message)
              } else {
                messages[message.type] = [message]
              }
              break
            default:
              messageTypes.push(message.type)
              messages[message.type] = message
              break
          }
        })
      })
      function actionA({ path, state }) {
        assert.ok(true)
        state.set('foo', 'foo')
        return path.success()
      }
      function actionB() {
        assert.ok(true)
        return { bar: 'baz' }
      }
  
      const rootModule = Module({
        state: {
          foo: 'bar',
          bar: 'foo',
        },
        sequences: {
          test: [
            actionA,
            {
              success: [actionB],
            },
          ],
        },
      })
      devtools = new Devtools({
        host: 'localhost:8585',
        disableDebounce: true,
      })
      const controller = Controller(rootModule, {
        devtools,
      })
      const view = new View({
        dependencies: {
          foo: state`foo`,
          bar: state`bar`,
          test: sequences`test`,
        },
        controller,
        displayName: 'Test',
        onUpdate: (stateChanges, force) => {
          view.updateFromState(stateChanges, null, force)
        },
      })
      view.mount()
      view.render({}, (componentProps) => {
        assert.equal(componentProps.foo, 'bar')
        assert.equal(componentProps.bar, 'foo')
      })
  
      setTimeout(() => {
        assert.deepEqual(messageTypes, ['watchMap'])
        assert.equal(controller.devtools.isConnected, true)
  
        assert.deepEqual(toJSON(controller.devtools.debuggerWatchMap.foo), [
          { name: 'Test', executedCount: 1, type: 'View', id: 'Watch_0' },
        ])
        assert.deepEqual(toJSON(controller.devtools.debuggerWatchMap.bar), [
          { name: 'Test', executedCount: 1, type: 'View', id: 'Watch_0' },
        ])
        assert.equal(controller.devtools.debuggerWatchMap.test, undefined)
  
        assert.equal(messages.watchMap.source, 'c')
        assert.deepEqual(messages.watchMap.data.watchMap.foo, [
          { name: 'Test', executedCount: 1, type: 'View', id: 'Watch_0' },
        ])
        assert.deepEqual(messages.watchMap.data.watchMap.bar, [
          { name: 'Test', executedCount: 1, type: 'View', id: 'Watch_0' },
        ])
        assert.deepEqual(messages.watchMap.data.updates, [])
  
        view.controller.getSequence('test')({
          foo: 'bar',
        })
  
        assert.deepEqual(toJSON(controller.devtools.debuggerWatchMap.foo), [
          { name: 'Test', executedCount: 1, type: 'View', id: 'Watch_0' },
        ])
        assert.deepEqual(toJSON(controller.devtools.debuggerWatchMap.bar), [
          { name: 'Test', executedCount: 1, type: 'View', id: 'Watch_0' },
        ])
        assert.equal(controller.devtools.debuggerWatchMap.test, undefined)
  
        assert.deepEqual(messageTypes, [
          'watchMap',
          'executionStart',
          'execution',
          'execution',
          'executionPathStart',
          'execution',
          'executionFunctionEnd',
          'executionEnd',
          'watchMap',
        ])
        assert.ok(messages.executionStart.data.execution)
        assert.equal(messages.executionStart.source, 'c')
  
        assert.equal(messages.execution.length, 3)
        assert.ok(messages.execution[0].data.execution)
        assert.equal(messages.execution[0].source, 'c')
        assert.equal(messages.execution[0].version, version)
        assert.deepEqual(messages.execution[0].data.execution.payload, {
          foo: 'bar',
        })
  
        assert.ok(messages.execution[1].data.execution)
        assert.equal(messages.execution[1].source, 'c')
        assert.equal(messages.execution[1].version, version)
        assert.deepEqual(messages.execution[1].data.execution.payload, {
          foo: 'bar',
        })
        assert.equal(messages.execution[1].data.execution.data.method, 'set')
        assert.deepEqual(messages.execution[1].data.execution.data.args, [
          ['foo'],
          'foo',
        ])
        assert.equal(messages.execution[1].data.execution.data.type, 'mutation')
        assert.equal(messages.execution[1].data.execution.data.color, '#333')
  
        assert.ok(messages.executionPathStart.data.execution)
        assert.equal(messages.executionPathStart.source, 'c')
        assert.equal(messages.executionPathStart.version, version)
        assert.equal(messages.executionPathStart.data.execution.path, 'success')
  
        assert.ok(messages.execution[2].data.execution)
        assert.equal(messages.execution[2].source, 'c')
        assert.equal(messages.execution[2].version, version)
        assert.deepEqual(messages.execution[2].data.execution.payload, {
          foo: 'bar',
        })
  
        assert.ok(messages.executionFunctionEnd.data.execution)
        assert.equal(messages.executionFunctionEnd.source, 'c')
        assert.equal(messages.executionFunctionEnd.version, version)
        assert.deepEqual(messages.executionFunctionEnd.data.execution.output, {
          bar: 'baz',
        })
  
        assert.ok(messages.executionEnd.data.execution)
        assert.equal(messages.executionEnd.version, version)
        assert.equal(messages.executionEnd.source, 'c')
        mockServer.stop(done)
      }, 70)
    })
    it('should watch sequence execution error', (done) => {
      const mockServer = new Server('ws://localhost:8585')
      let messages = {}
      let messageTypes = []
      mockServer.on('connection', (server) => {
        server.on('message', (event) => {
          const message = JSON.parse(event)
          switch (message.type) {
            case 'pong':
              server.send(JSON.stringify({ type: 'ping' }))
              break
            case 'ping':
              server.send(JSON.stringify({ type: 'pong' }))
              break
            case 'init':
              break
            case 'execution':
              messageTypes.push(message.type)
              if (Array.isArray(messages[message.type])) {
                messages[message.type].push(message)
              } else {
                messages[message.type] = [message]
              }
              break
            default:
              messageTypes.push(message.type)
              messages[message.type] = message
              break
          }
        })
      })
      function actionA() {
        return {
          foo: 'bar',
        }
      }
      let errorCount = 0
      const rootModule = Module({
        state: {
          foo: 'bar',
          bar: 'foo',
        },
        sequences: {
          test: [
            actionA,
            {
              success: [],
            },
          ],
        },
        catch: [
          [
            FunctionTreeExecutionError,
            [
              ({ props }) => {
                errorCount++
                assert.ok(
                  props.error.message.match(
                    /needs to be a path of either success/
                  )
                )
              },
            ],
          ],
        ],
      })
      devtools = new Devtools({
        host: 'localhost:8585',
        disableDebounce: true,
      })
      const controller = Controller(rootModule, {
        devtools,
        throwToConsole: false,
      })
      const view = new View({
        dependencies: {
          foo: state`foo`,
          bar: state`bar`,
          test: sequences`test`,
        },
        controller,
        displayName: 'Test',
        onUpdate: (stateChanges, force) => {
          view.updateFromState(stateChanges, null, force)
        },
      })
      view.mount()
      view.render({}, (componentProps) => {
        assert.equal(componentProps.foo, 'bar')
        assert.equal(componentProps.bar, 'foo')
      })
  
      setTimeout(() => {
        controller.getSequence('test')()
        assert.equal(errorCount, 1)
        assert.deepEqual(messageTypes, [
          'watchMap',
          'executionStart',
          'execution',
          'executionFunctionError',
          'executionStart',
          'execution',
          'executionEnd',
        ])
        mockServer.stop(done)
      }, 70)
    })
    it('should reset the state', (done) => {
      const mockServer = new Server('ws://localhost:8585')
      mockServer.on('connection', (server) => {
        server.on('message', (event) => {
          const message = JSON.parse(event)
          switch (message.type) {
            case 'pong':
              server.send(JSON.stringify({ type: 'ping' }))
              break
            case 'ping':
              server.send(JSON.stringify({ type: 'pong' }))
              break
            default:
              break
          }
        })
        setTimeout(() => {
          server.send(JSON.stringify({ type: 'reset' }))
        }, 150)
      })
      function actionA({ path, state }) {
        state.set('foo', 'foo')
        return path.success()
      }
      function actionB() {
        return { bar: 'baz' }
      }
  
      const rootModule = Module({
        state: {
          foo: 'bar',
          bar: 'foo',
        },
        sequences: {
          test: [
            actionA,
            {
              success: [actionB],
            },
          ],
        },
      })
      devtools = new Devtools({
        host: 'localhost:8585',
        disableDebounce: true,
      })
      const controller = Controller(rootModule, {
        devtools,
      })
      const view = new View({
        dependencies: {
          foo: state`foo`,
          bar: state`bar`,
          test: sequences`test`,
        },
        controller,
        displayName: 'Test',
        onUpdate: (stateChanges, force) => {
          view.updateFromState(stateChanges, null, force)
        },
      })
      view.mount()
      view.render({}, (componentProps) => {
        assert.equal(componentProps.foo, 'bar')
        assert.equal(componentProps.bar, 'foo')
      })
  
      setTimeout(() => {
        assert.deepEqual(JSON.parse(controller.devtools.initialModelString), {
          foo: 'bar',
          bar: 'foo',
        })
        assert.equal(controller.devtools.isConnected, true)
  
        assert.deepEqual(toJSON(controller.devtools.debuggerWatchMap.foo), [
          { name: 'Test', executedCount: 1, type: 'View', id: 'Watch_0' },
        ])
        assert.deepEqual(toJSON(controller.devtools.debuggerWatchMap.bar), [
          { name: 'Test', executedCount: 1, type: 'View', id: 'Watch_0' },
        ])
        assert.equal(controller.devtools.debuggerWatchMap.test, undefined)
  
        controller.getSequence('test')({
          foo: 'bar',
        })
        assert.deepEqual(controller.model.state, {
          foo: 'foo',
          bar: 'foo',
        })
        assert.deepEqual(JSON.parse(controller.devtools.initialModelString), {
          foo: 'bar',
          bar: 'foo',
        })
        assert.deepEqual(toJSON(controller.devtools.debuggerWatchMap.foo), [
          { name: 'Test', executedCount: 1, type: 'View', id: 'Watch_0' },
        ])
        assert.deepEqual(toJSON(controller.devtools.debuggerWatchMap.bar), [
          { name: 'Test', executedCount: 1, type: 'View', id: 'Watch_0' },
        ])
        assert.equal(controller.devtools.debuggerWatchMap.test, undefined)
      }, 70)
  
      setTimeout(() => {
        assert.deepEqual(
          controller.model.state,
          JSON.parse(controller.devtools.initialModelString)
        )
        assert.deepEqual(controller.devtools.backlog, [])
        assert.deepEqual(controller.devtools.mutations, [])
        assert.equal(controller.devtools.debuggerWatchMap.test, undefined)
        mockServer.stop(done)
      }, 300)
    })
    it('should warn when remember message sent if storeMutations option is false', (done) => {
      let warnCount = 0
      const originWarn = console.warn
      console.warn = function(...args) {
        warnCount++
        assert.equal(
          args[0],
          'Cerebral Devtools - You tried to time travel, but you have turned of storing of mutations'
        )
        originWarn.apply(this, args)
      }
      const mockServer = new Server('ws://localhost:8585')
      mockServer.on('connection', (server) => {
        server.on('message', (event) => {
          const message = JSON.parse(event)
          switch (message.type) {
            case 'pong':
              server.send(JSON.stringify({ type: 'ping' }))
              break
            case 'ping':
              server.send(JSON.stringify({ type: 'pong' }))
              break
          }
        })
        setTimeout(() => {
          server.send(JSON.stringify({ type: 'remember', data: 0 }))
        }, 70)
      })
      const rootModule = Module({})
      devtools = new Devtools({
        host: 'localhost:8585',
        reconnect: true,
        storeMutations: false,
        disableDebounce: true,
      })
      const controller = Controller(rootModule, {
        devtools,
      })
      setTimeout(() => {
        assert.equal(warnCount, 1)
        assert.equal(controller.devtools.storeMutations, false)
        console.warn = originWarn
        mockServer.stop(done)
      }, 100)
    })
    it('should travel back in time', (done) => {
      const mockServer = new Server('ws://localhost:8585')
      mockServer.on('connection', (server) => {
        server.on('message', (event) => {
          const message = JSON.parse(event)
          switch (message.type) {
            case 'pong':
              server.send(JSON.stringify({ type: 'ping' }))
              break
            case 'ping':
              server.send(JSON.stringify({ type: 'pong' }))
              break
          }
        })
        setTimeout(() => {
          server.send(JSON.stringify({ type: 'remember', data: 1 }))
        }, 200)
        setTimeout(() => {
          server.send(JSON.stringify({ type: 'remember', data: 0 }))
        }, 400)
        setTimeout(() => {
          server.send(JSON.stringify({ type: 'remember', data: 1 }))
        }, 600)
      })
      function actionA({ state }) {
        state.set('foo', 'foo')
      }
      function actionB({ state }) {
        state.set('bar', 'bar')
      }
  
      const rootModule = Module({
        state: {
          foo: 'bar',
          bar: 'foo',
        },
        sequences: {
          testA: [actionA],
          testB: [actionB],
        },
      })
      devtools = new Devtools({
        host: 'localhost:8585',
        disableDebounce: true,
      })
      const controller = Controller(rootModule, {
        devtools,
      })
      let rememberCount = 0
      controller.on('remember', (datetime) => {
        rememberCount++
      })
      const view = new View({
        dependencies: {
          foo: state`foo`,
          bar: state`bar`,
        },
        controller,
        displayName: 'Test',
        onUpdate: (stateChanges, force) => {
          view.updateFromState(stateChanges, null, force)
        },
      })
      view.mount()
      view.render({}, (componentProps) => {
        assert.equal(componentProps.foo, 'bar')
        assert.equal(componentProps.bar, 'foo')
      })
  
      setTimeout(() => {
        assert.deepEqual(JSON.parse(controller.devtools.initialModelString), {
          foo: 'bar',
          bar: 'foo',
        })
        assert.equal(controller.devtools.isConnected, true)
  
        controller.getSequence('testA')()
        assert.deepEqual(controller.model.state, {
          foo: 'foo',
          bar: 'foo',
        })
        controller.getSequence('testB')()
        assert.deepEqual(controller.model.state, {
          foo: 'foo',
          bar: 'bar',
        })
        assert.deepEqual(JSON.parse(controller.devtools.initialModelString), {
          foo: 'bar',
          bar: 'foo',
        })
        assert.deepEqual(toJSON(controller.devtools.debuggerWatchMap.foo), [
          { name: 'Test', executedCount: 1, id: 'Watch_0', type: 'View' },
        ])
        assert.deepEqual(toJSON(controller.devtools.debuggerWatchMap.bar), [
          { name: 'Test', executedCount: 1, id: 'Watch_0', type: 'View' },
        ])
        assert.equal(controller.devtools.debuggerWatchMap.test, undefined)
        assert.equal(controller.devtools.mutations.length, 2)
        assert.equal(rememberCount, 0)
      }, 70)
  
      setTimeout(() => {
        assert.deepEqual(controller.model.state, {
          foo: 'foo',
          bar: 'foo',
        })
        assert.equal(controller.devtools.mutations.length, 2)
        assert.equal(rememberCount, 1)
      }, 300)
      setTimeout(() => {
        assert.deepEqual(controller.model.state, {
          foo: 'foo',
          bar: 'bar',
        })
        assert.equal(controller.devtools.mutations.length, 2)
        assert.equal(rememberCount, 2)
      }, 500)
      setTimeout(() => {
        assert.deepEqual(controller.model.state, {
          foo: 'foo',
          bar: 'foo',
        })
        assert.equal(controller.devtools.mutations.length, 2)
        assert.equal(rememberCount, 3)
  
        mockServer.stop(done)
      }, 800)
    })
    it('should warn when the sequence fired while debugger is remembering state', (done) => {
      let warnCount = 0
      const originWarn = console.warn
      console.warn = function(...args) {
        warnCount++
        assert.equal(
          args[0],
          'The sequence "testB" fired while debugger is remembering state, it was ignored'
        )
        originWarn.apply(this, args)
      }
      const mockServer = new Server('ws://localhost:8585')
      mockServer.on('connection', (server) => {
        server.on('message', (event) => {
          const message = JSON.parse(event)
          switch (message.type) {
            case 'pong':
              server.send(JSON.stringify({ type: 'ping' }))
              break
            case 'ping':
              server.send(JSON.stringify({ type: 'pong' }))
              break
          }
        })
        setTimeout(() => {
          server.send(JSON.stringify({ type: 'remember', data: 1 }))
        }, 150)
      })
      function actionA({ state }) {
        state.set('foo', 'foo')
      }
      function actionB({ state }) {
        state.set('bar', 'bar')
      }
  
      const rootModule = Module({
        state: {
          foo: 'bar',
          bar: 'foo',
        },
        sequences: {
          testA: [actionA],
          testB: [actionB],
        },
      })
      devtools = new Devtools({
        host: 'localhost:8585',
        disableDebounce: true,
      })
      const controller = Controller(rootModule, {
        devtools,
      })
      const view = new View({
        dependencies: {
          foo: state`foo`,
          bar: state`bar`,
        },
        controller,
        displayName: 'Test',
        onUpdate: (stateChanges, force) => {
          view.updateFromState(stateChanges, null, force)
        },
      })
      view.mount()
      view.render({}, (componentProps) => {
        assert.equal(componentProps.foo, 'bar')
        assert.equal(componentProps.bar, 'foo')
      })
  
      setTimeout(() => {
        assert.deepEqual(JSON.parse(controller.devtools.initialModelString), {
          foo: 'bar',
          bar: 'foo',
        })
        assert.equal(controller.devtools.isConnected, true)
  
        controller.getSequence('testA')()
        assert.deepEqual(controller.model.state, {
          foo: 'foo',
          bar: 'foo',
        })
        controller.getSequence('testB')()
        assert.deepEqual(controller.model.state, {
          foo: 'foo',
          bar: 'bar',
        })
        assert.deepEqual(JSON.parse(controller.devtools.initialModelString), {
          foo: 'bar',
          bar: 'foo',
        })
        assert.deepEqual(toJSON(controller.devtools.debuggerWatchMap.foo), [
          { name: 'Test', executedCount: 1, id: 'Watch_0', type: 'View' },
        ])
        assert.deepEqual(toJSON(controller.devtools.debuggerWatchMap.bar), [
          { name: 'Test', executedCount: 1, id: 'Watch_0', type: 'View' },
        ])
        assert.equal(controller.devtools.debuggerWatchMap.test, undefined)
        assert.equal(controller.devtools.mutations.length, 2)
      }, 70)
  
      setTimeout(() => {
        assert.deepEqual(controller.model.state, {
          foo: 'foo',
          bar: 'foo',
        })
        assert.equal(controller.devtools.mutations.length, 2)
        controller.getSequence('testB')()
        assert.deepEqual(controller.model.state, {
          foo: 'foo',
          bar: 'foo',
        })
  
        assert.equal(warnCount, 1)
        console.warn = originWarn
        mockServer.stop(done)
      }, 300)
    })
    it('should change model state when debugger model state changed', (done) => {
      const mockServer = new Server('ws://localhost:8585')
      mockServer.on('connection', (server) => {
        server.on('message', (event) => {
          const message = JSON.parse(event)
          switch (message.type) {
            case 'pong':
              server.send(JSON.stringify({ type: 'ping' }))
              break
            case 'ping':
              server.send(JSON.stringify({ type: 'pong' }))
              break
          }
        })
        setTimeout(() => {
          server.send(
            JSON.stringify({
              type: 'changeModel',
              data: { path: ['foo'], value: 'baz' },
            })
          )
        }, 70)
      })
  
      const rootModule = Module({
        state: {
          foo: 'bar',
          bar: 'foo',
        },
      })
      devtools = new Devtools({
        host: 'localhost:8585',
        disableDebounce: true,
      })
      const controller = Controller(rootModule, {
        devtools,
      })
      const view = new View({
        dependencies: {
          foo: state`foo`,
          bar: state`bar`,
        },
        controller,
        displayName: 'Test',
        onUpdate: (stateChanges, force) => {
          view.updateFromState(stateChanges, null, force)
        },
      })
      view.mount()
      view.render({}, (componentProps) => {
        assert.equal(componentProps.foo, 'bar')
        assert.equal(componentProps.bar, 'foo')
      })
  
      setTimeout(() => {
        assert.deepEqual(controller.model.state, {
          foo: 'baz',
          bar: 'foo',
        })
        mockServer.stop(done)
      }, 100)
    })
  })
  