immutable#is JavaScript Examples

The following examples show how to use immutable#is. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: getHTML.js    From spring-boot-ecommerce with Apache License 2.0 6 votes vote down vote up
function getStyleRanges(text, charMetaList) {
    var charStyle = EMPTY_SET;
    var prevCharStyle = EMPTY_SET;
    var ranges = [];
    var rangeStart = 0;
    for (var i = 0, len = text.length; i < len; i++) {
        prevCharStyle = charStyle;
        var meta = charMetaList.get(i);
        charStyle = meta ? meta.getStyle() : EMPTY_SET;
        if (i > 0 && !is(charStyle, prevCharStyle)) {
            ranges.push([text.slice(rangeStart, i), prevCharStyle]);
            rangeStart = i;
        }
    }
    ranges.push([text.slice(rangeStart), charStyle]);
    return ranges;
}
Example #2
Source File: root-test.js    From ThreatMapper with Apache License 2.0 4 votes vote down vote up
// Root reducer test suite using Jasmine matchers
describe('RootReducer', () => {
  const ActionTypes = require('../../constants/action-types').default;
  const reducer = require('../root').default;
  const { initialState } = require('../root');
  const topologyUtils = require('../../utils/topology-utils');
  const topologySelectors = require('../../selectors/topology');
  // TODO maybe extract those to topology-utils tests?
  const { activeTopologyOptionsSelector } = topologySelectors;
  const { getAdjacentNodes, isNodesDisplayEmpty, isTopologyNodeCountZero } = topologyUtils;
  const { getUrlState } = require('../../utils/router-utils');

  // fixtures

  const NODE_SET = {
    n1: {
      adjacency: ['n1', 'n2'],
      filtered: false,
      id: 'n1',
    },
    n2: {
      filtered: false,
      id: 'n2',
    }
  };

  const topologies = [
    {
      fullName: 'Processes',
      hide_if_empty: true,
      id: 'processes',
      name: 'Processes',
      options: [
        {
          defaultValue: 'hide',
          id: 'unconnected',
          options: [
            {
              label: 'Unconnected nodes hidden',
              value: 'hide'
            }
          ],
          selectType: 'one'
        }
      ],
      rank: 1,
      stats: {
        edge_count: 379,
        filtered_nodes: 214,
        node_count: 320,
        nonpseudo_node_count: 320
      },
      sub_topologies: [],
      url: '/api/topology/processes'
    },
    {
      hide_if_empty: true,
      name: 'Pods',
      options: [
        {
          defaultValue: 'default',
          id: 'namespace',
          options: [
            {
              label: 'monitoring',
              value: 'monitoring'
            },
            {
              label: 'scope',
              value: 'scope'
            },
            {
              label: 'All Namespaces',
              value: 'all'
            }
          ],
          selectType: 'many'
        },
        {
          defaultValue: 'hide',
          id: 'pseudo',
          options: [
            {
              label: 'Show Unmanaged',
              value: 'show'
            },
            {
              label: 'Hide Unmanaged',
              value: 'hide'
            }
          ]
        }
      ],
      rank: 3,
      stats: {
        edge_count: 15,
        filtered_nodes: 16,
        node_count: 32,
        nonpseudo_node_count: 27
      },
      sub_topologies: [
        {
          hide_if_empty: true,
          name: 'services',
          options: [
            {
              defaultValue: 'default',
              id: 'namespace',
              options: [
                {
                  label: 'monitoring',
                  value: 'monitoring'
                },
                {
                  label: 'scope',
                  value: 'scope'
                },
                {
                  label: 'All Namespaces',
                  value: 'all'
                }
              ],
              selectType: 'many'
            }
          ],
          rank: 0,
          stats: {
            edge_count: 14,
            filtered_nodes: 16,
            node_count: 159,
            nonpseudo_node_count: 154
          },
          url: '/api/topology/services'
        }
      ],
      url: '/api/topology/pods'
    }
  ];

  // actions

  const ChangeTopologyOptionAction = {
    option: 'option1',
    topologyId: 'topo1',
    type: ActionTypes.CHANGE_TOPOLOGY_OPTION,
    value: ['on']
  };

  const ChangeTopologyOptionAction2 = {
    option: 'option1',
    topologyId: 'topo1',
    type: ActionTypes.CHANGE_TOPOLOGY_OPTION,
    value: ['off']
  };

  const ClickNodeAction = {
    nodeId: 'n1',
    type: ActionTypes.CLICK_NODE
  };

  const ClickNode2Action = {
    nodeId: 'n2',
    type: ActionTypes.CLICK_NODE
  };

  const ClickRelativeAction = {
    nodeId: 'rel1',
    type: ActionTypes.CLICK_RELATIVE
  };

  const ClickShowTopologyForNodeAction = {
    nodeId: 'rel1',
    topologyId: 'topo2',
    type: ActionTypes.CLICK_SHOW_TOPOLOGY_FOR_NODE
  };

  const ClickSubTopologyAction = {
    topologyId: 'topo1-grouped',
    type: ActionTypes.CLICK_TOPOLOGY
  };

  const ClickTopologyAction = {
    topologyId: 'topo1',
    type: ActionTypes.CLICK_TOPOLOGY
  };

  const ClickTopology2Action = {
    topologyId: 'topo2',
    type: ActionTypes.CLICK_TOPOLOGY
  };

  const CloseWebsocketAction = {
    type: ActionTypes.CLOSE_WEBSOCKET
  };

  const deSelectNode = {
    type: ActionTypes.DESELECT_NODE
  };

  const OpenWebsocketAction = {
    type: ActionTypes.OPEN_WEBSOCKET
  };

  const ReceiveNodesDeltaAction = {
    delta: {
      add: [{
        adjacency: ['n1', 'n2'],
        id: 'n1'
      }, {
        id: 'n2'
      }]
    },
    type: ActionTypes.RECEIVE_NODES_DELTA
  };

  const ReceiveNodesDeltaUpdateAction = {
    delta: {
      remove: ['n2'],
      update: [{
        adjacency: ['n1'],
        id: 'n1'
      }]
    },
    type: ActionTypes.RECEIVE_NODES_DELTA
  };

  const ReceiveTopologiesAction = {
    topologies: [{
      name: 'Topo1',
      options: [{
        defaultValue: 'off',
        id: 'option1',
        options: [
          {value: 'on'},
          {value: 'off'}
        ]
      }],
      stats: {
        node_count: 1
      },
      sub_topologies: [{
        name: 'topo 1 grouped',
        url: '/topo1-grouped'
      }],
      url: '/topo1'
    }, {
      name: 'Topo2',
      stats: {
        node_count: 0
      },
      sub_topologies: [{
        name: 'topo 2 sub',
        url: '/topo2-sub'
      }],
      url: '/topo2'
    }],
    type: ActionTypes.RECEIVE_TOPOLOGIES
  };

  const ReceiveTopologiesHiddenAction = {
    topologies: [{
      name: 'Topo1',
      stats: {
        node_count: 1
      },
      url: '/topo1'
    }, {
      hide_if_empty: true,
      name: 'Topo2',
      stats: { filtered_nodes: 0, node_count: 0 },
      sub_topologies: [{
        hide_if_empty: true,
        name: 'topo 2 sub',
        stats: { filtered_nodes: 0, node_count: 0 },
        url: '/topo2-sub',
      }],
      url: '/topo2'
    }],
    type: ActionTypes.RECEIVE_TOPOLOGIES
  };

  const RouteAction = {
    state: {},
    type: ActionTypes.ROUTE_TOPOLOGY
  };

  const ChangeInstanceAction = {
    type: ActionTypes.CHANGE_INSTANCE
  };

  // Basic tests

  it('returns initial state', () => {
    const nextState = reducer(undefined, {});
    expect(is(nextState, initialState)).toBeTruthy();
  });

  // topology tests

  it('init with no topologies', () => {
    const nextState = reducer(undefined, {});
    expect(nextState.get('topologies').size).toBe(0);
    expect(nextState.get('currentTopology')).toBeFalsy();
  });

  it('get current topology', () => {
    let nextState = initialState;
    nextState = reducer(nextState, ReceiveTopologiesAction);
    nextState = reducer(nextState, ClickTopologyAction);

    expect(nextState.get('topologies').size).toBe(2);
    expect(nextState.get('currentTopology').get('name')).toBe('Topo1');
    expect(nextState.get('currentTopology').get('url')).toBe('/topo1');
    expect(nextState.get('currentTopology').get('options').first().get('id')).toEqual('option1');
    expect(nextState.getIn(['currentTopology', 'options']).toJS()).toEqual([{
      defaultValue: 'off',
      id: 'option1',
      options: [
        { value: 'on'},
        { value: 'off'}
      ],
      selectType: 'one'
    }]);
  });

  it('get sub-topology', () => {
    let nextState = initialState;
    nextState = reducer(nextState, ReceiveTopologiesAction);
    nextState = reducer(nextState, ClickSubTopologyAction);

    expect(nextState.get('topologies').size).toBe(2);
    expect(nextState.get('currentTopology').get('name')).toBe('topo 1 grouped');
    expect(nextState.get('currentTopology').get('url')).toBe('/topo1-grouped');
    expect(nextState.get('currentTopology').get('options')).toBeUndefined();
  });

  // topology options

  it('changes topology option', () => {
    let nextState = initialState;
    nextState = reducer(nextState, ReceiveTopologiesAction);
    nextState = reducer(nextState, ClickTopologyAction);

    // default options
    expect(activeTopologyOptionsSelector(nextState).has('option1')).toBeTruthy();
    expect(activeTopologyOptionsSelector(nextState).get('option1')).toBeInstanceOf(Array);
    expect(activeTopologyOptionsSelector(nextState).get('option1')).toEqual(['off']);
    expect(getUrlState(nextState).topologyOptions).toBeUndefined();

    // turn on
    nextState = reducer(nextState, ChangeTopologyOptionAction);
    expect(activeTopologyOptionsSelector(nextState).get('option1')).toEqual(['on']);
    expect(getUrlState(nextState).topologyOptions.topo1.option1).toEqual(['on']);

    // turn off
    nextState = reducer(nextState, ChangeTopologyOptionAction2);
    expect(activeTopologyOptionsSelector(nextState).get('option1')).toEqual(['off']);
    expect(getUrlState(nextState).topologyOptions).toBeUndefined();

    // sub-topology should retain main topo options
    nextState = reducer(nextState, ClickSubTopologyAction);
    expect(activeTopologyOptionsSelector(nextState).get('option1')).toEqual(['off']);
    expect(getUrlState(nextState).topologyOptions).toBeUndefined();

    // other topology w/o options dont return options, but keep in app state
    nextState = reducer(nextState, ClickTopology2Action);
    expect(activeTopologyOptionsSelector(nextState).size).toEqual(0);
    expect(getUrlState(nextState).topologyOptions).toBeUndefined();
  });

  it('adds/removes a topology option', () => {
    const addAction = {
      option: 'namespace',
      topologyId: 'services',
      type: ActionTypes.CHANGE_TOPOLOGY_OPTION,
      value: ['default', 'scope'],
    };
    const removeAction = {
      option: 'namespace',
      topologyId: 'services',
      type: ActionTypes.CHANGE_TOPOLOGY_OPTION,
      value: ['default']
    };
    let nextState = initialState;
    nextState = reducer(nextState, { topologies, type: ActionTypes.RECEIVE_TOPOLOGIES});
    nextState = reducer(nextState, { topologyId: 'services', type: ActionTypes.CLICK_TOPOLOGY });
    nextState = reducer(nextState, addAction);
    expect(activeTopologyOptionsSelector(nextState).toJS()).toEqual({
      namespace: ['default', 'scope'],
      pseudo: ['hide']
    });
    nextState = reducer(nextState, removeAction);
    expect(activeTopologyOptionsSelector(nextState).toJS()).toEqual({
      namespace: ['default'],
      pseudo: ['hide']
    });
  });

  it('sets topology options from route', () => {
    RouteAction.state = {
      selectedNodeId: null,
      topologyId: 'topo1',
      topologyOptions: {topo1: {option1: 'on'}}
    };

    let nextState = initialState;
    nextState = reducer(nextState, RouteAction);
    expect(activeTopologyOptionsSelector(nextState).get('option1')).toBe('on');
    expect(getUrlState(nextState).topologyOptions.topo1.option1).toBe('on');

    // stay same after topos have been received
    nextState = reducer(nextState, ReceiveTopologiesAction);
    nextState = reducer(nextState, ClickTopologyAction);
    expect(activeTopologyOptionsSelector(nextState).get('option1')).toBe('on');
    expect(getUrlState(nextState).topologyOptions.topo1.option1).toBe('on');
  });

  it('uses default topology options from route', () => {
    RouteAction.state = {
      selectedNodeId: null,
      topologyId: 'topo1',
      topologyOptions: null
    };
    let nextState = initialState;
    nextState = reducer(nextState, RouteAction);
    nextState = reducer(nextState, ReceiveTopologiesAction);
    nextState = reducer(nextState, ClickTopologyAction);
    expect(activeTopologyOptionsSelector(nextState).get('option1')).toEqual(['off']);
    expect(getUrlState(nextState).topologyOptions).toBeUndefined();
  });

  // nodes delta

  it('replaces adjacency on update', () => {
    let nextState = initialState;
    nextState = reducer(nextState, ReceiveNodesDeltaAction);
    expect(nextState.get('nodes').toJS().n1.adjacency).toEqual(['n1', 'n2']);
    nextState = reducer(nextState, ReceiveNodesDeltaUpdateAction);
    expect(nextState.get('nodes').toJS().n1.adjacency).toEqual(['n1']);
  });

  // browsing

  it('shows nodes that were received', () => {
    let nextState = initialState;
    nextState = reducer(nextState, ReceiveNodesDeltaAction);
    expect(nextState.get('nodes').toJS()).toEqual(NODE_SET);
  });

  it('knows a route was set', () => {
    let nextState = initialState;
    expect(nextState.get('routeSet')).toBeFalsy();
    nextState = reducer(nextState, RouteAction);
    expect(nextState.get('routeSet')).toBeTruthy();
  });

  it('gets selected node after click', () => {
    let nextState = initialState;
    nextState = reducer(nextState, ReceiveNodesDeltaAction);
    nextState = reducer(nextState, ClickNodeAction);

    expect(nextState.get('selectedNodeId')).toBe('n1');
    expect(nextState.get('nodes').toJS()).toEqual(NODE_SET);

    nextState = reducer(nextState, deSelectNode);
    expect(nextState.get('selectedNodeId')).toBe(null);
    expect(nextState.get('nodes').toJS()).toEqual(NODE_SET);
  });

  it('keeps showing nodes on navigating back after node click', () => {
    let nextState = initialState;
    nextState = reducer(nextState, ReceiveTopologiesAction);
    nextState = reducer(nextState, ClickTopologyAction);
    nextState = reducer(nextState, ReceiveNodesDeltaAction);
    expect(getUrlState(nextState).selectedNodeId).toBeUndefined();

    nextState = reducer(nextState, ClickNodeAction);
    expect(getUrlState(nextState).selectedNodeId).toEqual('n1');

    // go back in browsing
    RouteAction.state = {selectedNodeId: null, topologyId: 'topo1'};
    nextState = reducer(nextState, RouteAction);
    expect(nextState.get('selectedNodeId')).toBeNull();
    expect(nextState.get('nodes').toJS()).toEqual(NODE_SET);
  });

  it('closes details when changing topologies', () => {
    let nextState = initialState;
    nextState = reducer(nextState, ReceiveTopologiesAction);
    nextState = reducer(nextState, ClickTopologyAction);
    nextState = reducer(nextState, ReceiveNodesDeltaAction);

    expect(getUrlState(nextState).selectedNodeId).toBeUndefined();
    expect(getUrlState(nextState).topologyId).toEqual('topo1');

    nextState = reducer(nextState, ClickNodeAction);
    expect(getUrlState(nextState).selectedNodeId).toEqual('n1');
    expect(getUrlState(nextState).topologyId).toEqual('topo1');

    nextState = reducer(nextState, ClickSubTopologyAction);
    expect(getUrlState(nextState).selectedNodeId).toBeUndefined();
    expect(getUrlState(nextState).topologyId).toEqual('topo1-grouped');
  });

  // connection errors

  it('resets topology on websocket reconnect', () => {
    let nextState = initialState;
    nextState = reducer(nextState, ReceiveNodesDeltaAction);
    expect(nextState.get('nodes').toJS()).toEqual(NODE_SET);

    nextState = reducer(nextState, CloseWebsocketAction);
    expect(nextState.get('websocketClosed')).toBeTruthy();
    // keep showing old nodes
    expect(nextState.get('nodes').toJS()).toEqual(NODE_SET);

    nextState = reducer(nextState, OpenWebsocketAction);
    expect(nextState.get('websocketClosed')).toBeFalsy();
  });

  // adjacency test

  it('returns the correct adjacency set for a node', () => {
    let nextState = initialState;
    nextState = reducer(nextState, ReceiveNodesDeltaAction);
    expect(getAdjacentNodes(nextState).size).toEqual(0);

    nextState = reducer(nextState, ClickNodeAction);
    expect(getAdjacentNodes(nextState, 'n1').size).toEqual(2);
    expect(getAdjacentNodes(nextState, 'n1').has('n1')).toBeTruthy();
    expect(getAdjacentNodes(nextState, 'n1').has('n2')).toBeTruthy();

    nextState = reducer(nextState, deSelectNode);
    expect(getAdjacentNodes(nextState).size).toEqual(0);
  });

  // empty topology

  it('detects that the nodes display is empty', () => {
    let nextState = initialState;
    nextState = reducer(nextState, ReceiveTopologiesAction);
    nextState = reducer(nextState, ClickTopologyAction);
    expect(isNodesDisplayEmpty(nextState)).toBeTruthy();

    nextState = reducer(nextState, ReceiveNodesDeltaAction);
    expect(isNodesDisplayEmpty(nextState)).toBeFalsy();

    nextState = reducer(nextState, ClickTopology2Action);
    expect(isNodesDisplayEmpty(nextState)).toBeTruthy();

    nextState = reducer(nextState, ReceiveNodesDeltaAction);
    expect(isNodesDisplayEmpty(nextState)).toBeFalsy();
  });

  it('detects that the topo stats are empty', () => {
    let nextState = initialState;
    nextState = reducer(nextState, ReceiveTopologiesAction);
    nextState = reducer(nextState, ClickTopologyAction);
    expect(isTopologyNodeCountZero(nextState)).toBeFalsy();

    nextState = reducer(nextState, ReceiveNodesDeltaAction);
    expect(isTopologyNodeCountZero(nextState)).toBeFalsy();

    nextState = reducer(nextState, ClickTopology2Action);
    expect(isTopologyNodeCountZero(nextState)).toBeTruthy();

    nextState = reducer(nextState, ClickTopologyAction);
    expect(isTopologyNodeCountZero(nextState)).toBeFalsy();
  });

  it('keeps hidden topology visible if selected', () => {
    let nextState = initialState;
    nextState = reducer(nextState, ReceiveTopologiesAction);
    nextState = reducer(nextState, ClickTopology2Action);
    nextState = reducer(nextState, ReceiveTopologiesHiddenAction);
    expect(nextState.get('currentTopologyId')).toEqual('topo2');
    expect(nextState.get('topologies').toJS().length).toEqual(2);
  });

  it('keeps hidden topology visible if sub_topology selected', () => {
    let nextState = initialState;
    nextState = reducer(nextState, ReceiveTopologiesAction);
    nextState = reducer(nextState, { topologyId: 'topo2-sub', type: ActionTypes.CLICK_TOPOLOGY });
    nextState = reducer(nextState, ReceiveTopologiesHiddenAction);
    expect(nextState.get('currentTopologyId')).toEqual('topo2-sub');
    expect(nextState.get('topologies').toJS().length).toEqual(2);
  });

  it('hides hidden topology if not selected', () => {
    let nextState = initialState;
    nextState = reducer(nextState, ClickTopologyAction);
    nextState = reducer(nextState, ReceiveTopologiesHiddenAction);
    expect(nextState.get('topologies').toJS().length).toEqual(1);
  });

  // selection of relatives

  it('keeps relatives as a stack', () => {
    let nextState = initialState;
    nextState = reducer(nextState, ClickNodeAction);
    expect(nextState.get('selectedNodeId')).toBe('n1');
    expect(nextState.get('nodeDetails').size).toEqual(1);
    expect(nextState.get('nodeDetails').has('n1')).toBeTruthy();
    expect(nextState.get('nodeDetails').keySeq().last()).toEqual('n1');

    nextState = reducer(nextState, ClickRelativeAction);
    // stack relative, first node stays main node
    expect(nextState.get('selectedNodeId')).toBe('n1');
    expect(nextState.get('nodeDetails').keySeq().last()).toEqual('rel1');
    expect(nextState.get('nodeDetails').size).toEqual(2);
    expect(nextState.get('nodeDetails').has('rel1')).toBeTruthy();

    // click on first node should clear the stack
    nextState = reducer(nextState, ClickNodeAction);
    expect(nextState.get('selectedNodeId')).toBe('n1');
    expect(nextState.get('nodeDetails').keySeq().last()).toEqual('n1');
    expect(nextState.get('nodeDetails').size).toEqual(1);
    expect(nextState.get('nodeDetails').has('rel1')).toBeFalsy();
  });

  it('keeps clears stack when sibling is clicked', () => {
    let nextState = initialState;
    nextState = reducer(nextState, ClickNodeAction);
    expect(nextState.get('selectedNodeId')).toBe('n1');
    expect(nextState.get('nodeDetails').size).toEqual(1);
    expect(nextState.get('nodeDetails').has('n1')).toBeTruthy();
    expect(nextState.get('nodeDetails').keySeq().last()).toEqual('n1');

    nextState = reducer(nextState, ClickRelativeAction);
    // stack relative, first node stays main node
    expect(nextState.get('selectedNodeId')).toBe('n1');
    expect(nextState.get('nodeDetails').keySeq().last()).toEqual('rel1');
    expect(nextState.get('nodeDetails').size).toEqual(2);
    expect(nextState.get('nodeDetails').has('rel1')).toBeTruthy();

    // click on sibling node should clear the stack
    nextState = reducer(nextState, ClickNode2Action);
    expect(nextState.get('selectedNodeId')).toBe('n2');
    expect(nextState.get('nodeDetails').keySeq().last()).toEqual('n2');
    expect(nextState.get('nodeDetails').size).toEqual(1);
    expect(nextState.get('nodeDetails').has('n1')).toBeFalsy();
    expect(nextState.get('nodeDetails').has('rel1')).toBeFalsy();
  });

  it('selectes relatives topology while keeping node selected', () => {
    let nextState = initialState;
    nextState = reducer(nextState, ClickTopologyAction);
    nextState = reducer(nextState, ReceiveTopologiesAction);
    expect(nextState.get('currentTopology').get('name')).toBe('Topo1');

    nextState = reducer(nextState, ClickNodeAction);
    expect(nextState.get('selectedNodeId')).toBe('n1');
    expect(nextState.get('nodeDetails').size).toEqual(1);
    expect(nextState.get('nodeDetails').has('n1')).toBeTruthy();
    expect(nextState.get('nodeDetails').keySeq().last()).toEqual('n1');

    nextState = reducer(nextState, ClickRelativeAction);
    // stack relative, first node stays main node
    expect(nextState.get('selectedNodeId')).toBe('n1');
    expect(nextState.get('nodeDetails').keySeq().last()).toEqual('rel1');
    expect(nextState.get('nodeDetails').size).toEqual(2);
    expect(nextState.get('nodeDetails').has('rel1')).toBeTruthy();

    // click switches over to relative's topology and selectes relative
    nextState = reducer(nextState, ClickShowTopologyForNodeAction);
    expect(nextState.get('selectedNodeId')).toBe('rel1');
    expect(nextState.get('nodeDetails').keySeq().last()).toEqual('rel1');
    expect(nextState.get('nodeDetails').size).toEqual(1);
    expect(nextState.get('currentTopology').get('name')).toBe('Topo2');
  });
  it('closes the help dialog if the canvas is clicked', () => {
    let nextState = initialState.set('showingHelp', true);
    nextState = reducer(nextState, { type: ActionTypes.CLICK_BACKGROUND });
    expect(nextState.get('showingHelp')).toBe(false);
  });
  it('switches to table view when complexity is high', () => {
    let nextState = initialState.set('currentTopology', fromJS(topologies[0]));
    nextState = reducer(nextState, {type: ActionTypes.SET_RECEIVED_NODES_DELTA});
    expect(nextState.get('topologyViewMode')).toEqual(TABLE_VIEW_MODE);
    expect(nextState.get('initialNodesLoaded')).toBe(true);
  });
  it('cleans up old adjacencies', () => {
    // Add some nodes
    const action1 = {
      delta: { add: [{ id: 'n1' }, { id: 'n2' }] },
      type: ActionTypes.RECEIVE_NODES_DELTA
    };
    // Show nodes as connected
    const action2 = {
      delta: {
        update: [{ adjacency: ['n2'], id: 'n1' }]
      },
      type: ActionTypes.RECEIVE_NODES_DELTA
    };
    // Remove the connection
    const action3 = {
      delta: {
        update: [{ id: 'n1' }]
      },
      type: ActionTypes.RECEIVE_NODES_DELTA
    };
    let nextState = reducer(initialState, action1);
    nextState = reducer(nextState, action2);
    nextState = reducer(nextState, action3);
    expect(nextState.getIn(['nodes', 'n1', 'adjacency'])).toBeFalsy();
  });
  it('removes non-transferrable state values when changing instances', () => {
    let nextState = initialState;
    nextState = reducer(nextState, ClickNodeAction);
    expect(nextState.get('selectedNodeId')).toEqual('n1');
    expect(nextState.getIn(['nodeDetails', 'n1'])).toBeTruthy();
    nextState = reducer(nextState, ChangeInstanceAction);
    expect(nextState.get('selectedNodeId')).toBeFalsy();
    expect(nextState.getIn(['nodeDetails', 'n1'])).toBeFalsy();
  });
  it('highlights bidirectional edges', () => {
    const action = {
      edgeId: constructEdgeId('abc123', 'def456'),
      type: ActionTypes.ENTER_EDGE
    };
    const nextState = reducer(initialState, action);
    expect(highlightedEdgeIdsSelector(nextState).toJS()).toEqual([
      constructEdgeId('abc123', 'def456'),
      constructEdgeId('def456', 'abc123')
    ]);
  });
});