Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix(web-react): FileUploaderInput className #DS-1508 #1719

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
8 changes: 6 additions & 2 deletions configs/jest-config-spirit/jsdom/setup/setupAfterEnv.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// eslint-disable-next-line import/no-extraneous-dependencies
import '@testing-library/jest-dom';
// eslint-disable-next-line import/no-extraneous-dependencies
import { TextEncoder, TextDecoder } from 'util';
import ResizeObserverPolyfill from 'resize-observer-polyfill';

/**
Expand All @@ -18,3 +17,8 @@ global.ResizeObserver = ResizeObserverPolyfill;
* Also consider better patching of the Console.
* @see { @link https://github.com/carbon-design-system/carbon/blob/main/config/jest-config-carbon/setup/setupAfterEnv.js }
*/

/**
* While it should be bundled with jsdom, it isn't with jsdom 16.
*/
Object.assign(global, { TextDecoder, TextEncoder });
69 changes: 69 additions & 0 deletions examples/next-with-app-router/src/app/fileuploader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
'use client';

import {
FileUploader,
FileUploaderAttachment,
FileUploaderInput,
FileUploaderList,
SpiritFileUploaderAttachmentProps,
useFileQueue,
} from '@lmc-eu/spirit-web-react';
import React, { useState } from 'react';

const FileuploaderTest = () => {
const [errorMessage, setErrorMessage] = useState<(string | Error)[]>([]);

const {
fileQueue: fileQueueWarning,
addToQueue: addToQueueWarning,
clearQueue: clearQueueWarning,
onDismiss: onDismissWarning,
findInQueue: findInQueueWarning,
updateQueue: updateQueueWarning,
} = useFileQueue();

const attachmentComponent = ({ id, ...props }: SpiritFileUploaderAttachmentProps) => (
<FileUploaderAttachment key={id} id={id} {...props} />
);

return (
<FileUploader
addToQueue={(key, file) => {
return addToQueueWarning(key, file);
}}
clearQueue={clearQueueWarning}
fileQueue={fileQueueWarning}
findInQueue={findInQueueWarning}
id="file-uploader-validation-states-warning"
onDismiss={onDismissWarning}
updateQueue={updateQueueWarning}
>
<FileUploaderInput
helperText="Max file size is 50 kb"
id="file-uploader-validation-states-warning-input"
label="Label"
labelText="or drag and drop here"
linkText="Upload your file"
name="attachmentsWarning"
onError={(error) => {
setErrorMessage((prevErrors) => [...prevErrors, error]);
}}
validationText={`${errorMessage.join(', ')}`}
validationState="warning"
isRequired
isMultiple
maxFileSize={51200} // 50 kb
// add onInput callback should solve issue if upload files by select them (not for drag and drop)
// onInput={() => setErrorMessage([])}
/>
<FileUploaderList
attachmentComponent={attachmentComponent}
id="file-uploader-validation-states-warning-attachment"
inputName="attachmentsWarning"
label="Attachments"
/>
</FileUploader>
);
};

export default FileuploaderTest;
4 changes: 2 additions & 2 deletions examples/next-with-app-router/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Heading } from '@lmc-eu/spirit-web-react';
import FileuploaderTest from '@/app/fileuploader';
import { NextPage } from 'next';

const Home: NextPage = () => <Heading size="large">Spirit App Router</Heading>;
const Home: NextPage = () => <FileuploaderTest />;

export default Home;
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client';

import classNames from 'classnames';
import React from 'react';
import React, { useEffect, useState } from 'react';
import { useDeprecationMessage, useStyleProps } from '../../hooks';
import { SpiritFileUploaderInputProps } from '../../types';
import { HelperText, ValidationText, useAriaIds } from '../Field';
Expand All @@ -11,6 +11,7 @@ import { useFileUploaderInput } from './useFileUploaderInput';
import { useFileUploaderStyleProps } from './useFileUploaderStyleProps';

const FileUploaderInput = (props: SpiritFileUploaderInputProps) => {
const [isDragAndDropSupported, setIsDragAndDropSupported] = useState(false);
const {
accept,
'aria-describedby': ariaDescribedBy = '',
Expand All @@ -34,10 +35,6 @@ const FileUploaderInput = (props: SpiritFileUploaderInputProps) => {
validationText,
...restProps
} = props;

const isDragAndDropSupported =
typeof document !== 'undefined' ? 'draggable' in document.createElement('span') : false;

const {
isDisabledByQueueLimitBehavior,
isDragging,
Expand Down Expand Up @@ -67,9 +64,12 @@ const FileUploaderInput = (props: SpiritFileUploaderInputProps) => {
validationState,
});
const { styleProps, props: transferProps } = useStyleProps(restProps);

const [ids, register] = useAriaIds(ariaDescribedBy);

useEffect(() => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we able to test this?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://stackoverflow.com/questions/75376392/how-to-test-server-side-render-with-testing-library-react

It would be nice to have 2 tests. One for CSR where event listeners like onDragOver, etc. are set, and one for SSR where there are undefined. :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sorry, but I still can't figure out how to test it :(

setIsDragAndDropSupported('draggable' in document.createElement('span'));
}, []);

useDeprecationMessage({
method: 'custom',
trigger: !id,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,107 @@
import { render, screen, act, fireEvent, waitFor } from '@testing-library/react';
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import { classNamePrefixProviderTest } from '../../../../tests/providerTests/classNamePrefixProviderTest';
import { restPropsTest } from '../../../../tests/providerTests/restPropsTest';
import { validationTextPropsTest } from '../../../../tests/providerTests/validationTextPropsTest';
import FileUploaderInput from '../FileUploaderInput';
import '@testing-library/jest-dom';

describe('FileUploaderInput', () => {
classNamePrefixProviderTest(FileUploaderInput, 'FileUploaderInput');

restPropsTest(FileUploaderInput, 'div');

validationTextPropsTest(FileUploaderInput, '.FileUploaderInput__validationText');

it('should have drag-and-drop listeners in CSR when draggable is supported', () => {
render(<FileUploaderInput id="test-uploader" name="test-uploader" label="upload" data-testid="test" />);

const dropZone = screen.getAllByTestId('test')[0];

expect(dropZone).toHaveClass('has-drag-and-drop');
});

it('should not have drag-and-drop listeners in SSR', () => {
const ui = <FileUploaderInput id="test-uploader" name="test-uploader" label="upload" data-testid="test" />;
const container = document.createElement('div');
document.body.appendChild(container);
container.innerHTML = ReactDOMServer.renderToString(ui);

act(() => {
render(ui, { hydrate: true, container });
});

const dropZone = screen.getAllByTestId('test')[0];

expect(dropZone).not.toHaveClass('has-drag-and-drop');
});
});

describe('Listener approach', () => {
it('should not register onDragOver event listener during SSR', () => {
// Simulace addEventListener pomocí jest.spyOn
const addEventListenerSpy = jest.spyOn(document, 'addEventListener');

// Simulujeme SSR renderováním pouze komponenty bez vykonání useEffect
// Abychom simulovali SSR, renderování komponenty nebude obsahovat "hydration" logiku.
render(<FileUploaderInput name="bagr" id="pokus" data-testid="test" />, { hydrate: false });

// Ověřujeme, že event listener nebyl zaregistrován
expect(addEventListenerSpy).not.toHaveBeenCalledWith('dragover', expect.any(Function));

// Vyčistíme spy
addEventListenerSpy.mockRestore();
});

it('should register onDragOver event listener during CSR', () => {
const addEventListenerSpy = jest.spyOn(document, 'addEventListener');

// Simulujeme CSR renderováním komponenty s vykonáním useEffect
render(<FileUploaderInput name="bagr" id="pokus" data-testid="test" />, { hydrate: true });

expect(addEventListenerSpy).toHaveBeenCalledWith('dragover', expect.any(Function));

addEventListenerSpy.mockRestore();
});
});

describe('mock useEffect approach', () => {
it('should not register onDragOver event listener during SSR', () => {
jest.spyOn(React, 'useEffect').mockImplementation(() => {});
const addEventListenerSpy = jest.spyOn(window, 'addEventListener');

// Simulujeme SSR renderování komponenty
render(<FileUploaderInput name="bagr" id="pokus" data-testid="test" />);

// Kontrolujeme, že během SSR nebyl zaregistrován žádný onDragOver listener
expect(addEventListenerSpy).not.toHaveBeenCalledWith('dragover', expect.any(Function));

addEventListenerSpy.mockRestore();
});

it('should register onDragOver event listener during CSR', () => {
const addEventListenerSpy = jest.spyOn(window, 'addEventListener');

// Simulujeme CSR renderování komponenty
render(<FileUploaderInput name="bagr" id="pokus" data-testid="test" />);

// Kontrolujeme, že během CSR byl zaregistrován onDragOver listener
expect(addEventListenerSpy).toHaveBeenCalledWith('dragover', expect.any(Function));

addEventListenerSpy.mockRestore();
});
});

describe('is-dragging class onDragOver event', () => {
it('should have is-dragging class on dragOver', () => {
render(<FileUploaderInput name="bagr" id="pokus" data-testid="test" />);
const dropZone = screen.getAllByTestId('test')[0];

fireEvent.dragOver(dropZone);

waitFor(() => {
expect(dropZone).toHaveClass('is-dragging');
});
});
});
Loading