import {ActiveFilter, noFilter} from 'sentry/components/events/interfaces/spans/filter';
import {EnhancedProcessedSpanType} from 'sentry/components/events/interfaces/spans/types';
import WaterfallModel from 'sentry/components/events/interfaces/spans/waterfallModel';
import {EntryType, EventTransaction} from 'sentry/types/event';
import {assert} from 'sentry/types/utils';

describe('WaterfallModel', () => {
  const event = {
    id: '2b658a829a21496b87fd1f14a61abf65',
    eventID: '2b658a829a21496b87fd1f14a61abf65',
    title: '/organizations/:orgId/discover/results/',
    type: 'transaction',
    startTimestamp: 1622079935.86141,
    endTimestamp: 1622079940.032905,
    contexts: {
      trace: {
        trace_id: '8cbbc19c0f54447ab702f00263262726',
        span_id: 'a934857184bdf5a6',
        op: 'pageload',
        status: 'unknown',
        type: 'trace',
      },
    },
    entries: [
      {
        data: [
          {
            timestamp: 1622079937.227645,
            start_timestamp: 1622079936.90689,
            description: 'GET /api/0/organizations/?member=1',
            op: 'http',
            span_id: 'b23703998ae619e7',
            parent_span_id: 'a934857184bdf5a6',
            trace_id: '8cbbc19c0f54447ab702f00263262726',
            status: 'ok',
            tags: {
              'http.status_code': '200',
            },
            data: {
              'http.method': 'GET',
              type: 'fetch',
              url: '/api/0/organizations/?member=1',
            },
          },
          {
            timestamp: 1622079938.20331,
            start_timestamp: 1622079937.907515,
            description: 'GET /api/0/internal/health/',
            op: 'http',
            span_id: 'a453cc713e5baf9c',
            parent_span_id: 'a934857184bdf5a6',
            trace_id: '8cbbc19c0f54447ab702f00263262726',
            status: 'ok',
            tags: {
              'http.status_code': '200',
            },
            data: {
              'http.method': 'GET',
              type: 'fetch',
              url: '/api/0/internal/health/',
            },
          },
          {
            timestamp: 1622079936.05839,
            start_timestamp: 1622079936.048125,
            description: '/_static/dist/sentry/sentry.541f5b.css',
            op: 'resource.link',
            span_id: 'a23f26b939d1a735',
            parent_span_id: 'a453cc713e5baf9c',
            trace_id: '8cbbc19c0f54447ab702f00263262726',
            data: {
              'http.decoded_response_content_length': 159248,
              'http.response_content_length': 159248,
              'http.response_transfer_size': 275,
            },
          },
          {
            timestamp: 1622079938.32451,
            start_timestamp: 1622079938.31431,
            description: 'fonts_bundle.css',
            op: 'fonts',
            span_id: 'a0e89ce4e0900ad5',
            parent_span_id: 'a23f26b939d1a735',
            trace_id: '8cbbc19c0f54447ab702f00263262726',
            data: {
              'http.decoded_response_content_length': 159248,
              'http.response_content_length': 159248,
              'http.response_transfer_size': 275,
            },
          },
          {
            timestamp: 1622079938.32451,
            start_timestamp: 1622079938.31431,
            description: 'google-fonts.css',
            op: 'fonts',
            span_id: 'aa764a4e9d70c907',
            parent_span_id: 'a0e89ce4e0900ad5',
            trace_id: '8cbbc19c0f54447ab702f00263262726',
            data: {
              'http.decoded_response_content_length': 159248,
              'http.response_content_length': 159248,
              'http.response_transfer_size': 275,
            },
          },
          {
            timestamp: 1622079938.32451,
            start_timestamp: 1622079938.31431,
            description: 'icon-fonts.css',
            op: 'fonts',
            span_id: '883837a47bc0d836',
            parent_span_id: 'a0e89ce4e0900ad5',
            trace_id: '8cbbc19c0f54447ab702f00263262726',
            data: {
              'http.decoded_response_content_length': 159248,
              'http.response_content_length': 159248,
              'http.response_transfer_size': 275,
            },
          },
          {
            timestamp: 1622079938.32451,
            start_timestamp: 1622079938.31431,
            description: '/_static/dist/sentry/sentry.fonts.min.css',
            op: 'css',
            span_id: 'b5795cf4ba68bbb4',
            parent_span_id: 'a934857184bdf5a6',
            trace_id: '8cbbc19c0f54447ab702f00263262726',
            data: {
              'http.decoded_response_content_length': 159248,
              'http.response_content_length': 159248,
              'http.response_transfer_size': 275,
            },
          },
          {
            timestamp: 1622079938.32451,
            start_timestamp: 1622079938.31431,
            description: 'fonts1.css',
            op: 'fonts',
            span_id: 'b5795cf4ba68bbb5',
            parent_span_id: 'b5795cf4ba68bbb4',
            trace_id: '8cbbc19c0f54447ab702f00263262726',
            data: {
              'http.decoded_response_content_length': 159248,
              'http.response_content_length': 159248,
              'http.response_transfer_size': 275,
            },
          },
          {
            timestamp: 1622079938.32451,
            start_timestamp: 1622079938.31431,
            description: 'fonts2.css',
            op: 'fonts',
            span_id: 'b5795cf4ba68bbb6',
            parent_span_id: 'b5795cf4ba68bbb5',
            trace_id: '8cbbc19c0f54447ab702f00263262726',
            data: {
              'http.decoded_response_content_length': 159248,
              'http.response_content_length': 159248,
              'http.response_transfer_size': 275,
            },
          },
          {
            timestamp: 1622079938.32451,
            start_timestamp: 1622079938.31431,
            description: 'fonts3.css',
            op: 'fonts',
            span_id: 'b5795cf4ba68bbb7',
            parent_span_id: 'b5795cf4ba68bbb6',
            trace_id: '8cbbc19c0f54447ab702f00263262726',
            data: {
              'http.decoded_response_content_length': 159248,
              'http.response_content_length': 159248,
              'http.response_transfer_size': 275,
            },
          },
        ],
        type: EntryType.SPANS,
      },
    ],
  } as unknown as EventTransaction;

  const fullWaterfall: EnhancedProcessedSpanType[] = [
    {
      type: 'root_span',
      span: {
        trace_id: '8cbbc19c0f54447ab702f00263262726',
        span_id: 'a934857184bdf5a6',
        start_timestamp: 1622079935.86141,
        timestamp: 1622079940.032905,
        description: undefined,
        parent_span_id: undefined,
        op: 'pageload',
        data: {},
        status: 'unknown',
      },
      numOfSpanChildren: 3,
      treeDepth: 0,
      isLastSibling: true,
      continuingTreeDepths: [],
      showEmbeddedChildren: false,
      toggleEmbeddedChildren: expect.anything(),
      fetchEmbeddedChildrenState: 'idle',
      isEmbeddedTransactionTimeAdjusted: false,
    },
    {
      type: 'span',
      span: {
        timestamp: 1622079937.227645,
        start_timestamp: 1622079936.90689,
        description: 'GET /api/0/organizations/?member=1',
        op: 'http',
        span_id: 'b23703998ae619e7',
        parent_span_id: 'a934857184bdf5a6',
        trace_id: '8cbbc19c0f54447ab702f00263262726',
        status: 'ok',
        tags: {'http.status_code': '200'},
        data: {
          'http.method': 'GET',
          type: 'fetch',
          url: '/api/0/organizations/?member=1',
        },
      },
      numOfSpanChildren: 0,
      treeDepth: 1,
      isLastSibling: false,
      continuingTreeDepths: [],
      showEmbeddedChildren: false,
      toggleEmbeddedChildren: expect.anything(),
      fetchEmbeddedChildrenState: 'idle',
      toggleNestedSpanGroup: undefined,
      toggleSiblingSpanGroup: undefined,
      isEmbeddedTransactionTimeAdjusted: false,
    },
    {
      type: 'gap',
      span: {
        type: 'gap',
        start_timestamp: 1622079937.227645,
        timestamp: 1622079937.907515,
        description: 'Missing span instrumentation',
        isOrphan: false,
      },
      numOfSpanChildren: 0,
      treeDepth: 1,
      isLastSibling: false,
      continuingTreeDepths: [],
      showEmbeddedChildren: false,
      toggleEmbeddedChildren: undefined,
      fetchEmbeddedChildrenState: 'idle',
      isEmbeddedTransactionTimeAdjusted: false,
    },
    {
      type: 'span',
      span: {
        timestamp: 1622079938.20331,
        start_timestamp: 1622079937.907515,
        description: 'GET /api/0/internal/health/',
        op: 'http',
        span_id: 'a453cc713e5baf9c',
        parent_span_id: 'a934857184bdf5a6',
        trace_id: '8cbbc19c0f54447ab702f00263262726',
        status: 'ok',
        tags: {'http.status_code': '200'},
        data: {'http.method': 'GET', type: 'fetch', url: '/api/0/internal/health/'},
      },
      numOfSpanChildren: 1,
      treeDepth: 1,
      isLastSibling: false,
      continuingTreeDepths: [],
      showEmbeddedChildren: false,
      toggleEmbeddedChildren: expect.anything(),
      fetchEmbeddedChildrenState: 'idle',
      toggleNestedSpanGroup: undefined,
      toggleSiblingSpanGroup: undefined,
      isEmbeddedTransactionTimeAdjusted: false,
    },
    {
      type: 'span',
      span: {
        timestamp: 1622079936.05839,
        start_timestamp: 1622079936.048125,
        description: '/_static/dist/sentry/sentry.541f5b.css',
        op: 'resource.link',
        span_id: 'a23f26b939d1a735',
        parent_span_id: 'a453cc713e5baf9c',
        trace_id: '8cbbc19c0f54447ab702f00263262726',
        data: {
          'http.decoded_response_content_length': 159248,
          'http.response_content_length': 159248,
          'http.response_transfer_size': 275,
        },
      },
      numOfSpanChildren: 1,
      treeDepth: 2,
      isLastSibling: true,
      continuingTreeDepths: [1],
      showEmbeddedChildren: false,
      toggleEmbeddedChildren: expect.anything(),
      fetchEmbeddedChildrenState: 'idle',
      toggleNestedSpanGroup: undefined,
      toggleSiblingSpanGroup: undefined,
      isEmbeddedTransactionTimeAdjusted: false,
    },
    {
      type: 'span',
      span: {
        timestamp: 1622079938.32451,
        start_timestamp: 1622079938.31431,
        description: 'fonts_bundle.css',
        op: 'fonts',
        span_id: 'a0e89ce4e0900ad5',
        parent_span_id: 'a23f26b939d1a735',
        trace_id: '8cbbc19c0f54447ab702f00263262726',
        data: {
          'http.decoded_response_content_length': 159248,
          'http.response_content_length': 159248,
          'http.response_transfer_size': 275,
        },
      },
      numOfSpanChildren: 2,
      treeDepth: 3,
      isLastSibling: true,
      continuingTreeDepths: [1],
      showEmbeddedChildren: false,
      toggleEmbeddedChildren: expect.anything(),
      fetchEmbeddedChildrenState: 'idle',
      toggleNestedSpanGroup: undefined,
      toggleSiblingSpanGroup: undefined,
      isEmbeddedTransactionTimeAdjusted: false,
    },
    {
      type: 'span',
      span: {
        timestamp: 1622079938.32451,
        start_timestamp: 1622079938.31431,
        description: 'google-fonts.css',
        op: 'fonts',
        span_id: 'aa764a4e9d70c907',
        parent_span_id: 'a0e89ce4e0900ad5',
        trace_id: '8cbbc19c0f54447ab702f00263262726',
        data: {
          'http.decoded_response_content_length': 159248,
          'http.response_content_length': 159248,
          'http.response_transfer_size': 275,
        },
      },
      numOfSpanChildren: 0,
      treeDepth: 4,
      isLastSibling: false,
      continuingTreeDepths: [1],
      showEmbeddedChildren: false,
      toggleEmbeddedChildren: expect.anything(),
      fetchEmbeddedChildrenState: 'idle',
      toggleNestedSpanGroup: undefined,
      toggleSiblingSpanGroup: undefined,
      isEmbeddedTransactionTimeAdjusted: false,
    },
    {
      type: 'span',
      span: {
        timestamp: 1622079938.32451,
        start_timestamp: 1622079938.31431,
        description: 'icon-fonts.css',
        op: 'fonts',
        span_id: '883837a47bc0d836',
        parent_span_id: 'a0e89ce4e0900ad5',
        trace_id: '8cbbc19c0f54447ab702f00263262726',
        data: {
          'http.decoded_response_content_length': 159248,
          'http.response_content_length': 159248,
          'http.response_transfer_size': 275,
        },
      },
      numOfSpanChildren: 0,
      treeDepth: 4,
      isLastSibling: true,
      continuingTreeDepths: [1],
      showEmbeddedChildren: false,
      toggleEmbeddedChildren: expect.anything(),
      fetchEmbeddedChildrenState: 'idle',
      toggleNestedSpanGroup: undefined,
      toggleSiblingSpanGroup: undefined,
      isEmbeddedTransactionTimeAdjusted: false,
    },
    {
      type: 'gap',
      span: {
        type: 'gap',
        start_timestamp: 1622079938.20331,
        timestamp: 1622079938.31431,
        description: 'Missing span instrumentation',
        isOrphan: false,
      },
      numOfSpanChildren: 0,
      treeDepth: 1,
      isLastSibling: false,
      continuingTreeDepths: [],
      showEmbeddedChildren: false,
      toggleEmbeddedChildren: undefined,
      fetchEmbeddedChildrenState: 'idle',
      isEmbeddedTransactionTimeAdjusted: false,
    },
    {
      type: 'span',
      span: {
        timestamp: 1622079938.32451,
        start_timestamp: 1622079938.31431,
        description: '/_static/dist/sentry/sentry.fonts.min.css',
        op: 'css',
        span_id: 'b5795cf4ba68bbb4',
        parent_span_id: 'a934857184bdf5a6',
        trace_id: '8cbbc19c0f54447ab702f00263262726',
        data: {
          'http.decoded_response_content_length': 159248,
          'http.response_content_length': 159248,
          'http.response_transfer_size': 275,
        },
      },
      numOfSpanChildren: 1,
      treeDepth: 1,
      isLastSibling: true,
      continuingTreeDepths: [],
      showEmbeddedChildren: false,
      toggleEmbeddedChildren: expect.anything(),
      fetchEmbeddedChildrenState: 'idle',
      toggleNestedSpanGroup: undefined,
      toggleSiblingSpanGroup: undefined,
      isEmbeddedTransactionTimeAdjusted: false,
    },
    {
      type: 'span_group_chain',
      span: {
        timestamp: 1622079938.32451,
        start_timestamp: 1622079938.31431,
        description: 'fonts3.css',
        op: 'fonts',
        span_id: 'b5795cf4ba68bbb7',
        parent_span_id: 'b5795cf4ba68bbb6',
        trace_id: '8cbbc19c0f54447ab702f00263262726',
        data: {
          'http.decoded_response_content_length': 159248,
          'http.response_content_length': 159248,
          'http.response_transfer_size': 275,
        },
      },
      treeDepth: 2,
      continuingTreeDepths: [],
      spanNestedGrouping: [
        {
          type: 'span',
          span: {
            timestamp: 1622079938.32451,
            start_timestamp: 1622079938.31431,
            description: 'fonts1.css',
            op: 'fonts',
            span_id: 'b5795cf4ba68bbb5',
            parent_span_id: 'b5795cf4ba68bbb4',
            trace_id: '8cbbc19c0f54447ab702f00263262726',
            data: {
              'http.decoded_response_content_length': 159248,
              'http.response_content_length': 159248,
              'http.response_transfer_size': 275,
            },
          },
          numOfSpanChildren: 1,
          treeDepth: 2,
          isLastSibling: true,
          continuingTreeDepths: [],
          showEmbeddedChildren: false,
          toggleEmbeddedChildren: expect.anything(),
          fetchEmbeddedChildrenState: 'idle',
          toggleNestedSpanGroup: undefined,
          toggleSiblingSpanGroup: undefined,
          isEmbeddedTransactionTimeAdjusted: false,
        },
        {
          type: 'span',
          span: {
            timestamp: 1622079938.32451,
            start_timestamp: 1622079938.31431,
            description: 'fonts2.css',
            op: 'fonts',
            span_id: 'b5795cf4ba68bbb6',
            parent_span_id: 'b5795cf4ba68bbb5',
            trace_id: '8cbbc19c0f54447ab702f00263262726',
            data: {
              'http.decoded_response_content_length': 159248,
              'http.response_content_length': 159248,
              'http.response_transfer_size': 275,
            },
          },
          numOfSpanChildren: 1,
          treeDepth: 2,
          isLastSibling: true,
          continuingTreeDepths: [],
          showEmbeddedChildren: false,
          toggleEmbeddedChildren: expect.anything(),
          fetchEmbeddedChildrenState: 'idle',
          toggleNestedSpanGroup: undefined,
          toggleSiblingSpanGroup: undefined,
          isEmbeddedTransactionTimeAdjusted: false,
        },
      ],
      isNestedSpanGroupExpanded: false,
      toggleNestedSpanGroup: expect.anything(),
      toggleSiblingSpanGroup: undefined,
    },
    {
      type: 'span',
      span: {
        timestamp: 1622079938.32451,
        start_timestamp: 1622079938.31431,
        description: 'fonts3.css',
        op: 'fonts',
        span_id: 'b5795cf4ba68bbb7',
        parent_span_id: 'b5795cf4ba68bbb6',
        trace_id: '8cbbc19c0f54447ab702f00263262726',
        data: {
          'http.decoded_response_content_length': 159248,
          'http.response_content_length': 159248,
          'http.response_transfer_size': 275,
        },
      },
      numOfSpanChildren: 0,
      treeDepth: 3,
      isLastSibling: true,
      continuingTreeDepths: [],
      showEmbeddedChildren: false,
      toggleEmbeddedChildren: expect.anything(),
      fetchEmbeddedChildrenState: 'idle',
      toggleNestedSpanGroup: undefined,
      toggleSiblingSpanGroup: undefined,
      isEmbeddedTransactionTimeAdjusted: false,
    },
  ];

  it('isEvent', () => {
    const waterfallModel = new WaterfallModel(event);

    expect(waterfallModel.event).toMatchObject(event);
    expect(waterfallModel.isEvent(event)).toBe(true);
    expect(
      waterfallModel.isEvent({
        ...event,
        id: 'somethingelse',
      })
    ).toBe(false);
  });

  it('get operationNameCounts', () => {
    const waterfallModel = new WaterfallModel(event);

    expect(Object.fromEntries(waterfallModel.operationNameCounts)).toMatchObject({
      http: 2,
      pageload: 1,
      'resource.link': 1,
    });
  });

  it('toggleOperationNameFilter', () => {
    const waterfallModel = new WaterfallModel(event);

    expect(waterfallModel.operationNameFilters).toEqual(noFilter);

    // toggle http filter
    waterfallModel.toggleOperationNameFilter('http');

    const operationNameFilters = waterfallModel.operationNameFilters as ActiveFilter;

    expect(operationNameFilters.type).toBe('active_filter');
    expect(Array.from(operationNameFilters.operationNames)).toEqual(['http']);

    // un-toggle http filter
    waterfallModel.toggleOperationNameFilter('http');
    expect(waterfallModel.operationNameFilters).toEqual(noFilter);
  });

  it('toggleAllOperationNameFilters', () => {
    const waterfallModel = new WaterfallModel(event);

    expect(waterfallModel.operationNameFilters).toEqual(noFilter);

    // toggle all operation names
    waterfallModel.toggleAllOperationNameFilters();
    let operationNameFilters = waterfallModel.operationNameFilters as ActiveFilter;

    expect(operationNameFilters.type).toBe('active_filter');
    expect(Array.from(operationNameFilters.operationNames)).toEqual([
      'css',
      'fonts',
      'http',
      'pageload',
      'resource.link',
    ]);

    // toggle http filter
    waterfallModel.toggleOperationNameFilter('http');
    operationNameFilters = waterfallModel.operationNameFilters as ActiveFilter;

    expect(operationNameFilters.type).toBe('active_filter');
    expect(Array.from(operationNameFilters.operationNames)).toEqual([
      'css',
      'fonts',
      'pageload',
      'resource.link',
    ]);

    // toggle all operation names; expect un-toggled operation names to be toggled on
    waterfallModel.toggleAllOperationNameFilters();
    operationNameFilters = waterfallModel.operationNameFilters as ActiveFilter;

    expect(operationNameFilters.type).toBe('active_filter');
    expect(Array.from(operationNameFilters.operationNames)).toEqual([
      'css',
      'fonts',
      'http',
      'pageload',
      'resource.link',
    ]);

    // un-toggle all operation names
    waterfallModel.toggleAllOperationNameFilters();
    expect(waterfallModel.operationNameFilters).toEqual(noFilter);
  });

  it('querySpanSearch', async () => {
    const waterfallModel = new WaterfallModel(event);
    expect(waterfallModel.fuse).toBe(undefined);

    // Fuzzy search needs to be loaded asynchronously
    await tick();

    // expect fuse index to be created
    expect(waterfallModel.fuse).not.toBe(undefined);

    expect(waterfallModel.filterSpans).toBe(undefined);
    expect(waterfallModel.searchQuery).toBe(undefined);

    waterfallModel.querySpanSearch('GET /api/0/organizations/?member=1');

    expect(Array.from(waterfallModel.filterSpans!.spanIDs).sort()).toEqual(
      ['a453cc713e5baf9c', 'b23703998ae619e7'].sort()
    );
    expect(waterfallModel.searchQuery).toBe('GET /api/0/organizations/?member=1');
  });

  it('getWaterfall()', async () => {
    const waterfallModel = new WaterfallModel(event);
    // Fuzzy search needs to be loaded asynchronously
    await tick();

    // show all spans in the waterfall
    let spans = waterfallModel.getWaterfall({
      viewStart: 0,
      viewEnd: 1,
    });

    expect(spans).toEqual(fullWaterfall);

    // perform a window selection
    spans = waterfallModel.getWaterfall({
      viewStart: 0.4,
      viewEnd: 0.65,
    });

    let expected = [...fullWaterfall];

    expected[1] = {
      type: 'out_of_view',
      span: fullWaterfall[1].span,
    } as EnhancedProcessedSpanType;

    expected[4] = {
      type: 'out_of_view',
      span: fullWaterfall[4].span,
    } as EnhancedProcessedSpanType;

    expect(spans).toEqual(expected);

    // toggle http filter with a window selection
    waterfallModel.toggleOperationNameFilter('http');
    spans = waterfallModel.getWaterfall({
      viewStart: 0.4,
      viewEnd: 0.65,
    });

    assert(
      fullWaterfall[10].type === 'span_group_chain' &&
        fullWaterfall[10].spanNestedGrouping
    );
    expected = [
      {
        type: 'filtered_out',
        span: fullWaterfall[0].span,
      },
      {
        type: 'out_of_view',
        span: fullWaterfall[1].span,
      },
      fullWaterfall[2],
      fullWaterfall[3],
      {
        type: 'filtered_out',
        span: fullWaterfall[4].span,
      },
      {
        type: 'filtered_out',
        span: fullWaterfall[5].span,
      },
      {
        type: 'filtered_out',
        span: fullWaterfall[6].span,
      },
      {
        type: 'filtered_out',
        span: fullWaterfall[7].span,
      },
      {
        type: 'filtered_out',
        span: fullWaterfall[9].span,
      },
      {
        type: 'filtered_out',
        span: fullWaterfall[10].spanNestedGrouping[0].span,
      },
      {
        type: 'filtered_out',
        span: fullWaterfall[10].spanNestedGrouping[1].span,
      },
      {
        type: 'filtered_out',
        span: fullWaterfall[11].span,
      },
    ] as EnhancedProcessedSpanType[];

    expect(spans).toEqual(expected);

    // toggle ops filters with a window selection and search query
    // NOTE: http was toggled on in the previous case
    waterfallModel.toggleOperationNameFilter('pageload');
    waterfallModel.querySpanSearch('a453cc713e5baf9c');
    expect(waterfallModel.searchQuery).toBe('a453cc713e5baf9c');
    spans = waterfallModel.getWaterfall({
      viewStart: 0.2,
      viewEnd: 0.65,
    });

    expected[1].type = 'filtered_out';

    expect(spans).toEqual(expected);
  });

  it('toggleSpanSubTree()', () => {
    const waterfallModel = new WaterfallModel(event);

    let spans = waterfallModel.getWaterfall({
      viewStart: 0,
      viewEnd: 1,
    });

    expect(spans).toEqual(fullWaterfall);

    // toggle a span to hide their sub-tree
    waterfallModel.toggleSpanSubTree('a453cc713e5baf9c');

    spans = waterfallModel.getWaterfall({
      viewStart: 0,
      viewEnd: 1,
    });

    expect(spans).toEqual(
      fullWaterfall.filter((_span, index) => {
        // 5th through 8th spans should be hidden
        return !(index >= 4 && index <= 7);
      })
    );

    // toggle a span to reveal their sub-tree
    waterfallModel.toggleSpanSubTree('a453cc713e5baf9c');

    spans = waterfallModel.getWaterfall({
      viewStart: 0,
      viewEnd: 1,
    });

    expect(spans).toEqual(fullWaterfall);
  });

  it('span grouping - only child parent-child chain - root is not grouped', () => {
    const event2 = {
      ...event,
      entries: [],
    };

    const waterfallModel = new WaterfallModel(event2);

    const spans = waterfallModel.getWaterfall({
      viewStart: 0,
      viewEnd: 1,
    });

    expect(spans).toEqual([
      {
        ...fullWaterfall[0],
        numOfSpanChildren: 0,
        toggleNestedSpanGroup: undefined,
      },
    ]);
  });

  it('span grouping - only child parent-child chain - root span and a span (2 spans) are not grouped', () => {
    const event2: EventTransaction = {
      ...event,
      entries: [
        {
          data: [event.entries[0].data[0]],
          type: EntryType.SPANS,
        },
      ],
    };

    const waterfallModel = new WaterfallModel(event2);

    const spans = waterfallModel.getWaterfall({
      viewStart: 0,
      viewEnd: 1,
    });

    expect(spans).toEqual([
      {
        ...fullWaterfall[0],
        numOfSpanChildren: 1,
        toggleNestedSpanGroup: undefined,
      },
      {
        ...fullWaterfall[1],
        isLastSibling: true,
        numOfSpanChildren: 0,
        toggleNestedSpanGroup: undefined,
      },
    ]);
  });

  it('span grouping - only child parent-child chain - root span and 2 spans (3 spans) are not grouped', () => {
    const event2: EventTransaction = {
      ...event,
      entries: [
        {
          data: [
            event.entries[0].data[0],
            {
              ...event.entries[0].data[0],
              parent_span_id: event.entries[0].data[0].span_id,
              span_id: 'foo',
            },
          ],
          type: EntryType.SPANS,
        },
      ],
    };

    const waterfallModel = new WaterfallModel(event2);

    const spans = waterfallModel.getWaterfall({
      viewStart: 0,
      viewEnd: 1,
    });

    expect(spans).toEqual([
      {
        ...fullWaterfall[0],
        treeDepth: 0,
        numOfSpanChildren: 1,
        toggleNestedSpanGroup: undefined,
      },
      {
        ...fullWaterfall[1],
        treeDepth: 1,
        isLastSibling: true,
        numOfSpanChildren: 1,
        toggleNestedSpanGroup: undefined,
      },
      {
        ...fullWaterfall[1],
        span: {
          ...fullWaterfall[1].span,
          parent_span_id: event.entries[0].data[0].span_id,
          span_id: 'foo',
        },
        treeDepth: 2,
        isLastSibling: true,
        numOfSpanChildren: 0,
        toggleNestedSpanGroup: undefined,
      },
    ]);
  });

  it('span grouping - only child parent-child chain - root span and 3+ spans (4 spans) are not grouped', () => {
    const event2: EventTransaction = {
      ...event,
      entries: [
        {
          data: [
            event.entries[0].data[0],
            {
              ...event.entries[0].data[0],
              parent_span_id: event.entries[0].data[0].span_id,
              span_id: 'foo',
            },
            {
              ...event.entries[0].data[0],
              parent_span_id: 'foo',
              span_id: 'bar',
            },
          ],
          type: EntryType.SPANS,
        },
      ],
    };
    const waterfallModel = new WaterfallModel(event2);

    let spans = waterfallModel.getWaterfall({
      viewStart: 0,
      viewEnd: 1,
    });

    // expect 1 or more spans are grouped
    expect(spans).toHaveLength(3);

    assert(fullWaterfall[1].type === 'span');
    const collapsedWaterfallExpected = [
      {
        ...fullWaterfall[0],
        numOfSpanChildren: 1,
        toggleNestedSpanGroup: undefined,
      },
      {
        type: 'span_group_chain',
        treeDepth: 1,
        continuingTreeDepths: fullWaterfall[1].continuingTreeDepths,
        span: {
          ...fullWaterfall[1].span,
          parent_span_id: 'foo',
          span_id: 'bar',
        },
        spanNestedGrouping: [
          {
            ...fullWaterfall[1],
            isLastSibling: true,
            numOfSpanChildren: 1,
            toggleNestedSpanGroup: undefined,
          },
          {
            ...fullWaterfall[1],
            span: {
              ...fullWaterfall[1].span,
              parent_span_id: event.entries[0].data[0].span_id,
              span_id: 'foo',
            },
            isLastSibling: true,
            numOfSpanChildren: 1,
            toggleNestedSpanGroup: undefined,
          },
        ],
        isNestedSpanGroupExpanded: false,
        toggleNestedSpanGroup: expect.anything(),
      },
      {
        ...fullWaterfall[1],
        span: {
          ...fullWaterfall[1].span,
          parent_span_id: 'foo',
          span_id: 'bar',
        },
        isLastSibling: true,
        numOfSpanChildren: 0,
        treeDepth: 2,
        toggleNestedSpanGroup: undefined,
      },
    ];

    expect(spans).toEqual(collapsedWaterfallExpected);

    // Expand span group
    assert(spans[1].type === 'span' && spans[1].toggleNestedSpanGroup);
    spans[1].toggleNestedSpanGroup();

    spans = waterfallModel.getWaterfall({
      viewStart: 0,
      viewEnd: 1,
    });

    // expect span group to be expanded
    expect(spans).toHaveLength(4);

    expect(spans).toEqual([
      {
        ...fullWaterfall[0],
        numOfSpanChildren: 1,
        treeDepth: 0,
        toggleNestedSpanGroup: undefined,
      },
      {
        ...fullWaterfall[1],
        isLastSibling: true,
        numOfSpanChildren: 1,
        treeDepth: 1,
        toggleNestedSpanGroup: expect.anything(),
      },
      {
        ...fullWaterfall[1],
        span: {
          ...fullWaterfall[1].span,
          parent_span_id: event.entries[0].data[0].span_id,
          span_id: 'foo',
        },
        isLastSibling: true,
        numOfSpanChildren: 1,
        treeDepth: 2,
        toggleNestedSpanGroup: undefined,
      },
      {
        ...fullWaterfall[1],
        span: {
          ...fullWaterfall[1].span,
          parent_span_id: 'foo',
          span_id: 'bar',
        },
        isLastSibling: true,
        numOfSpanChildren: 0,
        treeDepth: 3,
        toggleNestedSpanGroup: undefined,
      },
    ]);

    // Collapse span group
    assert(spans[1].type === 'span' && spans[1].toggleNestedSpanGroup);
    spans[1].toggleNestedSpanGroup();

    spans = waterfallModel.getWaterfall({
      viewStart: 0,
      viewEnd: 1,
    });

    expect(spans).toHaveLength(3);
    expect(spans).toEqual(collapsedWaterfallExpected);
  });
});
