@@ -42,8 +42,7 @@ import { Client as CrowdinClient } from '@crowdin/crowdin-api-client';
4242import {
4343 EN_US ,
4444 fetchSourceStrings ,
45- fetchProjectApprovals ,
46- fetchProjectTranslations ,
45+ fetchApprovedProjectTranslations ,
4746 fetchTranslations ,
4847 fetchApprovals ,
4948 addTranslation ,
@@ -535,39 +534,9 @@ describe('syncStringTranslation', () => {
535534 } ) ;
536535} ) ;
537536
538- // fetchProjectApprovals
537+ // fetchApprovedProjectTranslations
539538
540- describe ( 'fetchProjectApprovals' , ( ) => {
541- beforeEach ( ( ) => jest . clearAllMocks ( ) ) ;
542-
543- it ( 'returns unwrapped approval data objects' , async ( ) => {
544- const raw = [
545- { data : makeApproval ( { id : 30 , translationId : 20 } ) } ,
546- { data : makeApproval ( { id : 31 , translationId : 21 } ) } ,
547- ] ;
548- mockListTranslationApprovals . mockResolvedValue ( { data : raw } ) ;
549-
550- const result = await fetchProjectApprovals ( '42' ) ;
551- expect ( result ) . toHaveLength ( 2 ) ;
552- expect ( result [ 0 ] . id ) . toBe ( 30 ) ;
553- expect ( result [ 1 ] . id ) . toBe ( 31 ) ;
554- } ) ;
555-
556- it ( 'returns an empty array when response.data is null/undefined' , async ( ) => {
557- mockListTranslationApprovals . mockResolvedValue ( { } ) ;
558- expect ( await fetchProjectApprovals ( '42' ) ) . toEqual ( [ ] ) ;
559- } ) ;
560-
561- it ( 'calls listTranslationApprovals with projectId and EN_US only (no stringId)' , async ( ) => {
562- mockListTranslationApprovals . mockResolvedValue ( { data : [ ] } ) ;
563- await fetchProjectApprovals ( '42' ) ;
564- expect ( mockListTranslationApprovals ) . toHaveBeenCalledWith ( '42' , { languageId : EN_US } ) ;
565- } ) ;
566- } ) ;
567-
568- // fetchProjectTranslations
569-
570- describe ( 'fetchProjectTranslations' , ( ) => {
539+ describe ( 'fetchApprovedProjectTranslations' , ( ) => {
571540 beforeEach ( ( ) => jest . clearAllMocks ( ) ) ;
572541
573542 it ( 'returns unwrapped translation data objects' , async ( ) => {
@@ -577,22 +546,22 @@ describe('fetchProjectTranslations', () => {
577546 ] ;
578547 mockListLanguageTranslations . mockResolvedValue ( { data : raw } ) ;
579548
580- const result = await fetchProjectTranslations ( '42' ) ;
549+ const result = await fetchApprovedProjectTranslations ( '42' ) ;
581550 expect ( result ) . toHaveLength ( 2 ) ;
582551 expect ( result [ 0 ] . translationId ) . toBe ( 10 ) ;
583552 expect ( result [ 1 ] . translationId ) . toBe ( 11 ) ;
584553 } ) ;
585554
586555 it ( 'returns an empty array when response.data is null/undefined' , async ( ) => {
587556 mockListLanguageTranslations . mockResolvedValue ( { } ) ;
588- expect ( await fetchProjectTranslations ( '42' ) ) . toEqual ( [ ] ) ;
557+ expect ( await fetchApprovedProjectTranslations ( '42' ) ) . toEqual ( [ ] ) ;
589558 } ) ;
590559
591- it ( 'calls listLanguageTranslations with projectId and EN_US ' , async ( ) => {
560+ it ( 'calls listLanguageTranslations with projectId, EN_US, and approvedOnly:1 ' , async ( ) => {
592561 mockListLanguageTranslations . mockResolvedValue ( { data : [ ] } ) ;
593- await fetchProjectTranslations ( '42' ) ;
562+ await fetchApprovedProjectTranslations ( '42' ) ;
594563 expect ( mockTranslationsWithFetchAll ) . toHaveBeenCalled ( ) ;
595- expect ( mockListLanguageTranslations ) . toHaveBeenCalledWith ( '42' , EN_US ) ;
564+ expect ( mockListLanguageTranslations ) . toHaveBeenCalledWith ( '42' , EN_US , { approvedOnly : 1 } ) ;
596565 } ) ;
597566} ) ;
598567
@@ -601,61 +570,45 @@ describe('fetchProjectTranslations', () => {
601570describe ( 'isFullyApproved' , ( ) => {
602571 it ( 'returns true for a plain string with an approved translation' , ( ) => {
603572 const string = makeSourceString ( { id : 1 , text : 'Hello' } ) ;
604- const approvedIds = new Set ( [ 100 ] ) ;
605- const stringTranslationMap = new Map ( [ [ 1 , [ { translationId : 100 , pluralCategoryName : null } ] ] ] ) ;
606- expect ( isFullyApproved ( string , approvedIds , stringTranslationMap ) ) . toBe ( true ) ;
573+ const map = new Map ( [ [ 1 , new Set ( [ null ] ) ] ] ) ;
574+ expect ( isFullyApproved ( string , map ) ) . toBe ( true ) ;
607575 } ) ;
608576
609577 it ( 'returns false for a plain string with no entry in the map' , ( ) => {
610578 const string = makeSourceString ( { id : 1 , text : 'Hello' } ) ;
611- const approvedIds = new Set ( ) ;
612- const stringTranslationMap = new Map ( ) ;
613- expect ( isFullyApproved ( string , approvedIds , stringTranslationMap ) ) . toBe ( false ) ;
579+ expect ( isFullyApproved ( string , new Map ( ) ) ) . toBe ( false ) ;
614580 } ) ;
615581
616- it ( 'returns false for a plain string whose translation is not approved' , ( ) => {
582+ it ( 'returns false for a plain string whose category is not in the approved set ' , ( ) => {
617583 const string = makeSourceString ( { id : 1 , text : 'Hello' } ) ;
618- const approvedIds = new Set ( [ 999 ] ) ; // different ID
619- const stringTranslationMap = new Map ( [ [ 1 , [ { translationId : 100 , pluralCategoryName : null } ] ] ] ) ;
620- expect ( isFullyApproved ( string , approvedIds , stringTranslationMap ) ) . toBe ( false ) ;
584+ // Map has the string but an empty set – no category approved
585+ const map = new Map ( [ [ 1 , new Set ( ) ] ] ) ;
586+ expect ( isFullyApproved ( string , map ) ) . toBe ( false ) ;
621587 } ) ;
622588
623589 it ( 'returns true for a plural string when all forms are approved' , ( ) => {
624590 const string = makeSourceString ( { id : 2 , text : { one : 'One' , other : 'Other' } } ) ;
625- const approvedIds = new Set ( [ 10 , 11 ] ) ;
626- const stringTranslationMap = new Map ( [ [ 2 , [
627- { translationId : 10 , pluralCategoryName : 'one' } ,
628- { translationId : 11 , pluralCategoryName : 'other' } ,
629- ] ] ] ) ;
630- expect ( isFullyApproved ( string , approvedIds , stringTranslationMap ) ) . toBe ( true ) ;
591+ const map = new Map ( [ [ 2 , new Set ( [ 'one' , 'other' ] ) ] ] ) ;
592+ expect ( isFullyApproved ( string , map ) ) . toBe ( true ) ;
631593 } ) ;
632594
633595 it ( 'returns false for a plural string when only some forms are approved' , ( ) => {
634596 const string = makeSourceString ( { id : 2 , text : { one : 'One' , other : 'Other' } } ) ;
635- const approvedIds = new Set ( [ 10 ] ) ; // only 'one' approved
636- const stringTranslationMap = new Map ( [ [ 2 , [
637- { translationId : 10 , pluralCategoryName : 'one' } ,
638- { translationId : 11 , pluralCategoryName : 'other' } ,
639- ] ] ] ) ;
640- expect ( isFullyApproved ( string , approvedIds , stringTranslationMap ) ) . toBe ( false ) ;
597+ const map = new Map ( [ [ 2 , new Set ( [ 'one' ] ) ] ] ) ;
598+ expect ( isFullyApproved ( string , map ) ) . toBe ( false ) ;
641599 } ) ;
642600
643601 it ( 'returns false for a plural string when no forms are approved' , ( ) => {
644602 const string = makeSourceString ( { id : 2 , text : { one : 'One' , other : 'Other' } } ) ;
645- const approvedIds = new Set ( ) ;
646- const stringTranslationMap = new Map ( [ [ 2 , [
647- { translationId : 10 , pluralCategoryName : 'one' } ,
648- { translationId : 11 , pluralCategoryName : 'other' } ,
649- ] ] ] ) ;
650- expect ( isFullyApproved ( string , approvedIds , stringTranslationMap ) ) . toBe ( false ) ;
603+ const map = new Map ( [ [ 2 , new Set ( ) ] ] ) ;
604+ expect ( isFullyApproved ( string , map ) ) . toBe ( false ) ;
651605 } ) ;
652606
653- it ( 'does not consider translations belonging to other strings' , ( ) => {
607+ it ( 'does not consider approvals belonging to other strings' , ( ) => {
654608 const string = makeSourceString ( { id : 1 , text : 'Hello' } ) ;
655- const approvedIds = new Set ( [ 200 ] ) ;
656- // Only string 2 has translation 200; string 1 has no entry
657- const stringTranslationMap = new Map ( [ [ 2 , [ { translationId : 200 , pluralCategoryName : null } ] ] ] ) ;
658- expect ( isFullyApproved ( string , approvedIds , stringTranslationMap ) ) . toBe ( false ) ;
609+ // Only string 2 has an approved null category; string 1 has no entry
610+ const map = new Map ( [ [ 2 , new Set ( [ null ] ) ] ] ) ;
611+ expect ( isFullyApproved ( string , map ) ) . toBe ( false ) ;
659612 } ) ;
660613} ) ;
661614
@@ -672,9 +625,9 @@ describe('syncProject', () => {
672625 it ( 'logs the project and string count' , async ( ) => {
673626 const strings = [ makeSourceString ( { id : 1 } ) , makeSourceString ( { id : 2 } ) ] ;
674627 mockListProjectStrings . mockResolvedValue ( { data : strings . map ( ( s ) => ( { data : s } ) ) } ) ;
675- mockListTranslationApprovals . mockResolvedValue ( { data : [ ] } ) ;
676628 mockListLanguageTranslations . mockResolvedValue ( { data : [ ] } ) ;
677629 mockListStringTranslations . mockResolvedValue ( { data : [ ] } ) ;
630+ mockListTranslationApprovals . mockResolvedValue ( { data : [ ] } ) ;
678631 mockAddTranslation . mockResolvedValue ( { data : { id : 50 } } ) ;
679632 mockAddApproval . mockResolvedValue ( { data : { id : 200 } } ) ;
680633
@@ -687,50 +640,45 @@ describe('syncProject', () => {
687640 it ( 'processes strings that are not fully approved' , async ( ) => {
688641 const strings = [ makeSourceString ( { id : 10 } ) , makeSourceString ( { id : 11 } ) ] ;
689642 mockListProjectStrings . mockResolvedValue ( { data : strings . map ( ( s ) => ( { data : s } ) ) } ) ;
690- // No project-level approvals → both strings need processing
691- mockListTranslationApprovals . mockResolvedValue ( { data : [ ] } ) ;
643+ // No approved translations upfront → both strings need processing
692644 mockListLanguageTranslations . mockResolvedValue ( { data : [ ] } ) ;
693645 mockListStringTranslations . mockResolvedValue ( { data : [ ] } ) ;
646+ mockListTranslationApprovals . mockResolvedValue ( { data : [ ] } ) ;
694647 mockAddTranslation . mockResolvedValue ( { data : { id : 50 } } ) ;
695648 mockAddApproval . mockResolvedValue ( { data : { id : 200 } } ) ;
696649
697650 await syncProject ( '42' ) ;
698651
699- // One pair of fetchTranslations+fetchApprovals per string that needs processing
652+ // One fetchTranslations call per string that needs processing
700653 expect ( mockListStringTranslations ) . toHaveBeenCalledTimes ( 2 ) ;
701654 } ) ;
702655
703656 it ( 'skips strings that are already fully approved' , async ( ) => {
704657 const string = makeSourceString ( { id : 5 , text : 'Hello' } ) ;
705658 mockListProjectStrings . mockResolvedValue ( { data : [ { data : string } ] } ) ;
706- mockListTranslationApprovals . mockResolvedValue ( {
707- data : [ { data : makeApproval ( { translationId : 77 } ) } ] ,
708- } ) ;
659+ // approvedOnly response includes stringId=5, category=null → fully covered
709660 mockListLanguageTranslations . mockResolvedValue ( {
710661 data : [ { data : { translationId : 77 , stringId : 5 , pluralCategoryName : null } } ] ,
711662 } ) ;
712663
713664 await syncProject ( '42' ) ;
714665
715- // No per-string API calls should have been made
716666 expect ( mockListStringTranslations ) . not . toHaveBeenCalled ( ) ;
717667 expect ( console . log ) . toHaveBeenCalledWith ( expect . stringContaining ( '1 string(s) skipped' ) ) ;
718668 } ) ;
719669
720- it ( 'processes only the strings that are not fully approved , skipping the rest' , async ( ) => {
670+ it ( 'processes only unapproved strings, skipping the rest' , async ( ) => {
721671 const approvedString = makeSourceString ( { id : 5 , text : 'Hello' } ) ;
722672 const unapprovedString = makeSourceString ( { id : 6 , text : 'World' } ) ;
723673 mockListProjectStrings . mockResolvedValue ( {
724674 data : [ { data : approvedString } , { data : unapprovedString } ] ,
725675 } ) ;
726- mockListTranslationApprovals . mockResolvedValue ( {
727- data : [ { data : makeApproval ( { translationId : 77 } ) } ] ,
728- } ) ;
676+ // Only string 5 is approved
729677 mockListLanguageTranslations . mockResolvedValue ( {
730678 data : [ { data : { translationId : 77 , stringId : 5 , pluralCategoryName : null } } ] ,
731679 } ) ;
732- // Per-string calls for unapproved string
733680 mockListStringTranslations . mockResolvedValue ( { data : [ ] } ) ;
681+ mockListTranslationApprovals . mockResolvedValue ( { data : [ ] } ) ;
734682 mockAddTranslation . mockResolvedValue ( { data : { id : 50 } } ) ;
735683 mockAddApproval . mockResolvedValue ( { data : { id : 200 } } ) ;
736684
@@ -744,12 +692,6 @@ describe('syncProject', () => {
744692 it ( 'skips a plural string only when all its forms are approved' , async ( ) => {
745693 const string = makeSourceString ( { id : 8 , text : { one : 'One' , other : 'Other' } } ) ;
746694 mockListProjectStrings . mockResolvedValue ( { data : [ { data : string } ] } ) ;
747- mockListTranslationApprovals . mockResolvedValue ( {
748- data : [
749- { data : makeApproval ( { translationId : 80 } ) } ,
750- { data : makeApproval ( { translationId : 81 } ) } ,
751- ] ,
752- } ) ;
753695 // Two entries share the same stringId=8 — exercises the map-accumulation branch
754696 mockListLanguageTranslations . mockResolvedValue ( {
755697 data : [
@@ -765,8 +707,22 @@ describe('syncProject', () => {
765707 } ) ;
766708
767709 it ( 'does not log skipped message when no strings are skipped' , async ( ) => {
768- mockListProjectStrings . mockResolvedValue ( { data : [ ] } ) ;
710+ const string = makeSourceString ( { id : 7 , text : 'Test' } ) ;
711+ mockListProjectStrings . mockResolvedValue ( { data : [ { data : string } ] } ) ;
712+ mockListLanguageTranslations . mockResolvedValue ( { data : [ ] } ) ;
713+ mockListStringTranslations . mockResolvedValue ( { data : [ ] } ) ;
769714 mockListTranslationApprovals . mockResolvedValue ( { data : [ ] } ) ;
715+ mockAddTranslation . mockResolvedValue ( { data : { id : 50 } } ) ;
716+ mockAddApproval . mockResolvedValue ( { data : { id : 200 } } ) ;
717+
718+ await syncProject ( '42' ) ;
719+
720+ const logs = console . log . mock . calls . map ( ( c ) => c [ 0 ] ) ;
721+ expect ( logs . some ( ( m ) => m . includes ( 'skipped' ) ) ) . toBe ( false ) ;
722+ } ) ;
723+
724+ it ( 'handles a project with no source strings' , async ( ) => {
725+ mockListProjectStrings . mockResolvedValue ( { data : [ ] } ) ;
770726 mockListLanguageTranslations . mockResolvedValue ( { data : [ ] } ) ;
771727
772728 await syncProject ( '99' ) ;
@@ -802,7 +758,6 @@ describe('main', () => {
802758 } ) ;
803759
804760 mockListProjectStrings . mockResolvedValue ( { data : [ ] } ) ;
805- mockListTranslationApprovals . mockResolvedValue ( { data : [ ] } ) ;
806761 mockListLanguageTranslations . mockResolvedValue ( { data : [ ] } ) ;
807762
808763 await freshMain ( ) ;
0 commit comments