kana.ts 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. import { test } from 'uvu';
  2. import * as assert from 'uvu/assert';
  3. import { KANA_MAPPING, normalizeInput, KanaInputState } from '../src/kana';
  4. import { TransitionResult } from '../src/state';
  5. function testInput(input: string, line: string) {
  6. const inputState = new KanaInputState(line);
  7. let kanaCount = 0;
  8. inputState.map((_, m) => {
  9. m.addObserver((result, meta) => {
  10. if (m.isFinished()) {
  11. kanaCount += meta;
  12. }
  13. assert.is(
  14. result,
  15. TransitionResult.SUCCESS,
  16. `Expected ${input} to match ${line}`
  17. );
  18. });
  19. });
  20. for (const c of input.split('')) {
  21. inputState.handleInput(c);
  22. }
  23. assert.ok(inputState.isFinished(), `Expected inputState to be finished`);
  24. assert.is(
  25. kanaCount,
  26. line.length,
  27. `${line}: Expected ${line.length} boundaries, got ${kanaCount}`
  28. );
  29. }
  30. function testFail(input: string, line: string) {
  31. const inputState = new KanaInputState(line);
  32. let fail = false;
  33. inputState.map((_, m) => {
  34. m.addObserver((result, _boundary) => {
  35. fail =
  36. fail ||
  37. result === TransitionResult.FAILED ||
  38. result === TransitionResult.SKIPPED;
  39. });
  40. });
  41. for (const c of input.split('')) {
  42. inputState.handleInput(c);
  43. }
  44. fail = fail || !inputState.isFinished();
  45. assert.ok(fail, `Expected ${input} to fail on ${line}`);
  46. }
  47. function testSkip(input: string, line: string, expectedSkips: number) {
  48. const inputState = new KanaInputState(line);
  49. let kanaCount = 0;
  50. let skipCount = 0;
  51. inputState.map((_, m) => {
  52. m.addObserver((result, meta) => {
  53. if (result === TransitionResult.SKIPPED) {
  54. kanaCount += meta;
  55. skipCount += 1;
  56. } else if (result === TransitionResult.SUCCESS) {
  57. kanaCount += meta;
  58. } else {
  59. assert.unreachable(`Expected ${input} to match ${line}`);
  60. }
  61. });
  62. });
  63. for (const c of input.split('')) {
  64. inputState.handleInput(c);
  65. }
  66. assert.ok(inputState.isFinished(), `Expected inputState to be finished`);
  67. assert.is(
  68. kanaCount,
  69. line.length,
  70. `Expected ${line.length} boundaries, got ${kanaCount}`
  71. );
  72. assert.is(skipCount, expectedSkips, `Expected skip count to match`);
  73. }
  74. test('normalizeInput', () => {
  75. assert.is(normalizeInput('ABCdef'), 'abcdef');
  76. assert.is(normalizeInput('フェスティバル'), 'ふぇすてぃばる');
  77. assert.is(normalizeInput('  '), ' ');
  78. });
  79. test('multiple romanization single kana', () => {
  80. testInput('si', 'し');
  81. testInput('shi', 'し');
  82. testInput('ji', 'じ');
  83. testInput('zi', 'じ');
  84. testInput('ti', 'ち');
  85. testInput('chi', 'ち');
  86. testInput('tu', 'つ');
  87. testInput('tsu', 'つ');
  88. testInput('fu', 'ふ');
  89. testInput('hu', 'ふ');
  90. });
  91. test('multiple romanization double kana', () => {
  92. testInput('kya', 'きゃ');
  93. testInput('kiya', 'きゃ');
  94. testInput('kilya', 'きゃ');
  95. testInput('sha', 'しゃ');
  96. testInput('sya', 'しゃ');
  97. testInput('shilya', 'しゃ');
  98. testInput('silya', 'しゃ');
  99. testInput('cha', 'ちゃ');
  100. testInput('tya', 'ちゃ');
  101. testInput('chilya', 'ちゃ');
  102. testInput('tilya', 'ちゃ');
  103. testInput('ja', 'じゃ');
  104. testInput('jya', 'じゃ');
  105. testInput('zya', 'じゃ');
  106. testInput('jilya', 'じゃ');
  107. testInput('zilya', 'じゃ');
  108. testInput('fe', 'ふぇ');
  109. testInput('fue', 'ふぇ');
  110. testInput('fule', 'ふぇ');
  111. });
  112. test('small kana', () => {
  113. testInput('ka', 'ヵ');
  114. testInput('lka', 'ヵ');
  115. testInput('xka', 'ヵ');
  116. testFail('llka', 'ヵ');
  117. });
  118. test('small tsu', () => {
  119. testInput('katto', 'カット');
  120. testInput('kaltsuto', 'かっと');
  121. testInput('kaltuto', 'かっと');
  122. testInput('ejji', 'エッジ');
  123. testInput('ezzi', 'エッジ');
  124. testInput('extuji', 'エッジ');
  125. testInput('extsuzi', 'エッジ');
  126. testInput('hassha', 'はっしゃ');
  127. testInput('hassya', 'はっしゃ');
  128. testInput('haltusha', 'はっしゃ');
  129. testInput('haltusya', 'はっしゃ');
  130. });
  131. test('nn', () => {
  132. testInput('nn', 'ん');
  133. testInput('nna', 'んあ');
  134. testFail('na', 'んあ');
  135. testInput('nda', 'んだ');
  136. testInput('nnda', 'んだ');
  137. testFail('nnnda', 'んだ');
  138. testInput('nnnda', 'んんだ');
  139. testInput('nnnnda', 'んんだ');
  140. testFail('nya', 'んにゃ');
  141. testFail('nnya', 'んにゃ');
  142. testInput('nnnya', 'んにゃ');
  143. });
  144. test('skipping', () => {
  145. testSkip('a', 'は', 1);
  146. testSkip('hao', 'はろ', 1);
  147. testSkip('hro', 'はろ', 1);
  148. });
  149. test('display matches', () => {
  150. for (const line in KANA_MAPPING) {
  151. if (line === ' ') {
  152. continue;
  153. }
  154. testInput(KANA_MAPPING[line].getDisplay(), line);
  155. }
  156. });
  157. test.run();