/** * 메일 검색의 접기 · 글자 두 개 묶음 · 맞은 곳 · 말머리 떼기 표본 — 단위 테스트 전용 (앱 코드에서 import 하지 않는다). * 서버의 색인 · 찾기(src/shared/utils/search-text.ts · subject-base.ts)와 화면의 미리 확인 · 강조(frontend `shared/utils/search-match.ts`)를 같은 표로 본다. * * ⚠️ frontend `src/test-utils/search-text-samples.ts` 와 **같은 표**다(행과 순서까지 그대로). * 한쪽을 고치면 둘 다 고친다 — 접기 · 위치 규칙을 바꾸면 두 쪽 함수와 두 표를 함께 (서버가 준 맞은 곳의 위치를 화면이 그대로 그린다). * 앱 시험(`flutter/test/features/mail/search_text_test.dart`)도 이 파일의 `FOLD_SAMPLES` · `GRAM_SAMPLES` 를 그대로 읽는다(작은따옴표 글의 배열 · 이스케이프 · 주석만 읽는다) * — 표의 모양을 바꾸면 앱 시험도 본다(이 파일이 바뀌면 Flutter CI 도 돈다). * 답은 손으로 적었고 두 쪽 테스트가 함수의 결과와 견준다. 단위 테스트는 `@/../test/search-text-samples` 로 부른다 (cpu-time.ts 와 같다). */ /** 접기 — [넣은 글, 접은 글]. 접은 글은 NFC 원문과 UTF-16 길이가 늘 같다 */ export const FOLD_SAMPLES: readonly (readonly [ input: string, folded: string, ])[] = [ ['계약서', '계약서'], ['Project PLAN', 'project plan'], // 전각 ASCII → 반각 (대문자는 소문자로까지) ['ABC123!', 'abc123!'], ['RE:회의', 're:회의'], ['[공지]', '[공지]'], // 공백류(전각 공백 · 줄바꿈 · 탭 · NBSP · CR)는 띄어쓰기 하나로 1:1 (색인 판 2) — 이어진 공백을 줄이지는 않는다(위치를 지킨다) ['회의\u3000일정', '회의 일정'], ['계약\n갱신\t안내\u00a0끝', '계약 갱신 안내 끝'], ['a\r\n\nb', 'a b'], // 소문자가 코드 포인트 하나 · 같은 길이일 때만 — 'İ'(U+0130)는 'i̇' 두 글자가 되므로 그대로 ['İstanbul', 'İstanbul'], ['ÀÉÎÕÜ', 'àéîõü'], ['ΣΑΣ', 'σασ'], // 풀어 쓴 한글(NFD)은 NFC 로 모은다 ['\u1112\u1161\u11ab\u1100\u1173\u11af', '한글'], // ㈜ 는 그대로 (NFKC 가 아니다 — 길이를 바꾸지 않는다) ['㈜아라', '㈜아라'], // BMP 밖 글자 — Deseret 대문자는 소문자로(둘 다 두 단위), 이모지는 그대로 ['\u{10400}😀', '\u{10428}😀'], ['', ''], ]; /** 글자 두 개 묶음 — [접은 글, 묶음(나온 차례, 겹치지 않게)] */ export const GRAM_SAMPLES: readonly (readonly [ fold: string, grams: readonly string[], ])[] = [ ['계약서', ['계약', '약서']], ['회의 일정', ['회의', '일정']], ['계약', ['계약']], ['계', []], ['a b c', []], ['aaaa', ['aa']], ['ab\nab\tab', ['ab']], // 전각 공백 · NBSP 도 공백이다 ['회의\u3000일정\u00a0안내', ['회의', '일정', '안내']], // 문장부호는 글자다 — 're:' 의 'e:' 도 묶음 ['re:회의', ['re', 'e:', ':회', '회의']], // BMP 밖 글자는 코드 포인트 하나 ['😀😁', ['😀😁']], ['', []], ]; /** 맞은 곳 — [원문(NFC), 찾는 말들, 맞은 곳들 [시작, 끝) UTF-16] — 겹치거나 맞닿으면 합친다 */ export const MATCH_SAMPLES: readonly (readonly [ original: string, terms: readonly string[], ranges: readonly (readonly [number, number])[], ])[] = [ ['계약서를 보냈습니다', ['계약'], [[0, 2]]], ['Project plan', ['PROJ'], [[0, 4]]], ['ABC 주문', ['abc'], [[0, 3]]], [ '회의 회의록 회의실', ['회의'], [ [0, 2], [3, 5], [7, 9], ], ], // 겹치는 자리도 찾아 합친다 ['aaa', ['aa'], [[0, 3]]], // 맞닿은 두 말은 하나로 ['계약서', ['계약', '서'], [[0, 3]]], [ '회의 일정 안내', ['일정', '회의'], [ [0, 2], [3, 5], ], ], // 구절은 줄바꿈 · NBSP 로 갈린 글에도 맞는다 (공백류 → 띄어쓰기 1:1) — 위치는 원문 그대로 ['계약\n갱신 안내', ['계약 갱신'], [[0, 5]]], ['계약\u00a0갱신', ['계약 갱신'], [[0, 5]]], // 구절은 띄어쓰기까지 그대로 ['회의 일정 안내', ['회의 일정'], [[0, 5]]], ['회의 일정', ['회의 일정'], []], [ '[공지] Re: 회의', ['공지', 're'], [ [1, 3], [5, 7], ], ], // 빈 말은 건너뛴다 · 없는 말은 맞은 곳 없음 ['계약', ['', '없음'], []], // BMP 밖 글자 앞뒤에서도 UTF-16 위치 ['😀계약', ['계약'], [[2, 4]]], ['', ['계약'], []], ]; /** 말머리 떼기 — [제목, 말머리를 뗀 접기 글] */ export const SUBJECT_BASE_SAMPLES: readonly (readonly [ subject: string, base: string, ])[] = [ ['회의 일정 안내', '회의 일정 안내'], ['Re: 회의 일정 안내', '회의 일정 안내'], ['RE: Re: FW: 회의 일정 안내', '회의 일정 안내'], ['Re:회의', '회의'], ['Fwd: 견적서', '견적서'], ['AW: WG: Angebot', 'angebot'], // 한국어 Outlook — 엔진은 떼지 않는다 ['회신: 회의 일정 안내', '회의 일정 안내'], ['답장: 회신: 전달: 회의', '회의'], ['회신 : 회의', '회의'], // 수 · 전각 쌍점 ['Re[2]: 회의', '회의'], ['RE(3): 회의', '회의'], ['Re:회의', '회의'], ['RE: 회의', '회의'], // 대괄호 꼬리표 (여러 겹 · 말머리와 섞여도) ['[공지] 회의 일정', '회의 일정'], ['Re: [공지] [중요] 회의', '회의'], ['[외부] RE: 견적', '견적'], // 공백은 하나로 · 대소문자 없이 [' Re: 회의 일정 ', '회의 일정'], ['Project PLAN', 'project plan'], // 쌍점이 없으면 말머리가 아니다 ['Re 회의', 're 회의'], ['Reply to 회의', 'reply to 회의'], ['Regarding: 회의', 'regarding: 회의'], ['회신드립니다', '회신드립니다'], // 다 떼면 빈 글 ['Re:', ''], ['[공지]', ''], ['', ''], ];