Skip to content

Commit

Permalink
feat(components/forms): file drop harness (#3029) (#3039)
Browse files Browse the repository at this point in the history
🍒 Cherry picked from #3029 [feat(components/forms): file drop
harness](#3029)

Co-authored-by: Sandhya Raja Sabeson <[email protected]>
Co-authored-by: Trevor Burch <[email protected]>
  • Loading branch information
3 people authored Jan 16, 2025
1 parent 650282b commit b5c37ad
Show file tree
Hide file tree
Showing 14 changed files with 912 additions and 48 deletions.
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
<form [formGroup]="formGroup">
<sky-file-drop
data-sky-id="logo-upload"
linkUploadHintText="Start with http:// or https://"
formControlName="fileDrop"
[acceptedTypes]="acceptedTypes"
[allowLinks]="true"
[hintText]="hintText"
[helpPopoverContent]="inlineHelpContent"
[hintText]="hintText"
[labelText]="labelText"
[maxFileSize]="maxFileSize"
[stacked]="stacked"
[validateFn]="validateFile"
/>
>
@if (fileDrop.errors?.['maxNumberOfFilesReached']) {
<sky-form-error
errorName="maxNumberOfFilesReached"
errorText="Do not upload more than 3 files."
/>
}
</sky-file-drop>
</form>

@for (file of fileDrop.value; track file) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { HarnessLoader } from '@angular/cdk/testing';
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { TestBed } from '@angular/core/testing';
import { FormControl } from '@angular/forms';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import {
SkyFileDropHarness,
SkyFileItemHarness,
provideSkyFileAttachmentTesting,
} from '@skyux/forms/testing';

import { DemoComponent } from './demo.component';

describe('Basic file drop demo', () => {
async function setupTest(options: { dataSkyId: string }): Promise<{
harness: SkyFileDropHarness;
formControl: FormControl;
loader: HarnessLoader;
}> {
TestBed.configureTestingModule({
providers: [provideSkyFileAttachmentTesting()],
});
const fixture = TestBed.createComponent(DemoComponent);
const loader = TestbedHarnessEnvironment.loader(fixture);

const harness = await loader.getHarness(
SkyFileDropHarness.with({ dataSkyId: options.dataSkyId }),
);

fixture.detectChanges();
await fixture.whenStable();

const formControl = fixture.componentInstance.fileDrop;

return { harness, formControl, loader };
}

beforeEach(() => {
TestBed.configureTestingModule({
imports: [DemoComponent, NoopAnimationsModule],
});
});

it('should set initial values', async () => {
const { harness } = await setupTest({
dataSkyId: 'logo-upload',
});

await expectAsync(harness.getLabelText()).toBeResolvedTo('Logo image');
await expectAsync(harness.getAcceptedTypes()).toBeResolvedTo(
'image/png,image/jpeg',
);
await expectAsync(harness.getHintText()).toBeResolvedTo(
'Upload up to 3 files under 50MB.',
);
await expectAsync(harness.isStacked()).toBeResolvedTo(true);

await expectAsync(harness.getLinkUploadHintText()).toBeResolvedTo(
'Start with http:// or https://',
);
});

it('should not upload invalid files starting with `a`', async () => {
const { harness, formControl } = await setupTest({
dataSkyId: 'logo-upload',
});

const filesToUpload: File[] = [
new File([], 'aWrongFile', { type: 'image/png' }),
new File([], 'validFile', { type: 'image/png' }),
];

await harness.loadFiles(filesToUpload);

expect(formControl.value?.length).toBe(1);
await expectAsync(harness.hasValidateFnError()).toBeResolvedTo(true);
});

it('should not allow more than 3 files to be uploaded', async () => {
const { harness, formControl, loader } = await setupTest({
dataSkyId: 'logo-upload',
});

await harness.loadFiles([
new File([], 'validFile1', { type: 'image/png' }),
new File([], 'validFile2', { type: 'image/png' }),
new File([], 'validFile3', { type: 'image/png' }),
]);

expect(formControl.value?.length).toBe(3);
await expectAsync(
harness.hasCustomError('maxNumberOfFilesReached'),
).toBeResolvedTo(false);
expect(formControl.valid).toBe(true);

await harness.enterLinkUploadText('foo.bar');
await harness.clickLinkUploadDoneButton();

expect(formControl.value?.length).toBe(4);
await expectAsync(
harness.hasCustomError('maxNumberOfFilesReached'),
).toBeResolvedTo(true);
expect(formControl.valid).toBe(false);

const validFileItemHarness = await loader.getHarness(
SkyFileItemHarness.with({ fileName: 'validFile2' }),
);
await validFileItemHarness.clickDeleteButton();

expect(formControl.value?.length).toBe(3);
expect(formControl.valid).toBe(true);
});
});
Original file line number Diff line number Diff line change
@@ -1,16 +1,32 @@
import { CommonModule } from '@angular/common';
import { Component, inject } from '@angular/core';
import {
AbstractControl,
FormBuilder,
FormControl,
FormGroup,
FormsModule,
ReactiveFormsModule,
ValidationErrors,
Validators,
} from '@angular/forms';
import { SkyFileDropModule, SkyFileItem, SkyFileLink } from '@skyux/forms';
import { SkyStatusIndicatorModule } from '@skyux/indicators';

/**
* Demonstrates how to create a custom validator function for your form control.
*/
function customValidator(
control: AbstractControl<(SkyFileItem | SkyFileLink)[] | null | undefined>,
): ValidationErrors | null {
if (control.value !== undefined && control.value !== null) {
if (control.value.length > 3) {
return { maxNumberOfFilesReached: true };
}
}
return null;
}

@Component({
selector: 'app-demo',
templateUrl: './demo.component.html',
Expand All @@ -24,19 +40,17 @@ import { SkyStatusIndicatorModule } from '@skyux/indicators';
})
export class DemoComponent {
protected acceptedTypes = 'image/png,image/jpeg';
protected allItems: (SkyFileItem | SkyFileLink)[] = [];
protected hintText = '5 MB maximum';
protected hintText = 'Upload up to 3 files under 50MB.';
protected inlineHelpContent =
'Your logo appears in places such as authentication pages, student and parent portals, and extracurricular home pages.';
protected labelText = 'Logo image';
protected maxFileSize = 5242880;
protected rejectedFiles: SkyFileItem[] = [];
protected stacked = 'true';

protected fileDrop = new FormControl<
public fileDrop = new FormControl<
(SkyFileItem | SkyFileLink)[] | null | undefined
>(undefined, Validators.required);
protected formGroup: FormGroup = inject(FormBuilder).group({
>(undefined, [Validators.required, customValidator]);
public formGroup: FormGroup = inject(FormBuilder).group({
fileDrop: this.fileDrop,
});

Expand All @@ -45,7 +59,13 @@ export class DemoComponent {

if (index !== undefined && index !== -1) {
this.fileDrop.value?.splice(index, 1);
/*
If you are adding custom validation through the form control,
be sure to include this line after deleting a file from the form.
*/
this.fileDrop.updateValueAndValidity();
}
// To ensure that empty arrays throw required errors, include this check.
if (this.fileDrop.value?.length === 0) {
this.fileDrop.setValue(null);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export class SkyAvatarHarness extends SkyComponentHarness {
if (waitForChange) {
await this.#dropAndWait(fileDrop, file);
} else {
await fileDrop.dropFile(file);
await fileDrop.loadFile(file);
}
}

Expand Down Expand Up @@ -136,7 +136,7 @@ export class SkyAvatarHarness extends SkyComponentHarness {
async #dropAndWait(fileDrop: SkyFileDropHarness, file: File): Promise<void> {
const currentUrl = await this.#getImageUrl();

await fileDrop.dropFile(file);
await fileDrop.loadFile(file);

return await new Promise<void>((resolve, reject) => {
const checkForFileChange = async (attempts: number): Promise<void> => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@
[dirty]="ngControl?.dirty"
[errors]="ngControl?.errors"
>
<ng-content select="sky-form-error" />
@for (rejectedFile of rejectedFiles; track rejectedFile) {
<div>
@if (rejectedFile.errorType === 'fileType') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
/>
</form>

{{fileDrop.value | json}} @for (file of fileDrop.value; track file) {
@for (file of fileDrop.value; track file) {
<sky-file-item [fileItem]="file" (deleteFile)="deleteFile($event)" />
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { CommonModule } from '@angular/common';
import { Component, inject } from '@angular/core';
import {
FormBuilder,
Expand All @@ -14,7 +13,7 @@ import { SkyFileDropModule } from '../file-drop.module';
import { SkyFileLink } from '../file-link';

@Component({
imports: [SkyFileDropModule, FormsModule, ReactiveFormsModule, CommonModule],
imports: [SkyFileDropModule, FormsModule, ReactiveFormsModule],
selector: 'sky-file-drop-reactive-test',
standalone: true,
templateUrl: './reactive-file-drop.component.fixture.html',
Expand Down
Loading

0 comments on commit b5c37ad

Please sign in to comment.