Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@ <h2 class="align-self-center inline-block">
<p-button
icon="fas fa-pencil"
text
(click)="toggleEditMode()"
(onClick)="toggleEditMode()"
(keydown.enter)="toggleEditMode()"
></p-button>
}
<p-button
icon="fas fa-trash"
severity="danger"
text
(click)="handleDisconnectAccount()"
(onClick)="handleDisconnectAccount()"
(keydown.enter)="handleDisconnectAccount()"
></p-button>
</div>
Expand All @@ -63,7 +63,7 @@ <h2 class="align-self-center">
<p-button
[label]="'common.buttons.back' | translate"
severity="info"
(click)="toggleEditMode()"
(onClick)="toggleEditMode()"
(keydown.enter)="toggleEditMode()"
></p-button>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { createDispatchMap, select, Store } from '@ngxs/store';

import { TranslatePipe } from '@ngx-translate/core';

import { BreadcrumbModule } from 'primeng/breadcrumb';
import { Button } from 'primeng/button';
import { Card } from 'primeng/card';
import { Skeleton } from 'primeng/skeleton';
Expand All @@ -19,7 +18,7 @@ import {
PLATFORM_ID,
signal,
} from '@angular/core';
import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms';
import { FormControl } from '@angular/forms';
import { ActivatedRoute, Router, RouterLink } from '@angular/router';

import { ENVIRONMENT } from '@core/provider/environment.provider';
Expand All @@ -46,18 +45,7 @@ import { AddonDialogService } from '../../services/addon-dialog.service';

@Component({
selector: 'osf-configure-addon',
imports: [
SubHeaderComponent,
TranslatePipe,
Button,
RouterLink,
Card,
ReactiveFormsModule,
FormsModule,
Skeleton,
BreadcrumbModule,
StorageItemSelectorComponent,
],
imports: [SubHeaderComponent, TranslatePipe, Button, RouterLink, Card, Skeleton, StorageItemSelectorComponent],
templateUrl: './configure-addon.component.html',
styleUrl: './configure-addon.component.scss',
providers: [AddonDialogService],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
class="btn-full-width"
[label]="'common.buttons.cancel' | translate"
severity="info"
(click)="dialogRef.close()"
(onClick)="dialogRef.close()"
[disabled]="isSubmitting()"
data-test-addon-cancel-button
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,187 @@
import { Store } from '@ngxs/store';

import { MockProvider } from 'ng-mocks';

import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog';

import { Mock, Mocked } from 'vitest';

import { ComponentFixture, TestBed } from '@angular/core/testing';

import { OperationNames } from '@osf/shared/enums/operation-names.enum';
import { OperationInvocationRequestJsonApi } from '@osf/shared/models/addons/addon-operations-json-api.model';
import { AuthorizedAccountModel } from '@osf/shared/models/addons/authorized-account.model';
import { AddonOperationInvocationService } from '@osf/shared/services/addons/addon-operation-invocation.service';
import { AddonsSelectors, CreateAddonOperationInvocation } from '@osf/shared/stores/addons';

import { MOCK_ADDON } from '@testing/mocks/addon.mock';
import { provideOSFCore } from '@testing/osf.testing.provider';
import { AddonOperationInvocationServiceMockFactory } from '@testing/providers/addon-operation-invocation.service.mock';
import { provideDynamicDialogRefMock } from '@testing/providers/dynamic-dialog-ref.mock';
import {
BaseSetupOverrides,
mergeSignalOverrides,
provideMockStore,
SignalOverride,
} from '@testing/providers/store-provider.mock';

import { ConfirmAccountConnectionModalComponent } from './confirm-account-connection-modal.component';

describe.skip('ConfirmAccountConnectionModalComponent', () => {
describe('ConfirmAccountConnectionModalComponent', () => {
let component: ConfirmAccountConnectionModalComponent;
let fixture: ComponentFixture<ConfirmAccountConnectionModalComponent>;
let store: Store;
let dialogRef: DynamicDialogRef;
let operationInvocationService: Mocked<AddonOperationInvocationService>;

const selectedAccount: AuthorizedAccountModel = {
...MOCK_ADDON,
id: 'account-1',
displayName: 'Google Drive',
type: 'authorized-storage-accounts',
authUrl: null,
authorizedCapabilities: ['ACCESS'],
authorizedOperationNames: [OperationNames.LIST_ROOT_ITEMS],
credentialsAvailable: true,
apiBaseUrl: 'https://www.googleapis.com',
defaultRootFolder: '',
oauthToken: 'token',
accountOwnerId: 'owner-1',
externalStorageServiceId: 'service-1',
};

const invocationPayload = {
data: {
type: 'addon-operation-invocations',
attributes: {
invocation_status: null,
operation_name: OperationNames.LIST_ROOT_ITEMS,
operation_kwargs: {},
operation_result: {},
created: null,
modified: null,
},
relationships: {},
},
} as OperationInvocationRequestJsonApi;

const defaultSignals: SignalOverride[] = [
{ selector: AddonsSelectors.getOperationInvocationSubmitting, value: false },
];

interface SetupOverrides extends BaseSetupOverrides {
message?: string;
omitMessage?: boolean;
selectedAccount?: AuthorizedAccountModel | null;
isGoogleDrive?: boolean;
}

function setup(overrides: SetupOverrides = {}) {
const account = overrides.selectedAccount === undefined ? selectedAccount : overrides.selectedAccount;
const data = {
...(overrides.omitMessage ? {} : { message: overrides.message ?? 'Connect this account?' }),
selectedAccount: account,
isGoogleDrive: overrides.isGoogleDrive ?? false,
};
const signals = mergeSignalOverrides(defaultSignals, overrides.selectorOverrides);
operationInvocationService = AddonOperationInvocationServiceMockFactory();
operationInvocationService.createInitialOperationInvocationPayload.mockReturnValue(invocationPayload);

beforeEach(() => {
TestBed.configureTestingModule({
imports: [ConfirmAccountConnectionModalComponent],
providers: [provideOSFCore()],
providers: [
provideOSFCore(),
provideDynamicDialogRefMock(),
MockProvider(DynamicDialogConfig, { data }),
MockProvider(AddonOperationInvocationService, operationInvocationService),
provideMockStore({ signals }),
],
});

store = TestBed.inject(Store);
dialogRef = TestBed.inject(DynamicDialogRef);
fixture = TestBed.createComponent(ConfirmAccountConnectionModalComponent);
component = fixture.componentInstance;
fixture.detectChanges();
}

it('should read the message from dialog config', () => {
setup();

expect(component.dialogMessage).toBe('Connect this account?');
});

it('should use an empty message when dialog data omits it', () => {
setup({ omitMessage: true });

expect(component.dialogMessage).toBe('');
});

it('should expose the submitting state from the store', () => {
setup({
selectorOverrides: [{ selector: AddonsSelectors.getOperationInvocationSubmitting, value: true }],
});

expect(component.isSubmitting()).toBe(true);
});

it('should render the confirmation message', () => {
setup();

expect(fixture.nativeElement.textContent).toContain('Connect this account?');
});

it('should disable cancel while the connection is submitting', () => {
setup({
selectorOverrides: [{ selector: AddonsSelectors.getOperationInvocationSubmitting, value: true }],
});

const buttons = fixture.nativeElement.querySelectorAll('button');
expect(buttons[0].disabled).toBe(true);
});

it('should not connect when the selected account is missing', () => {
setup({ selectedAccount: null });
(store.dispatch as Mock).mockClear();

component.handleConnectAddonAccount();

expect(operationInvocationService.createInitialOperationInvocationPayload).not.toHaveBeenCalled();
expect(store.dispatch).not.toHaveBeenCalled();
expect(dialogRef.close).not.toHaveBeenCalled();
});

it('should create', () => {
expect(component).toBeTruthy();
it('should close with success for Google Drive without creating an invocation', () => {
setup({ isGoogleDrive: true });
(store.dispatch as Mock).mockClear();

component.handleConnectAddonAccount();

expect(dialogRef.close).toHaveBeenCalledWith({ success: true });
expect(operationInvocationService.createInitialOperationInvocationPayload).not.toHaveBeenCalled();
expect(store.dispatch).not.toHaveBeenCalled();
});

it('should create a root-items invocation and close with success', () => {
setup();
(store.dispatch as Mock).mockClear();

component.handleConnectAddonAccount();

expect(operationInvocationService.createInitialOperationInvocationPayload).toHaveBeenCalledWith(
OperationNames.LIST_ROOT_ITEMS,
selectedAccount
);
expect(store.dispatch).toHaveBeenCalledWith(new CreateAddonOperationInvocation(invocationPayload));
expect(dialogRef.close).toHaveBeenCalledWith({ success: true });
});

it('should close without a result when cancel is clicked', () => {
setup();

const buttons = fixture.nativeElement.querySelectorAll('button');
buttons[0].click();

expect(dialogRef.close).toHaveBeenCalledWith();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,26 @@ import { Button } from 'primeng/button';
import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog';

import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';

import { OperationNames } from '@osf/shared/enums/operation-names.enum';
import { AddonOperationInvocationService } from '@osf/shared/services/addons/addon-operation-invocation.service';
import { AddonsSelectors, CreateAddonOperationInvocation } from '@osf/shared/stores/addons';

@Component({
selector: 'osf-confirm-account-connection-modal',
imports: [Button, ReactiveFormsModule, TranslatePipe],
imports: [Button, TranslatePipe],
templateUrl: './confirm-account-connection-modal.component.html',
styleUrl: './confirm-account-connection-modal.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ConfirmAccountConnectionModalComponent {
private dialogConfig = inject(DynamicDialogConfig);
private operationInvocationService = inject(AddonOperationInvocationService);
dialogRef = inject(DynamicDialogRef);
private readonly dialogConfig = inject(DynamicDialogConfig);
private readonly operationInvocationService = inject(AddonOperationInvocationService);
readonly dialogRef = inject(DynamicDialogRef);

dialogMessage = this.dialogConfig.data.message || '';
isSubmitting = select(AddonsSelectors.getOperationInvocationSubmitting);
readonly isSubmitting = select(AddonsSelectors.getOperationInvocationSubmitting);

actions = createDispatchMap({
createAddonOperationInvocation: CreateAddonOperationInvocation,
});
private readonly actions = createDispatchMap({ createAddonOperationInvocation: CreateAddonOperationInvocation });

handleConnectAddonAccount(): void {
const selectedAccount = this.dialogConfig.data.selectedAccount;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,22 +70,22 @@ <h2 class="align-self-center inline-block">{{ loginOrChooseAccountText() }}</h2>
[label]="'common.buttons.back' | translate"
severity="info"
class="w-7rem btn-full-width"
(click)="activateCallback(AddonStepperValue.TERMS)"
(onClick)="activateCallback(AddonStepperValue.TERMS)"
data-test-addon-back-button
></p-button>
</div>

<div class="flex mt-3 gap-4 justify-content-end">
<p-button
[label]="'settings.addons.form.buttons.existingAccount' | translate"
(click)="activateCallback(AddonStepperValue.CHOOSE_ACCOUNT)"
(onClick)="activateCallback(AddonStepperValue.CHOOSE_ACCOUNT)"
data-test-addon-existing-account-button
></p-button>

<p-button
[label]="'settings.addons.form.buttons.newAccount' | translate"
severity="secondary"
(click)="activateCallback(AddonStepperValue.SETUP_NEW_ACCOUNT)"
(onClick)="activateCallback(AddonStepperValue.SETUP_NEW_ACCOUNT)"
data-test-addon-new-account-button
></p-button>
</div>
Expand Down Expand Up @@ -134,25 +134,27 @@ <h2 class="pt-2">{{ 'settings.addons.connectAddon.chooseExistingAccount' | trans
<ng-template #content let-activateCallback="activateCallback">
<section class="flex flex-column gap-5">
<h2 class="pt-2">{{ 'settings.addons.connectAddon.configure' | translate }} {{ addon()?.displayName }}</h2>
<osf-storage-item-selector
[isGoogleFilePicker]="isGoogleDrive()"
[accountId]="chosenAccountId()"
[accountName]="chosenAccountName()"
[operationInvocationResult]="operationInvocation()?.operationResult || []"
[accountNameControl]="accountNameControl"
[isCreateMode]="true"
[currentAddonType]="addonTypeString()"
[supportedResourceTypes]="supportedResourceTypes()"
[(selectedStorageItemId)]="selectedStorageItemId"
[(selectedStorageItemUrl)]="selectedStorageItemUrl"
[(selectedResourceType)]="selectedResourceType"
(operationInvoke)="handleCreateOperationInvocation($event.operationName, $event.itemId)"
(operationInvokeWithCursor)="
handleCreateOperationInvocationWithCursor($event.operationName, $event.itemId, $event.pageCursor)
"
(save)="handleCreateConfiguredAddon()"
(cancelSelection)="handleNavigateToAccountSelection()"
/>
@if (chosenAccountId()) {
<osf-storage-item-selector
[isGoogleFilePicker]="isGoogleDrive()"
[accountId]="chosenAccountId()"
[accountName]="chosenAccountName()"
[operationInvocationResult]="operationInvocation()?.operationResult || []"
[accountNameControl]="accountNameControl"
[isCreateMode]="true"
[currentAddonType]="addonTypeString()"
[supportedResourceTypes]="supportedResourceTypes()"
[(selectedStorageItemId)]="selectedStorageItemId"
[(selectedStorageItemUrl)]="selectedStorageItemUrl"
[(selectedResourceType)]="selectedResourceType"
(operationInvoke)="handleCreateOperationInvocation($event.operationName, $event.itemId)"
(operationInvokeWithCursor)="
handleCreateOperationInvocationWithCursor($event.operationName, $event.itemId, $event.pageCursor)
"
(save)="handleCreateConfiguredAddon()"
(cancelSelection)="handleNavigateToAccountSelection()"
/>
}
</section>
</ng-template>
</p-step-panel>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,5 @@ describe.skip('ConnectAddonComponent', () => {
it('should create and initialize with addon data from router state', () => {
expect(component).toBeTruthy();
expect(component['addon']()).toEqual(mockAddon);
expect(component['terms']().length).toBeGreaterThan(0);
});
});
Loading
Loading