@@ -420,6 +420,220 @@ class LearnedWordDaoTest {
420420 assertEquals(1 , swipedCount)
421421 }
422422
423+ @Test
424+ fun `learnWord promotes camelCase over lowercase` () =
425+ runTest {
426+ dao.learnWord(createTestWord(" iphone" , " iphone" , " en" ))
427+ dao.learnWord(createTestWord(" iPhone" , " iphone" , " en" ))
428+
429+ val retrieved = dao.findExactWord(" en" , " iphone" )
430+ assertEquals(" iPhone" , retrieved?.word)
431+ assertEquals(2 , retrieved?.frequency)
432+ }
433+
434+ @Test
435+ fun `learnWord promotes title case over lowercase` () =
436+ runTest {
437+ dao.learnWord(createTestWord(" maryland" , " maryland" , " en" ))
438+ dao.learnWord(createTestWord(" Maryland" , " maryland" , " en" ))
439+
440+ val retrieved = dao.findExactWord(" en" , " maryland" )
441+ assertEquals(" Maryland" , retrieved?.word)
442+ assertEquals(2 , retrieved?.frequency)
443+ }
444+
445+ @Test
446+ fun `learnWord does not demote camelCase to lowercase` () =
447+ runTest {
448+ dao.learnWord(createTestWord(" iPhone" , " iphone" , " en" ))
449+ dao.learnWord(createTestWord(" iphone" , " iphone" , " en" ))
450+
451+ val retrieved = dao.findExactWord(" en" , " iphone" )
452+ assertEquals(" iPhone" , retrieved?.word)
453+ assertEquals(2 , retrieved?.frequency)
454+ }
455+
456+ @Test
457+ fun `learnWord does not demote McLaren to lowercase` () =
458+ runTest {
459+ dao.learnWord(createTestWord(" McLaren" , " mclaren" , " en" ))
460+ repeat(5 ) {
461+ dao.learnWord(createTestWord(" mclaren" , " mclaren" , " en" ))
462+ }
463+
464+ val retrieved = dao.findExactWord(" en" , " mclaren" )
465+ assertEquals(" McLaren" , retrieved?.word)
466+ assertEquals(6 , retrieved?.frequency)
467+ }
468+
469+ @Test
470+ fun `learnWord promotes short acronym over lowercase` () =
471+ runTest {
472+ dao.learnWord(createTestWord(" api" , " api" , " en" ))
473+ dao.learnWord(createTestWord(" API" , " api" , " en" ))
474+
475+ val retrieved = dao.findExactWord(" en" , " api" )
476+ assertEquals(" API" , retrieved?.word)
477+ }
478+
479+ @Test
480+ fun `learnWord promotes long ALL CAPS over lowercase` () =
481+ runTest {
482+ dao.learnWord(createTestWord(" guhh" , " guhh" , " en" ))
483+ dao.learnWord(createTestWord(" GUHH" , " guhh" , " en" ))
484+
485+ val retrieved = dao.findExactWord(" en" , " guhh" )
486+ assertEquals(" GUHH" , retrieved?.word)
487+ }
488+
489+ @Test
490+ fun `learnWord does not demote ALL CAPS to lowercase` () =
491+ runTest {
492+ dao.learnWord(createTestWord(" NASA" , " nasa" , " en" ))
493+ dao.learnWord(createTestWord(" nasa" , " nasa" , " en" ))
494+
495+ val retrieved = dao.findExactWord(" en" , " nasa" )
496+ assertEquals(" NASA" , retrieved?.word)
497+ }
498+
499+ @Test
500+ fun `learnWord promotes camelCase over ALL CAPS` () =
501+ runTest {
502+ dao.learnWord(createTestWord(" IPHONE" , " iphone" , " en" ))
503+ dao.learnWord(createTestWord(" iPhone" , " iphone" , " en" ))
504+
505+ val retrieved = dao.findExactWord(" en" , " iphone" )
506+ assertEquals(" iPhone" , retrieved?.word)
507+ }
508+
509+ @Test
510+ fun `learnWord promotes camelCase over title case` () =
511+ runTest {
512+ dao.learnWord(createTestWord(" Iphone" , " iphone" , " en" ))
513+ dao.learnWord(createTestWord(" iPhone" , " iphone" , " en" ))
514+
515+ val retrieved = dao.findExactWord(" en" , " iphone" )
516+ assertEquals(" iPhone" , retrieved?.word)
517+ }
518+
519+ @Test
520+ fun `learnWord keeps same casing stable across repeated learns` () =
521+ runTest {
522+ repeat(10 ) {
523+ dao.learnWord(createTestWord(" hello" , " hello" , " en" ))
524+ }
525+
526+ val retrieved = dao.findExactWord(" en" , " hello" )
527+ assertEquals(" hello" , retrieved?.word)
528+ assertEquals(10 , retrieved?.frequency)
529+ }
530+
531+ @Test
532+ fun `importWordWithMerge promotes camelCase casing` () =
533+ runTest {
534+ dao.insertWord(createTestWord(" iphone" , " iphone" , " en" , frequency = 50 ))
535+
536+ val imported = createTestWord(" iPhone" , " iphone" , " en" , frequency = 3 )
537+ dao.importWordWithMerge(imported)
538+
539+ val retrieved = dao.findExactWord(" en" , " iphone" )
540+ assertEquals(" iPhone" , retrieved?.word)
541+ assertEquals(53 , retrieved?.frequency)
542+ }
543+
544+ @Test
545+ fun `casingIntentScore scores lowercase as 0` () {
546+ assertEquals(0 , LearnedWord .casingIntentScore(" hello" ))
547+ assertEquals(0 , LearnedWord .casingIntentScore(" iphone" ))
548+ assertEquals(0 , LearnedWord .casingIntentScore(" fasty" ))
549+ }
550+
551+ @Test
552+ fun `casingIntentScore scores long ALL CAPS as 1` () {
553+ assertEquals(1 , LearnedWord .casingIntentScore(" HELLO" ))
554+ assertEquals(1 , LearnedWord .casingIntentScore(" GUHH" ))
555+ assertEquals(1 , LearnedWord .casingIntentScore(" FASTY" ))
556+ }
557+
558+ @Test
559+ fun `casingIntentScore scores title case as 2` () {
560+ assertEquals(2 , LearnedWord .casingIntentScore(" Maryland" ))
561+ assertEquals(2 , LearnedWord .casingIntentScore(" Google" ))
562+ assertEquals(2 , LearnedWord .casingIntentScore(" Hello" ))
563+ }
564+
565+ @Test
566+ fun `casingIntentScore scores short acronym as 3` () {
567+ assertEquals(3 , LearnedWord .casingIntentScore(" API" ))
568+ assertEquals(3 , LearnedWord .casingIntentScore(" US" ))
569+ assertEquals(3 , LearnedWord .casingIntentScore(" UK" ))
570+ }
571+
572+ @Test
573+ fun `casingIntentScore scores mixed case as 4` () {
574+ assertEquals(4 , LearnedWord .casingIntentScore(" iPhone" ))
575+ assertEquals(4 , LearnedWord .casingIntentScore(" McLaren" ))
576+ assertEquals(4 , LearnedWord .casingIntentScore(" myAPI" ))
577+ assertEquals(4 , LearnedWord .casingIntentScore(" DeepMind" ))
578+ }
579+
580+ @Test
581+ fun `casingIntentScore returns 0 for empty string` () {
582+ assertEquals(0 , LearnedWord .casingIntentScore(" " ))
583+ }
584+
585+ @Test
586+ fun `casingIntentScore hierarchy mixed beats title beats lowercase` () {
587+ val lowercase = LearnedWord .casingIntentScore(" iphone" )
588+ val allCaps = LearnedWord .casingIntentScore(" IPHONE" )
589+ val titleCase = LearnedWord .casingIntentScore(" Iphone" )
590+ val mixedCase = LearnedWord .casingIntentScore(" iPhone" )
591+
592+ assertTrue(mixedCase > titleCase)
593+ assertTrue(titleCase > allCaps)
594+ assertTrue(allCaps > lowercase)
595+ }
596+
597+ @Test
598+ fun `updateCasingIfPreferred promotes to higher intent` () {
599+ val word = createTestWord(" iphone" , " iphone" , " en" , frequency = 10 )
600+ val updated = word.updateCasingIfPreferred(" iPhone" )
601+
602+ assertEquals(" iPhone" , updated.word)
603+ assertEquals(11 , updated.frequency)
604+ }
605+
606+ @Test
607+ fun `updateCasingIfPreferred does not demote to lower intent` () {
608+ val word = createTestWord(" iPhone" , " iphone" , " en" , frequency = 10 )
609+ val updated = word.updateCasingIfPreferred(" iphone" )
610+
611+ assertEquals(" iPhone" , updated.word)
612+ assertEquals(11 , updated.frequency)
613+ }
614+
615+ @Test
616+ fun `updateCasingIfPreferred keeps same casing` () {
617+ val word = createTestWord(" iPhone" , " iphone" , " en" , frequency = 10 )
618+ val updated = word.updateCasingIfPreferred(" iPhone" )
619+
620+ assertEquals(" iPhone" , updated.word)
621+ assertEquals(11 , updated.frequency)
622+ }
623+
624+ @Test
625+ fun `importWordWithMerge does not demote camelCase` () =
626+ runTest {
627+ dao.insertWord(createTestWord(" iPhone" , " iphone" , " en" , frequency = 50 ))
628+
629+ val imported = createTestWord(" iphone" , " iphone" , " en" , frequency = 100 )
630+ dao.importWordWithMerge(imported)
631+
632+ val retrieved = dao.findExactWord(" en" , " iphone" )
633+ assertEquals(" iPhone" , retrieved?.word)
634+ assertEquals(150 , retrieved?.frequency)
635+ }
636+
423637 /* *
424638 * Creates test word with defaults for frequency, source, and timestamps.
425639 */
0 commit comments