Skip to content

Commit

Permalink
Tests/design system/initial (#368)
Browse files Browse the repository at this point in the history
* chore(): add tests to design-system

* chore(): add tests and various fixes

* chore(): add tests and various fixes

* chore(): add tests and various fixes

* fix(avatar): avatar does not require seed prop anymore. use src: string || ethAddress and guest: boolean

* fix(topbar-widget): merging error fix

* fix(): fix codefactor issues

* fix linter
  • Loading branch information
SeverS authored and kenshyx committed Jan 14, 2020
1 parent 4591553 commit 0109b10
Show file tree
Hide file tree
Showing 37 changed files with 735 additions and 98 deletions.
2 changes: 1 addition & 1 deletion examples/ui/feed-app/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,5 @@
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
<script type="text/javascript" src="/akasha.runtime.89fe2c05a990d962166d.js"></script><script type="text/javascript" src="/akasha.vendors.1ab698bae7fd4203f8f0.js"></script><script type="text/javascript" src="/akasha.main.6d0ea570cac9cfa7e191.js"></script></body>
<script type="text/javascript" src="/akasha.runtime.89fe2c05a990d962166d.js"></script><script type="text/javascript" src="/akasha.vendors.aa77836b5d4ffd4f96ac.js"></script><script type="text/javascript" src="/akasha.main.c2a17a194f5224a6cd66.js"></script></body>
</html>
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
"docs": "lerna run docs --scope @akashaproject/sdk --scope @akashaproject/sdk-common",
"install:clean": "npm run clean && npm run build:all",
"test:sdk": "lerna run test --scope @akashaproject/sdk-* --stream",
"test:ui": "lerna run test --scope @akashaproject/ui-* --stream",
"test:ui:watch": "lerna exec --parallel --scope @akashaproject/ui-* --stream -- jest --watch",
"test:ui": "lerna run test --scope @akashaproject/ui-* --scope @akashaproject/design-* --stream",
"test:ui:watch": "lerna exec --parallel --scope @akashaproject/ui-* --scope @akashaproject/design-* --stream -- jest --watch",
"tsc:sdk": "lerna run tsc --scope @akashaproject/sdk --scope @akashaproject/sdk-*",
"tsc:ui": "lerna run tsc --scope @akashaproject/design-* --scope @akashaproject/ui-*",
"tsc:scripts": "lerna run tsc --scope @akashaproject/script-*",
Expand Down
14 changes: 13 additions & 1 deletion ui/design/jest.config.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,14 @@
// add extra config for jest
module.exports = {};
module.exports = {
verbose: true,
roots: [
"<rootDir>/src"
],
testMatch: [
"**/__tests__/**/*.+(ts|tsx)",
"**/?(*.)+(spec|test).+(ts|tsx)"
],
transform: {
"^.+\\.(ts|tsx)$": "ts-jest"
},
};
3 changes: 3 additions & 0 deletions ui/design/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"lint": "tslint -c tslint.json src/**/*.{ts,tsx} --fix --format verbose",
"tslint-check": "tslint-config-prettier-check ./tslint.json",
"build": "tsc --build",
"test": "jest",
"tsc": "tsc --build",
"prepare": "npm run tsc",
"pack": "NODE_OPTIONS='--max-old-space-size=4096' webpack --config webpack.config.js",
Expand All @@ -55,11 +56,13 @@
"styled-components": "^4.4.1"
},
"devDependencies": {
"@testing-library/react": "^9.3.2",
"@types/classnames": "^2.2.9",
"@types/jest": "^24.0.21",
"@types/node": "^12.12.3",
"@types/react": "^16.9.11",
"@types/react-dom": "^16.9.3",
"@types/react-test-renderer": "^16.9.1",
"@types/styled-components": "^4.1.20",
"@types/slate": "0.47.4",
"@types/slate-react": "^0.22.8",
Expand Down

Large diffs are not rendered by default.

109 changes: 109 additions & 0 deletions ui/design/src/components/Avatar/__test__/index.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import '@testing-library/jest-dom/extend-expect';
import { cleanup, fireEvent, waitForElement } from '@testing-library/react';
import * as React from 'react';
import { act, create, ReactTestRenderer } from 'react-test-renderer';
import { createFile, customRender, delay, wrapWithTheme } from '../../../test-utils';
import { MockFileReader, WindowWithFileReader } from '../../../test-utils/mocks';
import Avatar, { getAvatarFromSeed } from '../avatar';
import AvatarImage from '../avatar-image';
import EditableAvatar from '../editable-avatar';

describe('<Avatar /> component', () => {
let componentWrapper: ReactTestRenderer = create(<></>);
beforeEach(() => {
act(() => {
componentWrapper = create(wrapWithTheme(<Avatar guest={true} src={'0x01230123450012312'} />));
});
});

afterEach(() => {
act(() => {
componentWrapper.unmount();
});
cleanup();
});

it('when in guest mode, should mount', () => {
const root = componentWrapper.root;
const avatarComp = root.findByType(Avatar);
expect(avatarComp).toBeDefined();
});

it('when in guest mode, should match snapshot', async () => {
await delay();
expect(componentWrapper.toJSON()).toMatchSnapshot('avatar');
});

it('when in guest mode, should pass an image obj to AvatarImage', async () => {
const avatarRoot = componentWrapper.root.findByType(Avatar);
// this is required because the avatarImage may not be mounted yet.
// this should be used whenever we see React.Suspense in use!
await delay();
const avatarImageRoot = avatarRoot.findByType(AvatarImage);
const { image } = avatarImageRoot.props;
const src = image.read();
expect(src).toBeDefined();
});

it('should generate SAME placeholder name for a given Eth Address (with guest={true})', async () => {
const ethAddress = '0x12300ab456000ced9vAr130019mX24';
const targetResult = getAvatarFromSeed(ethAddress);
for (let i = 0; i < 10; i += 1) {
const testAvatar = getAvatarFromSeed(ethAddress);
expect(testAvatar).toEqual(targetResult);
}
});
it('when not in guest mode, should load src prop', async () => {
const src = 'http://placebeard.it/640/480';
const { findByTestId } = customRender(<Avatar src={src} />, {});
const image = await waitForElement(() => findByTestId('avatar-image'));
expect(image.getAttribute('src')).toEqual(src);
});
});

describe('<EditableAvatar /> Component', () => {
let componentWrapper: ReactTestRenderer = create(<></>);
const originalFileReader = FileReader;

beforeEach(() => {
(window as WindowWithFileReader).FileReader = MockFileReader;
act(() => {
componentWrapper = create(
wrapWithTheme(
<EditableAvatar guest={true} onChange={jest.fn()} src="0x1230am3421h3i14cvv21n4" />,
),
);
});
});
afterEach(() => {
(window as WindowWithFileReader).FileReader = originalFileReader;
act(() => {
componentWrapper.unmount();
});
cleanup();
});
it('should match snapshot', async () => {
// delay to allow avatar to mount the AvatarImage component
await delay();
expect(componentWrapper.toJSON()).toMatchSnapshot('editable-avatar');
});
it('should have 1 input type file', async () => {
const { getAllByTestId } = customRender(
<EditableAvatar onChange={jest.fn()} src="0x1230am3421h3i14cvv21n4" />,
{},
);
const fileInput = await waitForElement(() => getAllByTestId('avatar-file-input'));
expect(fileInput).toHaveLength(1);
expect(fileInput[0].getAttribute('type')).toEqual('file');
});
it('should trigger onChange event when input is changed', async () => {
const onChange = jest.fn();
const { findByTestId } = customRender(
<EditableAvatar onChange={onChange} src="0x1230am3421h3i14cvv21n4" />,
{},
);
const fileInput = await waitForElement(() => findByTestId('avatar-file-input'));
fireEvent.change(fileInput, { target: { file: createFile('test-file.png') } });
expect(onChange).toBeCalledTimes(1);
});
});
16 changes: 16 additions & 0 deletions ui/design/src/components/Avatar/avatar-image.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import * as React from 'react';

const AvatarImage = (props: any) => {
const { image } = props;
let avatar;
if (typeof image === 'object' && image.hasOwnProperty('read')) {
avatar = image.read();
}
if (typeof image === 'string') {
avatar = image;
}

return <img data-testid="avatar-image" src={avatar} />;
};

export default AvatarImage;
56 changes: 24 additions & 32 deletions ui/design/src/components/Avatar/avatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,44 @@
import * as React from 'react';
import CommonInterface from '../../interfaces/common.interface';
import MarginInterface from '../../interfaces/margin.interface';
import AvatarImage from './avatar-image';
import { loadPlaceholder } from './placeholders';
import StyledAvatar, { AvatarSize } from './styled-avatar';

export interface AvatarProps extends CommonInterface<HTMLDivElement> {
src?: string;
// @Todo: fix this
onClick?: React.EventHandler<React.SyntheticEvent>;
src: string;
onClick?: React.MouseEventHandler<any>;
alt?: string;
margin?: MarginInterface;
backgroundColor?: string;
withBorder?: boolean;
guest?: boolean;
seed: string;
size?: AvatarSize;
}

const getAvatarFromSeed = (seed: string) => {
// @Todo: fix this to be more reliable
const avatarOption = Array.from(seed.replace('0x', '')).reduce(
// @ts-ignore
(sum: number, letter: string) => sum + letter.codePointAt(0),
0,
);

return (avatarOption % 7) + 1;
export const getAvatarFromSeed = (seed: string) => {
let str = seed;
if (seed.startsWith('0x')) {
str = seed.replace('0x', '');
}
if (str && str.length) {
const avatarOption = Array.from(str).reduce((sum: number, letter: string) => {
if (letter.codePointAt(0)) {
return sum + letter.codePointAt(0)!;
}
return sum;
}, 0);
return (avatarOption % 7) + 1;
}
// load the first placeholder, just to not throw and error
return 1;
};

const defaultProps: Partial<AvatarProps> = {
size: 'md' as AvatarSize,
withBorder: false,
seed: '0x0000000000000000000000000000000',
guest: false,
src: '0x0000000000000000000000000000000',
};

/*
Expand All @@ -46,11 +53,11 @@ const defaultProps: Partial<AvatarProps> = {
*/

const Avatar: React.FC<AvatarProps & typeof defaultProps> = props => {
const { onClick, guest, src, seed, className, size, margin, withBorder } = props;
const { onClick, guest, src, className, size, margin, withBorder } = props;
const isClickable = typeof onClick === 'function';
let avatarImage;
if (guest || !src) {
avatarImage = loadPlaceholder(`placeholder_${getAvatarFromSeed(seed)}`);
if (guest) {
avatarImage = loadPlaceholder(`placeholder_${getAvatarFromSeed(src)}`);
} else if (src) {
avatarImage = src;
}
Expand All @@ -73,19 +80,4 @@ const Avatar: React.FC<AvatarProps & typeof defaultProps> = props => {

Avatar.defaultProps = defaultProps;

const AvatarImage = (props: any) => {
const { image } = props;
let avatar;
if (image.hasOwnProperty('read') && typeof image.read === 'function') {
const img = image.read();
if (img) {
avatar = img;
}
} else if (typeof image === 'string') {
avatar = image;
}

return <img src={avatar} />;
};

export default Avatar;
7 changes: 6 additions & 1 deletion ui/design/src/components/Avatar/editable-avatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@ const EditableAvatar: React.FC<EditableAvatarProps & Partial<typeof defaultProps
return (
<>
<Avatar {...props} src={newAvatar} onClick={handleClick} />
<StyleFileInput type="file" ref={inputRef} onChange={handleChange} />
<StyleFileInput
data-testid="avatar-file-input"
type="file"
ref={inputRef}
onChange={handleChange}
/>
</>
);
};
Expand Down
9 changes: 0 additions & 9 deletions ui/design/src/components/Avatar/index.test.tsx

This file was deleted.

25 changes: 23 additions & 2 deletions ui/design/src/components/Avatar/placeholders/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,29 @@ const REJECTED = 'REJECTED';
export const loadPlaceholder = (placeholderName: string) => {
let status = PENDING;
let result: any;
// @ts-ignore
const promise = import(`./${placeholderName}`);
let promise;
switch (placeholderName) {
case 'placeholder_1':
promise = import('./placeholder_1');
return;
case 'placeholder_2':
promise = import('./placeholder_2');
return;
case 'placeholder_3':
promise = import('./placeholder_3');
return;
case 'placeholder_4':
promise = import('./placeholder_4');
case 'placeholder_5':
promise = import('./placeholder_5');
case 'placeholder_6':
promise = import('./placeholder_6');
case 'placeholder_7':
promise = import('./placeholder_7');
default:
promise = import('./placeholder_1');
}

const suspender = promise
.then(r => {
status = RESOLVED;
Expand Down

0 comments on commit 0109b10

Please sign in to comment.