From f57e6f10deb8c1adb72625980c852bcb50473ba1 Mon Sep 17 00:00:00 2001 From: Brian Pilati Date: Fri, 22 Aug 2025 11:38:23 -0500 Subject: [PATCH 1/9] chore(misc): Fixed a test failure, updated jest.config.ts and changed to standAlone` --- jest.config.js | 3 +++ src/app/shared/services/addons/addons.service.spec.ts | 2 ++ src/app/shared/stores/addons/addons.state.spec.ts | 5 ++--- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/jest.config.js b/jest.config.js index 2697c5838..43787f775 100644 --- a/jest.config.js +++ b/jest.config.js @@ -72,6 +72,9 @@ module.exports = { '/src/app/app.config.ts', '/src/app/app.routes.ts', '/src/app/features/registry/', + '/src/app/features/project/addons/components/configure-configure-addon/', + '/src/app/features/project/addons/components/connect-configured-addon/', + '/src/app/features/project/addons/components/disconnect-addon-modal/', '/src/app/features/files/', '/src/app/features/my-projects/', '/src/app/features/preprints/', diff --git a/src/app/shared/services/addons/addons.service.spec.ts b/src/app/shared/services/addons/addons.service.spec.ts index 634631352..4f20953d2 100644 --- a/src/app/shared/services/addons/addons.service.spec.ts +++ b/src/app/shared/services/addons/addons.service.spec.ts @@ -39,6 +39,7 @@ describe('Service: Addons', () => { providerName: 'figshare', supportedFeatures: ['DOWNLOAD_AS_ZIP', 'FORKING', 'LOGS', 'PERMISSIONS', 'REGISTERING'], type: 'external-storage-services', + wbKey: 'figshare', }) ); @@ -63,6 +64,7 @@ describe('Service: Addons', () => { baseAccountType: 'authorized-storage-accounts', connectedCapabilities: ['ACCESS', 'UPDATE'], connectedOperationNames: ['list_child_items', 'list_root_items', 'get_item_info'], + externalStorageServiceId: '8aeb85e9-3a73-426f-a89b-5624b4b9d418', currentUserIsOwner: true, displayName: 'Google Drive', externalServiceName: 'googledrive', diff --git a/src/app/shared/stores/addons/addons.state.spec.ts b/src/app/shared/stores/addons/addons.state.spec.ts index f491f52f8..079c44857 100644 --- a/src/app/shared/stores/addons/addons.state.spec.ts +++ b/src/app/shared/stores/addons/addons.state.spec.ts @@ -1,4 +1,4 @@ -import { NgxsModule, Store } from '@ngxs/store'; +import { provideStore, Store } from '@ngxs/store'; import { provideHttpClient } from '@angular/common/http'; import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; @@ -18,8 +18,7 @@ describe('State: Addons', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [NgxsModule.forRoot([AddonsState])], - providers: [provideHttpClient(), provideHttpClientTesting(), AddonsService], + providers: [provideStore([AddonsState]), provideHttpClient(), provideHttpClientTesting(), AddonsService], }); store = TestBed.inject(Store); From 6829f2328302f11caf4d27a9844769b65b797769 Mon Sep 17 00:00:00 2001 From: Brian Pilati Date: Fri, 22 Aug 2025 11:39:19 -0500 Subject: [PATCH 2/9] chore(compodocs): Disabled the pre-commit as it is useless for now --- .husky/pre-commit | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.husky/pre-commit b/.husky/pre-commit index c173aba2f..9c5a72e69 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -3,10 +3,10 @@ npx lint-staged || { exit 1 } -npm run docs:coverage || { - printf "\n\nERROR: Documentation Coverage thresholds are not met." - printf "\n\nIn the future this will block your ability to commit locally until it is resolved." - printf "\n\nThe same pipeline runs via GitHub actions." - printf "\n\nYou are seeing this error because code was added without documentation." +# npm run docs:coverage || { +# printf "\n\nERROR: Documentation Coverage thresholds are not met." +# printf "\n\nIn the future this will block your ability to commit locally until it is resolved." +# printf "\n\nThe same pipeline runs via GitHub actions." +# printf "\n\nYou are seeing this error because code was added without documentation." # exit 1 -} +# } From d4c2b5f4e864f25981fad2e49cb4df3de2800015 Mon Sep 17 00:00:00 2001 From: Brian Pilati Date: Fri, 22 Aug 2025 11:41:39 -0500 Subject: [PATCH 3/9] chore(jest): actions are now testable --- jest.config.js | 1 - 1 file changed, 1 deletion(-) diff --git a/jest.config.js b/jest.config.js index 43787f775..9f660bd78 100644 --- a/jest.config.js +++ b/jest.config.js @@ -30,7 +30,6 @@ module.exports = { 'src/app/**/*.{ts,js}', '!src/app/app.config.ts', '!src/app/**/*.routes.{ts.js}', - '!src/app/**/*.actions.{ts.js}', '!src/app/**/*.models.{ts.js}', '!src/app/**/*.model.{ts.js}', '!src/app/**/*.route.{ts,js}', From 8e3fd3c1a14bf141d84c5aae3ac7e0f13f9abf46 Mon Sep 17 00:00:00 2001 From: Brian Pilati Date: Fri, 22 Aug 2025 11:43:18 -0500 Subject: [PATCH 4/9] chore(jest): enabled configure-addon.commponent.tst to be testable --- jest.config.js | 1 - 1 file changed, 1 deletion(-) diff --git a/jest.config.js b/jest.config.js index 9f660bd78..5038e2ae2 100644 --- a/jest.config.js +++ b/jest.config.js @@ -77,7 +77,6 @@ module.exports = { '/src/app/features/files/', '/src/app/features/my-projects/', '/src/app/features/preprints/', - '/src/app/features/project/addons/', '/src/app/features/project/analytics/', '/src/app/features/project/contributors/', '/src/app/features/project/files/', From 4717da6fe8dcc387187e6fe9c05834071a3b0252 Mon Sep 17 00:00:00 2001 From: Brian Pilati Date: Fri, 22 Aug 2025 11:58:36 -0500 Subject: [PATCH 5/9] chore(testing): updated the osf.testing.module for translation --- .../configure-addon.component.spec.ts | 4 ++-- src/testing/osf.testing.module.ts | 17 ++++++++++++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/app/features/project/addons/components/configure-addon/configure-addon.component.spec.ts b/src/app/features/project/addons/components/configure-addon/configure-addon.component.spec.ts index a8b21e556..ebfcc0049 100644 --- a/src/app/features/project/addons/components/configure-addon/configure-addon.component.spec.ts +++ b/src/app/features/project/addons/components/configure-addon/configure-addon.component.spec.ts @@ -4,13 +4,13 @@ import { ConfigureAddonComponent } from './configure-addon.component'; import { OSFTestingModule } from '@testing/osf.testing.module'; -describe('ConfigureAddonComponent', () => { +describe('Component: Configure Addon', () => { let component: ConfigureAddonComponent; let fixture: ComponentFixture; beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [ConfigureAddonComponent, OSFTestingModule], + imports: [OSFTestingModule, ConfigureAddonComponent], }).compileComponents(); fixture = TestBed.createComponent(ConfigureAddonComponent); diff --git a/src/testing/osf.testing.module.ts b/src/testing/osf.testing.module.ts index 3a0412bf9..8a9590c4d 100644 --- a/src/testing/osf.testing.module.ts +++ b/src/testing/osf.testing.module.ts @@ -1,6 +1,6 @@ import { Store } from '@ngxs/store'; -import { TranslateModule, TranslateService } from '@ngx-translate/core'; +import { TranslateModule, TranslateService, TranslateStore } from '@ngx-translate/core'; import { of } from 'rxjs'; @@ -42,8 +42,23 @@ import { ToastService } from '@osf/shared/services'; useValue: { get: jest.fn().mockImplementation((key) => of(key || '')), instant: jest.fn().mockImplementation((key) => key || ''), + stream: jest.fn().mockImplementation((key) => of(key || '')), use: jest.fn(), onLangChange: of({}), + onTranslationChange: of({ + lang: 'en', + translations: {}, + }), + onDefaultLangChange: of({ + lang: 'en', + translations: {}, + }), + }, + }, + { + provide: TranslateStore, + useValue: { + onLangChange: of({}), }, }, ], From 938ee32adf5a934e5acefdab898eea72f4aca62f Mon Sep 17 00:00:00 2001 From: Brian Pilati Date: Fri, 22 Aug 2025 12:20:33 -0500 Subject: [PATCH 6/9] chore(mocks): added global testing mocks and a new osftestingstoremodule --- .../file-keywords.component.spec.ts | 4 +- .../file-metadata.component.spec.ts | 4 +- .../file-resource-metadata.component.spec.ts | 4 +- .../file-revisions.component.spec.ts | 4 +- .../file-detail/file-detail.component.spec.ts | 4 +- .../project/addons/addons.component.spec.ts | 4 +- .../configure-addon.component.spec.ts | 6 ++ .../configure-addon.component.ts | 8 +++ .../services/addons/addons.service.spec.ts | 4 +- src/testing/mocks/store.mock.ts | 12 ++++ src/testing/mocks/toast.service.mock.ts | 11 ++++ src/testing/mocks/translation.service.mock.ts | 22 +++++++ src/testing/osf.testing.module.ts | 58 ++++--------------- 13 files changed, 84 insertions(+), 61 deletions(-) create mode 100644 src/testing/mocks/store.mock.ts create mode 100644 src/testing/mocks/toast.service.mock.ts create mode 100644 src/testing/mocks/translation.service.mock.ts diff --git a/src/app/features/files/components/file-keywords/file-keywords.component.spec.ts b/src/app/features/files/components/file-keywords/file-keywords.component.spec.ts index d0ca6ab3e..b7c2426e9 100644 --- a/src/app/features/files/components/file-keywords/file-keywords.component.spec.ts +++ b/src/app/features/files/components/file-keywords/file-keywords.component.spec.ts @@ -2,7 +2,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { FileKeywordsComponent } from './file-keywords.component'; -import { OSFTestingModule } from '@testing/osf.testing.module'; +import { OSFTestingStoreModule } from '@testing/osf.testing.module'; describe('FileKeywordsComponent', () => { let component: FileKeywordsComponent; @@ -10,7 +10,7 @@ describe('FileKeywordsComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [FileKeywordsComponent, OSFTestingModule], + imports: [FileKeywordsComponent, OSFTestingStoreModule], }).compileComponents(); fixture = TestBed.createComponent(FileKeywordsComponent); diff --git a/src/app/features/files/components/file-metadata/file-metadata.component.spec.ts b/src/app/features/files/components/file-metadata/file-metadata.component.spec.ts index ed03b4d62..9925a21c8 100644 --- a/src/app/features/files/components/file-metadata/file-metadata.component.spec.ts +++ b/src/app/features/files/components/file-metadata/file-metadata.component.spec.ts @@ -2,7 +2,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { FileMetadataComponent } from './file-metadata.component'; -import { OSFTestingModule } from '@testing/osf.testing.module'; +import { OSFTestingStoreModule } from '@testing/osf.testing.module'; describe('FileMetadataComponent', () => { let component: FileMetadataComponent; @@ -10,7 +10,7 @@ describe('FileMetadataComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [FileMetadataComponent, OSFTestingModule], + imports: [FileMetadataComponent, OSFTestingStoreModule], }).compileComponents(); fixture = TestBed.createComponent(FileMetadataComponent); diff --git a/src/app/features/files/components/file-resource-metadata/file-resource-metadata.component.spec.ts b/src/app/features/files/components/file-resource-metadata/file-resource-metadata.component.spec.ts index dcac95f90..478d6f2c1 100644 --- a/src/app/features/files/components/file-resource-metadata/file-resource-metadata.component.spec.ts +++ b/src/app/features/files/components/file-resource-metadata/file-resource-metadata.component.spec.ts @@ -2,7 +2,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { FileResourceMetadataComponent } from './file-resource-metadata.component'; -import { OSFTestingModule } from '@testing/osf.testing.module'; +import { OSFTestingStoreModule } from '@testing/osf.testing.module'; describe('FileResourceMetadataComponent', () => { let component: FileResourceMetadataComponent; @@ -10,7 +10,7 @@ describe('FileResourceMetadataComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [FileResourceMetadataComponent, OSFTestingModule], + imports: [FileResourceMetadataComponent, OSFTestingStoreModule], }).compileComponents(); fixture = TestBed.createComponent(FileResourceMetadataComponent); diff --git a/src/app/features/files/components/file-revisions/file-revisions.component.spec.ts b/src/app/features/files/components/file-revisions/file-revisions.component.spec.ts index c414095f9..e03f8a315 100644 --- a/src/app/features/files/components/file-revisions/file-revisions.component.spec.ts +++ b/src/app/features/files/components/file-revisions/file-revisions.component.spec.ts @@ -2,7 +2,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { FileRevisionsComponent } from './file-revisions.component'; -import { OSFTestingModule } from '@testing/osf.testing.module'; +import { OSFTestingStoreModule } from '@testing/osf.testing.module'; describe('FileRevisionsComponent', () => { let component: FileRevisionsComponent; @@ -10,7 +10,7 @@ describe('FileRevisionsComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [FileRevisionsComponent, OSFTestingModule], + imports: [FileRevisionsComponent, OSFTestingStoreModule], }).compileComponents(); fixture = TestBed.createComponent(FileRevisionsComponent); diff --git a/src/app/features/files/pages/file-detail/file-detail.component.spec.ts b/src/app/features/files/pages/file-detail/file-detail.component.spec.ts index cfcb55f87..ab916e9af 100644 --- a/src/app/features/files/pages/file-detail/file-detail.component.spec.ts +++ b/src/app/features/files/pages/file-detail/file-detail.component.spec.ts @@ -6,7 +6,7 @@ import { SubHeaderComponent } from '@shared/components'; import { FileDetailComponent } from './file-detail.component'; -import { OSFTestingModule } from '@testing/osf.testing.module'; +import { OSFTestingStoreModule } from '@testing/osf.testing.module'; describe('FileDetailComponent', () => { let component: FileDetailComponent; @@ -14,7 +14,7 @@ describe('FileDetailComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [FileDetailComponent, MockComponent(SubHeaderComponent), OSFTestingModule], + imports: [FileDetailComponent, MockComponent(SubHeaderComponent), OSFTestingStoreModule], providers: [], }).compileComponents(); diff --git a/src/app/features/project/addons/addons.component.spec.ts b/src/app/features/project/addons/addons.component.spec.ts index 1608b053d..641fa80bd 100644 --- a/src/app/features/project/addons/addons.component.spec.ts +++ b/src/app/features/project/addons/addons.component.spec.ts @@ -2,7 +2,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { AddonsComponent } from './addons.component'; -import { OSFTestingModule } from '@testing/osf.testing.module'; +import { OSFTestingStoreModule } from '@testing/osf.testing.module'; describe('AddonsComponent', () => { let component: AddonsComponent; @@ -10,7 +10,7 @@ describe('AddonsComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [AddonsComponent, OSFTestingModule], + imports: [AddonsComponent, OSFTestingStoreModule], }).compileComponents(); fixture = TestBed.createComponent(AddonsComponent); diff --git a/src/app/features/project/addons/components/configure-addon/configure-addon.component.spec.ts b/src/app/features/project/addons/components/configure-addon/configure-addon.component.spec.ts index ebfcc0049..acefac3cb 100644 --- a/src/app/features/project/addons/components/configure-addon/configure-addon.component.spec.ts +++ b/src/app/features/project/addons/components/configure-addon/configure-addon.component.spec.ts @@ -1,7 +1,12 @@ +import { provideStore } from '@ngxs/store'; + import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { AddonsState } from '@osf/shared/stores'; + import { ConfigureAddonComponent } from './configure-addon.component'; +import { ToastServiceMock } from '@testing/mocks/toast.service.mock'; import { OSFTestingModule } from '@testing/osf.testing.module'; describe('Component: Configure Addon', () => { @@ -11,6 +16,7 @@ describe('Component: Configure Addon', () => { beforeEach(async () => { await TestBed.configureTestingModule({ imports: [OSFTestingModule, ConfigureAddonComponent], + providers: [provideStore([AddonsState]), ToastServiceMock], }).compileComponents(); fixture = TestBed.createComponent(ConfigureAddonComponent); diff --git a/src/app/features/project/addons/components/configure-addon/configure-addon.component.ts b/src/app/features/project/addons/components/configure-addon/configure-addon.component.ts index 35cf69c2c..44ab2cc60 100644 --- a/src/app/features/project/addons/components/configure-addon/configure-addon.component.ts +++ b/src/app/features/project/addons/components/configure-addon/configure-addon.component.ts @@ -118,19 +118,27 @@ export class ConfigureAddonComponent implements OnInit { } private initializeAddon(): void { + console.log('Initializing addon configuration...'); const addon = this.router.getCurrentNavigation()?.extras.state?.['addon'] as ConfiguredStorageAddonModel; + console.log(2); if (addon) { + console.log(3); this.storageAddon.set( this.store.selectSnapshot((state) => AddonsSelectors.getStorageAddon(state.addons, addon.externalStorageServiceId || '') ) ); + console.log(4); this.addon.set(addon); + console.log(5); this.selectedRootFolderId.set(addon.selectedFolderId); + console.log(6); this.accountNameControl.setValue(addon.displayName); + console.log(7); } else { + console.log(8); this.router.navigate([`${this.baseUrl()}/addons`]); } } diff --git a/src/app/shared/services/addons/addons.service.spec.ts b/src/app/shared/services/addons/addons.service.spec.ts index 4f20953d2..a1c773f20 100644 --- a/src/app/shared/services/addons/addons.service.spec.ts +++ b/src/app/shared/services/addons/addons.service.spec.ts @@ -5,14 +5,14 @@ import { AddonsService } from './addons.service'; import { getConfiguredAddonsData } from '@testing/data/addons/addons.configured.data'; import { getAddonsExternalStorageData } from '@testing/data/addons/addons.external-storage.data'; -import { OSFTestingModule } from '@testing/osf.testing.module'; +import { OSFTestingStoreModule } from '@testing/osf.testing.module'; describe('Service: Addons', () => { let service: AddonsService; beforeEach(() => { TestBed.configureTestingModule({ - imports: [OSFTestingModule], + imports: [OSFTestingStoreModule], providers: [AddonsService], }); diff --git a/src/testing/mocks/store.mock.ts b/src/testing/mocks/store.mock.ts new file mode 100644 index 000000000..e6ba2d570 --- /dev/null +++ b/src/testing/mocks/store.mock.ts @@ -0,0 +1,12 @@ +import { Store } from '@ngxs/store'; + +import { of } from 'rxjs'; + +export const StoreMock = { + provide: Store, + useValue: { + select: jest.fn().mockReturnValue(of([])), + selectSignal: jest.fn().mockReturnValue(of([])), + dispatch: jest.fn().mockReturnValue(of({})), + } as unknown as jest.Mocked, +}; diff --git a/src/testing/mocks/toast.service.mock.ts b/src/testing/mocks/toast.service.mock.ts new file mode 100644 index 000000000..5c219a9ec --- /dev/null +++ b/src/testing/mocks/toast.service.mock.ts @@ -0,0 +1,11 @@ +import { ToastService } from '@osf/shared/services'; + +export const ToastServiceMock = { + provide: ToastService, + useValue: { + success: jest.fn(), + error: jest.fn(), + info: jest.fn(), + warning: jest.fn(), + }, +}; diff --git a/src/testing/mocks/translation.service.mock.ts b/src/testing/mocks/translation.service.mock.ts new file mode 100644 index 000000000..010ae3167 --- /dev/null +++ b/src/testing/mocks/translation.service.mock.ts @@ -0,0 +1,22 @@ +import { TranslateService } from '@ngx-translate/core'; + +import { of } from 'rxjs'; + +export const translationServiceMock = { + provide: TranslateService, + useValue: { + get: jest.fn().mockImplementation((key) => of(key || '')), + instant: jest.fn().mockImplementation((key) => key || ''), + stream: jest.fn().mockImplementation((key) => of(key || '')), + use: jest.fn(), + onLangChange: of({}), + onTranslationChange: of({ + lang: 'en', + translations: {}, + }), + onDefaultLangChange: of({ + lang: 'en', + translations: {}, + }), + }, +}; diff --git a/src/testing/osf.testing.module.ts b/src/testing/osf.testing.module.ts index 8a9590c4d..b5c7cc404 100644 --- a/src/testing/osf.testing.module.ts +++ b/src/testing/osf.testing.module.ts @@ -1,8 +1,4 @@ -import { Store } from '@ngxs/store'; - -import { TranslateModule, TranslateService, TranslateStore } from '@ngx-translate/core'; - -import { of } from 'rxjs'; +import { TranslateModule } from '@ngx-translate/core'; import { CommonModule } from '@angular/common'; import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; @@ -12,7 +8,9 @@ import { BrowserModule } from '@angular/platform-browser'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { provideRouter } from '@angular/router'; -import { ToastService } from '@osf/shared/services'; +import { StoreMock } from './mocks/store.mock'; +import { ToastServiceMock } from './mocks/toast.service.mock'; +import { translationServiceMock } from './mocks/translation.service.mock'; @NgModule({ imports: [NoopAnimationsModule, BrowserModule, CommonModule, TranslateModule.forRoot()], @@ -20,47 +18,13 @@ import { ToastService } from '@osf/shared/services'; provideRouter([]), provideHttpClient(withInterceptorsFromDi()), provideHttpClientTesting(), - { - provide: Store, - useValue: { - select: jest.fn().mockReturnValue(of([])), - selectSignal: jest.fn().mockReturnValue(of([])), - dispatch: jest.fn().mockReturnValue(of({})), - } as unknown as jest.Mocked, - }, - { - provide: ToastService, - useValue: { - success: jest.fn(), - error: jest.fn(), - info: jest.fn(), - warning: jest.fn(), - }, - }, - { - provide: TranslateService, - useValue: { - get: jest.fn().mockImplementation((key) => of(key || '')), - instant: jest.fn().mockImplementation((key) => key || ''), - stream: jest.fn().mockImplementation((key) => of(key || '')), - use: jest.fn(), - onLangChange: of({}), - onTranslationChange: of({ - lang: 'en', - translations: {}, - }), - onDefaultLangChange: of({ - lang: 'en', - translations: {}, - }), - }, - }, - { - provide: TranslateStore, - useValue: { - onLangChange: of({}), - }, - }, + translationServiceMock, ], }) export class OSFTestingModule {} + +@NgModule({ + imports: [OSFTestingModule], + providers: [StoreMock, ToastServiceMock], +}) +export class OSFTestingStoreModule {} From d44047b749f808603a8c2fb6f1174681aa7fb6c3 Mon Sep 17 00:00:00 2001 From: Brian Pilati Date: Fri, 22 Aug 2025 13:09:54 -0500 Subject: [PATCH 7/9] chore(tests): Added tests to continue the process --- .../configure-addon.component.spec.ts | 167 ++++++++++++++++-- .../configure-addon.component.ts | 26 ++- .../addons.operation-invocation.data.ts | 73 ++++++++ 3 files changed, 239 insertions(+), 27 deletions(-) create mode 100644 src/testing/data/addons/addons.operation-invocation.data.ts diff --git a/src/app/features/project/addons/components/configure-addon/configure-addon.component.spec.ts b/src/app/features/project/addons/components/configure-addon/configure-addon.component.spec.ts index acefac3cb..604ce4c4b 100644 --- a/src/app/features/project/addons/components/configure-addon/configure-addon.component.spec.ts +++ b/src/app/features/project/addons/components/configure-addon/configure-addon.component.spec.ts @@ -1,11 +1,17 @@ import { provideStore } from '@ngxs/store'; -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { of } from 'rxjs'; + +import { HttpTestingController } from '@angular/common/http/testing'; +import { ComponentFixture, inject, TestBed } from '@angular/core/testing'; +import { ActivatedRoute, Router } from '@angular/router'; import { AddonsState } from '@osf/shared/stores'; import { ConfigureAddonComponent } from './configure-addon.component'; +import { getConfiguredAddonsData } from '@testing/data/addons/addons.configured.data'; +import { getAddonsOperationInvocation } from '@testing/data/addons/addons.operation-invocation.data'; import { ToastServiceMock } from '@testing/mocks/toast.service.mock'; import { OSFTestingModule } from '@testing/osf.testing.module'; @@ -13,18 +19,157 @@ describe('Component: Configure Addon', () => { let component: ConfigureAddonComponent; let fixture: ComponentFixture; - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [OSFTestingModule, ConfigureAddonComponent], - providers: [provideStore([AddonsState]), ToastServiceMock], - }).compileComponents(); + const mockActivatedRoute = { + parent: { + parent: { + snapshot: { + params: { + id: 'mocked-id', + }, + }, + }, + }, + snapshot: { + paramMap: new Map(), + queryParamMap: new Map(), + data: {}, + }, + paramMap: of(new Map()), + queryParamMap: of(new Map()), + // ... if you use `route.root`, add a dummy root too + root: {}, + }; + + describe('addon present', () => { + beforeEach(async () => { + const mockRouter = { + url: '/project/abc123/addons/configure', // mock the actual URL used in your component + navigate: jest.fn(), + getCurrentNavigation: jest.fn().mockReturnValue({ + extras: { + state: { + addon: getConfiguredAddonsData(0), + }, + }, + }), + } as unknown as Router; + + await TestBed.configureTestingModule({ + imports: [OSFTestingModule, ConfigureAddonComponent], + providers: [ + provideStore([AddonsState]), + ToastServiceMock, + { provide: Router, useValue: mockRouter }, + { + provide: ActivatedRoute, + useValue: mockActivatedRoute, + }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(ConfigureAddonComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should validate the constuctor values', () => { + expect(component.storageAddon()).toBeUndefined(); + expect(component.addon()).toEqual( + Object({ + attributes: { + connected_capabilities: ['ACCESS', 'UPDATE'], + connected_operation_names: ['list_child_items', 'list_root_items', 'get_item_info'], + current_user_is_owner: true, + display_name: 'Google Drive', + external_service_name: 'googledrive', + root_folder: '0AIl0aR4C9JAFUk9PVA', + }, + id: '756579dc-3a24-4849-8866-698a60846ac3', + links: expect.any(Object), + relationships: expect.any(Object), + type: 'configured-storage-addons', + }) + ); + + expect(component.baseUrl()).toBe('/project/abc123'); + expect(component.resourceUri()).toBe('https://staging4.osf.io/mocked-id'); + expect(component.addonTypeString()).toBe('storage'); + expect(component.selectedRootFolderId()).toBeUndefined(); + expect(component.accountNameControl.value).toBeUndefined(); + }); - fixture = TestBed.createComponent(ConfigureAddonComponent); - component = fixture.componentInstance; - fixture.detectChanges(); + it('should valid onInit - action called', inject([HttpTestingController], (httpMock: HttpTestingController) => { + const request = httpMock.expectOne('https://addons.staging4.osf.io/v1/addon-operation-invocations/'); + expect(request.request.method).toBe('POST'); + request.flush(getAddonsOperationInvocation()); + + expect(component.operationInvocation()).toEqual( + Object({ + id: '022c80d6-06b5-452d-9932-19bb135cd5c2', + invocationStatus: 'SUCCESS', + itemCount: 0, + operationKwargs: { + itemId: '0AIl0aR4C9JAFUk9PVA', + itemType: undefined, + }, + operationName: 'get_item_info', + operationResult: [ + { + canBeRoot: true, + itemId: '0AIl0aR4C9JAFUk9PVA', + itemName: 'My Drive', + itemType: 'FOLDER', + mayContainRootCandidates: true, + }, + ], + type: 'addon-operation-invocations', + }) + ); + expect(httpMock.verify).toBeTruthy(); + })); }); - it('should create', () => { - expect(component).toBeTruthy(); + describe('addon not-present', () => { + beforeEach(async () => { + const mockRouter = { + url: '/project/abc123/addons/configure', // mock the actual URL used in your component + navigate: jest.fn(), + getCurrentNavigation: jest.fn().mockReturnValue({ + extras: { + state: { + addon: null, + }, + }, + }), + } as unknown as Router; + + await TestBed.configureTestingModule({ + imports: [OSFTestingModule, ConfigureAddonComponent], + providers: [ + provideStore([AddonsState]), + ToastServiceMock, + { provide: Router, useValue: mockRouter }, + { + provide: ActivatedRoute, + useValue: mockActivatedRoute, + }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(ConfigureAddonComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should validate user is redirected', () => { + const spy = jest.spyOn(component['router'], 'navigate'); + expect(spy).toHaveBeenCalledWith(['/project/abc123/addons']); + }); + + it('should valid onInit - action not called', inject([HttpTestingController], (httpMock: HttpTestingController) => { + httpMock.expectNone('https://addons.staging4.osf.io/v1/addon-operation-invocations/'); + + expect(httpMock.verify).toBeTruthy(); + })); }); }); diff --git a/src/app/features/project/addons/components/configure-addon/configure-addon.component.ts b/src/app/features/project/addons/components/configure-addon/configure-addon.component.ts index 44ab2cc60..10a047a01 100644 --- a/src/app/features/project/addons/components/configure-addon/configure-addon.component.ts +++ b/src/app/features/project/addons/components/configure-addon/configure-addon.component.ts @@ -71,34 +71,34 @@ export class ConfigureAddonComponent implements OnInit { /** * Form control for capturing or displaying the userโ€™s selected account name. */ - protected accountNameControl = new FormControl(''); + public accountNameControl = new FormControl(''); /** * Signal representing the currently selected `Addon` from the list of available storage addons. * This value updates reactively as the selection changes. */ - protected storageAddon = signal(undefined); + public storageAddon = signal(undefined); /** * Signal representing the currently selected and configured storage addon model. * This may be `null` if no addon has been configured. */ - protected addon = signal(null); + public addon = signal(null); protected isEditMode = signal(false); - protected selectedRootFolderId = signal(''); + public selectedRootFolderId = signal(''); protected addonsUserReference = select(AddonsSelectors.getAddonsUserReference); - protected operationInvocation = select(AddonsSelectors.getOperationInvocation); + public operationInvocation = select(AddonsSelectors.getOperationInvocation); protected selectedFolderOperationInvocation = select(AddonsSelectors.getSelectedFolderOperationInvocation); protected selectedFolder = select(AddonsSelectors.getSelectedFolder); - protected readonly baseUrl = computed(() => { + readonly baseUrl = computed(() => { const currentUrl = this.router.url; return currentUrl.split('/addons')[0]; }); - protected readonly resourceUri = computed(() => { + readonly resourceUri = computed(() => { const id = this.route.parent?.parent?.snapshot.params['id']; return `${environment.webUrl}/${id}`; }); - protected readonly addonTypeString = computed(() => { + readonly addonTypeString = computed(() => { return getAddonTypeString(this.addon()); }); protected readonly actions = createDispatchMap({ @@ -118,27 +118,21 @@ export class ConfigureAddonComponent implements OnInit { } private initializeAddon(): void { - console.log('Initializing addon configuration...'); + // TODO this should be reviewed to have the addon be retrieved from the store + // I have limited my testing because it will create a false/positive test based on the required data const addon = this.router.getCurrentNavigation()?.extras.state?.['addon'] as ConfiguredStorageAddonModel; - console.log(2); if (addon) { - console.log(3); this.storageAddon.set( this.store.selectSnapshot((state) => AddonsSelectors.getStorageAddon(state.addons, addon.externalStorageServiceId || '') ) ); - console.log(4); this.addon.set(addon); - console.log(5); this.selectedRootFolderId.set(addon.selectedFolderId); - console.log(6); this.accountNameControl.setValue(addon.displayName); - console.log(7); } else { - console.log(8); this.router.navigate([`${this.baseUrl()}/addons`]); } } diff --git a/src/testing/data/addons/addons.operation-invocation.data.ts b/src/testing/data/addons/addons.operation-invocation.data.ts new file mode 100644 index 000000000..e3c57a659 --- /dev/null +++ b/src/testing/data/addons/addons.operation-invocation.data.ts @@ -0,0 +1,73 @@ +import structuredClone from 'structured-clone'; + +const OperationInvocation = { + data: { + type: 'addon-operation-invocations', + id: '022c80d6-06b5-452d-9932-19bb135cd5c2', + attributes: { + invocation_status: 'SUCCESS', + operation_kwargs: { + item_id: '0AIl0aR4C9JAFUk9PVA', + }, + operation_result: { + item_id: '0AIl0aR4C9JAFUk9PVA', + item_name: 'My Drive', + item_type: 'FOLDER', + can_be_root: true, + may_contain_root_candidates: true, + }, + created: '2025-08-22T18:02:49.901038Z', + modified: '2025-08-22T18:02:50.849893Z', + operation_name: 'get_item_info', + }, + relationships: { + operation: { + links: { + related: + 'https://addons.staging4.osf.io/v1/addon-operation-invocations/022c80d6-06b5-452d-9932-19bb135cd5c2/operation', + }, + data: { + type: 'addon-operations', + id: 'U1RPUkFHRTpnZXRfaXRlbV9pbmZv', + }, + }, + by_user: { + links: { + related: + 'https://addons.staging4.osf.io/v1/addon-operation-invocations/022c80d6-06b5-452d-9932-19bb135cd5c2/by_user', + }, + data: { + type: 'user-references', + id: '0b441148-83e5-4f7f-b302-b07b528b160b', + }, + }, + thru_account: { + links: { + related: + 'https://addons.staging4.osf.io/v1/addon-operation-invocations/022c80d6-06b5-452d-9932-19bb135cd5c2/thru_account', + }, + data: { + type: 'authorized-storage-accounts', + id: '62ed6dd7-f7b7-4003-b7b4-855789c1f991', + }, + }, + thru_addon: { + links: { + related: + 'https://addons.staging4.osf.io/v1/addon-operation-invocations/022c80d6-06b5-452d-9932-19bb135cd5c2/thru_addon', + }, + data: { + type: 'configured-storage-addons', + id: '756579dc-3a24-4849-8866-698a60846ac3', + }, + }, + }, + links: { + self: 'https://addons.staging4.osf.io/v1/addon-operation-invocations/022c80d6-06b5-452d-9932-19bb135cd5c2', + }, + }, +}; + +export function getAddonsOperationInvocation() { + return structuredClone(OperationInvocation); +} From f9fe430b0fd992e1da1276136e4a7e3ad5240c8b Mon Sep 17 00:00:00 2001 From: Brian Pilati Date: Fri, 22 Aug 2025 13:16:55 -0500 Subject: [PATCH 8/9] feat(code): added the condition for google drive --- .../configure-addon.component.html | 24 +++++++++---------- .../configure-addon.component.spec.ts | 1 + .../configure-addon.component.ts | 4 ++++ 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/app/features/project/addons/components/configure-addon/configure-addon.component.html b/src/app/features/project/addons/components/configure-addon/configure-addon.component.html index 32b811407..06ccb05e2 100644 --- a/src/app/features/project/addons/components/configure-addon/configure-addon.component.html +++ b/src/app/features/project/addons/components/configure-addon/configure-addon.component.html @@ -63,21 +63,21 @@

(keydown.enter)="toggleEditMode()" > - - - + } @else { + + } } diff --git a/src/app/features/project/addons/components/configure-addon/configure-addon.component.spec.ts b/src/app/features/project/addons/components/configure-addon/configure-addon.component.spec.ts index 604ce4c4b..9a092dff3 100644 --- a/src/app/features/project/addons/components/configure-addon/configure-addon.component.spec.ts +++ b/src/app/features/project/addons/components/configure-addon/configure-addon.component.spec.ts @@ -96,6 +96,7 @@ describe('Component: Configure Addon', () => { expect(component.addonTypeString()).toBe('storage'); expect(component.selectedRootFolderId()).toBeUndefined(); expect(component.accountNameControl.value).toBeUndefined(); + expect(component.isGoogleDrive()).toBeFalsy(); }); it('should valid onInit - action called', inject([HttpTestingController], (httpMock: HttpTestingController) => { diff --git a/src/app/features/project/addons/components/configure-addon/configure-addon.component.ts b/src/app/features/project/addons/components/configure-addon/configure-addon.component.ts index 10a047a01..0b378a0b6 100644 --- a/src/app/features/project/addons/components/configure-addon/configure-addon.component.ts +++ b/src/app/features/project/addons/components/configure-addon/configure-addon.component.ts @@ -83,6 +83,10 @@ export class ConfigureAddonComponent implements OnInit { */ public addon = signal(null); + public readonly isGoogleDrive = computed(() => { + return this.storageAddon()?.wbKey === 'googledrive'; + }); + protected isEditMode = signal(false); public selectedRootFolderId = signal(''); protected addonsUserReference = select(AddonsSelectors.getAddonsUserReference); From 35d887d212ca061f6be2406fc80dfe22e5a86013 Mon Sep 17 00:00:00 2001 From: Brian Pilati Date: Fri, 22 Aug 2025 14:25:22 -0500 Subject: [PATCH 9/9] feat(google-file-picker): Added the initial google file picker component --- jest.config.js | 1 + .../project/addons/addons.component.spec.ts | 19 +- .../configure-addon.component.html | 25 +- .../connect-configured-addon.component.html | 1 + .../folder-selector.component.html | 2 + .../folder-selector.component.ts | 10 +- .../google-file-picker.component.html | 17 ++ .../google-file-picker.component.scss | 25 ++ .../google-file-picker.component.spec.ts | 25 ++ .../google-file-picker.component.ts | 223 ++++++++++++++++++ 10 files changed, 328 insertions(+), 20 deletions(-) create mode 100644 src/app/shared/components/addons/folder-selector/google-file-picker/google-file-picker.component.html create mode 100644 src/app/shared/components/addons/folder-selector/google-file-picker/google-file-picker.component.scss create mode 100644 src/app/shared/components/addons/folder-selector/google-file-picker/google-file-picker.component.spec.ts create mode 100644 src/app/shared/components/addons/folder-selector/google-file-picker/google-file-picker.component.ts diff --git a/jest.config.js b/jest.config.js index 5038e2ae2..c1d279c92 100644 --- a/jest.config.js +++ b/jest.config.js @@ -74,6 +74,7 @@ module.exports = { '/src/app/features/project/addons/components/configure-configure-addon/', '/src/app/features/project/addons/components/connect-configured-addon/', '/src/app/features/project/addons/components/disconnect-addon-modal/', + '/src/app/features/project/addons/components/confirm-account-connection-modal/', '/src/app/features/files/', '/src/app/features/my-projects/', '/src/app/features/preprints/', diff --git a/src/app/features/project/addons/addons.component.spec.ts b/src/app/features/project/addons/addons.component.spec.ts index 641fa80bd..e90e4cc21 100644 --- a/src/app/features/project/addons/addons.component.spec.ts +++ b/src/app/features/project/addons/addons.component.spec.ts @@ -1,8 +1,14 @@ +import { provideStore } from '@ngxs/store'; + +import { signal } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { UserSelectors, UserState } from '@core/store/user'; +import { AddonsState } from '@osf/shared/stores'; + import { AddonsComponent } from './addons.component'; -import { OSFTestingStoreModule } from '@testing/osf.testing.module'; +import { OSFTestingModule } from '@testing/osf.testing.module'; describe('AddonsComponent', () => { let component: AddonsComponent; @@ -10,7 +16,16 @@ describe('AddonsComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [AddonsComponent, OSFTestingStoreModule], + imports: [AddonsComponent, OSFTestingModule], + providers: [ + provideStore([UserState, AddonsState]), + { + provide: UserSelectors, + useValue: { + getCurrentUser: () => signal({ id: 'mock-user' }), + }, + }, + ], }).compileComponents(); fixture = TestBed.createComponent(AddonsComponent); diff --git a/src/app/features/project/addons/components/configure-addon/configure-addon.component.html b/src/app/features/project/addons/components/configure-addon/configure-addon.component.html index 06ccb05e2..ab72be55c 100644 --- a/src/app/features/project/addons/components/configure-addon/configure-addon.component.html +++ b/src/app/features/project/addons/components/configure-addon/configure-addon.component.html @@ -63,21 +63,16 @@

(keydown.enter)="toggleEditMode()" > - @if (isGoogleDrive()) { -

- {{ storageAddon()?.wbKey }} -

- } @else { - - } + } diff --git a/src/app/features/project/addons/components/connect-configured-addon/connect-configured-addon.component.html b/src/app/features/project/addons/components/connect-configured-addon/connect-configured-addon.component.html index 2feea04b3..a44822703 100644 --- a/src/app/features/project/addons/components/connect-configured-addon/connect-configured-addon.component.html +++ b/src/app/features/project/addons/components/connect-configured-addon/connect-configured-addon.component.html @@ -114,6 +114,7 @@

{{ 'settings.addons.connectAddon.chooseExistingAccount' | trans

{{ 'settings.addons.connectAddon.configure' | translate }} {{ addon()?.displayName }}

@if (isOperationInvocationSubmitting()) { + } @else if (isGoogleFilePicker()) { + } @else {
diff --git a/src/app/shared/components/addons/folder-selector/folder-selector.component.ts b/src/app/shared/components/addons/folder-selector/folder-selector.component.ts index bd415ffbf..ae4b61947 100644 --- a/src/app/shared/components/addons/folder-selector/folder-selector.component.ts +++ b/src/app/shared/components/addons/folder-selector/folder-selector.component.ts @@ -30,26 +30,30 @@ import { OperationNames } from '@osf/features/project/addons/enums'; import { OperationInvokeData, StorageItem } from '@shared/models'; import { AddonsSelectors } from '@shared/stores/addons'; +import { GoogleFilePickerComponent } from './google-file-picker/google-file-picker.component'; + @Component({ selector: 'osf-folder-selector', templateUrl: './folder-selector.component.html', styleUrl: './folder-selector.component.scss', imports: [ - TranslatePipe, + BreadcrumbModule, Button, Card, + FormsModule, + GoogleFilePickerComponent, InputText, RadioButton, ReactiveFormsModule, Skeleton, - BreadcrumbModule, - FormsModule, + TranslatePipe, ], changeDetection: ChangeDetectionStrategy.OnPush, }) export class FolderSelectorComponent implements OnInit { private destroyRef = inject(DestroyRef); private translateService = inject(TranslateService); + isGoogleFilePicker = input.required(); accountName = input.required(); operationInvocationResult = input.required(); accountNameControl = input(new FormControl()); diff --git a/src/app/shared/components/addons/folder-selector/google-file-picker/google-file-picker.component.html b/src/app/shared/components/addons/folder-selector/google-file-picker/google-file-picker.component.html new file mode 100644 index 000000000..725aa2a44 --- /dev/null +++ b/src/app/shared/components/addons/folder-selector/google-file-picker/google-file-picker.component.html @@ -0,0 +1,17 @@ +
Hello World again
+ + diff --git a/src/app/shared/components/addons/folder-selector/google-file-picker/google-file-picker.component.scss b/src/app/shared/components/addons/folder-selector/google-file-picker/google-file-picker.component.scss new file mode 100644 index 000000000..79d9e6515 --- /dev/null +++ b/src/app/shared/components/addons/folder-selector/google-file-picker/google-file-picker.component.scss @@ -0,0 +1,25 @@ +// stylelint-disable max-nesting-depth, selector-max-compound-selectors, selector-no-qualifying-type + +.google-file-picker-container { + width: 0; + + &.file-picker { + width: 100%; + } + + .instruction-container { + width: 100%; + } + + .action-container { + width: 100%; + + .authorize-button { + visibility: hidden; + + &.visible { + visibility: visible; + } + } + } +} diff --git a/src/app/shared/components/addons/folder-selector/google-file-picker/google-file-picker.component.spec.ts b/src/app/shared/components/addons/folder-selector/google-file-picker/google-file-picker.component.spec.ts new file mode 100644 index 000000000..de34fea92 --- /dev/null +++ b/src/app/shared/components/addons/folder-selector/google-file-picker/google-file-picker.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { GoogleFilePickerComponent } from './google-file-picker.component'; + +import { OSFTestingStoreModule } from '@testing/osf.testing.module'; + +describe('Component: Configure Addon', () => { + let component: GoogleFilePickerComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [OSFTestingStoreModule, GoogleFilePickerComponent], + providers: [], + }).compileComponents(); + + fixture = TestBed.createComponent(GoogleFilePickerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should validate the component', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/shared/components/addons/folder-selector/google-file-picker/google-file-picker.component.ts b/src/app/shared/components/addons/folder-selector/google-file-picker/google-file-picker.component.ts new file mode 100644 index 000000000..d95f59c4e --- /dev/null +++ b/src/app/shared/components/addons/folder-selector/google-file-picker/google-file-picker.component.ts @@ -0,0 +1,223 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; + +@Component({ + selector: 'osf-google-file-picker', + imports: [], + templateUrl: './google-file-picker.component.html', + styleUrl: './google-file-picker.component.scss', + providers: [], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class GoogleFilePickerComponent {} + +// import Store from '@ember-data/store'; +// import { action } from '@ember/object'; +// import { waitFor } from '@ember/test-waiters'; +// import Component from '@glimmer/component'; +// import { tracked } from '@glimmer/tracking'; +// import { task } from 'ember-concurrency'; +// import { taskFor } from 'ember-concurrency-ts'; +// import config from 'ember-osf-web/config/environment'; +// import { Item } from 'ember-osf-web/models/addon-operation-invocation'; +// import StorageManager from 'osf-components/components/storage-provider-manager/storage-manager/component'; +// import { inject as service } from '@ember/service'; +// import Intl from 'ember-intl/services/intl'; + +// const { +// GOOGLE_FILE_PICKER_SCOPES, +// GOOGLE_FILE_PICKER_API_KEY, +// GOOGLE_FILE_PICKER_APP_ID, +// } = config.OSF.googleFilePicker; + +// // +// // ๐Ÿ“š Interface for Expected Arguments +// // +// interface Args { +// /** +// * selectFolder +// * +// * @description +// * A callback function passed into the component +// * that accepts a partial Item object and handles it (e.g., selects a file). +// */ +// selectFolder?: (a: Partial) => void; +// onRegisterChild?: (a: GoogleFilePickerWidget) => void; +// selectedFolderName?: string; +// isFolderPicker: boolean; +// rootFolderId: string; +// manager: StorageManager; +// accountId: string; +// } + +// // +// // ๐Ÿ“š Extend Global Window Type +// // +// // Declares that `window` can optionally have a GoogleFilePickerWidget instance. +// // This allows safe typing when accessing it elsewhere. +// // +// declare global { +// interface Window { +// GoogleFilePickerWidget?: GoogleFilePickerWidget; +// gapi?: any; +// google?: any; +// } +// } + +// // +// // GoogleFilePickerWidget Component +// // +// // @description +// // An Ember Glimmer component that exposes itself to the global `window` +// // so that external JavaScript (like Google Picker API callbacks) +// // can interact with it directly. +// // +// export default class GoogleFilePickerWidget extends Component { +// @service intl!: Intl; +// @service store!: Store; +// @tracked folderName!: string | undefined; +// @tracked isFolderPicker = false; +// @tracked openGoogleFilePicker = false; +// @tracked visible = false; +// @tracked isGFPDisabled = true; +// pickerInited = false; +// selectFolder: any = undefined; +// accessToken!: string; +// scopes = GOOGLE_FILE_PICKER_SCOPES; +// apiKey = GOOGLE_FILE_PICKER_API_KEY; +// appId = GOOGLE_FILE_PICKER_APP_ID; +// mimeTypes = ''; +// parentId = ''; +// isMultipleSelect: boolean; +// title!: string; + +// /** +// * Constructor +// * +// * @description +// * Initializes the GoogleFilePickerWidget component and exposes its key methods to the global `window` object +// * for integration with external JavaScript (e.g., Google Picker API). +// * +// * - Sets `window.GoogleFilePickerWidget` to the current component instance (`this`), +// * allowing external scripts to call methods like `filePickerCallback()`. +// * - Captures the closure action `selectFolder` from `this.args` and assigns it directly to `window.selectFolder`, +// * preserving the correct closure reference even outside of Ember's internal context. +// * +// * @param owner - The owner/context passed by Ember at component instantiation. +// * @param args - The arguments passed to the component, including closure actions like `selectFolder`. +// */ +// constructor(owner: unknown, args: Args) { +// super(owner, args); + +// window.GoogleFilePickerWidget = this; +// this.selectFolder = this.args.selectFolder; +// this.mimeTypes = this.args.isFolderPicker ? 'application/vnd.google-apps.folder' : ''; +// this.parentId = this.args.isFolderPicker ? '': this.args.rootFolderId; +// this.title = this.args.isFolderPicker ? +// this.intl.t('addons.configure.google-file-picker.root-folder-title') : +// this.intl.t('addons.configure.google-file-picker.file-folder-title'); +// this.isMultipleSelect = !this.args.isFolderPicker; +// this.isFolderPicker = this.args.isFolderPicker; + +// this.folderName = this.args.selectedFolderName; + +// taskFor(this.loadOauthToken).perform(); +// } + +// @task +// @waitFor +// private async loadOauthToken(): Promise{ +// if (this.args.accountId) { +// const authorizedStorageAccount = await this.store. +// findRecord('authorized-storage-account', this.args.accountId); +// authorizedStorageAccount.serializeOauthToken = true; +// const token = await authorizedStorageAccount.save(); +// this.accessToken = token.oauthToken; +// this.isGFPDisabled = this.accessToken ? false : true; +// } +// } + +// /** +// * filePickerCallback +// * +// * @description +// * Action triggered when a file is selected via an external picker. +// * Logs the file data and notifies the parent system by calling `selectFolder`. +// * +// * @param file - The file object selected (format determined by external API) +// */ +// @action +// filePickerCallback(data: any) { +// if (this.selectFolder !== undefined) { +// this.folderName = data.name; +// this.selectFolder({ +// itemName: data.name, +// itemId: data.id, +// }); +// } else { +// this.args.manager.reload(); +// } +// } + +// @action +// registerComponent() { +// if (this.args.onRegisterChild) { +// this.args.onRegisterChild(this); // Pass the child's instance to the parent +// } +// } + +// willDestroy() { +// super.willDestroy(); +// this.pickerInited = false; +// } + +// /** +// * Callback after api.js is loaded. +// */ +// gapiLoaded() { +// window.gapi.load('client:picker', this.initializePicker.bind(this)); +// } + +// /** +// * Callback after the API client is loaded. Loads the +// * discovery doc to initialize the API. +// */ +// async initializePicker() { +// this.pickerInited = true; +// if (this.isFolderPicker) { +// this.visible = true; +// } +// } + +// /** +// * Create and render a Picker object for searching images. +// */ +// @action +// createPicker() { +// const googlePickerView = new window.google.picker.DocsView(window.google.picker.ViewId.DOCS); +// googlePickerView.setSelectFolderEnabled(true); +// googlePickerView.setMimeTypes(this.mimeTypes); +// googlePickerView.setIncludeFolders(true); +// googlePickerView.setParent(this.parentId); + +// const picker = new window.google.picker.PickerBuilder() +// .enableFeature(this.isMultipleSelect ? window.google.picker.Feature.MULTISELECT_ENABLED : '') +// .setDeveloperKey(this.apiKey) +// .setAppId(this.appId) +// .addView(googlePickerView) +// .setTitle(this.title) +// .setOAuthToken(this.accessToken) +// .setCallback(this.pickerCallback.bind(this)) +// .build(); +// picker.setVisible(true); +// } + +// /** +// * Displays the file details of the user's selection. +// * @param {object} data - Containers the user selection from the picker +// */ +// async pickerCallback(data: any) { +// if (data.action === window.google.picker.Action.PICKED) { +// this.filePickerCallback(data.docs[0]); +// } +// } +// }