Skip to content

Commit

Permalink
va-file-input: Adding a V3-compatible (USWDS) version (#1017)
Browse files Browse the repository at this point in the history
* WIP switching branches

* USWDS V3-compatible File Input

* Adding missing asterisk

* Version bump

* Adding tests

* Removing onChange call

* Attempt at improving change event handling

* Changing file input storybook to use React binding

* Style updates

* Fixes to event handling and testing (from Jami)

* Fixes for drag-and-drop and change events

* Update package.json

* Removing changelistnerid

No longer needed

* Style fixes

* Fix to 'generic' preview image sizes when uploading

* Removing no-longer-needed styles

* Fixing version bump

* Change sr-only to usa-sr-only

* Removing uswds reflectivity

* Couple more formation overrides

* Fixing usa-sr-only span

* Skipping flaky event listener test
  • Loading branch information
Andrew565 authored Jan 24, 2024
1 parent 9875373 commit 557e41f
Show file tree
Hide file tree
Showing 10 changed files with 1,143 additions and 85 deletions.
101 changes: 101 additions & 0 deletions packages/storybook/stories/va-file-input-uswds.stories.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/* eslint-disable react/prop-types */
import React from 'react';
import { VaFileInput } from '@department-of-veterans-affairs/web-components/react-bindings';
import { getWebComponentDocs, propStructure, StoryDocs } from './wc-helpers';

const fileInputDocs = getWebComponentDocs('va-file-input');

export default {
title: 'USWDS/File input USWDS',
id: 'uswds/va-file-input',
parameters: {
componentSubtitle: `va-file-input web component`,
docs: {
page: () => <StoryDocs data={fileInputDocs} />,
},
},
};

const defaultArgs = {
'label': 'Input accepts a single file',
'name': 'my-file-input',
'accept': null,
'required': false,
'error': '',
'enable-analytics': false,
'hint': null,
'multiple': false,
'uswds': true,
'vaChange': event =>
alert(`File change event received: ${event?.detail?.files[0]?.name}`),
};

const Template = ({
label,
name,
accept,
error,
required,
hint,
multiple,
'enable-analytics': enableAnalytics,
uswds,
vaChange,
}) => {
return (
<VaFileInput
label={label}
name={name}
accept={accept}
required={required}
error={error}
hint={hint}
multiple={multiple}
enable-analytics={enableAnalytics}
onVaChange={vaChange}
uswds={uswds}
/>
);
};

export const Default = Template.bind(null);
Default.args = { ...defaultArgs };
Default.argTypes = propStructure(fileInputDocs);

export const Required = Template.bind(null);
Required.args = { ...defaultArgs, required: true };

export const AcceptsOnlySpecificFileTypes = Template.bind(null);
AcceptsOnlySpecificFileTypes.args = {
...defaultArgs,
label: 'Input accepts only specific file types',
hint: 'Select PDF or TXT files',
accept: '.pdf,.txt',
};

export const AcceptsAnyKindOfImage = Template.bind(null);
AcceptsAnyKindOfImage.args = {
...defaultArgs,
label: 'Input accepts any kind of image',
hint: 'Select any type of image format',
accept: 'image/*',
};

export const AcceptsMultipleFiles = Template.bind(null);
AcceptsMultipleFiles.args = {
...defaultArgs,
label: 'Input accepts multiple files',
hint: 'Select one or more files',
multiple: true,
};

export const ErrorMessage = Template.bind(null);
ErrorMessage.args = {
...defaultArgs,
label: 'Input has an error',
hint: 'Select any valid file',
error: 'Display a helpful error message',
};

export const WithAnalytics = Template.bind(null);
WithAnalytics.args = { ...defaultArgs, 'enable-analytics': true };
2 changes: 1 addition & 1 deletion packages/web-components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,4 @@
"i18next": "*",
"i18next-browser-languagedetector": "*"
}
}
}
16 changes: 16 additions & 0 deletions packages/web-components/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,10 @@ export namespace Components {
* The label for the file input.
*/
"label"?: string;
/**
* Optionally allow multiple files (USWDS Only)
*/
"multiple"?: boolean;
/**
* The name for the input element.
*/
Expand All @@ -442,6 +446,10 @@ export namespace Components {
* Sets the input to required and renders the (*Required) text.
*/
"required"?: boolean;
/**
* Whether or not the component will use USWDS v3 styling.
*/
"uswds"?: boolean;
}
interface VaIcon {
/**
Expand Down Expand Up @@ -2359,6 +2367,10 @@ declare namespace LocalJSX {
* The label for the file input.
*/
"label"?: string;
/**
* Optionally allow multiple files (USWDS Only)
*/
"multiple"?: boolean;
/**
* The name for the input element.
*/
Expand All @@ -2375,6 +2387,10 @@ declare namespace LocalJSX {
* Sets the input to required and renders the (*Required) text.
*/
"required"?: boolean;
/**
* Whether or not the component will use USWDS v3 styling.
*/
"uswds"?: boolean;
}
interface VaIcon {
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,15 @@ describe('va-file-input', () => {

it('emits the vaChange event only once', async () => {
const page = await newE2EPage();
await page.setContent(`<va-file-input buttonText="Upload a file" />`);
await page.setContent(`<va-file-input button-text="Upload a file" />`);

const fileUploadSpy = await page.spyOnEvent('vaChange');
const filePath = path.relative(process.cwd(), __dirname + '/1x1.png');
const input = (
await page.waitForFunction(() =>
document.querySelector("va-file-input").shadowRoot.querySelector("input[type=file]")
document
.querySelector('va-file-input')
.shadowRoot.querySelector('input[type=file]'),
)
).asElement();

Expand All @@ -113,4 +115,124 @@ describe('va-file-input', () => {

await axeCheck(page);
});

/** USWDS v3 mode tests */

it('v3 renders', async () => {
const page = await newE2EPage();
await page.setContent(
'<va-file-input label="This is the file upload label" buttonText="Upload a file" required="false" multiple="false" class="hydrated" uswds></va-file-input>',
);

const element = await page.find('va-file-input');
expect(element).not.toBeNull();
});

it('v3 displays an error message when `error` is defined', async () => {
const page = await newE2EPage();
await page.setContent(
`<va-file-input error="This is an error" buttonText="Upload a file" uswds />`,
);

const errorSpan = await page.find('va-file-input >>> .usa-error-message');
expect(errorSpan.innerText.includes('This is an error')).toBe(true);
});

it('v3 no error message when `error` is not defined', async () => {
const page = await newE2EPage();
await page.setContent(`<va-file-input uswds />`);

const errorSpan = await page.find('va-file-input >>> .usa-error-message');
expect(errorSpan).toBeUndefined;
});

it('v3 renders hint text', async () => {
const page = await newE2EPage();
await page.setContent('<va-file-input hint="This is hint text" uswds />');

// Render the hint text
const hintTextElement = await page.find('va-file-input >>> span.usa-hint');
expect(hintTextElement.innerText).toContain('This is hint text');
});

it('v3 renders a required span', async () => {
const page = await newE2EPage();
await page.setContent(
`<va-file-input required label="Example file input." buttonText="Upload a file" uswds />`,
);

const requiredSpan = await page.find(
'va-file-input >>> .usa-label--required',
);
expect(requiredSpan).not.toBeNull();
});

it('v3 the `multiple` attributes exists if set', async () => {
const page = await newE2EPage();
await page.setContent(
`<va-file-input buttonText="Upload a file" multiple="true" uswds />`,
);

const fileInput = await page.find('va-file-input >>> input');
expect(fileInput.getAttribute('multiple')).toBeTruthy;
});

it('v3 the `multiple` attributes does not apply if omitted', async () => {
const page = await newE2EPage();
await page.setContent(`<va-file-input buttonText="Upload a file" uswds />`);

const fileInput = await page.find('va-file-input >>> input');
expect(fileInput.getAttribute('multiple')).toBeFalsy;
});

it('v3 the `accept` attribute exists if set', async () => {
const page = await newE2EPage();
await page.setContent(
`<va-file-input buttonText="Upload a file" accept=".png" uswds />`,
);

const fileInput = await page.find('va-file-input >>> input');
expect(fileInput.getAttribute('accept')).toBeTruthy;
});

it('the `accept` attribute does not apply if omitted', async () => {
const page = await newE2EPage();
await page.setContent(`<va-file-input buttonText="Upload a file" uswds />`);

const fileInput = await page.find('va-file-input >>> input');
expect(fileInput.getAttribute('accept')).toBeFalsy;
});

// Skipping due to test flakiness, but this event does work in the browser
it.skip('v3 emits the vaChange event only once', async () => {
const page = await newE2EPage();
await page.setContent(`<va-file-input buttonText="Upload a file" uswds />`);

const fileUploadSpy = await page.spyOnEvent('vaChange');
const filePath = path.relative(process.cwd(), __dirname + '/1x1.png');
const instructions = await page.find(
'va-file-input >>> .usa-file-input__instructions',
);

expect(instructions).not.toBeNull();

const input = await page.$('pierce/.usa-file-input__input');
expect(input).not.toBeNull();

await input
.uploadFile(filePath)
.catch(e => console.log('uploadFile error', e));

expect(fileUploadSpy).toHaveReceivedEventTimes(1);
});

it('v3 passes an aXe check', async () => {
const page = await newE2EPage();

await page.setContent(
'<va-file-input required label="This is a test" buttonText="Upload a file" error="With an error message" uswds />',
);

await axeCheck(page);
});
});
Loading

0 comments on commit 557e41f

Please sign in to comment.