gloriaJun / til

Lessoned Learned
3 stars 0 forks source link

filter for mobile or string search #128

Open gloriaJun opened 2 years ago

gloriaJun commented 2 years ago

keywrod matching

getSpecialCharEscapedRegex

export const getSpecialCharEscapedRegex = (keyword: string) => {
  const SEARCH_REGEX = '[+!@#$%^&*(),.?":{}[]|<>\\\\/]';
  const specialCharacterRegex = new RegExp(SEARCH_REGEX, 'g');
  const escapedKeyword = keyword.replace(specialCharacterRegex, '\\$&');

  return new RegExp(`(${escapedKeyword})`, 'gi');
};
describe('getSpecialCharEscapedRegex', () => {
  it.each(['tests', 'test@@', '+test@!', 'test.'])(`should be return true, keyword is '%s'`, (keyword) => {
    const re = getSpecialCharEscapedRegex(keyword);
    expect(re.test(`xxx${keyword}abc`)).toBe(true);
  });
});

getNumberByRegex

function getNumberByRegex(keyword = '') {
  if (!keyword) {
    return '';
  }
  const numberPattern = /\d+/g;
  const regRes = keyword.match(numberPattern);
  if (regRes?.length) {
    return regRes.join([]);
  }
  return '';
}

isKeywordMatched / isPhoneNumberString

export function isPhoneNumberString(keyword: string) {
  const re = new RegExp(`^[+]*[(]{0,1}[0-9]{1,4}[)]{0,1}[-\\s\\./0-9]*$`);
  return re.test(keyword);
}

export function isKeywordMatched(title?: string | null, keyword?: string | null) {
  if (!keyword || !title) {
    return false;
  }

  const isMatched = (text: string, searchStr: string) => {
    const regexp = getSpecialCharEscapedRegex(searchStr);
    return regexp.test(text);
  };

  if (isPhoneNumberString(keyword)) {
    return isMatched(
      isPhoneNumberString(title) ? getNumberByRegex(title) : title,
      getNumberByRegex(keyword)
    );
  } else {
    return isMatched(title, keyword);
  }
}
import { isPhoneNumberString, isKeywordMatched } from './isKeywordMatched';

describe('isPhoneNumberString', () => {
  it.each([
    '66 21 234 681',
    '21-234-681',
    '21 234-681',
    '21 234 681',
    '010-1234-1234',
    '010 1234',
    '+82 010',
    '1544-1234',
    '1234 123 1234',
    '911',
  ])(`should be return true, keyword is phone number '%s'`, (keyword) => {
    const result = isPhoneNumberString(keyword);
    expect(result).toBe(true);
  });

  it.each(['66*21*234*681'])(`should be return false, keyword is not phone number '%s'`, (keyword) => {
    const result = isPhoneNumberString(keyword);
    expect(result).toBe(false);
  });
});

describe('isKeywordMatched', () => {
  it.each(['', null, undefined])(`should be return false, title is '%s'`, (title) => {
    const result = isKeywordMatched(title, 'keyword');
    expect(result).toBe(false);
  });

  it.each(['', null, undefined])(`should be return false, keyword is '%s'`, (keyword) => {
    const result = isKeywordMatched('title', keyword);
    expect(result).toBe(false);
  });

  it('should match normal keyword correctly', () => {
    const messageWithKeyword = 'messagetest';
    const meessageNoKeyword = 'message';
    const keyword = 'test';

    const resultOne = isKeywordMatched(messageWithKeyword, keyword);
    const resultTwo = isKeywordMatched(meessageNoKeyword, keyword);

    expect(resultOne).toBe(true);
    expect(resultTwo).toBe(false);
  });

  it('should match special character keyword correctly', () => {
    const messageWithKeyword = 'message@!';
    const meessageNoKeyword = 'message';
    const keyword = '@!';

    const resultOne = isKeywordMatched(messageWithKeyword, keyword);
    const resultTwo = isKeywordMatched(meessageNoKeyword, keyword);

    expect(resultOne).toBe(true);
    expect(resultTwo).toBe(false);
  });

  it.each(['0101234', '010-1234', '010 1234'])(
    `should be return true, title is phone number and keyword is phone number '%s'`,
    (keyword) => {
      const result = isKeywordMatched('82-010-1234-5678', keyword);
      expect(result).toBe(true);
    }
  );

  it.each(['82*010-1234-5678', 'test'])(
    `should be return false, title is '%s' and keyword is phone number`,
    (title) => {
      const result = isKeywordMatched(title, '0101234');
      expect(result).toBe(false);
    }
  );
});

marking with react

import React from 'react';

type IOjbectType<T> = T | null | undefined;

const isNull = <T>(obj: IOjbectType<T>): obj is null => {
  return obj === null || (typeof obj === 'string' && obj === 'null');
};

const isUndefined = <T>(obj: IOjbectType<T>): obj is undefined => {
  return typeof obj === 'undefined' || (typeof obj === 'string' && obj === 'undefined');
};

const isNullOrUndefined = <T>(obj: IOjbectType<T>): obj is null | undefined => {
  return isNull(obj) || isUndefined(obj);
};

const isEmptyString = (v: unknown): v is null | undefined => {
  return isNullOrUndefined(v) || (isString(v) && v.trim() === '');
};

function generateFromGeneral(title: string, keyword: string) {
  let matches;
  let startIdx = 0;
  const markedComponent = [];
  const keywordRegex = getSpecialCharEscapedRegex(keyword);

  while ((matches = keywordRegex.exec(title)) !== null) {
    const matchIndex = matches.index;

    if (matchIndex !== 0) {
      markedComponent.push(title.substr(startIdx, matchIndex - startIdx));
    }

    markedComponent.push(<mark>{matches[0]}</mark>);

    startIdx = keywordRegex.lastIndex;
  }

  if (startIdx < title.length) {
    markedComponent.push(title.substr(startIdx));
  }

  return markedComponent.map((item, index) => {
    return <React.Fragment key={index}>{item}</React.Fragment>;
  });
}

function generateFromPhoneNumber(title: string, keyword: string) {
  const markedComponent = [];

  const keywordNumStr = NumberUtils.getNumberByRegex(keyword);
  const keywordRegex = getSpecialCharEscapedRegex(keywordNumStr);
  const matchesWithOnlyNumber = keywordRegex.exec(getNumberByRegex(title));

  const getActualStringLength = (startIndex: number, length: number) => {
    let count = 0;

    const includedCharMatched = new RegExp('\\D+', 'gi').exec(title.substr(startIndex, length));
    if (includedCharMatched) {
      includedCharMatched.map((item) => {
        count += item.length;
      });
    }

    return length + count;
  };

  if (matchesWithOnlyNumber) {
    // check index for before matched
    let actualMatchedStartIndex = getActualStringLength(0, matchesWithOnlyNumber.index);
    while (keywordNumStr.substr(0, 1) !== title.substr(actualMatchedStartIndex, 1)) {
      actualMatchedStartIndex++;
    }

    // check matched keyword length
    const actualMatchedLength = getActualStringLength(actualMatchedStartIndex, keywordNumStr.length);
    // check index for after matched
    const actualMatchedEndIndex = actualMatchedStartIndex + actualMatchedLength;

    // console.log('### check - titleSplitByMatchedIndex', {
    //   title,
    //   keywordNumStr,
    //   actualMatchedStartIndex,
    //   actualMatchedEndIndex,
    //   actualMatchedLength,
    //   beforeMactched: title.substr(0, actualMatchedStartIndex),
    //   matched: title.substr(actualMatchedStartIndex, actualMatchedLength),
    //   afterMactched: title.substr(actualMatchedEndIndex),
    // });

    if (actualMatchedStartIndex !== 0) {
      markedComponent.push(title.substr(0, actualMatchedStartIndex));
    }

    markedComponent.push(<mark>{title.substr(actualMatchedStartIndex, actualMatchedLength)}</mark>);

    if (actualMatchedEndIndex < title.length) {
      markedComponent.push(title.substr(actualMatchedEndIndex));
    }
  }

  return markedComponent.map((item, index) => {
    return <React.Fragment key={index}>{item}</React.Fragment>;
  });
}

export function generateMarkedComponent(title: string, keyword?: string | null) {
  if (isEmptyString(title) || isEmptyString(keyword) || !isKeywordMatched(title, keyword)) {
    return title;
  }

  return isPhoneNumberString(keyword) ? generateFromPhoneNumber(title, keyword) : generateFromGeneral(title, keyword);
}
import React from 'react';

import { generateMarkedComponent } from './generateMarkedComponent';

describe('generateMarkedComponent', function () {
  it(`should be the plain text, if title is empty string`, () => {
    const title = '';
    const result = generateMarkedComponent(title, 'keyword');
    expect(result).toBe(title);
  });

  it.each(['', null, undefined])(`should be the plain text, if keyword is empty('%s)`, (keyword) => {
    const title = 'Hello, world!';
    const result = generateMarkedComponent(title, keyword);
    expect(result).toBe(title);
  });

  it(`should be return the plain text, if keyword is not matched`, () => {
    const title = 'Starbucks Coffee';
    const result = generateMarkedComponent(title, 'ABC');
    expect(result).toBe(title);
  });

  it(`should be add mark, if upper keyword is matched`, () => {
    const result = generateMarkedComponent('Starbucks Coffee', 'Star');

    expect(result.length).toBe(2);
    expect(React.isValidElement(result[0])).toBeTruthy();
    expect(result).toMatchSnapshot();
  });

  it(`should be add mark, if lower keyword is matched`, () => {
    const result = generateMarkedComponent('Starbucks Coffee', 'coff');

    expect(result.length).toBe(3);
    expect(React.isValidElement(result[1])).toBeTruthy();
    expect(result).toMatchSnapshot();
  });

  it.each(['+66223', '66'])(
    `should be add mark in the first word, if phone keyword '%s' is matched`,
    (keyword: string) => {
      const result = generateMarkedComponent('66-223-1234', keyword);

      expect(result.length).toBe(2);
      expect(React.isValidElement(result[0])).toBeTruthy();
      expect(result).toMatchSnapshot();
    }
  );

  it.each(['223', '23', '312', '123', '6223', '3123'])(
    `should be add mark in the middle word, if phone keyword '%s' is matched`,
    (keyword: string) => {
      const result = generateMarkedComponent('66-223-1234', keyword);

      expect(result.length).toBe(3);
      expect(React.isValidElement(result[1])).toBeTruthy();
      expect(result).toMatchSnapshot();
    }
  );

  it.each(['234', '3-1234'])(
    `should be add mark in the last word, if phone keyword '%s' is matched`,
    (keyword: string) => {
      const message = '66-223-1234';
      const result = generateMarkedComponent(message, keyword);

      expect(result.length).toBe(2);
      // @ts-ignore
      expect(result[0].props.children).toBe(message.split(keyword)[0]);
      expect(React.isValidElement(result[1])).toBeTruthy();
      expect(result).toMatchSnapshot();
    }
  );

  it.each(['66 123 1234', '66 123  1234', '66--123--1234', '66 - 123 - 1234', '+66-10-123-1234'])(
    `should be add mark, if message is '%s' matched with phone keyword`,
    (message: string) => {
      const keyword = '123';
      const result = generateMarkedComponent(message, keyword);

      const firstWord = message.split(new RegExp(keyword))[0];

      expect(result.length).toBe(3);
      // @ts-ignore
      expect(result[0].props.children).toBe(firstWord);
      expect(React.isValidElement(result[1])).toBeTruthy();
      // @ts-ignore
      expect(result[2].props.children).toBe(message.split(`${firstWord}${keyword}`)[1]);
      expect(result).toMatchSnapshot();
    }
  );
});