text: import { useLocalStorageSpy } from 'helpers/local_storage_helper';
  
  import AccessorUtilities from '~/lib/utils/accessor';
  
  import { FILTERED_SEARCH_TERM } from '~/vue_shared/components/filtered_search_bar/constants';
  
  import {
    stripQuotes,
    uniqueTokens,
    prepareTokens,
    processFilters,
    filterToQueryObject,
    urlQueryToFilter,
    getRecentlyUsedTokenValues,
    setTokenValueToRecentlyUsed,
  } from '~/vue_shared/components/filtered_search_bar/filtered_search_utils';
  
  import {
    tokenValueAuthor,
    tokenValueLabel,
    tokenValueMilestone,
    tokenValuePlain,
  } from './mock_data';
  
  const mockStorageKey = 'recent-tokens';
  
  function setLocalStorageAvailability(isAvailable) {
    jest.spyOn(AccessorUtilities, 'isLocalStorageAccessSafe').mockReturnValue(isAvailable);
  }
  
  describe('Filtered Search Utils', () => {
    describe('stripQuotes', () => {
      it.each`
        inputValue     | outputValue
        ${'"Foo Bar"'} | ${'Foo Bar'}
        ${"'Foo Bar'"} | ${'Foo Bar'}
        ${'FooBar'}    | ${'FooBar'}
        ${"Foo'Bar"}   | ${"Foo'Bar"}
        ${'Foo"Bar'}   | ${'Foo"Bar'}
        ${'Foo Bar'}   | ${'Foo Bar'}
      `(
        'returns string $outputValue when called with string $inputValue',
        ({ inputValue, outputValue }) => {
          expect(stripQuotes(inputValue)).toBe(outputValue);
        },
      );
    });
  
    describe('uniqueTokens', () => {
      it('returns tokens array with duplicates removed', () => {
        expect(
          uniqueTokens([
            tokenValueAuthor,
            tokenValueLabel,
            tokenValueMilestone,
            tokenValueLabel,
            tokenValuePlain,
          ]),
        ).toHaveLength(4); // Removes 2nd instance of tokenValueLabel
      });
  
      it('returns tokens array as it is if it does not have duplicates', () => {
        expect(
          uniqueTokens([tokenValueAuthor, tokenValueLabel, tokenValueMilestone, tokenValuePlain]),
        ).toHaveLength(4);
      });
    });
  });
  
  describe('prepareTokens', () => {
    describe('with empty data', () => {
      it('returns an empty array', () => {
        expect(prepareTokens()).toEqual([]);
        expect(prepareTokens({})).toEqual([]);
        expect(prepareTokens({ milestone: null, author: null, assignees: [], labels: [] })).toEqual(
          [],
        );
      });
    });
  
    it.each([
      [
        'milestone',
        { value: 'v1.0', operator: '=' },
        [{ type: 'milestone', value: { data: 'v1.0', operator: '=' } }],
      ],
      [
        'author',
        { value: 'mr.popo', operator: '!=' },
        [{ type: 'author', value: { data: 'mr.popo', operator: '!=' } }],
      ],
      [
        'labels',
        [{ value: 'z-fighters', operator: '=' }],
        [{ type: 'labels', value: { data: 'z-fighters', operator: '=' } }],
      ],
      [
        'assignees',
        [
          { value: 'krillin', operator: '=' },
          { value: 'piccolo', operator: '!=' },
        ],
        [
          { type: 'assignees', value: { data: 'krillin', operator: '=' } },
          { type: 'assignees', value: { data: 'piccolo', operator: '!=' } },
        ],
      ],
      [
        'foo',
        [
          { value: 'bar', operator: '!=' },
          { value: 'baz', operator: '!=' },
        ],
        [
          { type: 'foo', value: { data: 'bar', operator: '!=' } },
          { type: 'foo', value: { data: 'baz', operator: '!=' } },
        ],
      ],
    ])('gathers %s=%j into result=%j', (token, value, result) => {
      const res = prepareTokens({ [token]: value });
      expect(res).toEqual(result);
    });
  });
  
  describe('processFilters', () => {
    it('processes multiple filter values', () => {
      const result = processFilters([
        { type: 'foo', value: { data: 'foo', operator: '=' } },
        { type: 'bar', value: { data: 'bar1', operator: '=' } },
        { type: 'bar', value: { data: 'bar2', operator: '!=' } },
      ]);
  
      expect(result).toStrictEqual({
        foo: [{ value: 'foo', operator: '=' }],
        bar: [
          { value: 'bar1', operator: '=' },
          { value: 'bar2', operator: '!=' },
        ],
      });
    });
  
    it('does not remove wrapping double quotes from the data', () => {
      const result = processFilters([
        { type: 'foo', value: { data: '"value with spaces"', operator: '=' } },
      ]);
  
      expect(result).toStrictEqual({
        foo: [{ value: '"value with spaces"', operator: '=' }],
      });
    });
  });
  
  describe('filterToQueryObject', () => {
    describe('with empty data', () => {
      it('returns an empty object', () => {
        expect(filterToQueryObject()).toEqual({});
        expect(filterToQueryObject({})).toEqual({});
        expect(filterToQueryObject({ author_username: null, label_name: [] })).toEqual({
          author_username: null,
          label_name: null,
          'not[author_username]': null,
          'not[label_name]': null,
        });
      });
    });
  
    it.each([
      [
        'author_username',
        { value: 'v1.0', operator: '=' },
        { author_username: 'v1.0', 'not[author_username]': null },
      ],
      [
        'author_username',
        { value: 'v1.0', operator: '!=' },
        { author_username: null, 'not[author_username]': 'v1.0' },
      ],
      [
        'label_name',
        [{ value: 'z-fighters', operator: '=' }],
        { label_name: ['z-fighters'], 'not[label_name]': null },
      ],
      [
        'label_name',
        [{ value: 'z-fighters', operator: '!=' }],
        { label_name: null, 'not[label_name]': ['z-fighters'] },
      ],
      [
        'foo',
        [
          { value: 'bar', operator: '=' },
          { value: 'baz', operator: '=' },
        ],
        { foo: ['bar', 'baz'], 'not[foo]': null },
      ],
      [
        'foo',
        [
          { value: 'bar', operator: '!=' },
          { value: 'baz', operator: '!=' },
        ],
        { foo: null, 'not[foo]': ['bar', 'baz'] },
      ],
      [
        'foo',
        [
          { value: 'bar', operator: '!=' },
          { value: 'baz', operator: '=' },
        ],
        { foo: ['baz'], 'not[foo]': ['bar'] },
      ],
    ])('gathers filter values %s=%j into query object=%j', (token, value, result) => {
      const res = filterToQueryObject({ [token]: value });
      expect(res).toEqual(result);
    });
  
    it.each([
      [FILTERED_SEARCH_TERM, [{ value: '' }], { search: '' }],
      [FILTERED_SEARCH_TERM, [{ value: 'bar' }], { search: 'bar' }],
      [FILTERED_SEARCH_TERM, [{ value: 'bar' }, { value: '' }], { search: 'bar' }],
      [FILTERED_SEARCH_TERM, [{ value: 'bar' }, { value: 'baz' }], { search: 'bar baz' }],
    ])(
      'when filteredSearchTermKey=search gathers filter values %s=%j into query object=%j',
      (token, value, result) => {
        const res = filterToQueryObject({ [token]: value }, { filteredSearchTermKey: 'search' });
        expect(res).toEqual(result);
      },
    );
  });
  
  describe('urlQueryToFilter', () => {
    describe('with empty data', () => {
      it('returns an empty object', () => {
        expect(urlQueryToFilter()).toEqual({});
        expect(urlQueryToFilter('')).toEqual({});
        expect(urlQueryToFilter('author_username=&milestone_title=&')).toEqual({});
      });
    });
  
    it.each([
      ['author_username=v1.0', { author_username: { value: 'v1.0', operator: '=' } }],
      ['not[author_username]=v1.0', { author_username: { value: 'v1.0', operator: '!=' } }],
      ['foo=bar&foo=baz', { foo: { value: 'baz', operator: '=' } }],
      ['foo=bar&foo[]=baz', { foo: [{ value: 'baz', operator: '=' }] }],
      ['not[foo]=bar&foo=baz', { foo: { value: 'baz', operator: '=' } }],
      [
        'foo[]=bar&foo[]=baz&not[foo]=',
        {
          foo: [
            { value: 'bar', operator: '=' },
            { value: 'baz', operator: '=' },
          ],
        },
      ],
      [
        'foo[]=&not[foo][]=bar&not[foo][]=baz',
        {
          foo: [
            { value: 'bar', operator: '!=' },
            { value: 'baz', operator: '!=' },
          ],
        },
      ],
      [
        'foo[]=baz&not[foo][]=bar',
        {
          foo: [
            { value: 'baz', operator: '=' },
            { value: 'bar', operator: '!=' },
          ],
        },
      ],
      ['not[foo][]=bar', { foo: [{ value: 'bar', operator: '!=' }] }],
      ['nop=1&not[nop]=2', {}, { filterNamesAllowList: ['foo'] }],
      [
        'foo[]=bar&not[foo][]=baz&nop=xxx&not[nop]=yyy',
        {
          foo: [
            { value: 'bar', operator: '=' },
            { value: 'baz', operator: '!=' },
          ],
        },
        { filterNamesAllowList: ['foo'] },
      ],
      [
        'search=term&foo=bar',
        {
          [FILTERED_SEARCH_TERM]: [{ value: 'term' }],
          foo: { value: 'bar', operator: '=' },
        },
        { filteredSearchTermKey: 'search' },
      ],
      [
        'search=my terms',
        {
          [FILTERED_SEARCH_TERM]: [{ value: 'my' }, { value: 'terms' }],
        },
        { filteredSearchTermKey: 'search' },
      ],
      [
        'search[]=my&search[]=terms',
        {
          [FILTERED_SEARCH_TERM]: [{ value: 'my' }, { value: 'terms' }],
        },
        { filteredSearchTermKey: 'search' },
      ],
      [
        'search=my+terms',
        {
          [FILTERED_SEARCH_TERM]: [{ value: 'my' }, { value: 'terms' }],
        },
        { filteredSearchTermKey: 'search', legacySpacesDecode: false },
      ],
      [
        'search=my terms&foo=bar&nop=xxx',
        {
          [FILTERED_SEARCH_TERM]: [{ value: 'my' }, { value: 'terms' }],
          foo: { value: 'bar', operator: '=' },
        },
        { filteredSearchTermKey: 'search', filterNamesAllowList: ['foo'] },
      ],
    ])(
      'gathers filter values %s into query object=%j when options %j',
      (query, result, options = undefined) => {
        const res = urlQueryToFilter(query, options);
        expect(res).toEqual(result);
      },
    );
  });
  
  describe('getRecentlyUsedTokenValues', () => {
    useLocalStorageSpy();
  
    beforeEach(() => {
      localStorage.removeItem(mockStorageKey);
    });
  
    it('returns array containing recently used token values from provided recentTokenValuesStorageKey', () => {
      setLocalStorageAvailability(true);
  
      const mockExpectedArray = [{ foo: 'bar' }];
      localStorage.setItem(mockStorageKey, JSON.stringify(mockExpectedArray));
  
      expect(getRecentlyUsedTokenValues(mockStorageKey)).toEqual(mockExpectedArray);
    });
  
    it('returns empty array when provided recentTokenValuesStorageKey does not have anything in localStorage', () => {
      setLocalStorageAvailability(true);
  
      expect(getRecentlyUsedTokenValues(mockStorageKey)).toEqual([]);
    });
  
    it('returns empty array when when access to localStorage is not available', () => {
      setLocalStorageAvailability(false);
  
      expect(getRecentlyUsedTokenValues(mockStorageKey)).toEqual([]);
    });
  });
  
  describe('setTokenValueToRecentlyUsed', () => {
    const mockTokenValue1 = { foo: 'bar' };
    const mockTokenValue2 = { bar: 'baz' };
    useLocalStorageSpy();
  
    beforeEach(() => {
      localStorage.removeItem(mockStorageKey);
    });
  
    it('adds provided tokenValue to localStorage for recentTokenValuesStorageKey', () => {
      setLocalStorageAvailability(true);
  
      setTokenValueToRecentlyUsed(mockStorageKey, mockTokenValue1);
  
      expect(JSON.parse(localStorage.getItem(mockStorageKey))).toEqual([mockTokenValue1]);
    });
  
    it('adds provided tokenValue to localStorage at the top of existing values (i.e. Stack order)', () => {
      setLocalStorageAvailability(true);
  
      setTokenValueToRecentlyUsed(mockStorageKey, mockTokenValue1);
      setTokenValueToRecentlyUsed(mockStorageKey, mockTokenValue2);
  
      expect(JSON.parse(localStorage.getItem(mockStorageKey))).toEqual([
        mockTokenValue2,
        mockTokenValue1,
      ]);
    });
  
    it('ensures that provided tokenValue is not added twice', () => {
      setLocalStorageAvailability(true);
  
      setTokenValueToRecentlyUsed(mockStorageKey, mockTokenValue1);
      setTokenValueToRecentlyUsed(mockStorageKey, mockTokenValue1);
  
      expect(JSON.parse(localStorage.getItem(mockStorageKey))).toEqual([mockTokenValue1]);
    });
  
    it('does not add any value when acess to localStorage is not available', () => {
      setLocalStorageAvailability(false);
  
      setTokenValueToRecentlyUsed(mockStorageKey, mockTokenValue1);
  
      expect(JSON.parse(localStorage.getItem(mockStorageKey))).toBeNull();
    });
  });
  